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

Support .mts, .cts, nodenext, and node16 #1694

Merged
merged 39 commits into from May 18, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
801b113
feat: support .mts .cts
bluelovers Dec 9, 2021
9b1d42d
stuff
cspotcode Jan 27, 2022
49832c9
WIP with TODOs
cspotcode Jan 27, 2022
7cd37a5
Merge remote-tracking branch 'origin/main' into support-mts-cts
cspotcode Mar 19, 2022
7e05614
WIP
cspotcode Mar 20, 2022
f6fcc9a
lint-fix
cspotcode Mar 20, 2022
637d232
WIP
cspotcode Mar 25, 2022
26aff19
WIP
cspotcode Mar 26, 2022
75bc08d
WIP
cspotcode Mar 26, 2022
d4d8434
Merge remote-tracking branch 'origin/main' into support-mts-cts
cspotcode May 15, 2022
f43292d
fixes
cspotcode May 15, 2022
842fe56
flatten resolver tests by splitting into more functions
cspotcode May 15, 2022
d09c552
address extension-handling code
cspotcode May 16, 2022
a09275f
lint-fix
cspotcode May 16, 2022
7f5f183
Fix; update resolver tests to test more things
cspotcode May 16, 2022
a83b026
fix
cspotcode May 16, 2022
54eccee
fix
cspotcode May 16, 2022
b7875c1
test against TS rc on nodes 14, 16, 18 (good sanity-checking with the…
cspotcode May 17, 2022
d0ef354
Teach ts.transpileModule to handle NodeNext correctly
cspotcode May 17, 2022
26001ed
tweak tests
cspotcode May 17, 2022
f217a1c
fix test typos; update pluggable dep tests; update ignore() tests for…
cspotcode May 17, 2022
44680b2
fix tests
cspotcode May 17, 2022
db98db6
fix
cspotcode May 17, 2022
7d4e1b1
fix build against stable ts, no need for 4.7.0 just yet
cspotcode May 17, 2022
26a26bb
Gate nodenext/node16 tests behind a TS version check; fix TSCommon types
cspotcode May 17, 2022
d4df204
Fix nyc require.extensions issues
cspotcode May 17, 2022
a865be3
lint-fix
cspotcode May 17, 2022
293b5cd
tricky types
cspotcode May 17, 2022
71066b2
skip nodenext tests on older TS
cspotcode May 17, 2022
3c5a2db
fix tests
cspotcode May 17, 2022
19f6442
fix bug and tests
cspotcode May 17, 2022
e3661c7
fix windows
cspotcode May 18, 2022
41e93ec
turn off another test on ancient TS versions
cspotcode May 18, 2022
b308f2b
fix windows
cspotcode May 18, 2022
5de2b23
fix allowing `moduleTypeOverrides` to override cts/cjs
cspotcode May 18, 2022
5b1dcdc
Update moduleTypes docs
cspotcode May 18, 2022
00755d0
add mts and cts awareness to internal/external classifier; add .d.cts…
cspotcode May 18, 2022
0288468
cleanup misc markdown files
cspotcode May 18, 2022
e545394
more markdown cleanup
cspotcode May 18, 2022
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
37 changes: 30 additions & 7 deletions .github/workflows/continuous-integration.yml
Expand Up @@ -51,7 +51,7 @@ jobs:
matrix:
os: [ubuntu, windows]
# Don't forget to add all new flavors to this list!
flavor: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]
flavor: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17]
include:
# Node 12.15
- flavor: 1
Expand Down Expand Up @@ -95,41 +95,64 @@ jobs:
nodeFlag: 14
typescript: next
typescriptFlag: next
- flavor: 8
node: 14
nodeFlag: 14
typescript: rc
typescriptFlag: rc
# Node 16
# Node 16.11.1
# Earliest version that supports old ESM Loader Hooks API: https://github.com/TypeStrong/ts-node/pull/1522
- flavor: 8
- flavor: 9
node: 16.11.1
nodeFlag: 16_11_1
typescript: latest
typescriptFlag: latest
- flavor: 9
- flavor: 10
node: 16
nodeFlag: 16
typescript: latest
typescriptFlag: latest
downgradeNpm: true
- flavor: 10
- flavor: 11
node: 16
nodeFlag: 16
typescript: 2.7
typescriptFlag: 2_7
downgradeNpm: true
- flavor: 11
- flavor: 12
node: 16
nodeFlag: 16
typescript: next
typescriptFlag: next
downgradeNpm: true
- flavor: 13
node: 16
nodeFlag: 16
typescript: rc
typescriptFlag: rc
downgradeNpm: true
# Node 18
- flavor: 12
- flavor: 14
node: 18
nodeFlag: 18
typescript: latest
typescriptFlag: latest
downgradeNpm: true
- flavor: 15
node: 18
nodeFlag: 18
typescript: next
typescriptFlag: next
downgradeNpm: true
- flavor: 16
node: 18
nodeFlag: 18
typescript: rc
typescriptFlag: rc
downgradeNpm: true
# Node nightly
- flavor: 13
- flavor: 17
node: nightly
nodeFlag: nightly
typescript: latest
Expand Down
20 changes: 12 additions & 8 deletions .vscode/launch.json
Expand Up @@ -10,29 +10,33 @@
"outputCapture": "std",
"skipFiles": [
"<node_internals>/**/*.js"
],
]
},
{
"name": "Debug resolver tests (example)",
"name": "Debug resolver test",
"type": "pwa-node",
"request": "launch",
"cwd": "${workspaceFolder}/tests/tmp/resolver-0015-preferSrc-typeModule-allowJs-experimentalSpecifierResolutionNode",
"cwd": "${workspaceFolder}/tests/tmp/resolver-0029-preferSrc-typeModule-allowJs-skipIgnore-experimentalSpecifierResolutionNode",
"runtimeArgs": [
"--loader", "../../../esm.mjs"
"--loader",
"../../../esm.mjs"
],
"program": "./src/entrypoint-0054-src-to-src.mjs"
"program": "./src/entrypoint-0000-src-to-src.cjs"
},
{
"name": "Debug Example: running a test fixture against local ts-node/esm loader",
"type": "pwa-node",
"request": "launch",
"cwd": "${workspaceFolder}/tests/esm",
"runtimeArgs": ["--loader", "../../ts-node/esm"],
"runtimeArgs": [
"--loader",
"../../ts-node/esm"
],
"program": "throw error.ts",
"outputCapture": "std",
"skipFiles": [
"<node_internals>/**/*.js"
],
]
}
]
}
}
10 changes: 10 additions & 0 deletions development-docs/README.md
@@ -0,0 +1,10 @@
This directory contains a variety of documents:

- notes
- old to-do lists
- design ideas from when I implemented various features
- templates for drafting release notes
- etc

It is useful to me to keep these notes. If you find their presence
confusing, you can safely ignore this directory.
53 changes: 53 additions & 0 deletions development-docs/nodenextNode16.md
@@ -0,0 +1,53 @@
# Adding support for NodeNext, Node16, `.cts`, `.mts`, `.cjs`, `.mjs`

*This feature has already been implemented. Here are my notes from when
I was doing the work*

## TODOs

Implement node module type classifier:
- if NodeNext or Node12: ask classifier for CJS or ESM determination
Add `ForceNodeNextCJSEmit`

Does our code check for .d.ts extensions anywhere?
- if so, teach it about .d.cts and .d.mts

For nodenext and node12, support supplemental "flavor" information:
-

Think about splitting out index.ts further:
- register.ts - hooking stuff
- types.ts
- env.ts - env vars and global registration types (process.symbol)
- service.ts

# TESTS

Matrix:

- package.json type absent, commonjs, and module
- import and require
- from cjs and esm
- .cts, .cjs
- .mts, .mjs
- typechecking, transpileOnly, and swc
- dynamic import
- import = require
- static import
- allowJs on and off

Notes about specific matrix entries:
- require mjs, mts from cjs throws error

Rethink:
`getOutput`: null in transpile-only mode. Also may return emitskipped
`getOutputTranspileOnly`: configured module option
`getOutputForceCommonJS`: `commonjs` module option
`getOutputForceNodeCommonJS`: `nodenext` cjs module option
`getOutputForceESM`: `esnext` module option

Add second layer of classification to classifier:
if classifier returns `auto` (no `moduleType` override)
- if `getOutput` emits, done
- else call `nodeModuleTypeClassifier`
- delegate to appropriate `getOutput` based on its response
2 changes: 2 additions & 0 deletions notes2.md → development-docs/rootDirOutDirMapping.md
@@ -1,3 +1,5 @@
## Musings about resolving between rootDir and outDir

When /dist and /src are understood to be overlaid because of src -> dist compiling
/dist/
/src/
Expand Down
4 changes: 1 addition & 3 deletions NOTES.md → development-docs/yarnPnpInterop.md
@@ -1,6 +1,4 @@
*Delete this file before merging this PR*

## PnP interop
## Yarn PnP interop

Asked about it here:
https://discord.com/channels/226791405589233664/654372321225605128/957301175609344070
Expand Down
87 changes: 48 additions & 39 deletions dist-raw/node-internal-modules-cjs-loader.js
Expand Up @@ -5,7 +5,10 @@
'use strict';

const {
ArrayIsArray,
ArrayPrototypeIncludes,
ArrayPrototypeJoin,
ArrayPrototypePush,
JSONParse,
ObjectKeys,
RegExpPrototypeTest,
Expand Down Expand Up @@ -46,6 +49,8 @@ const {

const Module = require('module');

const isWindows = process.platform === 'win32';

let statCache = null;

function stat(filename) {
Expand Down Expand Up @@ -133,12 +138,13 @@ function readPackageScope(checkPath) {
/**
* @param {{
* nodeEsmResolver: ReturnType<typeof import('./node-internal-modules-esm-resolve').createResolve>,
* compiledExtensions: string[],
* extensions: import('../src/file-extensions').Extensions,
* preferTsExts
* }} opts
*/
function createCjsLoader(opts) {
const {nodeEsmResolver, compiledExtensions, preferTsExts} = opts;
const {nodeEsmResolver, preferTsExts} = opts;
const {replacementsForCjs, replacementsForJs, replacementsForMjs} = opts.extensions;
const {
encodedSepRegEx,
packageExportsResolve,
Expand Down Expand Up @@ -209,47 +215,42 @@ function toRealPath(requestPath) {
});
}

/**
* TS's resolver can resolve foo.js to foo.ts, by replacing .js extension with several source extensions.
* IMPORTANT: preserve ordering according to preferTsExts; this affects resolution behavior!
*/
const extensions = Array.from(new Set([
...(preferTsExts ? compiledExtensions : []),
'.js', '.json', '.node', '.mjs', '.cjs',
...compiledExtensions
]));
const replacementExtensions = {
'.js': extensions.filter(ext => ['.js', '.jsx', '.ts', '.tsx'].includes(ext)),
'.cjs': extensions.filter(ext => ['.cjs', '.cts'].includes(ext)),
'.mjs': extensions.filter(ext => ['.mjs', '.mts'].includes(ext)),
};

const replacableExtensionRe = /(\.(?:js|cjs|mjs))$/;

function statReplacementExtensions(p) {
const match = p.match(replacableExtensionRe);
if (match) {
const replacementExts = replacementExtensions[match[1]];
const pathnameWithoutExtension = p.slice(0, -match[1].length);
for (let i = 0; i < replacementExts.length; i++) {
const filename = pathnameWithoutExtension + replacementExts[i];
const rc = stat(filename);
if (rc === 0) {
return [rc, filename];
const lastDotIndex = p.lastIndexOf('.');
if(lastDotIndex >= 0) {
const ext = p.slice(lastDotIndex);
if (ext === '.js' || ext === '.cjs' || ext === '.mjs') {
const pathnameWithoutExtension = p.slice(0, lastDotIndex);
const replacementExts =
ext === '.js' ? replacementsForJs
: ext === '.mjs' ? replacementsForMjs
: replacementsForCjs;
for (let i = 0; i < replacementExts.length; i++) {
const filename = pathnameWithoutExtension + replacementExts[i];
const rc = stat(filename);
if (rc === 0) {
return [rc, filename];
}
}
}
}
return [stat(p), p];
}
function tryReplacementExtensions(p, isMain) {
const match = p.match(replacableExtensionRe);
if (match) {
const replacementExts = replacementExtensions[match[1]];
const pathnameWithoutExtension = p.slice(0, -match[1].length);
for (let i = 0; i < replacementExts.length; i++) {
const filename = tryFile(pathnameWithoutExtension + replacementExts[i], isMain);
if (filename) {
return filename;
const lastDotIndex = p.lastIndexOf('.');
if(lastDotIndex >= 0) {
const ext = p.slice(lastDotIndex);
if (ext === '.js' || ext === '.cjs' || ext === '.mjs') {
const pathnameWithoutExtension = p.slice(0, lastDotIndex);
const replacementExts =
ext === '.js' ? replacementsForJs
: ext === '.mjs' ? replacementsForMjs
: replacementsForCjs;
for (let i = 0; i < replacementExts.length; i++) {
const filename = tryFile(pathnameWithoutExtension + replacementExts[i], isMain);
if (filename) {
return filename;
}
}
}
}
Expand Down Expand Up @@ -564,11 +565,18 @@ function assertScriptCanLoadAsCJSImpl(service, module, filename) {
const pkg = readPackageScope(filename);

// ts-node modification: allow our configuration to override
const tsNodeClassification = service.moduleTypeClassifier.classifyModule(normalizeSlashes(filename));
const tsNodeClassification = service.moduleTypeClassifier.classifyModuleByModuleTypeOverrides(normalizeSlashes(filename));
if(tsNodeClassification.moduleType === 'cjs') return;

// ignore package.json when file extension is ESM-only or CJS-only
// [MUST_UPDATE_FOR_NEW_FILE_EXTENSIONS]
const lastDotIndex = filename.lastIndexOf('.');
const ext = lastDotIndex >= 0 ? filename.slice(lastDotIndex) : '';

if((ext === '.cts' || ext === '.cjs') && tsNodeClassification.moduleType === 'auto') return;

// Function require shouldn't be used in ES modules.
if (tsNodeClassification.moduleType === 'esm' || (pkg && pkg.data && pkg.data.type === 'module')) {
if (ext === '.mts' || ext === '.mjs' || tsNodeClassification.moduleType === 'esm' || (pkg && pkg.data && pkg.data.type === 'module')) {
const parentPath = module.parent && module.parent.filename;
const packageJsonPath = pkg ? path.resolve(pkg.path, 'package.json') : null;
throw createErrRequireEsm(filename, parentPath, packageJsonPath);
Expand All @@ -578,5 +586,6 @@ function assertScriptCanLoadAsCJSImpl(service, module, filename) {

module.exports = {
createCjsLoader,
assertScriptCanLoadAsCJSImpl
assertScriptCanLoadAsCJSImpl,
readPackageScope
};