diff --git a/src/require.ts b/src/require.ts index 7593e75..bc931db 100644 --- a/src/require.ts +++ b/src/require.ts @@ -1,7 +1,8 @@ const { extname } = require('path'); +const { readFileSync } = require('fs'); const tsm = require('./utils'); -import type { Config } from 'tsm/config'; +import type { Config, Options } from 'tsm/config'; type TSM = typeof import('./utils.d'); type Module = NodeJS.Module & { @@ -16,11 +17,11 @@ let uconf = env.file && require(env.file); let config: Config = (tsm as TSM).$finalize(env, uconf); declare const $$req: NodeJS.Require; -const tsrequire = 'var $$req=require;require=(' + function () { +const tsrequire = 'var $$req=require("module").createRequire(__filename);require=(' + function () { let { existsSync } = $$req('fs'); let { URL, pathToFileURL } = $$req('url'); - return new Proxy($$req, { + return new Proxy(require, { // NOTE: only here if source is TS apply(req, ctx, args: [id: string]) { let [ident] = args; @@ -47,29 +48,46 @@ const tsrequire = 'var $$req=require;require=(' + function () { return existsSync(file) ? $$req(file) : $$req(ident); } }) -} + ')();' +} + ')();'; + +function transform(source: string, options: Options): string { + esbuild = esbuild || require('esbuild'); + return esbuild.transformSync(source, options).code; +} function loader(Module: Module, sourcefile: string) { let extn = extname(sourcefile); + let options = config[extn] || {}; let pitch = Module._compile!.bind(Module); - - Module._compile = source => { - let options = config[extn]; - if (options == null) return pitch(source, sourcefile); - - let banner = options.banner || ''; - if (/\.[mc]?tsx?$/.test(extn)) { - banner = tsrequire + banner; - } - - esbuild = esbuild || require('esbuild'); - let result = esbuild.transformSync(source, { ...options, banner, sourcefile }); - return pitch(result.code, sourcefile); - }; - - return loadJS(Module, sourcefile); + options.sourcefile = sourcefile; + + if (/\.[mc]?tsx?$/.test(extn)) { + options.banner = tsrequire + (options.banner || ''); + } + + if (config[extn] != null) { + Module._compile = source => { + let result = transform(source, options); + return pitch(result, sourcefile); + }; + } + + try { + return loadJS(Module, sourcefile); + } catch (err) { + let ec = err && (err as any).code; + if (ec !== 'ERR_REQUIRE_ESM') throw err; + + let input = readFileSync(sourcefile, 'utf8'); + let result = transform(input, { ...options, format: 'cjs' }); + return pitch(result, sourcefile); + } } for (let extn in config) { require.extensions[extn] = loader; } + +if (config['.js'] == null) { + require.extensions['.js'] = loader; +} diff --git a/test/config/index.ts b/test/config/index.ts index c0d4f70..25696b7 100644 --- a/test/config/index.ts +++ b/test/config/index.ts @@ -6,6 +6,8 @@ import * as js from '../fixtures/math.js'; import * as mjs from '../fixtures/utils.mjs'; // @ts-ignore - cannot find types import * as cjs from '../fixtures/utils.cjs'; +// @ts-ignore - cannot find types +import * as esm from '../fixtures/module/index.js'; // NOTE: avoid need for syntheticDefault + analysis import * as data from '../fixtures/data.json'; @@ -31,4 +33,11 @@ assert.equal(typeof cjs, 'object', 'CJS :: typeof'); assert.equal(typeof cjs.dashify, 'function', 'CJS :: typeof :: dashify'); assert.equal(cjs.dashify('FooBar'), 'foo-bar', 'CJS :: value :: dashify'); +// Checking ".js" with ESM content (type: module) +assert.equal(typeof esm, 'object', 'ESM.js :: typeof'); +// @ts-ignore +assert.equal(typeof esm.hello, 'function', 'ESM.js :: typeof :: hello'); +// @ts-ignore +assert.equal(esm.hello('you'), 'hello, you', 'ESM.js :: value :: hello'); + console.log('DONE~!'); diff --git a/test/fixtures/module/index.js b/test/fixtures/module/index.js new file mode 100644 index 0000000..ddadf48 --- /dev/null +++ b/test/fixtures/module/index.js @@ -0,0 +1,6 @@ +/** + * @param {string} name + */ +export function hello(name) { + return `hello, ${name}`; +} diff --git a/test/fixtures/module/index.mjs b/test/fixtures/module/index.mjs new file mode 100644 index 0000000..ddadf48 --- /dev/null +++ b/test/fixtures/module/index.mjs @@ -0,0 +1,6 @@ +/** + * @param {string} name + */ +export function hello(name) { + return `hello, ${name}`; +} diff --git a/test/fixtures/module/package.json b/test/fixtures/module/package.json new file mode 100644 index 0000000..3dbc1ca --- /dev/null +++ b/test/fixtures/module/package.json @@ -0,0 +1,3 @@ +{ + "type": "module" +} diff --git a/test/index.js b/test/index.js index abbb89c..14efd86 100644 --- a/test/index.js +++ b/test/index.js @@ -11,6 +11,10 @@ const ts = require('./fixtures/math.ts'); const mts = require('./fixtures/utils.mts'); // @ts-ignore – prefers extensionless const cts = require('./fixtures/utils.cts'); +// @ts-ignore – prefers extensionless +const esm1 = require('./fixtures/module/index.js'); +// @ts-ignore – prefers extensionless +const esm2 = require('./fixtures/module/index.mjs'); const props = { foo: 'bar' @@ -55,4 +59,14 @@ assert.equal(typeof cts, 'object', 'CTS :: typeof'); assert.equal(typeof cts.dashify, 'function', 'CTS :: typeof :: dashify'); assert.equal(cts.dashify('FooBar'), 'foo-bar', 'CTS :: value :: dashify'); +assert(esm1, 'ESM.js :: typeof'); +assert.equal(typeof esm1, 'object', 'ESM.js :: typeof'); +assert.equal(typeof esm1.hello, 'function', 'ESM.js :: typeof :: hello'); +assert.equal(esm1.hello('you'), 'hello, you', 'ESM.js :: value :: hello'); + +assert(esm2, 'ESM.mjs :: typeof'); +assert.equal(typeof esm2, 'object', 'ESM.mjs :: typeof'); +assert.equal(typeof esm2.hello, 'function', 'ESM.mjs :: typeof :: hello'); +assert.equal(esm2.hello('you'), 'hello, you', 'ESM.mjs :: value :: hello'); + console.log('DONE~!'); diff --git a/test/index.mjs b/test/index.mjs index cf7a521..93e14c6 100644 --- a/test/index.mjs +++ b/test/index.mjs @@ -11,6 +11,10 @@ import * as cts from './fixtures/utils.cts'; import * as ts from './fixtures/math.ts'; // @ts-ignore – prefers extensionless import tsx from './fixtures/App2.tsx'; +// @ts-ignore – prefers extensionless +import * as esm1 from './fixtures/module/index.js'; +// @ts-ignore – prefers extensionless +import * as esm2 from './fixtures/module/index.mjs'; const props = { foo: 'bar' @@ -53,4 +57,12 @@ assert.equal(typeof cts, 'object', 'CTS :: typeof'); assert.equal(typeof cts.dashify, 'function', 'CTS :: typeof :: dashify'); assert.equal(cts.dashify('FooBar'), 'foo-bar', 'CTS :: value :: dashify'); +assert.equal(typeof esm1, 'object', 'ESM.js :: typeof'); +assert.equal(typeof esm1.hello, 'function', 'ESM.js :: typeof :: hello'); +assert.equal(esm1.hello('you'), 'hello, you', 'ESM.js :: value :: hello'); + +assert.equal(typeof esm2, 'object', 'ESM.mjs :: typeof'); +assert.equal(typeof esm2.hello, 'function', 'ESM.mjs :: typeof :: hello'); +assert.equal(esm2.hello('you'), 'hello, you', 'ESM.mjs :: value :: hello'); + console.log('DONE~!');