UNPKG

@factor/cli

Version:

171 lines (145 loc) 4.23 kB
/* eslint-disable no-console */ import { URL, pathToFileURL, fileURLToPath } from "url" import fs from "fs" import path from "path" import { transformSync, build } from "esbuild" const isWindows = process.platform === "win32" const imgExtensionsRegex = /\.(webp|svg|jpg|png|gif|vue)$/ const tsExtensionsRegex = /\.m?(tsx?|json)$/ const esbuildResolve = async (id, dir) => { let result await build({ stdin: { contents: `import ${JSON.stringify(id)}`, resolveDir: dir, }, write: false, bundle: true, treeShaking: false, ignoreAnnotations: true, platform: "node", plugins: [ { name: "resolve", setup({ onLoad }) { onLoad({ filter: /.*/ }, (args) => { result = args.path return { contents: "" } }) }, }, ], }) return result } const esbuildTransformSync = (rawSource, filename, url, format) => { const { code: js, warnings, map: jsSourceMap, } = transformSync(rawSource.toString(), { sourcefile: filename, sourcemap: "both", loader: new URL(url).pathname.match(tsExtensionsRegex)[1], target: `node${process.versions.node}`, format: format === "module" ? "esm" : "cjs", }) if (warnings && warnings.length > 0) { for (const warning of warnings) { console.warn(warning.location) console.warn(warning.text) } } return { js, jsSourceMap } } const getTsCompatSpecifier = (parentURL, specifier) => { let tsSpecifier let search if (specifier.startsWith("./") || specifier.startsWith("../")) { // Relative import const url = new URL(specifier, parentURL) tsSpecifier = fileURLToPath(url).replace(/\.tsx?$/, "") search = url.search } else { // Bare import tsSpecifier = specifier search = "" } return { tsSpecifier, search, } } const isValidURL = (s) => { try { return !!new URL(s) } catch (error) { if (error instanceof TypeError) return false throw error } } export const resolve = async (specifier, context, defaultResolve) => { const { parentURL } = context let url // According to Node's algorithm, we first check if it is a valid URL. // When the module is the entry point, node will provides a file URL to it. if (isValidURL(specifier)) { url = new URL(specifier) } else { // Try to resolve the module according to typescript's algorithm, // and construct a valid url. const parsed = getTsCompatSpecifier(parentURL, specifier) const _path = await esbuildResolve( parsed.tsSpecifier, path.dirname(fileURLToPath(parentURL)), ) if (_path) { url = pathToFileURL(_path) url.search = parsed.search } } if (url) { // If the resolved file is typescript if (tsExtensionsRegex.test(url.pathname)) { return { url: url.href, format: "module", } } // https://github.com/nodejs/modules/issues/488#issuecomment-589541566 else if (/^file:\/{3}.*\/bin\//.test(url.href) && !path.extname(url.href)) { return { url: url.href, format: "commonjs", } } // Else, for other types, use default resolve with the valid path return defaultResolve(url.href, context, defaultResolve) } return defaultResolve(specifier, context, defaultResolve) } // New hook starting from Node v16.12.0 // See: https://github.com/nodejs/node/pull/37468 export const load = (url, context, defaultLoad) => { // if an image, just return the file name if (imgExtensionsRegex.test(new URL(url).pathname)) { const fn = new URL(url).pathname.replace(/^.*[/\\]/, "") return { format: "module", source: `export default "${fn}"`, } } if (tsExtensionsRegex.test(new URL(url).pathname)) { const { format } = context let filename = url if (!isWindows) filename = fileURLToPath(url) const rawSource = fs.readFileSync(new URL(url), { encoding: "utf8" }) const { js } = esbuildTransformSync(rawSource, filename, url, format) return { format: "module", source: js, } } // Let Node.js handle all other format / sources. return defaultLoad(url, context, defaultLoad) }