Skip to content
This repository has been archived by the owner on Oct 18, 2023. It is now read-only.

support new ts extensions #42

Draft
wants to merge 2 commits into
base: develop
Choose a base branch
from
Draft
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
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,14 @@
"main": "./dist/index.cjs",
"exports": "./dist/index.cjs",
"scripts": {
"build": "pkgroll --target node12.20 --minify",
"build": "pkgroll --target node12.20",
"lint": "eslint .",
"type-check": "tsc --noEmit",
"test": "pnpm build && tsx tests/index.ts",
"prepack": "pnpm build && clean-pkg-json"
},
"dependencies": {
"@esbuild-kit/core-utils": "^3.3.2",
"@esbuild-kit/core-utils": "github:esbuild-kit/core-utils#npm/support-ts-extensions",
"get-tsconfig": "^4.7.0"
},
"devDependencies": {
Expand Down
14 changes: 12 additions & 2 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions src/@types/module.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,16 @@ declare module 'module' {
export const _extensions: NodeJS.RequireExtensions;

export type Parent = {
/**
* I think filename is the more accurate but if it's not available,
* fallback to the id since is sometimes accurate.
*
* The filename is not available when a dynamic import is detected
* and it gets resolved before the parent module has finished ".load()"
* which is the method that sets the filename
*/
id: string;

/**
* Can be null if the parent id is 'internal/preload' (e.g. via --require)
* which doesn't have a file path.
Expand Down
117 changes: 86 additions & 31 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import Module from 'module';
import {
transformSync,
installSourceMapSupport,
resolveTsPath,
// resolveTsPath,
transformDynamicImport,
compareNodeVersion,
} from '@esbuild-kit/core-utils';
Expand All @@ -16,6 +16,40 @@ import {
} from 'get-tsconfig';
import type { TransformOptions } from 'esbuild';

// import path from 'path';

const tsExtensions: Record<string, string[]> = Object.create(null);
tsExtensions[''] = ['.ts', '.tsx', '.js', '.jsx'];
tsExtensions['.js'] = ['.ts', '.tsx', '.js', '.jsx'];
tsExtensions['.jsx'] = ['.tsx', '.ts', '.jsx', '.js'];
tsExtensions['.cjs'] = ['.cts'];
tsExtensions['.mjs'] = ['.mts'];

const resolveTsPath = (
filePath: string,
) => {
const extension = path.extname(filePath);
const [extensionNoQuery, query] = path.extname(filePath).split('?');
const possibleExtensions = tsExtensions[extensionNoQuery];

if (possibleExtensions) {
const extensionlessPath = filePath.slice(
0,
// If there's no extension (0), slicing to 0 returns an empty path
-extension.length || undefined,
);
return possibleExtensions.map(
tsExtension => (
extensionlessPath
+ tsExtension
+ (query ? `?${query}` : '')
),
);
}
};



const isRelativePathPattern = /^\.{1,2}\//;
const isTsFilePatten = /\.[cm]?tsx?$/;
const nodeModulesPath = `${path.sep}node_modules${path.sep}`;
Expand Down Expand Up @@ -109,17 +143,31 @@ const transformer = (
*/
'.js',

/**
* Loaders for implicitly resolvable extensions
* https://github.com/nodejs/node/blob/v12.16.0/lib/internal/modules/cjs/loader.js#L1166
*/
'.ts',
'.tsx',
'.jsx',
// /**
// * Loaders for implicitly resolvable extensions
// * https://github.com/nodejs/node/blob/v12.16.0/lib/internal/modules/cjs/loader.js#L1166
// */
// '.ts',
// '.tsx',
// '.jsx',
].forEach((extension) => {
extensions[extension] = transformer;
});

[
'.ts',
'.tsx',
'.jsx'
].forEach((ext) => {
Object.defineProperty(extensions, ext, {
value: transformer,

// Prevent Object.keys from detecting these extensions
// when CJS loader iterates over the possible extensions
enumerable: false,
});
});

/**
* Loaders for explicitly resolvable extensions
* (basically just .mjs because CJS loader has a special handler for it)
Expand All @@ -146,7 +194,7 @@ const supportsNodePrefix = (
);

// Add support for "node:" protocol
const resolveFilename = Module._resolveFilename.bind(Module);
const defaultResolveFilename = Module._resolveFilename.bind(Module);
Module._resolveFilename = (request, parent, isMain, options) => {
// Added in v12.20.0
// https://nodejs.org/api/esm.html#esm_node_imports
Expand All @@ -172,7 +220,7 @@ Module._resolveFilename = (request, parent, isMain, options) => {
}

try {
return resolveFilename(
return defaultResolveFilename(
possiblePath,
parent,
isMain,
Expand All @@ -187,7 +235,7 @@ Module._resolveFilename = (request, parent, isMain, options) => {
return tsFilename;
}

return resolveFilename(request, parent, isMain, options);
return defaultResolveFilename(request, parent, isMain, options);
};

type NodeError = Error & {
Expand All @@ -203,28 +251,35 @@ const resolveTsFilename = (
isMain: boolean,
options?: Record<PropertyKey, unknown>,
) => {
const tsPath = resolveTsPath(request);
const parentFileName = parent?.filename ?? parent?.id;
if (!parentFileName || !isTsFilePatten.test(parentFileName)) {
return;
}

if (
parent?.filename
&& isTsFilePatten.test(parent.filename)
&& tsPath
) {
try {
return resolveFilename(
tsPath,
parent,
isMain,
options,
);
} catch (error) {
const { code } = error as NodeError;
if (
code !== 'MODULE_NOT_FOUND'
&& code !== 'ERR_PACKAGE_PATH_NOT_EXPORTED'
) {
throw error;
const tsPaths = resolveTsPath(request);
if (tsPaths) {
for (const tsPath of tsPaths) {
try {
return defaultResolveFilename(
tsPath,
parent,
isMain,
options,
);
} catch (error) {
const { code } = error as NodeError;
if (
code !== 'MODULE_NOT_FOUND'
&& code !== 'ERR_PACKAGE_PATH_NOT_EXPORTED'
) {
throw error;
}
}
}

console.log('add index', {
request,
parent,
});
}
};
1 change: 1 addition & 0 deletions tests/fixtures/test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
require('./lib/ts-ext-ts');
16 changes: 8 additions & 8 deletions tests/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,18 @@ const nodeVersions = [
const node = await createNode(nodeVersion, './tests/fixtures');

await describe(`Node ${node.version}`, ({ runTestSuite }) => {
runTestSuite(
import('./specs/javascript/index.js'),
node,
);
// runTestSuite(
// import('./specs/javascript/index.js'),
// node,
// );
runTestSuite(
import('./specs/typescript/index.js'),
node,
);
runTestSuite(
import('./specs/negative-tests.js'),
node,
);
// runTestSuite(
// import('./specs/negative-tests.js'),
// node,
// );
});
}
})();
12 changes: 6 additions & 6 deletions tests/specs/typescript/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ import type { NodeApis } from '../../utils/node-with-loader.js';
export default testSuite(async ({ describe }, node: NodeApis) => {
describe('TypeScript', async ({ runTestSuite }) => {
runTestSuite(import('./ts.js'), node);
runTestSuite(import('./tsx.js'), node);
runTestSuite(import('./jsx.js'), node);
runTestSuite(import('./mts.js'), node);
runTestSuite(import('./cts.js'), node);
runTestSuite(import('./tsconfig.js'), node);
runTestSuite(import('./dependencies.js'), node);
// runTestSuite(import('./tsx.js'), node);
// runTestSuite(import('./jsx.js'), node);
// runTestSuite(import('./mts.js'), node);
// runTestSuite(import('./cts.js'), node);
// runTestSuite(import('./tsconfig.js'), node);
// runTestSuite(import('./dependencies.js'), node);
});
});
40 changes: 37 additions & 3 deletions tests/specs/typescript/ts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,11 +87,38 @@ export default testSuite(async ({ describe }, node: NodeApis) => {
});
});

describe('full path via .jsx', ({ test }) => {
const importPath = './lib/ts-ext-ts/index.jsx';

test('Load - should not work', async () => {
const nodeProcess = await node.load(importPath);
expect(nodeProcess.stderr).toMatch('Cannot find module');
});

test('Import', async () => {
const nodeProcess = await node.importDynamic(importPath, { mode: 'typescript' });

if (semver.satisfies(node.version, nodeSupports.import)) {
expect(nodeProcess.stderr).toMatch('Cannot find module');
} else {
assertResults(nodeProcess.stdout);
expect(nodeProcess.stdout).toMatch('{"default":1234}');
}
});

test('Require', async () => {
const nodeProcess = await node.require(importPath, { typescript: true });
assertResults(nodeProcess.stdout);
expect(nodeProcess.stdout).toMatch('{"default":1234}');
});
});

describe('extensionless', ({ test }) => {
const importPath = './lib/ts-ext-ts/index';

test('Load', async () => {
const nodeProcess = await node.load(importPath);
console.log({ nodeProcess });
assertResults(nodeProcess.stdout);
});

Expand All @@ -107,7 +134,10 @@ export default testSuite(async ({ describe }, node: NodeApis) => {
});

test('Require', async () => {
const nodeProcess = await node.require(importPath);
const nodeProcess = await node.require(importPath, {
// Breaking change?
typescript: true,
});
assertResults(nodeProcess.stdout);
expect(nodeProcess.stdout).toMatch('{"default":1234}');
});
Expand All @@ -133,7 +163,9 @@ export default testSuite(async ({ describe }, node: NodeApis) => {
});

test('Require', async () => {
const nodeProcess = await node.require(importPath);
const nodeProcess = await node.require(importPath, {
typescript: true,
});
assertResults(nodeProcess.stdout, 'loaded ts-ext-ts/index.tsx.ts\n');
expect(nodeProcess.stdout).toMatch('{"default":1234}');
});
Expand All @@ -159,7 +191,9 @@ export default testSuite(async ({ describe }, node: NodeApis) => {
});

test('Require', async () => {
const nodeProcess = await node.require(importPath);
const nodeProcess = await node.require(importPath, {
typescript: true,
});
assertResults(nodeProcess.stdout);
expect(nodeProcess.stdout).toMatch('{"default":1234}');
});
Expand Down
Loading