Skip to content

Commit

Permalink
fix: enhance esm parser (#517)
Browse files Browse the repository at this point in the history
* fix: enhance esm parser

* feat(loader): add 'setOptions' method
  • Loading branch information
KyLeoHC committed Jul 20, 2022
1 parent b350ee5 commit 33dc4c5
Show file tree
Hide file tree
Showing 11 changed files with 448 additions and 96 deletions.
202 changes: 168 additions & 34 deletions packages/core/__tests__/esmLoader.spec.ts
@@ -1,44 +1,178 @@
import { readFileSync } from 'fs';
import { init, parse } from 'es-module-lexer';
import {
STRING_REG,
COMMENT_REG,
DYNAMIC_IMPORT_REG,
ESModuleLoader,
getModuleImportProcessor,
} from '../src/module/esModule';

describe('Core: esm loader', () => {
it('comment reg', () => {
let code = `
a
// 1
//2
/* 1 */
/* //1 */
console.log(1/*f*/2)
`.trim();
code = code.replace(COMMENT_REG, '');
expect(code).toBe('a\n \n \n \n \n console.log(12)');
it('replace static import module name', async () => {
await init;

const code = `
import React from 'react';
import { render } from 'react-dom';
// import lodash from 'lodash';
/* import VueRouter from 'vue-router'; */
console.log('render', render);
console.log(\`import Vue from 'vue';console.log(Vue);\`);
const importString = 'import router from "react-router"';
`;
const processImportModule = getModuleImportProcessor(code);
const [imports] = parse(code);
let result = ['', code];

for (let i = 0, length = imports.length; i < length; i++) {
result = processImportModule(imports[i], 'new-module-name');
}
expect(result.join('')).toBe(`
import React from 'new-module-name';
import { render } from 'new-module-name';
// import lodash from 'lodash';
/* import VueRouter from 'vue-router'; */
console.log('render', render);
console.log(\`import Vue from 'vue';console.log(Vue);\`);
const importString = 'import router from "react-router"';
`);
});

it('replace dynamic import keyword with "_import_"', async () => {
await init;

const code = `
import React from 'react';
import { render } from 'react-dom';
// import lodash from 'lodash';
/* import VueRouter from 'vue-router'; */
console.log('render', render);
console.log(\`import Vue from 'vue';console.log(Vue);\`);
const dynamicImport = import('react-router');
`;
const processImportModule = getModuleImportProcessor(code);
const [imports] = parse(code);
let result = ['', code];

for (let i = 0, length = imports.length; i < length; i++) {
result = processImportModule(imports[i]);
}
expect(result.join('')).toBe(`
import React from '';
import { render } from '';
// import lodash from 'lodash';
/* import VueRouter from 'vue-router'; */
console.log('render', render);
console.log(\`import Vue from 'vue';console.log(Vue);\`);
const dynamicImport = _import_('react-router');
`);
});

it('string reg', () => {
let code =
'const a = \'chen\';const b = "tao";const c = \'ch\\\'en\';const tao = "t\\"tao"';
code = code.replace(STRING_REG, '');
expect(code).toBe('const a = ;const b = ;const c = ;const tao = ');
// eslint-disable-next-line quotes
code = "import a from './x.js';const d = \"import a from './x.js'\"";
code = code.replace(STRING_REG, '');
expect(code).toBe(';const d = ');
const baseUrl = 'http://www.test.com';
const esmFileMap = {
simpleEntry: `${baseUrl}/simpleEntry.js`,
esmA: `${baseUrl}/esmA.js`,
esmB: `${baseUrl}/esmB.js`,
esmC: `${baseUrl}/esmC.js`,
circularEntry: `${baseUrl}/circularEntry.js`,
circularEsmA: `${baseUrl}/circularEsmA.js`,
circularEsmB: `${baseUrl}/circularEsmB.js`,
};
const mockCache = {
[esmFileMap.esmA]: readFileSync(`${__dirname}/resources/js/esmA.js`, {
encoding: 'utf-8',
}),
[esmFileMap.esmB]: readFileSync(`${__dirname}/resources/js/esmB.js`, {
encoding: 'utf-8',
}),
[esmFileMap.esmC]: readFileSync(`${__dirname}/resources/js/esmC.js`, {
encoding: 'utf-8',
}),
[esmFileMap.circularEsmA]: readFileSync(
`${__dirname}/resources/js/circularEsmA.js`,
{ encoding: 'utf-8' },
),
[esmFileMap.circularEsmB]: readFileSync(
`${__dirname}/resources/js/circularEsmB.js`,
{ encoding: 'utf-8' },
),
};
const loader = new ESModuleLoader({
appId: 0,
name: 'unit test for esm module loader',
global: {},
context: {
loader: {
load({ url }) {
return {
resourceManager: {
url,
scriptCode: mockCache[url],
},
};
},
},
},
globalVarKey: 'globalVarKey',
} as any);
// @ts-ignore
loader.execModuleCode = () =>
// @ts-ignore
loader.app.global[loader.globalVarKey].resolve();
global.URL.revokeObjectURL = jest.fn();

it('load simple es module successfully', async () => {
let blobIndex = 0;
// @ts-ignore
loader.createBlobUrl = () => `blob:${++blobIndex}`;

const entryCode = 'import "./esmA.js";';
// @ts-ignore
await loader.load(entryCode, {}, esmFileMap.simpleEntry, {});

// @ts-ignore
const moduleCache = loader.moduleCache;
expect(moduleCache[esmFileMap.simpleEntry].blobUrl).toBe('blob:4');
expect(moduleCache[esmFileMap.simpleEntry].shellUrl).toBeUndefined();
expect(moduleCache[esmFileMap.esmA].blobUrl).toBe('blob:3');
expect(moduleCache[esmFileMap.esmA].shellUrl).toBeUndefined();
expect(moduleCache[esmFileMap.esmB].blobUrl).toBe('blob:1');
expect(moduleCache[esmFileMap.esmB].shellUrl).toBeUndefined();
expect(moduleCache[esmFileMap.esmC].blobUrl).toBe('blob:2');
expect(moduleCache[esmFileMap.esmC].shellUrl).toBeUndefined();

loader.destroy();
// @ts-ignore
expect(Object.keys(loader.moduleCache).length).toBe(0);
});

it('dynamic import reg', () => {
let code =
// eslint-disable-next-line quotes
"import('a');const obj = { import(a) {\nimport('b')\n} };import(a + fn(import(a + b)) + 'a');";
code = code.replace(DYNAMIC_IMPORT_REG, (k1) =>
k1.replace('import', '_import_'),
);
expect(code).toBe(
// eslint-disable-next-line quotes
"_import_('a');const obj = { import(a) {\n_import_('b')\n} };_import_(a + fn(_import_(a + b)) + 'a');",
);
it('load circular dep es module successfully', async () => {
let blobIndex = 0;
const blobCodeList: string[] = [];
// @ts-ignore
loader.createBlobUrl = (code: string) => {
blobCodeList.push(code);
return `blob:${++blobIndex}`;
};
const entryCode = 'import "./circularEsmA.js";';
// @ts-ignore
await loader.load(entryCode, {}, esmFileMap.circularEntry, {});
// @ts-ignore
const moduleCache = loader.moduleCache;
expect(moduleCache[esmFileMap.circularEntry].blobUrl).toBe('blob:5');
expect(moduleCache[esmFileMap.circularEntry].shellUrl).toBeUndefined();
expect(moduleCache[esmFileMap.circularEsmA].blobUrl).toBe('blob:4');
expect(moduleCache[esmFileMap.circularEsmA].shellUrl).toBe('blob:2');
expect(moduleCache[esmFileMap.circularEsmB].blobUrl).toBe('blob:3');
expect(moduleCache[esmFileMap.circularEsmB].shellUrl).toBeUndefined();
expect(moduleCache[esmFileMap.esmC].blobUrl).toBe('blob:1');
expect(moduleCache[esmFileMap.esmC].shellUrl).toBeUndefined();

// shell code for module A
expect(blobCodeList[1])
.toBe(`export function u$$_(m){sayA=m.sayA,execSayB=m.execSayB}export let sayA;export let execSayB;export * from 'blob:1'
//# sourceURL=http://www.test.com/circularEsmA.js?cycle`);

loader.destroy();
// @ts-ignore
expect(Object.keys(loader.moduleCache).length).toBe(0);
});
});
5 changes: 5 additions & 0 deletions packages/core/__tests__/resources/js/circularEsmA.js
@@ -0,0 +1,5 @@
import { sayB } from './circularEsmB.js';
export * from './esmC.js';

export const sayA = () => console.log('say A');
export const execSayB = () => sayB();
6 changes: 6 additions & 0 deletions packages/core/__tests__/resources/js/circularEsmB.js
@@ -0,0 +1,6 @@
import { sayA } from './circularEsmA.js';

export const sayB = () => {
sayA();
console.log('say B');
};
4 changes: 4 additions & 0 deletions packages/core/__tests__/resources/js/esmA.js
@@ -0,0 +1,4 @@
import { esmContent } from './esmB.js';
export * from './esmC.js';

window.esmContent = esmContent;
1 change: 1 addition & 0 deletions packages/core/__tests__/resources/js/esmB.js
@@ -0,0 +1 @@
export const esmContent = 'esm content';
1 change: 1 addition & 0 deletions packages/core/__tests__/resources/js/esmC.js
@@ -0,0 +1 @@
export const esmC = 'esm content c';
1 change: 1 addition & 0 deletions packages/core/package.json
Expand Up @@ -35,6 +35,7 @@
"@garfish/hooks": "workspace:*",
"@garfish/loader": "workspace:*",
"@garfish/utils": "workspace:*",
"es-module-lexer": "^0.10.5",
"eventemitter2": "^6.4.5"
},
"devDependencies": {
Expand Down

1 comment on commit 33dc4c5

@vercel
Copy link

@vercel vercel bot commented on 33dc4c5 Jul 20, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.