From 798f33800bfbd64ddd30ebd9649eb8c7ba41fee4 Mon Sep 17 00:00:00 2001 From: iuser Date: Mon, 1 Aug 2022 16:49:32 +0800 Subject: [PATCH] CoffeeScript ESM loader --- Cakefile | 8 +- lib/coffeescript/index.cjs | 217 ++++++++++++++++++++++++++++++++++++ lib/coffeescript/loader.mjs | 100 +++++++++++++++++ loader.mjs | 1 + package.json | 1 + src/loader.coffee | 58 ++++++++++ 6 files changed, 384 insertions(+), 1 deletion(-) create mode 100644 lib/coffeescript/index.cjs create mode 100644 lib/coffeescript/loader.mjs create mode 100644 loader.mjs create mode 100644 src/loader.coffee diff --git a/Cakefile b/Cakefile index fb52f25a6c..680d7db37b 100644 --- a/Cakefile +++ b/Cakefile @@ -60,7 +60,13 @@ buildParser = -> buildExceptParser = (callback) -> files = fs.readdirSync 'src' files = ('src/' + file for file in files when file.match(/\.(lit)?coffee$/)) - run ['-c', '-o', 'lib/coffeescript'].concat(files), callback + run ['-c', '-o', 'lib/coffeescript'].concat(files), -> + dir = 'lib/coffeescript/' + loader = dir+'loader.' + fs.renameSync(loader+'js',loader+'mjs') + index = dir+'index.' + fs.copyFileSync(index+'js',index+'cjs') + callback?.apply?(@,arguments) build = (callback) -> buildParser() diff --git a/lib/coffeescript/index.cjs b/lib/coffeescript/index.cjs new file mode 100644 index 0000000000..79c2b8eb66 --- /dev/null +++ b/lib/coffeescript/index.cjs @@ -0,0 +1,217 @@ +// Generated by CoffeeScript 2.7.0 +(function() { + // Node.js Implementation + var CoffeeScript, ext, fs, helpers, i, len, path, ref, universalCompile, vm, + hasProp = {}.hasOwnProperty; + + CoffeeScript = require('./coffeescript'); + + fs = require('fs'); + + vm = require('vm'); + + path = require('path'); + + helpers = CoffeeScript.helpers; + + CoffeeScript.transpile = function(js, options) { + var babel; + try { + babel = require('@babel/core'); + } catch (error) { + try { + babel = require('babel-core'); + } catch (error) { + // This error is only for Node, as CLI users will see a different error + // earlier if they don’t have Babel installed. + throw new Error('To use the transpile option, you must have the \'@babel/core\' module installed'); + } + } + return babel.transform(js, options); + }; + + // The `compile` method shared by the CLI, Node and browser APIs. + universalCompile = CoffeeScript.compile; + + // The `compile` method particular to the Node API. + CoffeeScript.compile = function(code, options) { + // Pass a reference to Babel into the compiler, so that the transpile option + // is available in the Node API. We need to do this so that tools like Webpack + // can `require('coffeescript')` and build correctly, without trying to + // require Babel. + if (options != null ? options.transpile : void 0) { + options.transpile.transpile = CoffeeScript.transpile; + } + return universalCompile.call(CoffeeScript, code, options); + }; + + // Compile and execute a string of CoffeeScript (on the server), correctly + // setting `__filename`, `__dirname`, and relative `require()`. + CoffeeScript.run = function(code, options = {}) { + var answer, dir, mainModule, ref; + mainModule = require.main; + // Set the filename. + mainModule.filename = process.argv[1] = options.filename ? fs.realpathSync(options.filename) : helpers.anonymousFileName(); + // Clear the module cache. + mainModule.moduleCache && (mainModule.moduleCache = {}); + // Assign paths for node_modules loading + dir = options.filename != null ? path.dirname(fs.realpathSync(options.filename)) : fs.realpathSync('.'); + mainModule.paths = require('module')._nodeModulePaths(dir); + // Save the options for compiling child imports. + mainModule.options = options; + options.filename = mainModule.filename; + options.inlineMap = true; + // Compile. + answer = CoffeeScript.compile(code, options); + code = (ref = answer.js) != null ? ref : answer; + return mainModule._compile(code, mainModule.filename); + }; + + // Compile and evaluate a string of CoffeeScript (in a Node.js-like environment). + // The CoffeeScript REPL uses this to run the input. + CoffeeScript.eval = function(code, options = {}) { + var Module, _module, _require, createContext, i, isContext, js, k, len, o, r, ref, ref1, ref2, ref3, sandbox, v; + if (!(code = code.trim())) { + return; + } + createContext = (ref = vm.Script.createContext) != null ? ref : vm.createContext; + isContext = (ref1 = vm.isContext) != null ? ref1 : function(ctx) { + return options.sandbox instanceof createContext().constructor; + }; + if (createContext) { + if (options.sandbox != null) { + if (isContext(options.sandbox)) { + sandbox = options.sandbox; + } else { + sandbox = createContext(); + ref2 = options.sandbox; + for (k in ref2) { + if (!hasProp.call(ref2, k)) continue; + v = ref2[k]; + sandbox[k] = v; + } + } + sandbox.global = sandbox.root = sandbox.GLOBAL = sandbox; + } else { + sandbox = global; + } + sandbox.__filename = options.filename || 'eval'; + sandbox.__dirname = path.dirname(sandbox.__filename); + // define module/require only if they chose not to specify their own + if (!(sandbox !== global || sandbox.module || sandbox.require)) { + Module = require('module'); + sandbox.module = _module = new Module(options.modulename || 'eval'); + sandbox.require = _require = function(path) { + return Module._load(path, _module, true); + }; + _module.filename = sandbox.__filename; + ref3 = Object.getOwnPropertyNames(require); + for (i = 0, len = ref3.length; i < len; i++) { + r = ref3[i]; + if (r !== 'paths' && r !== 'arguments' && r !== 'caller') { + _require[r] = require[r]; + } + } + // use the same hack node currently uses for their own REPL + _require.paths = _module.paths = Module._nodeModulePaths(process.cwd()); + _require.resolve = function(request) { + return Module._resolveFilename(request, _module); + }; + } + } + o = {}; + for (k in options) { + if (!hasProp.call(options, k)) continue; + v = options[k]; + o[k] = v; + } + o.bare = true; // ensure return value + js = CoffeeScript.compile(code, o); + if (sandbox === global) { + return vm.runInThisContext(js); + } else { + return vm.runInContext(js, sandbox); + } + }; + + CoffeeScript.register = function() { + return require('./register'); + }; + + // Throw error with deprecation warning when depending upon implicit `require.extensions` registration + if (require.extensions) { + ref = CoffeeScript.FILE_EXTENSIONS; + for (i = 0, len = ref.length; i < len; i++) { + ext = ref[i]; + (function(ext) { + var base; + return (base = require.extensions)[ext] != null ? base[ext] : base[ext] = function() { + throw new Error(`Use CoffeeScript.register() or require the coffeescript/register module to require ${ext} files.`); + }; + })(ext); + } + } + + CoffeeScript._compileRawFileContent = function(raw, filename, options = {}) { + var answer, err, stripped; + // Strip the Unicode byte order mark, if this file begins with one. + stripped = raw.charCodeAt(0) === 0xFEFF ? raw.substring(1) : raw; + options = Object.assign({}, options, { + filename: filename, + literate: helpers.isLiterate(filename), + sourceFiles: [filename] + }); + try { + answer = CoffeeScript.compile(stripped, options); + } catch (error) { + err = error; + // As the filename and code of a dynamically loaded file will be different + // from the original file compiled with CoffeeScript.run, add that + // information to error so it can be pretty-printed later. + throw helpers.updateSyntaxError(err, stripped, filename); + } + return answer; + }; + + CoffeeScript._compileFile = function(filename, options = {}) { + var raw; + raw = fs.readFileSync(filename, 'utf8'); + return CoffeeScript._compileRawFileContent(raw, filename, options); + }; + + module.exports = CoffeeScript; + + // Explicitly define all named exports so that Node’s automatic detection of + // named exports from CommonJS packages finds all of them. This enables consuming + // packages to write code like `import { compile } from 'coffeescript'`. + // Don’t simplify this into a loop or similar; the `module.exports.name` part is + // essential for Node’s algorithm to successfully detect the name. + module.exports.VERSION = CoffeeScript.VERSION; + + module.exports.FILE_EXTENSIONS = CoffeeScript.FILE_EXTENSIONS; + + module.exports.helpers = CoffeeScript.helpers; + + module.exports.registerCompiled = CoffeeScript.registerCompiled; + + module.exports.compile = CoffeeScript.compile; + + module.exports.tokens = CoffeeScript.tokens; + + module.exports.nodes = CoffeeScript.nodes; + + module.exports.register = CoffeeScript.register; + + module.exports.eval = CoffeeScript.eval; + + module.exports.run = CoffeeScript.run; + + module.exports.transpile = CoffeeScript.transpile; + + module.exports.patchStackTrace = CoffeeScript.patchStackTrace; + + module.exports._compileRawFileContent = CoffeeScript._compileRawFileContent; + + module.exports._compileFile = CoffeeScript._compileFile; + +}).call(this); diff --git a/lib/coffeescript/loader.mjs b/lib/coffeescript/loader.mjs new file mode 100644 index 0000000000..df70feb50e --- /dev/null +++ b/lib/coffeescript/loader.mjs @@ -0,0 +1,100 @@ +// Generated by CoffeeScript 2.7.0 +//!/usr/bin/env coffee +var baseURL, getPackageType, is_coffee; + +import { + readFile +} from "fs/promises"; + +import { + readFileSync +} from "fs"; + +import { + createRequire +} from "module"; + +import { + dirname, + extname, + resolve as resolvePath +} from "path"; + +import { + cwd +} from "process"; + +import { + fileURLToPath, + pathToFileURL +} from "url"; + +import CoffeeScript from "./index.cjs"; + +baseURL = pathToFileURL(`${cwd}/`).href; + +is_coffee = (specifier) => { + return specifier.slice(specifier.lastIndexOf(".") + 1) === 'coffee'; +}; + +export var resolve = (specifier, context, defaultResolve) => { + var parentURL; + ({ + parentURL = baseURL + } = context); + if (is_coffee(specifier)) { + return { + shortCircuit: true, + url: new URL(specifier, parentURL).href + }; + } + return defaultResolve(specifier, context, defaultResolve); +}; + +export var load = async (url, context, defaultLoad) => { + var format, rawSource, transformedSource; + if (is_coffee(url)) { + format = (await getPackageType(url)); + if (format === "commonjs") { + return { + format + }; + } + ({ + source: rawSource + } = (await defaultLoad(url, { + format + }))); + transformedSource = CoffeeScript.compile(rawSource.toString(), { + bare: true, + filename: url, + inlineMap: true + }); + return { + format, + source: transformedSource + }; + //console.log('!!!', ) + } + return defaultLoad(url, context, defaultLoad); +}; + +getPackageType = async (url) => { + var dir, isFilePath, packagePath, type; + isFilePath = !!extname(url); + dir = isFilePath ? dirname(fileURLToPath(url)) : url; + packagePath = resolvePath(dir, "package.json"); + type = (await readFile(packagePath, { + encoding: "utf8" + }).then((filestring) => { + return JSON.parse(filestring).type; + }).catch((err) => { + if ((err != null ? err.code : void 0) !== "ENOENT") { + return console.error(err); + } + })); + if (type) { + return type; + } + return dir.length > 1 && getPackageType(resolvePath(dir, "..")); +}; diff --git a/loader.mjs b/loader.mjs new file mode 100644 index 0000000000..2aaf7cdc0f --- /dev/null +++ b/loader.mjs @@ -0,0 +1 @@ +export * from './lib/coffeescript/loader.mjs'; diff --git a/package.json b/package.json index 06348d75b7..3fbabe826d 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "bin", "lib", "register.js", + "loader.mjs", "repl.js" ], "scripts": { diff --git a/src/loader.coffee b/src/loader.coffee new file mode 100644 index 0000000000..e829c26738 --- /dev/null +++ b/src/loader.coffee @@ -0,0 +1,58 @@ +#!/usr/bin/env coffee + +import { readFile } from "fs/promises" +import { readFileSync } from "fs" +import { createRequire } from "module" +import { dirname, extname, resolve as resolvePath } from "path" +import { cwd } from "process" +import { fileURLToPath, pathToFileURL } from "url" +import CoffeeScript from "./index.cjs" + +baseURL = pathToFileURL("#{cwd}/").href + +is_coffee = (specifier)=> + specifier.slice(specifier.lastIndexOf(".") + 1) == 'coffee' + +export resolve = (specifier, context, defaultResolve) => + { parentURL = baseURL } = context + + if is_coffee(specifier) + return { + shortCircuit: true, + url: new URL(specifier, parentURL).href + } + + defaultResolve(specifier, context, defaultResolve) + +export load = (url, context, defaultLoad)=> + if is_coffee(url) + format = await getPackageType(url) + if format == "commonjs" + return { format } + + { source: rawSource } = await defaultLoad(url, { format }) + transformedSource = CoffeeScript.compile(rawSource.toString(), { + bare: true, + filename: url, + inlineMap: true, + }) + + return { + format + source: transformedSource, + } + + return defaultLoad(url, context, defaultLoad) + +getPackageType = (url) => + isFilePath = !!extname(url) + dir = if isFilePath then dirname(fileURLToPath(url)) else url + packagePath = resolvePath(dir, "package.json") + type = await readFile(packagePath, { encoding: "utf8" }) + .then((filestring) => JSON.parse(filestring).type) + .catch (err) => + if err?.code != "ENOENT" + console.error(err) + if type + return type + return dir.length > 1 and getPackageType(resolvePath(dir, ".."))