Skip to content

Commit

Permalink
Merge branch 'next' into support-new-ts-extensions
Browse files Browse the repository at this point in the history
  • Loading branch information
privatenumber authored Nov 9, 2023
2 parents 17cd878 + 89cb240 commit 4d7e9cb
Show file tree
Hide file tree
Showing 5 changed files with 127 additions and 35 deletions.
36 changes: 22 additions & 14 deletions src/cjs/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import type { TransformOptions } from 'esbuild';
import { installSourceMapSupport } from '../source-map';
import { transformSync, transformDynamicImport } from '../utils/transform';
import { resolveTsPath } from '../utils/resolve-ts-path';
import { isESM } from '../utils/esm-pattern';

const isRelativePathPattern = /^\.{1,2}\//;
const isTsFilePatten = /\.[cm]?tsx?$/;
Expand All @@ -33,44 +34,51 @@ const applySourceMap = installSourceMapSupport();
const extensions = Module._extensions;
const defaultLoader = extensions['.js'];

const transformExtensions = [
'.js',
'.cjs',
const typescriptExtensions = [
'.cts',
'.mjs',
'.mts',
'.ts',
'.tsx',
'.jsx',
];

const transformExtensions = [
'.js',
'.cjs',
'.mjs',
];

const transformer = (
module: Module,
filePath: string,
) => {
const shouldTransformFile = transformExtensions.some(extension => filePath.endsWith(extension));
if (!shouldTransformFile) {
return defaultLoader(module, filePath);
}

/**
* For tracking dependencies in watch mode
*/
// For tracking dependencies in watch mode
if (process.send) {
process.send({
type: 'dependency',
path: filePath,
});
}

let code = fs.readFileSync(filePath, 'utf8');
const transformTs = typescriptExtensions.some(extension => filePath.endsWith(extension));
const transformJs = transformExtensions.some(extension => filePath.endsWith(extension));
if (!transformTs && !transformJs) {
return defaultLoader(module, filePath);
}

let code = fs.readFileSync(filePath, 'utf8');
if (filePath.endsWith('.cjs')) {
// Contains native ESM check
const transformed = transformDynamicImport(filePath, code);
if (transformed) {
code = applySourceMap(transformed, filePath);
}
} else {
} else if (
transformTs

// CommonJS file but uses ESM import/export
|| isESM(code)
) {
const transformed = transformSync(
code,
filePath,
Expand Down
5 changes: 5 additions & 0 deletions src/esm/loaders.ts
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,10 @@ export const load: LoadHook = async function (
context,
defaultLoad,
) {
/*
Filter out node:*
Maybe only handle files that start with file://
*/
if (sendToParent) {
sendToParent({
type: 'dependency',
Expand All @@ -264,6 +268,7 @@ export const load: LoadHook = async function (

const loaded = await defaultLoad(url, context);

// CommonJS and Internal modules (e.g. node:*)
if (!loaded.source) {
return loaded;
}
Expand Down
36 changes: 36 additions & 0 deletions src/utils/debug.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
export const time = <Argument>(
name: string,
_function: (...args: Argument[]) => unknown,
) => function (
this: unknown,
...args: Argument[]
) {
const timeStart = Date.now();
const logTimeElapsed = () => {
const elapsed = Date.now() - timeStart;

if (elapsed > 10) {
// console.log({
// name,
// args,
// elapsed,
// });
}
};

const result = Reflect.apply(_function, this, args);
if (
result
&& typeof result === 'object'
&& 'then' in result
) {
(result as Promise<unknown>).then(
logTimeElapsed,
// Ignore error in this chain
() => {},
);
} else {
logTimeElapsed();
}
return result;
};
24 changes: 24 additions & 0 deletions src/utils/esm-pattern.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { parseEsm } from './es-module-lexer';
/*
TODO: Add tests
Catches:
import a from 'b'
import 'b';
import('b');
export{a};
export default a;
Doesn't catch:
EXPORT{a}
exports.a = 1
module.exports = 1
*/
const esmPattern = /\b(?:import|export)\b/;

export const isESM = (code: string) => {
if (esmPattern.test(code)) {
const [imports, exports] = parseEsm(code);
return imports.length > 0 || exports.length > 0;
}
return false;
};
61 changes: 40 additions & 21 deletions src/utils/transform/transform-dynamic-import.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,33 @@ import MagicString from 'magic-string';
import type { RawSourceMap } from '../../source-map';
import { parseEsm } from '../es-module-lexer';

const checkEsModule = `.then((mod)=>{
const exports = Object.keys(mod);
if(
exports.length===1&&exports[0]==='default'&&mod.default&&mod.default.__esModule
){
return mod.default
const handlerName = '___tsxInteropDynamicImport';
const handleEsModuleFunction = `function ${handlerName}${(function (imported: Record<string, unknown>) {
const d = 'default';
const exports = Object.keys(imported);
if (
exports.length === 1
&& exports[0] === d
&& imported[d]
&& typeof imported[d] === 'object'
&& '__esModule' in imported[d]
) {
return imported[d];
}
return mod
})`
// replaceAll is not supported in Node 12
// eslint-disable-next-line unicorn/prefer-string-replace-all
.replace(/[\n\t]+/g, '');
export function transformDynamicImport(
return imported;
}).toString().slice('function'.length)}`;

const handleDynamicImport = `.then(${handlerName})`;

const esmImportPattern = /\bimport\b/;

export const transformDynamicImport = (
filePath: string,
code: string,
) {
) => {
// Naive check
if (!code.includes('import')) {
if (!esmImportPattern.test(code)) {
return;
}

Expand All @@ -33,14 +41,25 @@ export function transformDynamicImport(
const magicString = new MagicString(code);

for (const dynamicImport of dynamicImports) {
magicString.appendRight(dynamicImport.se, checkEsModule);
magicString.appendRight(dynamicImport.se, handleDynamicImport);
}

magicString.append(handleEsModuleFunction);

const newCode = magicString.toString();
const newMap = magicString.generateMap({
source: filePath,
includeContent: false,

/**
* The performance hit on this is very high
* Since we're only transforming import()s, I think this may be overkill
*/
// hires: 'boundary',
}) as unknown as RawSourceMap;

return {
code: magicString.toString(),
map: magicString.generateMap({
source: filePath,
hires: true,
}) as unknown as RawSourceMap,
code: newCode,
map: newMap,
};
}
};

0 comments on commit 4d7e9cb

Please sign in to comment.