Skip to content

Commit 0ef075b

Browse files
committed
module: allow type stripping in node_modules
Remove the restriction that disallowed stripping types from files inside node_modules folders, so packages can ship TypeScript sources directly. Without this change, in a monorepo every workspace package has to ship JavaScript (or both JavaScript and TypeScript) to be consumable by another package, or its contents have to be manually copied to a non-node_modules directory before they can be loaded. Neither of these workarounds is ideal. The ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING error is removed, and TypeScript files under node_modules are now stripped like any other TypeScript file. Signed-off-by: Yagiz Nizipli <yagiz@nizipli.com>
1 parent da00166 commit 0ef075b

11 files changed

Lines changed: 107 additions & 45 deletions

File tree

β€Ždoc/api/errors.mdβ€Ž

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3314,16 +3314,6 @@ import 'package-name'; // supported
33143314

33153315
`import` with URL schemes other than `file` and `data` is unsupported.
33163316

3317-
<a id="ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING"></a>
3318-
3319-
### `ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING`
3320-
3321-
<!-- YAML
3322-
added: v22.6.0
3323-
-->
3324-
3325-
Type stripping is not supported for files descendent of a `node_modules` directory.
3326-
33273317
<a id="ERR_UNSUPPORTED_RESOLVE_REQUEST"></a>
33283318

33293319
### `ERR_UNSUPPORTED_RESOLVE_REQUEST`
@@ -4190,6 +4180,19 @@ An attempt was made to launch a Node.js process with an unknown `stdout` or
41904180
`stderr` file type. This error is usually an indication of a bug within Node.js
41914181
itself, although it is possible for user code to trigger it.
41924182

4183+
<a id="ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING"></a>
4184+
4185+
### `ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING`
4186+
4187+
<!-- YAML
4188+
added: v22.6.0
4189+
removed: REPLACEME
4190+
-->
4191+
4192+
Type stripping was not supported for files descendent of a `node_modules`
4193+
directory. This restriction has been removed, and these files are now
4194+
type-stripped like any other TypeScript file.
4195+
41934196
<a id="ERR_V8BREAKITERATOR"></a>
41944197

41954198
### `ERR_V8BREAKITERATOR`

β€Ždoc/api/typescript.mdβ€Ž

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
<!-- YAML
44
changes:
5+
- version: REPLACEME
6+
pr-url: https://github.com/nodejs/node/pull/63853
7+
description: Type stripping is now supported for files inside
8+
`node_modules` folders.
59
- version: v26.0.0
610
pr-url: https://github.com/nodejs/node/pull/61803
711
description: Removed `--experimental-transform-types` flag.
@@ -213,9 +217,28 @@ correct line numbers in stack traces; and Node.js does not generate them.
213217

214218
### Type stripping in dependencies
215219

216-
To discourage package authors from publishing packages written in TypeScript,
217-
Node.js refuses to handle TypeScript files inside folders under a `node_modules`
218-
path.
220+
<!-- YAML
221+
changes:
222+
- version: REPLACEME
223+
pr-url: https://github.com/nodejs/node/pull/63853
224+
description: Type stripping is now supported for files inside
225+
`node_modules` folders.
226+
-->
227+
228+
Type stripping works for TypeScript files inside folders under a
229+
`node_modules` path, so packages can ship TypeScript sources directly
230+
instead of transpiling to JavaScript before publishing.
231+
232+
Like all type-stripped files, files in `node_modules` are limited to
233+
erasable TypeScript syntax and follow the same rules as the rest of the
234+
application code; for example, [file extensions are mandatory][] in `import`
235+
specifiers. Package authors who want to publish TypeScript sources should
236+
reference the `.ts` files directly from the [`"exports"`][] field of
237+
`package.json`.
238+
239+
To avoid paying the cost of stripping types from dependencies on every
240+
startup, the [module compile cache][] can be used to cache the stripped
241+
output on disk.
219242

220243
### Paths aliases
221244

@@ -226,13 +249,15 @@ with `#`.
226249
[CommonJS]: modules.md
227250
[ES Modules]: esm.md
228251
[Full TypeScript support]: #full-typescript-support
252+
[`"exports"`]: packages.md#exports
229253
[`--no-strip-types`]: cli.md#--no-strip-types
230254
[`ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX`]: errors.md#err_unsupported_typescript_syntax
231255
[`tsconfig` "paths"]: https://www.typescriptlang.org/tsconfig/#paths
232256
[`tsx`]: https://tsx.hirok.io/
233257
[`verbatimModuleSyntax`]: https://www.typescriptlang.org/tsconfig/#verbatimModuleSyntax
234258
[file extensions are mandatory]: esm.md#mandatory-file-extensions
235259
[full support]: #full-typescript-support
260+
[module compile cache]: module.md#module-compile-cache
236261
[subpath imports]: packages.md#subpath-imports
237262
[the same way as `.js` files.]: packages.md#determining-module-system
238263
[type stripping]: #type-stripping

β€Žlib/internal/errors.jsβ€Ž

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1898,9 +1898,6 @@ E('ERR_UNSUPPORTED_ESM_URL_SCHEME', (url, supported) => {
18981898
msg += `. Received protocol '${url.protocol}'`;
18991899
return msg;
19001900
}, Error);
1901-
E('ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING',
1902-
'Stripping types is currently unsupported for files under node_modules, for "%s"',
1903-
Error);
19041901
E('ERR_UNSUPPORTED_RESOLVE_REQUEST',
19051902
'Failed to resolve module specifier "%s" from "%s": Invalid relative URL or base scheme is not hierarchical.',
19061903
TypeError);

β€Žlib/internal/modules/typescript.jsβ€Ž

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,9 @@ const {
1111
const { assertTypeScript,
1212
emitExperimentalWarning,
1313
getLazy,
14-
isUnderNodeModules,
1514
kEmptyObject } = require('internal/util');
1615
const {
1716
ERR_INVALID_TYPESCRIPT_SYNTAX,
18-
ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING,
1917
ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX,
2018
} = require('internal/errors').codes;
2119
const assert = require('internal/assert');
@@ -151,9 +149,6 @@ function stripTypeScriptTypesForCoverage(code) {
151149
*/
152150
function stripTypeScriptModuleTypes(source, filename, sourceURL) {
153151
assert(typeof source === 'string');
154-
if (isUnderNodeModules(filename)) {
155-
throw new ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING(filename);
156-
}
157152
// Get a compile cache entry into the native compile cache store,
158153
// keyed by the filename. If the cache can already be loaded on disk,
159154
// cached.transpiled contains the cached string. Otherwise we should do

β€Žtest/es-module/test-typescript-commonjs.mjsβ€Ž

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -133,24 +133,26 @@ test('execute a .cts file importing a .mts file export', async () => {
133133
assert.strictEqual(result.code, 0);
134134
});
135135

136-
test('expect failure of a .cts file in node_modules', async () => {
136+
test('execute a .cts file in node_modules', async () => {
137137
const result = await spawnPromisified(process.execPath, [
138+
'--no-warnings',
138139
fixtures.path('typescript/cts/test-cts-node_modules.cts'),
139140
]);
140141

141-
assert.strictEqual(result.stdout, '');
142-
assert.match(result.stderr, /ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING/);
143-
assert.strictEqual(result.code, 1);
142+
assert.strictEqual(result.stderr, '');
143+
assert.match(result.stdout, /Hello, TypeScript!/);
144+
assert.strictEqual(result.code, 0);
144145
});
145146

146-
test('expect failure of a .ts file in node_modules', async () => {
147+
test('execute a .ts file in node_modules', async () => {
147148
const result = await spawnPromisified(process.execPath, [
149+
'--no-warnings',
148150
fixtures.path('typescript/cts/test-ts-node_modules.cts'),
149151
]);
150152

151-
assert.strictEqual(result.stdout, '');
152-
assert.match(result.stderr, /ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING/);
153-
assert.strictEqual(result.code, 1);
153+
assert.strictEqual(result.stderr, '');
154+
assert.match(result.stdout, /Hello, TypeScript!/);
155+
assert.strictEqual(result.code, 0);
154156
});
155157

156158
test('expect failure of a .cts requiring esm without default type module', async () => {
@@ -164,15 +166,16 @@ test('expect failure of a .cts requiring esm without default type module', async
164166
assert.strictEqual(result.code, 1);
165167
});
166168

167-
test('expect failure of a .cts file requiring esm in node_modules', async () => {
169+
test('execute a .cts file requiring esm in node_modules', async () => {
168170
const result = await spawnPromisified(process.execPath, [
169171
'--experimental-require-module',
172+
'--no-warnings',
170173
fixtures.path('typescript/cts/test-mts-node_modules.cts'),
171174
]);
172175

173-
assert.strictEqual(result.stdout, '');
174-
assert.match(result.stderr, /ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING/);
175-
assert.strictEqual(result.code, 1);
176+
assert.strictEqual(result.stderr, '');
177+
assert.match(result.stdout, /Hello, TypeScript!/);
178+
assert.strictEqual(result.code, 0);
176179
});
177180

178181
test('cts -> require mts -> import cts', async () => {

β€Žtest/es-module/test-typescript-module.mjsβ€Ž

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -49,32 +49,35 @@ test('execute an .mts file importing a .cts file', async () => {
4949

5050
test('execute an .mts file from node_modules', async () => {
5151
const result = await spawnPromisified(process.execPath, [
52+
'--no-warnings',
5253
fixtures.path('typescript/mts/test-mts-node_modules.mts'),
5354
]);
5455

55-
assert.match(result.stderr, /ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING/);
56-
assert.strictEqual(result.stdout, '');
57-
assert.strictEqual(result.code, 1);
56+
assert.strictEqual(result.stderr, '');
57+
assert.match(result.stdout, /Hello, TypeScript!/);
58+
assert.strictEqual(result.code, 0);
5859
});
5960

6061
test('execute a .cts file from node_modules', async () => {
6162
const result = await spawnPromisified(process.execPath, [
63+
'--no-warnings',
6264
fixtures.path('typescript/mts/test-cts-node_modules.mts'),
6365
]);
6466

65-
assert.match(result.stderr, /ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING/);
66-
assert.strictEqual(result.stdout, '');
67-
assert.strictEqual(result.code, 1);
67+
assert.strictEqual(result.stderr, '');
68+
assert.match(result.stdout, /Hello, TypeScript!/);
69+
assert.strictEqual(result.code, 0);
6870
});
6971

7072
test('execute a .ts file from node_modules', async () => {
7173
const result = await spawnPromisified(process.execPath, [
74+
'--no-warnings',
7275
fixtures.path('typescript/mts/test-ts-node_modules.mts'),
7376
]);
7477

75-
assert.match(result.stderr, /ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING/);
76-
assert.strictEqual(result.stdout, '');
77-
assert.strictEqual(result.code, 1);
78+
assert.strictEqual(result.stderr, '');
79+
assert.match(result.stdout, /Hello, TypeScript!/);
80+
assert.strictEqual(result.code, 0);
7881
});
7982

8083
test('execute an empty .ts file', async () => {

β€Žtest/es-module/test-typescript.mjsβ€Ž

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,17 @@ test('execute a TypeScript file with node_modules', async () => {
6161
assert.strictEqual(result.code, 0);
6262
});
6363

64+
test('execute a TypeScript package from node_modules via "exports"', async () => {
65+
const result = await spawnPromisified(process.execPath, [
66+
'--no-warnings',
67+
fixtures.path('typescript/ts/test-import-ts-exports-node-modules.ts'),
68+
]);
69+
70+
assert.strictEqual(result.stderr, '');
71+
assert.match(result.stdout, /Hello, TypeScript!\nHello, TypeScript!/);
72+
assert.strictEqual(result.code, 0);
73+
});
74+
6475
test('expect error when executing a TypeScript file with imports with no extensions', async () => {
6576
const result = await spawnPromisified(process.execPath, [
6677
fixtures.path('typescript/ts/test-import-no-extension.ts'),
@@ -166,12 +177,13 @@ test('expect stack trace of a TypeScript file to be correct', async () => {
166177

167178
test('execute CommonJS TypeScript file from node_modules with require-module', async () => {
168179
const result = await spawnPromisified(process.execPath, [
180+
'--no-warnings',
169181
fixtures.path('typescript/ts/test-import-ts-node-modules.ts'),
170182
]);
171183

172-
assert.match(result.stderr, /ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING/);
173-
assert.strictEqual(result.stdout, '');
174-
assert.strictEqual(result.code, 1);
184+
assert.strictEqual(result.stderr, '');
185+
assert.match(result.stdout, /Hello, TypeScript!/);
186+
assert.strictEqual(result.code, 0);
175187
});
176188

177189
test('execute a TypeScript file with CommonJS syntax requiring .cts', async () => {

β€Žtest/fixtures/typescript/ts/node_modules/typescript-pkg/index.tsβ€Ž

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

β€Žtest/fixtures/typescript/ts/node_modules/typescript-pkg/lib/sub.mtsβ€Ž

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

β€Žtest/fixtures/typescript/ts/node_modules/typescript-pkg/package.jsonβ€Ž

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
Β (0)