Skip to content

Commit ba6dd18

Browse files
module: fix discrepancy between .ts and .js
PR-URL: #54461 Fixes: #54457 Reviewed-By: Paolo Insogna <[email protected]> Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: Chengzhong Wu <[email protected]> Reviewed-By: Zeyu "Alex" Yang <[email protected]>
1 parent 613842d commit ba6dd18

File tree

6 files changed

+84
-56
lines changed

6 files changed

+84
-56
lines changed

lib/internal/modules/esm/get_format.js

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -161,14 +161,14 @@ function getFileProtocolModuleFormat(url, context = { __proto__: null }, ignoreE
161161
default: { // The user did not pass `--experimental-default-type`.
162162
// `source` is undefined when this is called from `defaultResolve`;
163163
// but this gets called again from `defaultLoad`/`defaultLoadSync`.
164-
let parsedSource;
165-
if (source) {
166-
const { stripTypeScriptTypes } = require('internal/modules/helpers');
167-
parsedSource = stripTypeScriptTypes(source, url);
168-
}
164+
// Since experimental-strip-types depends on detect-module, we always return null
165+
// if source is undefined.
166+
if (!source) { return null; }
167+
const { stripTypeScriptTypes, stringify } = require('internal/modules/helpers');
168+
const stringifiedSource = stringify(source);
169+
const parsedSource = stripTypeScriptTypes(stringifiedSource, fileURLToPath(url));
169170
const detectedFormat = detectModuleFormat(parsedSource, url);
170-
// When source is undefined, default to module-typescript.
171-
const format = detectedFormat ? `${detectedFormat}-typescript` : 'module-typescript';
171+
const format = `${detectedFormat}-typescript`;
172172
if (format === 'module-typescript' && foundPackageJson) {
173173
// This module has a .js extension, a package.json with no `type` field, and ESM syntax.
174174
// Warn about the missing `type` field so that the user can avoid the performance penalty of detection.

lib/internal/modules/esm/translators.js

Lines changed: 2 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,6 @@ const {
1818
globalThis: { WebAssembly },
1919
} = primordials;
2020

21-
/** @type {import('internal/util/types')} */
22-
let _TYPES = null;
23-
/**
24-
* Lazily loads and returns the internal/util/types module.
25-
*/
26-
function lazyTypes() {
27-
if (_TYPES !== null) { return _TYPES; }
28-
return _TYPES = require('internal/util/types');
29-
}
30-
3121
const {
3222
compileFunctionForCJSLoader,
3323
} = internalBinding('contextify');
@@ -37,7 +27,9 @@ const assert = require('internal/assert');
3727
const { readFileSync } = require('fs');
3828
const { dirname, extname, isAbsolute } = require('path');
3929
const {
30+
assertBufferSource,
4031
loadBuiltinModule,
32+
stringify,
4133
stripTypeScriptTypes,
4234
stripBOM,
4335
urlToFilename,
@@ -57,7 +49,6 @@ let debug = require('internal/util/debuglog').debuglog('esm', (fn) => {
5749
const { emitExperimentalWarning, kEmptyObject, setOwnProperty, isWindows } = require('internal/util');
5850
const {
5951
ERR_UNKNOWN_BUILTIN_MODULE,
60-
ERR_INVALID_RETURN_PROPERTY_VALUE,
6152
} = require('internal/errors').codes;
6253
const { maybeCacheSourceMap } = require('internal/source_map/source_map_cache');
6354
const moduleWrap = internalBinding('module_wrap');
@@ -107,44 +98,6 @@ function initCJSParseSync() {
10798
const translators = new SafeMap();
10899
exports.translators = translators;
109100

110-
let DECODER = null;
111-
/**
112-
* Asserts that the given body is a buffer source (either a string, array buffer, or typed array).
113-
* Throws an error if the body is not a buffer source.
114-
* @param {string | ArrayBufferView | ArrayBuffer} body - The body to check.
115-
* @param {boolean} allowString - Whether or not to allow a string as a valid buffer source.
116-
* @param {string} hookName - The name of the hook being called.
117-
* @throws {ERR_INVALID_RETURN_PROPERTY_VALUE} If the body is not a buffer source.
118-
*/
119-
function assertBufferSource(body, allowString, hookName) {
120-
if (allowString && typeof body === 'string') {
121-
return;
122-
}
123-
const { isArrayBufferView, isAnyArrayBuffer } = lazyTypes();
124-
if (isArrayBufferView(body) || isAnyArrayBuffer(body)) {
125-
return;
126-
}
127-
throw new ERR_INVALID_RETURN_PROPERTY_VALUE(
128-
`${allowString ? 'string, ' : ''}array buffer, or typed array`,
129-
hookName,
130-
'source',
131-
body,
132-
);
133-
}
134-
135-
/**
136-
* Converts a buffer or buffer-like object to a string.
137-
* @param {string | ArrayBuffer | ArrayBufferView} body - The buffer or buffer-like object to convert to a string.
138-
* @returns {string} The resulting string.
139-
*/
140-
function stringify(body) {
141-
if (typeof body === 'string') { return body; }
142-
assertBufferSource(body, false, 'load');
143-
const { TextDecoder } = require('internal/encoding');
144-
DECODER = DECODER === null ? new TextDecoder() : DECODER;
145-
return DECODER.decode(body);
146-
}
147-
148101
/**
149102
* Converts a URL to a file path if the URL protocol is 'file:'.
150103
* @param {string} url - The URL to convert.

lib/internal/modules/helpers.js

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ const {
1515
} = primordials;
1616
const {
1717
ERR_INVALID_ARG_TYPE,
18+
ERR_INVALID_RETURN_PROPERTY_VALUE,
1819
} = require('internal/errors').codes;
1920
const { BuiltinModule } = require('internal/bootstrap/realm');
2021

@@ -384,8 +385,59 @@ function isUnderNodeModules(filename) {
384385
return ArrayPrototypeIncludes(splitPath, 'node_modules');
385386
}
386387

388+
/** @type {import('internal/util/types')} */
389+
let _TYPES = null;
390+
/**
391+
* Lazily loads and returns the internal/util/types module.
392+
*/
393+
function lazyTypes() {
394+
if (_TYPES !== null) { return _TYPES; }
395+
return _TYPES = require('internal/util/types');
396+
}
397+
398+
399+
/**
400+
* Asserts that the given body is a buffer source (either a string, array buffer, or typed array).
401+
* Throws an error if the body is not a buffer source.
402+
* @param {string | ArrayBufferView | ArrayBuffer} body - The body to check.
403+
* @param {boolean} allowString - Whether or not to allow a string as a valid buffer source.
404+
* @param {string} hookName - The name of the hook being called.
405+
* @throws {ERR_INVALID_RETURN_PROPERTY_VALUE} If the body is not a buffer source.
406+
*/
407+
function assertBufferSource(body, allowString, hookName) {
408+
if (allowString && typeof body === 'string') {
409+
return;
410+
}
411+
const { isArrayBufferView, isAnyArrayBuffer } = lazyTypes();
412+
if (isArrayBufferView(body) || isAnyArrayBuffer(body)) {
413+
return;
414+
}
415+
throw new ERR_INVALID_RETURN_PROPERTY_VALUE(
416+
`${allowString ? 'string, ' : ''}array buffer, or typed array`,
417+
hookName,
418+
'source',
419+
body,
420+
);
421+
}
422+
423+
let DECODER = null;
424+
425+
/**
426+
* Converts a buffer or buffer-like object to a string.
427+
* @param {string | ArrayBuffer | ArrayBufferView} body - The buffer or buffer-like object to convert to a string.
428+
* @returns {string} The resulting string.
429+
*/
430+
function stringify(body) {
431+
if (typeof body === 'string') { return body; }
432+
assertBufferSource(body, false, 'load');
433+
const { TextDecoder } = require('internal/encoding');
434+
DECODER = DECODER === null ? new TextDecoder() : DECODER;
435+
return DECODER.decode(body);
436+
}
437+
387438
module.exports = {
388439
addBuiltinLibsToObject,
440+
assertBufferSource,
389441
getBuiltinModule,
390442
getCjsConditions,
391443
initializeCjsConditions,
@@ -394,6 +446,7 @@ module.exports = {
394446
makeRequireFunction,
395447
normalizeReferrerURL,
396448
stripTypeScriptTypes,
449+
stringify,
397450
stripBOM,
398451
toRealPath,
399452
hasStartedUserCJSExecution() {

test/es-module/test-typescript.mjs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,3 +313,14 @@ test('execute a TypeScript file with CommonJS syntax requiring .mts with require
313313
match(result.stdout, /Hello, TypeScript!/);
314314
strictEqual(result.code, 0);
315315
});
316+
317+
test('execute a JavaScript file importing a cjs TypeScript file', async () => {
318+
const result = await spawnPromisified(process.execPath, [
319+
'--experimental-strip-types',
320+
'--no-warnings',
321+
fixtures.path('typescript/ts/issue-54457.mjs'),
322+
]);
323+
strictEqual(result.stderr, '');
324+
match(result.stdout, /Hello, TypeScript!/);
325+
strictEqual(result.code, 0);
326+
});
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
// https://github.com/nodejs/node/issues/54457
2+
const { text } = await import('./test-require.ts');
3+
4+
console.log(text);
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
const util = require('node:util');
2+
3+
const text = 'Hello, TypeScript!';
4+
5+
module.exports = {
6+
text
7+
};

0 commit comments

Comments
 (0)