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

Commit

Permalink
feat: transform dynamic imports to read __esModule (#2)
Browse files Browse the repository at this point in the history
* feat: transform dynamic imports to read __esModule

* fix: handle uninitialized parser

* feat: use sync lexer for cjs

* fix: transform cjs file that uses import()

* refactor: remove unused cjs check in transformAsync

* feat: sourcemap support

* chore: remove outdated comment
  • Loading branch information
privatenumber authored May 17, 2022
1 parent 1bda96a commit 17494bb
Show file tree
Hide file tree
Showing 6 changed files with 151 additions and 26 deletions.
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,13 @@
"esbuild": "0.14.38"
},
"devDependencies": {
"@ampproject/remapping": "^2.2.0",
"@pvtnbr/eslint-config": "^0.22.0",
"@types/node": "^17.0.31",
"@types/source-map-support": "^0.5.4",
"es-module-lexer": "^0.10.5",
"eslint": "^8.15.0",
"magic-string": "^0.26.2",
"pkgroll": "^1.2.2",
"source-map-support": "^0.5.21",
"typescript": "^4.6.4"
Expand Down
53 changes: 50 additions & 3 deletions pnpm-lock.yaml

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

3 changes: 3 additions & 0 deletions src/@types/es-module-lexer.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
declare module 'es-module-lexer/js' {
export { parse } from 'es-module-lexer';
}
40 changes: 17 additions & 23 deletions src/esbuild/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,15 @@ import {
transformSync as esbuildTransformSync,
version as esbuildVersion,
} from 'esbuild';
import { transformDynamicImport } from '../transform-dynamic-import';
import { sha1 } from '../utils/sha1';
import { hasNativeSourceMapSupport } from '../utils/has-native-source-map-support';
import cache from './cache';

// Check if file is explicitly a CJS file
const isCJS = (sourcePath: string) => (
sourcePath.endsWith('.cjs')
|| sourcePath.endsWith('.cjs.js')
);

const nodeVersion = process.versions.node;

const sourcemap = hasNativeSourceMapSupport ? 'inline' : true;

const getTransformOptions = (
extendOptions: TransformOptions,
) => {
Expand All @@ -26,7 +23,7 @@ const getTransformOptions = (
// https://github.com/evanw/esbuild/blob/4a07b17adad23e40cbca7d2f8931e8fb81b47c33/internal/bundler/bundler.go#L158
loader: 'default',

sourcemap: hasNativeSourceMapSupport ? 'inline' : true,
sourcemap,

// Marginal performance improvement:
// https://twitter.com/evanwallace/status/1396336348366180359?s=20
Expand All @@ -51,14 +48,6 @@ export function transformSync(
filePath: string,
extendOptions?: TransformOptions,
): TransformResult {
if (isCJS(filePath)) {
return {
code,
map: '',
warnings: [],
};
}

const options = getTransformOptions({
sourcefile: filePath,
...extendOptions,
Expand All @@ -71,6 +60,13 @@ export function transformSync(
}

const transformed = esbuildTransformSync(code, options);

const dynamicImportTransformed = transformDynamicImport(transformed, sourcemap);
if (dynamicImportTransformed) {
transformed.code = dynamicImportTransformed.code;
transformed.map = dynamicImportTransformed.map;
}

if (transformed.warnings.length > 0) {
const { warnings } = transformed;
for (const warning of warnings) {
Expand All @@ -89,14 +85,6 @@ export async function transform(
filePath: string,
extendOptions?: TransformOptions,
): Promise<TransformResult> {
if (isCJS(filePath)) {
return {
code,
map: '',
warnings: [],
};
}

const options = getTransformOptions({
sourcefile: filePath,
...extendOptions,
Expand All @@ -110,6 +98,12 @@ export async function transform(

const transformed = await esbuildTransform(code, options);

const dynamicImportTransformed = transformDynamicImport(transformed, sourcemap);
if (dynamicImportTransformed) {
transformed.code = dynamicImportTransformed.code;
transformed.map = dynamicImportTransformed.map;
}

if (transformed.warnings.length > 0) {
const { warnings } = transformed;
for (const warning of warnings) {
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './source-map-support';
export * from './esbuild';
export * from './transform-dynamic-import';
77 changes: 77 additions & 0 deletions src/transform-dynamic-import.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { parse as parseWasm, init } from 'es-module-lexer';
import { parse as parseJs } from 'es-module-lexer/js'; // eslint-disable-line import/no-unresolved
import MagicString from 'magic-string';
import type { TransformResult } from 'esbuild';
import remapping from '@ampproject/remapping';

const checkEsModule = `.then((mod)=>{
const exports = Object.keys(mod);
if(
exports.length===1&&exports[0]==='default'&&mod.default.__esModule
){
return mod.default
}
return mod
})`.replace(/[\n\t]+/g, '');

let wasmParserInitialized = false;

// eslint-disable-next-line promise/catch-or-return
init.then(() => {
wasmParserInitialized = true;
});

const inlineSourceMapPrefix = '\n//# sourceMappingURL=data:application/json;base64,';

export function transformDynamicImport(
{ code, map }: TransformResult,
sourcemap?: boolean | 'inline',
) {
code = code.toString();

// Naive check
if (!code.includes('import')) {
return;
}

if (sourcemap === 'inline') {
const sourceMapIndex = code.indexOf(inlineSourceMapPrefix);
const inlineSourceMap = code.slice(sourceMapIndex + inlineSourceMapPrefix.length);

map = Buffer.from(inlineSourceMap, 'base64').toString();
code = code.slice(0, sourceMapIndex);
}

const [imports] = wasmParserInitialized ? parseWasm(code) : parseJs(code);

if (imports.length === 0) {
return;
}

const magicString = new MagicString(code);

for (const dynamicImport of imports) {
if (dynamicImport.d > -1) {
magicString.appendRight(dynamicImport.se, checkEsModule);
}
}

code = magicString.toString();

if (sourcemap) {
const generatedMap = magicString.generateMap({ hires: true });

map = remapping([generatedMap.toString(), map], () => null).toString();

if (sourcemap === 'inline') {
code += inlineSourceMapPrefix + Buffer.from(map, 'utf8').toString('base64');
map = '';
}
}

return {
code,
map,
};
}

0 comments on commit 17494bb

Please sign in to comment.