Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,8 @@ module.exports = [
'lib/nodejs/esm-utils.js',
'rollup.config.js',
'scripts/*.mjs',
'scripts/pick-from-package-json.js'
'scripts/pick-from-package-json.js',
'test/compiler-cjs/test.js'
],
languageOptions: {
sourceType: 'module'
Expand Down
30 changes: 29 additions & 1 deletion lib/nodejs/esm-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,11 @@ const formattedImport = async (file, esmDecorator = forward) => {

exports.doImport = async file => import(file);

exports.requireOrImport = async (file, esmDecorator) => {
// When require(esm) is not available, we need to use `import()` to load ESM modules.
// In this case, CJS modules are loaded using `import()` as well. When Node.js' builtin
// TypeScript support is enabled, `.ts` files are also loaded using `import()`, and
// compilers based on `require.extensions` are omitted.
const tryImportAndRequire = async (file, esmDecorator) => {
if (path.extname(file) === '.mjs') {
return formattedImport(file, esmDecorator);
}
Expand Down Expand Up @@ -81,6 +85,30 @@ exports.requireOrImport = async (file, esmDecorator) => {
}
};

// Utilize Node.js' require(esm) feature to load ESM modules
// and CJS modules. This keeps the require() features like `require.extensions`
// and `require.cache` effective, while allowing us to load ESM modules
// and CJS modules in the same way.
const requireModule = async (file, esmDecorator) => {
try {
return require(file);
} catch (err) {
if (
err.code === 'ERR_REQUIRE_ASYNC_MODULE'
) {
// Import if the module is async.
return formattedImport(file, esmDecorator);
}
throw err;
}
}

if (process.features.require_module) {
exports.requireOrImport = requireModule;
} else {
exports.requireOrImport = tryImportAndRequire;
}

function dealWithExports(module) {
if (module.default) {
return module.default;
Expand Down
9 changes: 9 additions & 0 deletions test/compiler-cjs/test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
const obj = { foo: 'bar' };

describe('cjs written in esm', () => {
it('should work', () => {
expect(obj, 'to equal', { foo: 'bar' });
});
});

export const foo = 'bar';
9 changes: 9 additions & 0 deletions test/compiler-cjs/test.js.compiled
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
const obj = { foo: 'bar' };

describe('cjs written in esm', () => {
it('should work', () => {
expect(obj, 'to equal', { foo: 'bar' });
});
});

module.exports.foo = 'bar';
9 changes: 9 additions & 0 deletions test/compiler-cjs/test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
const obj: unknown = { foo: 'bar' };

describe('cts written in esm', () => {
it('should work', () => {
expect(obj, 'to equal', { foo: 'bar' });
});
});

export const foo = 'bar';
9 changes: 9 additions & 0 deletions test/compiler-cjs/test.ts.compiled
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
const obj = { foo: 'bar' };

describe('cts written in esm', () => {
it('should work', () => {
expect(obj, 'to equal', { foo: 'bar' });
});
});

module.exports.foo = 'bar';
12 changes: 12 additions & 0 deletions test/compiler-fixtures/js.fixture.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
'use strict';

const fs = require('fs');

const original = require.extensions['.js'];
require.extensions['.js'] = function (module, filename) {
if (!filename.includes('compiler-cjs')) {
return original(module, filename);
}
const content = fs.readFileSync(filename + '.compiled', 'utf8');
return module._compile(content, filename);
};
8 changes: 8 additions & 0 deletions test/compiler-fixtures/ts.fixture.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
'use strict';

const fs = require('fs');

require.extensions['.ts'] = function (module, filename) {
const content = fs.readFileSync(filename + '.compiled', 'utf8');
return module._compile(content, filename);
};
64 changes: 64 additions & 0 deletions test/integration/compiler-cjs.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
'use strict';

var exec = require('node:child_process').exec;
var path = require('node:path');

describe('support CJS require.extension compilers with esm syntax', function () {
it('should support .js extension', function (done) {
exec(
'"' +
process.execPath +
'" "' +
path.join('bin', 'mocha') +
'" -R json --require test/compiler-fixtures/js.fixture "test/compiler-cjs/*.js"',
{cwd: path.join(__dirname, '..', '..')},
function (error, stdout) {
if (error && !stdout) {
return done(error);
}
var results = JSON.parse(stdout);
expect(results, 'to have property', 'tests');
var titles = [];
for (var index = 0; index < results.tests.length; index += 1) {
expect(results.tests[index], 'to have property', 'fullTitle');
titles.push(results.tests[index].fullTitle);
}
expect(
titles,
'to contain',
'cjs written in esm should work',
).and('to have length', 1);
done();
}
);
});

it('should support .ts extension', function (done) {
exec(
'"' +
process.execPath +
'" "' +
path.join('bin', 'mocha') +
'" -R json --require test/compiler-fixtures/ts.fixture "test/compiler-cjs/*.ts"',
{cwd: path.join(__dirname, '..', '..')},
function (error, stdout) {
if (error && !stdout) {
return done(error);
}
var results = JSON.parse(stdout);
expect(results, 'to have property', 'tests');
var titles = [];
for (var index = 0; index < results.tests.length; index += 1) {
expect(results.tests[index], 'to have property', 'fullTitle');
titles.push(results.tests[index].fullTitle);
}
expect(
titles,
'to contain',
'cts written in esm should work',
).and('to have length', 1);
done();
}
);
});
});
Loading