转译加载器


\Transpiler loader

可以使用 load 钩子 将 Node.js 无法理解的格式的源代码转换为 JavaScript。然而,在调用该钩子之前,resolve 钩子 需要告诉 Node.js 不要对未知文件类型抛出错误。

\Sources that are in formats Node.js doesn't understand can be converted into JavaScript using the load hook. Before that hook gets called, however, a resolve hook needs to tell Node.js not to throw an error on unknown file types.

这比在运行 Node.js 之前转换源文件的性能要差;转译器加载器应该只用于开发和测试目的。

\This is less performant than transpiling source files before running Node.js; a transpiler loader should only be used for development and testing purposes.

// coffeescript-loader.mjs
import { readFile } from 'node:fs/promises';
import { dirname, extname, resolve as resolvePath } from 'node:path';
import { cwd } from 'node:process';
import { fileURLToPath, pathToFileURL } from 'node:url';
import CoffeeScript from 'coffeescript';
const baseURL = pathToFileURL(`${cwd()}/`).href;
// CoffeeScript files end in .coffee, .litcoffee, or .coffee.md.
const extensionsRegex = /\.coffee$|\.litcoffee$|\.coffee\.md$/;
export async function resolve(specifier, context, nextResolve) {
 if (extensionsRegex.test(specifier)) {
 const { parentURL = baseURL } = context;
 // Node.js normally errors on unknown file extensions, so return a URL for
 // specifiers ending in the CoffeeScript file extensions.
 return {
 shortCircuit: true,
 url: new URL(specifier, parentURL).href
 };
 }
 // Let Node.js handle all other specifiers.
 return nextResolve(specifier);
}
export async function load(url, context, nextLoad) {
 if (extensionsRegex.test(url)) {
 // Now that we patched resolve to let CoffeeScript URLs through, we need to
 // tell Node.js what format such URLs should be interpreted as. Because
 // CoffeeScript transpiles into JavaScript, it should be one of the two
 // JavaScript formats: 'commonjs' or 'module'.
 // CoffeeScript files can be either CommonJS or ES modules, so we want any
 // CoffeeScript file to be treated by Node.js the same as a .js file at the
 // same location. To determine how Node.js would interpret an arbitrary .js
 // file, search up the file system for the nearest parent package.json file
 // and read its "type" field.
 const format = await getPackageType(url);
 // When a hook returns a format of 'commonjs', `source` is be ignored.
 // To handle CommonJS files, a handler needs to be registered with
 // `require.extensions` in order to process the files with the CommonJS
 // loader. Avoiding the need for a separate CommonJS handler is a future
 // enhancement planned for ES module loaders.
 if (format === 'commonjs') {
 return {
 format,
 shortCircuit: true,
 };
 }
 const { source: rawSource } = await nextLoad(url, { ...context, format });
 // This hook converts CoffeeScript source code into JavaScript source code
 // for all imported CoffeeScript files.
 const transformedSource = coffeeCompile(rawSource.toString(), url);
 return {
 format,
 shortCircuit: true,
 source: transformedSource,
 };
 }
 // Let Node.js handle all other URLs.
 return nextLoad(url);
}
async function getPackageType(url) {
 // `url` is only a file path during the first iteration when passed the
 // resolved url from the load() hook
 // an actual file path from load() will contain a file extension as it's
 // required by the spec
 // this simple truthy check for whether `url` contains a file extension will
 // work for most projects but does not cover some edge-cases (such as
 // extensionless files or a url ending in a trailing space)
 const isFilePath = !!extname(url);
 // If it is a file path, get the directory it's in
 const dir = isFilePath ?
 dirname(fileURLToPath(url)) :
 url;
 // Compose a file path to a package.json in the same directory,
 // which may or may not exist
 const packagePath = resolvePath(dir, 'package.json');
 // Try to read the possibly nonexistent package.json
 const type = await readFile(packagePath, { encoding: 'utf8' })
 .then((filestring) => JSON.parse(filestring).type)
 .catch((err) => {
 if (err?.code !== 'ENOENT') console.error(err);
 });
 // Ff package.json existed and contained a `type` field with a value, voila
 if (type) return type;
 // Otherwise, (if not at the root) continue checking the next directory up
 // If at the root, stop and return false
 return dir.length > 1 && getPackageType(resolvePath(dir, '..'));
} 
# main.coffee
import { scream } from './scream.coffee'
console.log scream 'hello, world'
import { version } from 'node:process'
console.log "Brought to you by Node.js version #{version}" 
# scream.coffee
export scream = (str) -> str.toUpperCase() 

使用前面的加载器,运行 node --experimental-loader ./coffeescript-loader.mjs main.coffee 会导致 main.coffee 在其源代码从磁盘加载之后但在 Node.js 执行之前被转换为 JavaScript;对于通过任何加载文件的 import 语句引用的任何 .coffee.litcoffee.coffee.md 文件,依此类推。

\With the preceding loader, running node --experimental-loader ./coffeescript-loader.mjs main.coffee causes main.coffee to be turned into JavaScript after its source code is loaded from disk but before Node.js executes it; and so on for any .coffee, .litcoffee or .coffee.md files referenced via import statements of any loaded file.

AltStyle によって変換されたページ (->オリジナル) /