Skip to content

Commit

Permalink
fix(require): allow require() of ESM in ".js" files;
Browse files Browse the repository at this point in the history
- Closes #7
  • Loading branch information
lukeed committed Oct 9, 2021
1 parent 9ab86f0 commit 822f0ba
Show file tree
Hide file tree
Showing 6 changed files with 83 additions and 19 deletions.
61 changes: 42 additions & 19 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 Down Expand Up @@ -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;
}
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~!');

0 comments on commit 822f0ba

Please sign in to comment.