Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(require): handle ERR_REQUIRE_ESM error #8

Merged
merged 5 commits into from Oct 9, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
58 changes: 38 additions & 20 deletions 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 & {
Expand All @@ -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;
Expand All @@ -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;
}
9 changes: 9 additions & 0 deletions test/config/index.ts
Expand Up @@ -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';
Expand All @@ -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~!');
6 changes: 6 additions & 0 deletions test/fixtures/module/index.js
@@ -0,0 +1,6 @@
/**
* @param {string} name
*/
export function hello(name) {
return `hello, ${name}`;
}
6 changes: 6 additions & 0 deletions test/fixtures/module/index.mjs
@@ -0,0 +1,6 @@
/**
* @param {string} name
*/
export function hello(name) {
return `hello, ${name}`;
}
3 changes: 3 additions & 0 deletions test/fixtures/module/package.json
@@ -0,0 +1,3 @@
{
"type": "module"
}
14 changes: 14 additions & 0 deletions test/index.js
Expand Up @@ -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'
Expand Down Expand Up @@ -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~!');
12 changes: 12 additions & 0 deletions test/index.mjs
Expand Up @@ -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'
Expand Down Expand Up @@ -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~!');