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

Update esm loader hooks API #1457

Merged
merged 36 commits into from Oct 10, 2021
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
2108b58
Initial commit
jonaskello Sep 12, 2021
f8ef837
Merge branch 'main' into resolver-hooks-update
jonaskello Sep 12, 2021
073bbfc
Update hooks
jonaskello Sep 12, 2021
765932f
Merge branch 'resolver-hooks-update' of github.com:jonaskello/ts-node…
jonaskello Sep 12, 2021
786ec34
wip impl of load
jonaskello Sep 12, 2021
14220ac
Expose old hooks for backward compat
jonaskello Sep 12, 2021
cd44ef3
Some logging
jonaskello Sep 13, 2021
245242b
Add raw copy of default get format
jonaskello Sep 17, 2021
0b0d50c
Adapt defaultGetFormat() from node source
jonaskello Sep 17, 2021
5534722
Fix defaultTransformSource
jonaskello Sep 17, 2021
1030783
Add missing newline
jonaskello Sep 17, 2021
9f28485
Fix require
jonaskello Sep 17, 2021
662b4b8
Check node version to avoid deprecation warning
jonaskello Sep 17, 2021
469437b
Remove load from old hooks
jonaskello Sep 17, 2021
b102ee6
Add some comments
jonaskello Sep 18, 2021
2044208
Use versionGte
jonaskello Sep 18, 2021
b2fffe7
Remove logging
jonaskello Sep 18, 2021
d41204c
Refine comments
jonaskello Sep 18, 2021
da34bf7
Wording
jonaskello Sep 18, 2021
a7e3475
Use format hint if available
jonaskello Sep 18, 2021
e228e08
One more comment
jonaskello Sep 18, 2021
f4dee40
Nitpicky changes to comments
cspotcode Sep 18, 2021
0025586
Update index.ts
cspotcode Sep 18, 2021
6ffe9ea
lint-fix
cspotcode Sep 18, 2021
521a1e2
attempt at downloading node nightly in tests
cspotcode Sep 18, 2021
8b94f1b
fix
cspotcode Sep 18, 2021
10e5580
fix
cspotcode Sep 18, 2021
77acae6
Windows install of node nightly
cspotcode Sep 18, 2021
8e40254
update version checks to be ready for node backporting
cspotcode Sep 23, 2021
d3ed4a1
Add guards for undefined source
jonaskello Sep 25, 2021
544584a
More error info
jonaskello Sep 25, 2021
a23bc47
Skip source transform for builtin and commonjs
jonaskello Sep 25, 2021
63c0618
Update transpile-only.mjs
cspotcode Oct 6, 2021
ca9cde9
Merge remote-tracking branch 'origin/main' into resolver-hooks-update
cspotcode Oct 10, 2021
e576772
Tweak `createEsmHooks` type
cspotcode Oct 10, 2021
8b328d3
fix test to accomodate new api
cspotcode Oct 10, 2021
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
83 changes: 83 additions & 0 deletions dist-raw/node-esm-default-get-format.js
@@ -0,0 +1,83 @@
// Copied from https://raw.githubusercontent.com/nodejs/node/v15.3.0/lib/internal/modules/esm/get_format.js
// Then modified to suite our needs.
// Formatting is intentionally bad to keep the diff as small as possible, to make it easier to merge
// upstream changes and understand our modifications.

'use strict';
const {
RegExpPrototypeExec,
StringPrototypeStartsWith,
} = require('./node-primordials');
const { extname } = require('path');
const { getOptionValue } = require('./node-options');

const experimentalJsonModules = getOptionValue('--experimental-json-modules');
const experimentalSpeciferResolution =
getOptionValue('--experimental-specifier-resolution');
const experimentalWasmModules = getOptionValue('--experimental-wasm-modules');
const { getPackageType } = require('./node-esm-resolve-implementation.js').createResolve({tsExtensions: [], jsExtensions: []});
const { URL, fileURLToPath } = require('url');
const { ERR_UNKNOWN_FILE_EXTENSION } = require('./node-errors').codes;

const extensionFormatMap = {
'__proto__': null,
'.cjs': 'commonjs',
'.js': 'module',
'.mjs': 'module'
};

const legacyExtensionFormatMap = {
'__proto__': null,
'.cjs': 'commonjs',
'.js': 'commonjs',
'.json': 'commonjs',
'.mjs': 'module',
'.node': 'commonjs'
};

if (experimentalWasmModules)
extensionFormatMap['.wasm'] = legacyExtensionFormatMap['.wasm'] = 'wasm';

if (experimentalJsonModules)
extensionFormatMap['.json'] = legacyExtensionFormatMap['.json'] = 'json';

function defaultGetFormat(url, context, defaultGetFormatUnused) {
if (StringPrototypeStartsWith(url, 'node:')) {
return { format: 'builtin' };
}
const parsed = new URL(url);
if (parsed.protocol === 'data:') {
const [ , mime ] = RegExpPrototypeExec(
/^([^/]+\/[^;,]+)(?:[^,]*?)(;base64)?,/,
parsed.pathname,
) || [ null, null, null ];
const format = ({
'__proto__': null,
'text/javascript': 'module',
'application/json': experimentalJsonModules ? 'json' : null,
'application/wasm': experimentalWasmModules ? 'wasm' : null
})[mime] || null;
return { format };
} else if (parsed.protocol === 'file:') {
const ext = extname(parsed.pathname);
let format;
if (ext === '.js') {
format = getPackageType(parsed.href) === 'module' ? 'module' : 'commonjs';
} else {
format = extensionFormatMap[ext];
}
if (!format) {
if (experimentalSpeciferResolution === 'node') {
process.emitWarning(
'The Node.js specifier resolution in ESM is experimental.',
'ExperimentalWarning');
format = legacyExtensionFormatMap[ext];
} else {
throw new ERR_UNKNOWN_FILE_EXTENSION(ext, fileURLToPath(url));
}
}
return { format: format || null };
}
return { format: null };
}
exports.defaultGetFormat = defaultGetFormat;
1 change: 1 addition & 0 deletions esm.mjs
Expand Up @@ -6,6 +6,7 @@ const require = createRequire(fileURLToPath(import.meta.url));
const esm = require('./dist/esm');
export const {
resolve,
load,
getFormat,
transformSource,
} = esm.registerAndCreateEsmHooks();
3 changes: 2 additions & 1 deletion package.json
Expand Up @@ -69,7 +69,8 @@
"test-local": "npm run lint-fix && npm run build-tsc && npm run build-pack && npm run test-spec --",
"coverage-report": "nyc report --reporter=lcov",
"prepare": "npm run clean && npm run build-nopack",
"api-extractor": "api-extractor run --local --verbose"
"api-extractor": "api-extractor run --local --verbose",
"esm-usage-example": "npm run build-tsc && cd esm-usage-example && node --experimental-specifier-resolution node --loader ../esm.mjs ./index"
},
"engines": {
"node": ">=12.0.0"
Expand Down
37 changes: 36 additions & 1 deletion src/esm.ts
Expand Up @@ -12,6 +12,7 @@ import { normalizeSlashes } from './util';
const {
createResolve,
} = require('../dist-raw/node-esm-resolve-implementation');
const { defaultGetFormat } = require('../dist-raw/node-esm-default-get-format');

// Note: On Windows, URLs look like this: file:///D:/dev/@TypeStrong/ts-node-examples/foo.ts

Expand All @@ -28,7 +29,12 @@ export function registerAndCreateEsmHooks(opts?: RegisterOptions) {
preferTsExts: tsNodeInstance.options.preferTsExts,
});

return { resolve, getFormat, transformSource };
// The hooks API changed in node version X so we need to check for backwards compatibility
// TODO: When the new API is released, change to the correct node version here
const newHooksAPI = parseInt(process.versions.node.split('.')[0], 10) >= 17;
jonaskello marked this conversation as resolved.
Show resolved Hide resolved
return newHooksAPI
? { resolve, load }
: { resolve, getFormat, transformSource };
jonaskello marked this conversation as resolved.
Show resolved Hide resolved

function isFileUrlOrNodeStyleSpecifier(parsed: UrlWithStringQuery) {
// We only understand file:// URLs, but in node, the specifier can be a node-style `./foo` or `foo`
Expand Down Expand Up @@ -72,6 +78,35 @@ export function registerAndCreateEsmHooks(opts?: RegisterOptions) {
);
}

async function load(
url: string,
context: {},
defaultLoad: typeof load
): Promise<{ format: Format; source: string | Buffer }> {
const { format } = await getFormat(url, context, defaultGetFormat);
cspotcode marked this conversation as resolved.
Show resolved Hide resolved

const { source: rawSource } = await defaultLoad(
url,
{ format },
defaultLoad
);

const defaultTransformSource: typeof transformSource = async (
cspotcode marked this conversation as resolved.
Show resolved Hide resolved
source,
_context,
_defaultTransformSource
) => ({ source });
const { source } = await transformSource(
rawSource,
{ url, format },
defaultTransformSource
);
return {
format,
source,
};
}

type Format = 'builtin' | 'commonjs' | 'dynamic' | 'json' | 'module' | 'wasm';
async function getFormat(
url: string,
Expand Down