From 822f0bad6c55249eb82eff7951ff9d6650f62e30 Mon Sep 17 00:00:00 2001 From: Luke Edwards Date: Sat, 9 Oct 2021 13:41:30 -0700 Subject: [PATCH] fix(require): allow `require()` of ESM in ".js" files; - Closes #7 --- src/require.ts | 61 +++++++++++++++++++++---------- test/fixtures/module/index.js | 6 +++ test/fixtures/module/index.mjs | 6 +++ test/fixtures/module/package.json | 3 ++ test/index.js | 14 +++++++ test/index.mjs | 12 ++++++ 6 files changed, 83 insertions(+), 19 deletions(-) create mode 100644 test/fixtures/module/index.js create mode 100644 test/fixtures/module/index.mjs create mode 100644 test/fixtures/module/package.json diff --git a/src/require.ts b/src/require.ts index 7593e75..69275a9 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 & { @@ -47,29 +48,51 @@ const tsrequire = 'var $$req=require;require=(' + function () { return existsSync(file) ? $$req(file) : $$req(ident); } }) -} + ')();' +} + ')();'; + +function transform(source: string, sourcefile: string, options: Options): string { + let banner = options.banner || ''; + if (/\.[mc]?tsx?$/.test(sourcefile)) { + banner = tsrequire + banner; + } + + esbuild = esbuild || require('esbuild'); + return esbuild.transformSync(source, { + ...options, banner, sourcefile + }).code; +} function loader(Module: Module, sourcefile: string) { - let extn = extname(sourcefile); 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); + let extn = extname(sourcefile); + let options = config[extn]; + + if (options != null) { + Module._compile = source => { + let result = transform(source, sourcefile, 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, sourcefile, { + ...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/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~!');