From 62a8cd982496e7b7ab1f1dd88249ff2a8d6a3c27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8D=89=E9=9E=8B=E6=B2=A1=E5=8F=B7?= <308487730@qq.com> Date: Fri, 28 Apr 2023 17:53:42 +0800 Subject: [PATCH 01/11] chore: cleanup types --- src/analyze.ts | 2 -- src/dynamic-require.ts | 2 +- src/generate-import.ts | 1 - src/types.ts | 1 + 4 files changed, 2 insertions(+), 4 deletions(-) create mode 100644 src/types.ts diff --git a/src/analyze.ts b/src/analyze.ts index d2f6bee..ca2f91c 100644 --- a/src/analyze.ts +++ b/src/analyze.ts @@ -1,7 +1,5 @@ import { walk } from 'vite-plugin-utils/function' -export type AcornNode = import('rollup').AcornNode & Record - // ①(🎯): Top-level scope statement types, it also means statements that can be converted // 顶级作用于语句类型,这种可以被无缝换成 import export enum TopScopeType { diff --git a/src/dynamic-require.ts b/src/dynamic-require.ts index 789fddb..f07e39d 100644 --- a/src/dynamic-require.ts +++ b/src/dynamic-require.ts @@ -10,7 +10,7 @@ import { } from 'vite-plugin-dynamic-import' import { normalizePath, relativeify } from 'vite-plugin-utils/function' import type { Options } from '.' -import type { AcornNode, Analyzed } from './analyze' +import type { Analyzed } from './analyze' import { normallyImporteeRE } from './utils' export interface DynamicRequireRecord { diff --git a/src/generate-import.ts b/src/generate-import.ts index c61f22d..a183c85 100644 --- a/src/generate-import.ts +++ b/src/generate-import.ts @@ -1,5 +1,4 @@ import { - type AcornNode, type Analyzed, type RequireStatement, TopScopeType, diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..710dd12 --- /dev/null +++ b/src/types.ts @@ -0,0 +1 @@ +type AcornNode = import('acorn').Node & Record \ No newline at end of file From e69a65a8cd426bdb58305d3dc1e03e22990ae90b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8D=89=E9=9E=8B=E6=B2=A1=E5=8F=B7?= <308487730@qq.com> Date: Fri, 28 Apr 2023 17:54:36 +0800 Subject: [PATCH 02/11] refactor: better support `node_modules` #23 --- src/index.ts | 254 ++++++++++++++++++++++++++++++++------------------- 1 file changed, 159 insertions(+), 95 deletions(-) diff --git a/src/index.ts b/src/index.ts index 60f6437..34d18c1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,12 +1,14 @@ +import fs from 'node:fs' import path from 'node:path' import type { Plugin, ResolvedConfig } from 'vite' +import { parse as parseAst } from 'acorn' import { DEFAULT_EXTENSIONS, KNOWN_SFC_EXTENSIONS, KNOWN_ASSET_TYPES, KNOWN_CSS_TYPES, } from 'vite-plugin-utils/constant' -import { MagicString } from 'vite-plugin-utils/function' +import { MagicString, cleanUrl } from 'vite-plugin-utils/function' import { analyzer, TopScopeType } from './analyze' import { generateImport } from './generate-import' import { generateExport } from './generate-export' @@ -47,7 +49,7 @@ export default function commonjs(options: Options = {}): Plugin { name: 'vite-plugin-commonjs', configResolved(_config) { config = _config - // https://github.com/vitejs/vite/blob/37ac91e5f680aea56ce5ca15ce1291adc3cbe05e/packages/vite/src/node/plugins/resolve.ts#L450 + // https://github.com/vitejs/vite/blob/v4.3.0/packages/vite/src/node/config.ts#L498 if (config.resolve?.extensions) extensions = config.resolve.extensions dynaimcRequire = new DynaimcRequire(_config, { ...options, @@ -58,106 +60,168 @@ export default function commonjs(options: Options = {}): Plugin { ...KNOWN_CSS_TYPES.map(type => '.' + type), ], }) + + // esbuild plugin for Vite's Pre-Bundling + _config.optimizeDeps.esbuildOptions ??= {} + _config.optimizeDeps.esbuildOptions.plugins ??= [] + _config.optimizeDeps.esbuildOptions.plugins.push({ + name: 'vite-plugin-dynamic-import:pre-bundle', + setup(build) { + build.onLoad({ filter: /.*/ }, async ({ path: id }) => { + let code: string + try { + code = fs.readFileSync(id, 'utf8') + } catch (error) { + return + } + + const contents = await transformCommonjs({ + options, + code, + id, + extensions, + dynaimcRequire, + }) + + if (contents != null) { + return { contents } + } + }) + }, + }) }, - async transform(code, id) { - if (/node_modules\/(?!\.vite\/)/.test(id) && !options.filter?.(id)) return - if (!extensions.includes(path.extname(id))) return - if (!isCommonjs(code)) return - if (options.filter?.(id) === false) return - - const ast = this.parse(code) - const analyzed = analyzer(ast, code, id) - const imports = generateImport(analyzed) - const exportRuntime = id.includes('node_modules/.vite') - // Bypass Pre-build - ? null - : generateExport(analyzed) - const dynamics = await dynaimcRequire.generateRuntime(analyzed) - - const hoistImports = [] - const ms = new MagicString(code) - - // require - for (const impt of imports) { - const { - node, - importee: imptee, - declaration, - importName, - topScopeNode, - } = impt - const importee = imptee + ';' - - let importStatement: string | undefined - if (topScopeNode) { - if (topScopeNode.type === TopScopeType.ExpressionStatement) { - importStatement = importee - } else if (topScopeNode.type === TopScopeType.VariableDeclaration) { - importStatement = declaration ? `${importee} ${declaration};` : importee - } - } else { - // TODO: Merge duplicated require id - hoistImports.push(importee) - importStatement = importName - } - - if (importStatement) { - const start = topScopeNode ? topScopeNode.start : node.start - const end = topScopeNode ? topScopeNode.end : node.end - ms.overwrite(start, end, importStatement) - } - } + transform(code, id) { + return transformCommonjs({ + options, + code, + id, + extensions, + dynaimcRequire, + }) + }, + } +} - if (hoistImports.length) { - ms.prepend(['/* import-hoist-S */', ...hoistImports, '/* import-hoist-E */'].join(' ')) - } +async function transformCommonjs({ + options, + code, + id, + extensions, + dynaimcRequire, +}: { + options: Options, + code: string, + id: string, + extensions: string[], + dynaimcRequire: DynaimcRequire, +}) { + if (!(extensions.includes(path.extname(id)) || extensions.includes(path.extname(cleanUrl(id))))) return + if (!isCommonjs(code)) return + + const userCondition = options.filter?.(id) + if (userCondition === false) return + // exclude `node_modules` by default + // here can only get the files in `node_modules/.vite` and `node_modules/vite/dist/client`, others will be handled by Pre-Bundling + if (userCondition !== true && id.includes('node_modules')) return + + let ast: AcornNode + try { + ast = parseAst(code, { ecmaVersion: 2020 }) as AcornNode + } catch (error) { + // ignore as it might not be a JS file, the subsequent plugins will catch the error + return null + } + + const analyzed = analyzer(ast, code, id) + const imports = generateImport(analyzed) + const exportRuntime = id.includes('node_modules/.vite') + // Bypass Pre-build + ? null + : generateExport(analyzed) + const dynamics = await dynaimcRequire.generateRuntime(analyzed) + + const hoistImports = [] + const ms = new MagicString(code) - // exports - if (exportRuntime) { - const polyfill = [ - '/* export-runtime-S */', - exportRuntime.polyfill, - '/* export-runtime-E */', - ].join(' ') - - const _exports = [ - '/* export-statement-S */', - exportRuntime.exportDeclaration, - '/* export-statement-E */', - ].filter(Boolean) - .join('\n') - ms.prepend(polyfill).append(_exports) + // require + for (const impt of imports) { + const { + node, + importee: imptee, + declaration, + importName, + topScopeNode, + } = impt + const importee = imptee + ';' + + let importStatement: string | undefined + if (topScopeNode) { + if (topScopeNode.type === TopScopeType.ExpressionStatement) { + importStatement = importee + } else if (topScopeNode.type === TopScopeType.VariableDeclaration) { + importStatement = declaration ? `${importee} ${declaration};` : importee } + } else { + // TODO: Merge duplicated require id + hoistImports.push(importee) + importStatement = importName + } + + if (importStatement) { + const start = topScopeNode ? topScopeNode.start : node.start + const end = topScopeNode ? topScopeNode.end : node.end + ms.overwrite(start, end, importStatement) + } + } + + if (hoistImports.length) { + ms.prepend(['/* import-hoist-S */', ...hoistImports, '/* import-hoist-E */'].join(' ')) + } - // dynamic require - if (dynamics) { - const requires: string[] = [] - const runtimes: string[] = [] - let count = 0 - - for (const dynamic of dynamics) { - const { node, normally, dynaimc: dymc } = dynamic - if (normally) { - const name = `__require2import__${count++}__` - requires.push(`import * as ${name} from "${normally}";`) - ms.overwrite(node.callee.start, node.callee.end, name) - } else if (dymc) { - requires.push(...dymc.importee.map(impt => impt + ';')) - runtimes.push(dymc.runtimeFn) - ms.overwrite(node.callee.start, node.callee.end, dymc.runtimeFn) - } - } - - if (requires.length) { - ms.prepend(['/* import-require2import-S */', ...requires, '/* import-require2import-E */'].join(' ')) - } - if (runtimes.length) { - ms.append(runtimes.join('\n')) - } + // exports + if (exportRuntime) { + const polyfill = [ + '/* [vite-plugin-commonjs] export-runtime-S */', + exportRuntime.polyfill, + '/* [vite-plugin-commonjs] export-runtime-E */', + ].join(' ') + + const _exports = [ + '/* [vite-plugin-commonjs] export-statement-S */', + exportRuntime.exportDeclaration, + '/* [vite-plugin-commonjs] export-statement-E */', + ].filter(Boolean) + .join('\n') + ms.prepend(polyfill).append(_exports) + } + + // dynamic require + if (dynamics) { + const requires: string[] = [] + const runtimes: string[] = [] + let count = 0 + + for (const dynamic of dynamics) { + const { node, normally, dynaimc: dymc } = dynamic + if (normally) { + const name = `__require2import__${count++}__` + requires.push(`import * as ${name} from "${normally}";`) + ms.overwrite(node.callee.start, node.callee.end, name) + } else if (dymc) { + requires.push(...dymc.importee.map(impt => impt + ';')) + runtimes.push(dymc.runtimeFn) + ms.overwrite(node.callee.start, node.callee.end, dymc.runtimeFn) } + } - const _code = ms.toString() - return _code === code ? null : _code + if (requires.length) { + ms.prepend(['/* [vite-plugin-commonjs] import-require2import-S */', ...requires, '/* [vite-plugin-commonjs] import-require2import-E */'].join(' ')) + } + if (runtimes.length) { + ms.append(runtimes.join('\n')) } } + + const str = ms.toString() + return str !== code ? str : null } From d3207f39239644f6865b7120692c2f5cbb639c73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8D=89=E9=9E=8B=E6=B2=A1=E5=8F=B7?= <308487730@qq.com> Date: Fri, 28 Apr 2023 17:55:29 +0800 Subject: [PATCH 03/11] refactor(build): better scripts --- .gitignore | 3 +-- package.json | 33 ++++++++++++------------ tsconfig.json | 17 +++++++----- vite.config.ts | 70 ++++++++++++++++++++++++++++++++++++++++---------- 4 files changed, 85 insertions(+), 38 deletions(-) diff --git a/.gitignore b/.gitignore index 643981d..fea8b62 100644 --- a/.gitignore +++ b/.gitignore @@ -109,5 +109,4 @@ package-lock.json pnpm-lock.yaml yarn.lock -/index.cjs -/index.js +/types diff --git a/package.json b/package.json index ad4b750..1888be4 100644 --- a/package.json +++ b/package.json @@ -1,15 +1,16 @@ { "name": "vite-plugin-commonjs", - "version": "0.6.2", + "version": "0.7.0", "description": "A pure JavaScript implementation of CommonJs", - "type": "module", - "main": "index.js", - "types": "src", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", "exports": { ".": { - "import": "./index.js", - "require": "./index.cjs" - } + "types": "./dist/index.d.ts", + "import": "./dist/index.mjs", + "require": "./dist/index.js" + }, + "./*": "./*" }, "repository": { "type": "git", @@ -21,17 +22,19 @@ "dev": "vite build --watch", "build": "vite build", "test": "vite -c test/vite.config.ts", + "types": "tsc", "prepublishOnly": "npm run build" }, "dependencies": { - "fast-glob": "~3.2.11" + "acorn": "^8.8.2", + "fast-glob": "^3.2.12", + "vite-plugin-dynamic-import": "^1.3.0" }, "devDependencies": { - "@types/node": "^18.7.14", - "typescript": "^4.7.4", - "vite": "^3.2.0-beta.2", - "vite-plugin-dynamic-import": "^1.2.3", - "vite-plugin-utils": "^0.3.3" + "@types/node": "^18.16.2", + "typescript": "^4.9.4", + "vite": "^4.3.3", + "vite-plugin-utils": "^0.4.2" }, "keywords": [ "vite", @@ -40,8 +43,6 @@ "require" ], "files": [ - "src", - "index.cjs", - "index.js" + "dist" ] } diff --git a/tsconfig.json b/tsconfig.json index 2aa8ac1..3d3516d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,15 +1,18 @@ { "compilerOptions": { - "target": "ES2019", - "module": "ES2022", + "target": "ES2022", + "module": "ESNext", "esModuleInterop": true, "moduleResolution": "Node", - "baseUrl": ".", + "resolveJsonModule": true, + "strict": true, + "allowJs": true, "declaration": true, - "outDir": "dist", - "allowSyntheticDefaultImports": true, "skipLibCheck": true, - "strict": true + "allowSyntheticDefaultImports": true, + "baseUrl": ".", + "outDir": "types", + "emitDeclarationOnly": true }, - "include": ["src/**/*.ts"] + "include": ["src"] } diff --git a/vite.config.ts b/vite.config.ts index 7a389b4..837be5e 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,27 +1,71 @@ -import path from 'path' -import { builtinModules } from 'module' +import fs from 'node:fs' +import path from 'node:path' +import { spawn } from 'node:child_process' +import { builtinModules } from 'node:module' import { defineConfig } from 'vite' import pkg from './package.json' +const isdev = process.argv.slice(2).includes('--watch') + export default defineConfig({ build: { minify: false, - outDir: '', - emptyOutDir: false, - target: 'node14', + emptyOutDir: !isdev, lib: { - entry: path.join(__dirname, 'src/index.ts'), - formats: ['es', 'cjs'], - fileName: format => format === 'cjs' ? '[name].cjs' : '[name].js', + entry: 'src/index.ts', + formats: ['cjs', 'es'], + fileName: format => format === 'es' ? '[name].mjs' : '[name].js', }, rollupOptions: { external: [ - ...builtinModules - .filter(m => !m.startsWith('_')) - .map(m => [m, `node:${m}`]) - .flat(), - ...Object.keys(pkg.dependencies), + 'vite', + ...builtinModules, + ...builtinModules.map(m => `node:${m}`), + ...Object.keys('dependencies' in pkg ? pkg.dependencies as object : {}), ], + output: { + exports: 'named', + }, }, }, + plugins: [{ + name: 'generate-types', + async closeBundle() { + if (process.env.NODE_ENV === 'test') return + + removeTypes() + await generateTypes() + moveTypesToDist() + removeTypes() + }, + }], }) + +function removeTypes() { + fs.rmSync(path.join(__dirname, 'types'), { recursive: true, force: true }) +} + +function generateTypes() { + return new Promise(resolve => { + const cp = spawn( + process.platform === 'win32' ? 'npm.cmd' : 'npm', + ['run', 'types'], + { stdio: 'inherit' }, + ) + cp.on('exit', code => { + !code && console.log('[types]', 'declaration generated') + resolve(code) + }) + cp.on('error', process.exit) + }) +} + +function moveTypesToDist() { + const types = path.join(__dirname, 'types') + const dist = path.join(__dirname, 'dist') + const files = fs.readdirSync(types).filter(n => n.endsWith('.d.ts')) + for (const file of files) { + fs.copyFileSync(path.join(types, file), path.join(dist, file)) + console.log('[types]', `types/${file} -> dist/${file}`) + } +} From 782db067c25ff880d3f4869184392a08a140d65d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8D=89=E9=9E=8B=E6=B2=A1=E5=8F=B7?= <308487730@qq.com> Date: Fri, 28 Apr 2023 18:00:56 +0800 Subject: [PATCH 04/11] docs: v0.7.0 --- README.md | 14 ++++++++++++++ README.zh-CN.md | 14 ++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/README.md b/README.md index 10224c4..25aa743 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,20 @@ export interface Options { } ``` +#### node_modules + +```js +commonjs({ + filter(id) { + // `node_modules` is exclude by default, so we need to include it explicitly + // https://github.com/vite-plugin/vite-plugin-commonjs/blob/v0.7.0/src/index.ts#L123-L125 + if (id.includes('node_modules/xxx')) { + return true + } + } +}) +``` + ## Cases [vite-plugin-commonjs/test](https://github.com/vite-plugin/vite-plugin-commonjs/tree/main/test) diff --git a/README.zh-CN.md b/README.zh-CN.md index 6c79664..c32f24a 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -54,6 +54,20 @@ export interface Options { } ``` +#### node_modules + +```js +commonjs({ + filter(id) { + // 默认会排除 `node_modules`,所以必须显式的包含它explicitly + // https://github.com/vite-plugin/vite-plugin-commonjs/blob/v0.7.0/src/index.ts#L123-L125 + if (id.includes('node_modules/xxx')) { + return true + } + } +}) +``` + ## 案例 [vite-plugin-commonjs/test](https://github.com/vite-plugin/vite-plugin-commonjs/tree/main/test) From 7d6a47ff3ea4ade436fb248c200f46c2e43d222e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8D=89=E9=9E=8B=E6=B2=A1=E5=8F=B7?= <308487730@qq.com> Date: Sun, 30 Apr 2023 08:37:05 +0800 Subject: [PATCH 05/11] chore: backup `v0.5.3` --- src/generate-import-v0.5.3.ts | 164 ++++++++++++++++++++++++++++++++++ 1 file changed, 164 insertions(+) create mode 100644 src/generate-import-v0.5.3.ts diff --git a/src/generate-import-v0.5.3.ts b/src/generate-import-v0.5.3.ts new file mode 100644 index 0000000..df73bb8 --- /dev/null +++ b/src/generate-import-v0.5.3.ts @@ -0,0 +1,164 @@ +/** + * @deprecated v0.6 + */ +import { + type Analyzed, + type RequireStatement, + TopScopeType, +} from './analyze' + +/** + * ``` + * At present, divide `require(id: Literal)` into three cases + * 目前,将 require() 分为三种情况 + * + * ①(🎯) + * In the top-level scope and can be converted to `import` directly + * 在顶层作用域,并且直接转换成 import + * + * ②(🚧) + * If the `id` in `require(id: Literal)` is a literal string, the `require` statement will be promoted to the top-level scope and become an `import` statement + * 如果 require(id: Literal) 中的 id 是字面量字符串,require 语句将会被提升到顶级作用域,变成 import 语句 + * + * ③(🚧) + * If the `id` in `require(dynamic-id)` is a dynamic-id, the `require` statement will be converted to `__matchRequireRuntime` function + * 如果 require(dynamic-id) 中的 id 动态 id,require 语句将会被转换成 __matchRequireRuntime 函数 + * ``` + */ + +export interface ImportRecord { + node: AcornNode + topScopeNode?: RequireStatement['topScopeNode'] + importee?: string + // e.g. + // source code 👉 const ast = require('acorn').parse() + // ↓ + // importee 👉 import * as __CJS_import__ from 'acorn' + // declaration 👉 const ast = __CJS_import__.parse() + declaration?: string + importName?: string +} + +export function generateImport(analyzed: Analyzed) { + const imports: ImportRecord[] = [] + let count = 0 + + for (const req of analyzed.require) { + const { + node, + ancestors, + topScopeNode, + dynamic, + } = req + + // ③(🚧) + // Processed in dynamic-require.ts + if (dynamic === 'dynamic') continue + + const impt: ImportRecord = { node, topScopeNode } + const importName = `__CJS__import__${count++}__` + + const requireIdNode = node.arguments[0] + let requireId: string + if (!requireIdNode) continue // Not value - require() + if (requireIdNode.type === 'Literal') { + requireId = requireIdNode.value + } else if (dynamic === 'Literal') { + requireId = requireIdNode.quasis[0].value.raw + } + + if (!requireId!) { + const codeSnippets = analyzed.code.slice(node.start, node.end) + throw new Error(`The following require statement cannot be converted. + -> ${codeSnippets} + ${'^'.repeat(codeSnippets.length)}`) + } + + if (topScopeNode) { + // ①(🎯) + + // @ts-ignore + switch (topScopeNode.type) { + case TopScopeType.ExpressionStatement: + // TODO: With members - e.g. `require().foo` + impt.importee = `import '${requireId}'` + break + + case TopScopeType.VariableDeclaration: + // TODO: Multiple declaration + // @ts-ignore + const VariableDeclarator = topScopeNode.declarations[0] + const { /* L-V */id, /* R-V */init } = VariableDeclarator as AcornNode + + // Left value + let LV: string | { key: string, value: string }[] + if (id.type === 'Identifier') { + LV = id.name + } else if (id.type === 'ObjectPattern') { + LV = [] + for (const { key, value } of id.properties) { + // @ts-ignore + LV.push({ key: key.name, value: value.name }) + } + } else { + throw new Error(`Unknown VariableDeclarator.id.type(L-V): ${id.type}`) + } + + const LV_str = (spe: string) => typeof LV === 'object' + ? LV.map(e => e.key === e.value ? e.key : `${e.key} ${spe} ${e.value}`).join(', ') + : '' + + // Right value + if (init.type === 'CallExpression') { + if (typeof LV === 'string') { + // const acorn = require('acorn') + impt.importee = `import * as ${LV} from '${requireId}'` + } else { + // const { parse } = require('acorn') + impt.importee = `import { ${LV_str('as')} } from '${requireId}'` + } + } else if (init.type === 'MemberExpression') { + const onlyOneMember = ancestors.find(an => an.type === 'MemberExpression')?.property.name + const importDefault = onlyOneMember === 'default' + if (typeof LV === 'string') { + if (importDefault) { + // const foo = require('foo').default + impt.importee = `import ${LV} from '${requireId}'` + } else { + impt.importee = onlyOneMember === LV + // const bar = require('foo').bar + ? `import { ${LV} } from '${requireId}'` + // const barAlias = require('foo').bar + : `import { ${onlyOneMember} as ${LV} } from '${requireId}'` + } + } else { + if (importDefault) { + // const { member1, member2 } = require('foo').default + impt.importee = `import ${importName} from '${requireId}'` + } else { + // const { member1, member2 } = require('foo').bar + impt.importee = `import { ${onlyOneMember} as ${importName} } from '${requireId}'` + } + impt.declaration = `const { ${LV_str(':')} } = ${importName}` + } + } else { + throw new Error(`Unknown VariableDeclarator.init.type(R-V): ${id.init}`) + } + break + + default: + throw new Error(`Unknown TopScopeType: ${topScopeNode}`) + } + } else { + // ②(🚧) + + // This is probably less accurate but is much cheaper than a full AST parse. + impt.importee = `import * as ${importName} from '${requireId}'` + impt.importName = `${importName}.default || ${importName}` // Loose + } + + imports.push(impt) + } + + return imports +} From 80f46f0af5928e9bdc8157227978ff734ef8cfd5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8D=89=E9=9E=8B=E6=B2=A1=E5=8F=B7?= <308487730@qq.com> Date: Sun, 30 Apr 2023 13:00:11 +0800 Subject: [PATCH 06/11] refactor: cleanup --- package.json | 2 +- src/analyze.ts | 19 ++---- src/dynamic-require.ts | 107 +++++--------------------------- src/generate-import.ts | 134 +++-------------------------------------- src/index.ts | 39 +++++------- src/utils.ts | 8 --- 6 files changed, 45 insertions(+), 264 deletions(-) diff --git a/package.json b/package.json index 1888be4..d496afc 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "dependencies": { "acorn": "^8.8.2", "fast-glob": "^3.2.12", - "vite-plugin-dynamic-import": "^1.3.0" + "vite-plugin-dynamic-import": "^1.3.1" }, "devDependencies": { "@types/node": "^18.16.2", diff --git a/src/analyze.ts b/src/analyze.ts index ca2f91c..6efa216 100644 --- a/src/analyze.ts +++ b/src/analyze.ts @@ -10,11 +10,13 @@ export enum TopScopeType { } export interface RequireStatement { + /** CallExpression */ node: AcornNode ancestors: AcornNode[] /** * If require statement located top-level scope ant it is convertible, this will have a value(🎯-①) * 如果 require 在顶级作用于,并且是可转换 import 的,那么 topScopeNode 将会被赋值 + * @deprecated 🤔 */ topScopeNode?: AcornNode & { type: TopScopeType } dynamic?: @@ -67,7 +69,7 @@ export function analyzer(ast: AcornNode, code: string, id: string): Analyzed { topScopeNode: dynamic === 'dynamic' ? undefined : findTopLevelScope(ancestors) as RequireStatement['topScopeNode'], - dynamic: checkDynamicId(node), + dynamic, }) }, AssignmentExpression(node) { @@ -112,22 +114,11 @@ function findTopLevelScope(ancestors: AcornNode[]): AcornNode | undefined { const ances = ancestors.map(an => an.type).join() const arr = [...ancestors].reverse() + // TODO: better top-scope detect + if (/Program,ExpressionStatement,(MemberExpression,)?CallExpression$/.test(ances)) { // Program,ExpressionStatement,CallExpression | require('foo') // Program,ExpressionStatement,MemberExpression,CallExpression | require('foo').bar return arr.find(e => e.type === TopScopeType.ExpressionStatement) } - - // TODO(#15): Loose conversion of `exports` is required to get elegant import statements, vice versa. - // 需要松散的 exports 转换,才能得到优雅的 import 语句,反之亦然。 - // 🚨-①: Vite also does the same. All statements are imported as `*`, which is simple and easy to implement. :) - // Vite 也是这么做的,所有语句都以 * 导入,即简单又好实现。 - return - - // At present, "ancestors" contains only one depth of "MemberExpression" - if (/Program,VariableDeclaration,VariableDeclarator,(MemberExpression,)?CallExpression$/.test(ances)) { - // const bar = require('foo').bar - // const { foo, bar: baz } = require('foo') - return arr.find(e => e.type === TopScopeType.VariableDeclaration) - } } diff --git a/src/dynamic-require.ts b/src/dynamic-require.ts index f07e39d..fd2a6c3 100644 --- a/src/dynamic-require.ts +++ b/src/dynamic-require.ts @@ -1,17 +1,13 @@ import path from 'node:path' import type { ResolvedConfig } from 'vite' -import fastGlob from 'fast-glob' import { - type Resolved, Resolve, - dynamicImportToGlob, mappingPath, - toLooseGlob, + globFiles, } from 'vite-plugin-dynamic-import' -import { normalizePath, relativeify } from 'vite-plugin-utils/function' +import { normalizePath } from 'vite-plugin-utils/function' import type { Options } from '.' import type { Analyzed } from './analyze' -import { normallyImporteeRE } from './utils' export interface DynamicRequireRecord { node: AcornNode @@ -43,28 +39,29 @@ export class DynaimcRequire { const { node, dynamic } = req if (dynamic !== 'dynamic') continue - const globResult = await globFiles( - node, - analyzed.code, - analyzed.id, - this.resolve, - this.options.extensions, - options.dynamic?.loose !== false, - ) + const globResult = await globFiles({ + importeeNode: node.arguments[0], + importExpression: analyzed.code.slice(node.start, node.end), + importer: analyzed.id, + resolve: this.resolve, + extensions: this.options.extensions, + loose: options.dynamic?.loose !== false, + }) if (!globResult) continue const record: DynamicRequireRecord = { node } let { files, resolved, normally } = globResult - // skip itself - files = files!.filter(f => normalizePath(path.join(path.dirname(id), f)) !== id) - // execute the dynamic.onFiles - options.dynamic?.onFiles && (files = options.dynamic?.onFiles(files, id) || files) if (normally) { record.normally = normally continue } + // skip itself + files = files!.filter(f => normalizePath(path.join(path.dirname(id), f)) !== id) + // execute the dynamic.onFiles + options.dynamic?.onFiles && (files = options.dynamic?.onFiles(files, id) || files) + if (!files?.length) continue const maps = mappingPath( @@ -108,77 +105,3 @@ ${cases.join('\n')} return records.length ? records : null } } - -async function globFiles( - /** ImportExpression */ - node: AcornNode, - code: string, - importer: string, - resolve: Resolve, - extensions: string[], - loose = true, -): Promise<{ - files?: string[] - resolved?: Resolved - /** After `expressiontoglob()` processing, it may become a normally path */ - normally?: string -} | undefined> { - let files: string[] - let resolved: Resolved | undefined - let normally: string - - const PAHT_FILL = '####/' - const EXT_FILL = '.extension' - let glob: string | null - let globRaw!: string - - glob = await dynamicImportToGlob( - node.arguments[0], - code.slice(node.start, node.end), - async (raw) => { - globRaw = raw - resolved = await resolve.tryResolve(raw, importer) - if (resolved) { - raw = resolved.import.resolved - } - if (!path.extname(raw)) { - // Bypass extension restrict - raw = raw + EXT_FILL - } - if (/^\.\/\*\.\w+$/.test(raw)) { - // Bypass ownDirectoryStarExtension (./*.ext) - raw = raw.replace('./*', `./${PAHT_FILL}*`) - } - return raw - }, - ) - if (!glob) { - if (normallyImporteeRE.test(globRaw)) { - normally = globRaw - return { normally } - } - return - } - - // @ts-ignore - const globs = [].concat(loose ? toLooseGlob(glob) : glob) - .map((g: any) => { - g.includes(PAHT_FILL) && (g = g.replace(PAHT_FILL, '')) - g.endsWith(EXT_FILL) && (g = g.replace(EXT_FILL, '')) - return g - }) - const fileGlobs = globs - .map(g => path.extname(g) - ? g - // If not ext is not specified, fill necessary extensions - // e.g. - // `./foo/*` -> `./foo/*.{js,ts,vue,...}` - : g + `.{${extensions.map(e => e.replace(/^\./, '')).join(',')}}` - ) - - files = fastGlob - .sync(fileGlobs, { cwd: /* 🚧-① */path.dirname(importer) }) - .map(file => relativeify(file)) - - return { files, resolved } -} diff --git a/src/generate-import.ts b/src/generate-import.ts index a183c85..09773fb 100644 --- a/src/generate-import.ts +++ b/src/generate-import.ts @@ -1,58 +1,21 @@ -import { - type Analyzed, - type RequireStatement, - TopScopeType, -} from './analyze' - -/** - * ``` - * At present, divide `require(id: Literal)` into three cases - * 目前,将 require() 分为三种情况 - * - * ①(🎯) - * In the top-level scope and can be converted to `import` directly - * 在顶层作用域,并且直接转换成 import - * - * ②(🚧) - * If the `id` in `require(id: Literal)` is a literal string, the `require` statement will be promoted to the top-level scope and become an `import` statement - * 如果 require(id: Literal) 中的 id 是字面量字符串,require 语句将会被提升到顶级作用域,变成 import 语句 - * - * ③(🚧) - * If the `id` in `require(dynamic-id)` is a dynamic-id, the `require` statement will be converted to `__matchRequireRuntime` function - * 如果 require(dynamic-id) 中的 id 动态 id,require 语句将会被转换成 __matchRequireRuntime 函数 - * ``` - */ +import type { Analyzed } from './analyze' export interface ImportRecord { node: AcornNode - topScopeNode?: RequireStatement['topScopeNode'] - importee?: string - // e.g. - // source code 👉 const ast = require('acorn').parse() - // ↓ - // importee 👉 import * as __CJS_import__ from 'acorn' - // declaration 👉 const ast = __CJS_import__.parse() - declaration?: string - importName?: string + importExpression?: string + importedName?: string } export function generateImport(analyzed: Analyzed) { const imports: ImportRecord[] = [] let count = 0 - for (const req of analyzed.require) { - const { - node, - ancestors, - topScopeNode, - dynamic, - } = req + for (const { node, dynamic } of analyzed.require) { - // ③(🚧) - // Processed in dynamic-require.ts + // Handled in `dynamic-require.ts` if (dynamic === 'dynamic') continue - const impt: ImportRecord = { node, topScopeNode } + const impt: ImportRecord = { node } const importName = `__CJS__import__${count++}__` const requireIdNode = node.arguments[0] @@ -71,88 +34,9 @@ export function generateImport(analyzed: Analyzed) { ${'^'.repeat(codeSnippets.length)}`) } - if (/* 🚨-① topScopeNode */false) { - // ①(🎯) - - // @ts-ignore - switch (topScopeNode.type) { - case TopScopeType.ExpressionStatement: - // TODO: With members - e.g. `require().foo` - impt.importee = `import '${requireId}'` - break - - case TopScopeType.VariableDeclaration: - // TODO: Multiple declaration - // @ts-ignore - const VariableDeclarator = topScopeNode.declarations[0] - const { /* L-V */id, /* R-V */init } = VariableDeclarator as AcornNode - - // Left value - let LV: string | { key: string, value: string }[] - if (id.type === 'Identifier') { - LV = id.name - } else if (id.type === 'ObjectPattern') { - LV = [] - for (const { key, value } of id.properties) { - // @ts-ignore - LV.push({ key: key.name, value: value.name }) - } - } else { - throw new Error(`Unknown VariableDeclarator.id.type(L-V): ${id.type}`) - } - - const LV_str = (spe: string) => typeof LV === 'object' - ? LV.map(e => e.key === e.value ? e.key : `${e.key} ${spe} ${e.value}`).join(', ') - : '' - - // Right value - if (init.type === 'CallExpression') { - if (typeof LV === 'string') { - // const acorn = require('acorn') - impt.importee = `import * as ${LV} from '${requireId}'` - } else { - // const { parse } = require('acorn') - impt.importee = `import { ${LV_str('as')} } from '${requireId}'` - } - } else if (init.type === 'MemberExpression') { - const onlyOneMember = ancestors.find(an => an.type === 'MemberExpression')?.property.name - const importDefault = onlyOneMember === 'default' - if (typeof LV === 'string') { - if (importDefault) { - // const foo = require('foo').default - impt.importee = `import ${LV} from '${requireId}'` - } else { - impt.importee = onlyOneMember === LV - // const bar = require('foo').bar - ? `import { ${LV} } from '${requireId}'` - // const barAlias = require('foo').bar - : `import { ${onlyOneMember} as ${LV} } from '${requireId}'` - } - } else { - if (importDefault) { - // const { member1, member2 } = require('foo').default - impt.importee = `import ${importName} from '${requireId}'` - } else { - // const { member1, member2 } = require('foo').bar - impt.importee = `import { ${onlyOneMember} as ${importName} } from '${requireId}'` - } - impt.declaration = `const { ${LV_str(':')} } = ${importName}` - } - } else { - throw new Error(`Unknown VariableDeclarator.init.type(R-V): ${id.init}`) - } - break - - default: - throw new Error(`Unknown TopScopeType: ${topScopeNode}`) - } - } else { - // ②(🚧) - - // This is probably less accurate but is much cheaper than a full AST parse. - impt.importee = `import * as ${importName} from '${requireId}'` - impt.importName = `${importName}.default || ${importName}` // Loose - } + // This is probably less accurate but is much cheaper than a full AST parse. + impt.importExpression = `import * as ${importName} from '${requireId}'` + impt.importedName = `${importName}.default || ${importName}` // Loose imports.push(impt) } diff --git a/src/index.ts b/src/index.ts index 34d18c1..84dc625 100644 --- a/src/index.ts +++ b/src/index.ts @@ -147,35 +147,22 @@ async function transformCommonjs({ for (const impt of imports) { const { node, - importee: imptee, - declaration, - importName, - topScopeNode, + importExpression, + importedName, } = impt - const importee = imptee + ';' - - let importStatement: string | undefined - if (topScopeNode) { - if (topScopeNode.type === TopScopeType.ExpressionStatement) { - importStatement = importee - } else if (topScopeNode.type === TopScopeType.VariableDeclaration) { - importStatement = declaration ? `${importee} ${declaration};` : importee - } - } else { + if (importExpression && importedName) { // TODO: Merge duplicated require id - hoistImports.push(importee) - importStatement = importName - } - - if (importStatement) { - const start = topScopeNode ? topScopeNode.start : node.start - const end = topScopeNode ? topScopeNode.end : node.end - ms.overwrite(start, end, importStatement) + hoistImports.push(importExpression + ';') + ms.overwrite(node.start, node.end, importedName) } } if (hoistImports.length) { - ms.prepend(['/* import-hoist-S */', ...hoistImports, '/* import-hoist-E */'].join(' ')) + ms.prepend([ + '/* [vite-plugin-commonjs] import-hoist-S */', + ...hoistImports, + '/* [vite-plugin-commonjs] import-hoist-E */', + ].join(' ')) } // exports @@ -215,7 +202,11 @@ async function transformCommonjs({ } if (requires.length) { - ms.prepend(['/* [vite-plugin-commonjs] import-require2import-S */', ...requires, '/* [vite-plugin-commonjs] import-require2import-E */'].join(' ')) + ms.prepend([ + '/* [vite-plugin-commonjs] import-require2import-S */', + ...requires, + '/* [vite-plugin-commonjs] import-require2import-E */', + ].join(' ')) } if (runtimes.length) { ms.append(runtimes.join('\n')) diff --git a/src/utils.ts b/src/utils.ts index 6e8b25c..be9ad31 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,19 +1,11 @@ import { builtinModules } from 'node:module' import { multilineCommentsRE, singlelineCommentsRE } from 'vite-plugin-utils/constant' -// ------------------------------------------------- RegExp - -export const normallyImporteeRE = /^\.{1,2}\/[.-/\w]+(\.\w+)$/ - -// ------------------------------------------------- const - export const builtins = [ ...builtinModules.map(m => !m.startsWith('_')), ...builtinModules.map(m => !m.startsWith('_')).map(m => `node:${m}`) ] -// ------------------------------------------------- function - export function isCommonjs(code: string) { // Avoid matching the content of the comment code = code From 4691bcc22dc480484bf8742bc16654dcaad73d26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8D=89=E9=9E=8B=E6=B2=A1=E5=8F=B7?= <308487730@qq.com> Date: Sun, 30 Apr 2023 21:56:54 +0800 Subject: [PATCH 07/11] feat: `glob` files log --- src/dynamic-require.ts | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/src/dynamic-require.ts b/src/dynamic-require.ts index fd2a6c3..30fd1d8 100644 --- a/src/dynamic-require.ts +++ b/src/dynamic-require.ts @@ -5,8 +5,11 @@ import { mappingPath, globFiles, } from 'vite-plugin-dynamic-import' -import { normalizePath } from 'vite-plugin-utils/function' -import type { Options } from '.' +import { normalizePath, COLOURS } from 'vite-plugin-utils/function' +import { + type Options, + TAG, +} from '.' import type { Analyzed } from './analyze' export interface DynamicRequireRecord { @@ -35,13 +38,13 @@ export class DynaimcRequire { const importCache = new Map(/* import-id, import-name */) const records: DynamicRequireRecord[] = [] - for (const req of analyzed.require) { - const { node, dynamic } = req + for (const { dynamic, node } of analyzed.require) { if (dynamic !== 'dynamic') continue + const importExpression = analyzed.code.slice(node.start, node.end) const globResult = await globFiles({ importeeNode: node.arguments[0], - importExpression: analyzed.code.slice(node.start, node.end), + importExpression, importer: analyzed.id, resolve: this.resolve, extensions: this.options.extensions, @@ -57,13 +60,20 @@ export class DynaimcRequire { continue } + if (!files?.length) { + console.log( + TAG, + COLOURS.yellow(`no files matched: ${importExpression}\n`), + ` file: ${analyzed.id}`, + ) + continue + } + // skip itself - files = files!.filter(f => normalizePath(path.join(path.dirname(id), f)) !== id) + files = files.filter(f => normalizePath(path.join(path.dirname(id), f)) !== id) // execute the dynamic.onFiles options.dynamic?.onFiles && (files = options.dynamic?.onFiles(files, id) || files) - if (!files?.length) continue - const maps = mappingPath( files, resolved ? { [resolved.alias.relative]: resolved.alias.findString } : undefined, From 750fd4ba82a4ea3fb39d75bf0972048d11dcf8f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8D=89=E9=9E=8B=E6=B2=A1=E5=8F=B7?= <308487730@qq.com> Date: Sun, 30 Apr 2023 21:57:15 +0800 Subject: [PATCH 08/11] chore: cleanup --- src/index.ts | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/index.ts b/src/index.ts index 84dc625..9be4e3b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -9,12 +9,14 @@ import { KNOWN_CSS_TYPES, } from 'vite-plugin-utils/constant' import { MagicString, cleanUrl } from 'vite-plugin-utils/function' -import { analyzer, TopScopeType } from './analyze' +import { analyzer } from './analyze' import { generateImport } from './generate-import' import { generateExport } from './generate-export' import { isCommonjs } from './utils' import { DynaimcRequire } from './dynamic-require' +export const TAG = '[vite-plugin-commonjs]' + export interface Options { filter?: (id: string) => boolean | undefined dynamic?: { @@ -126,7 +128,7 @@ async function transformCommonjs({ let ast: AcornNode try { - ast = parseAst(code, { ecmaVersion: 2020 }) as AcornNode + ast = parseAst(code, { sourceType: 'module', ecmaVersion: 2020 }) as AcornNode } catch (error) { // ignore as it might not be a JS file, the subsequent plugins will catch the error return null @@ -159,24 +161,24 @@ async function transformCommonjs({ if (hoistImports.length) { ms.prepend([ - '/* [vite-plugin-commonjs] import-hoist-S */', + `/* ${TAG} import-hoist-S */`, ...hoistImports, - '/* [vite-plugin-commonjs] import-hoist-E */', + `/* ${TAG} import-hoist-E */`, ].join(' ')) } // exports if (exportRuntime) { const polyfill = [ - '/* [vite-plugin-commonjs] export-runtime-S */', + `/* ${TAG} export-runtime-S */`, exportRuntime.polyfill, - '/* [vite-plugin-commonjs] export-runtime-E */', + `/* ${TAG} export-runtime-E */`, ].join(' ') const _exports = [ - '/* [vite-plugin-commonjs] export-statement-S */', + `/* ${TAG} export-statement-S */`, exportRuntime.exportDeclaration, - '/* [vite-plugin-commonjs] export-statement-E */', + `/* ${TAG} export-statement-E */`, ].filter(Boolean) .join('\n') ms.prepend(polyfill).append(_exports) @@ -203,9 +205,9 @@ async function transformCommonjs({ if (requires.length) { ms.prepend([ - '/* [vite-plugin-commonjs] import-require2import-S */', + `/* ${TAG} import-require2import-S */`, ...requires, - '/* [vite-plugin-commonjs] import-require2import-E */', + `/* ${TAG} import-require2import-E */`, ].join(' ')) } if (runtimes.length) { From 0c892f1dbd9270512a23ee898529f6980a318a53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8D=89=E9=9E=8B=E6=B2=A1=E5=8F=B7?= <308487730@qq.com> Date: Sun, 30 Apr 2023 21:58:01 +0800 Subject: [PATCH 09/11] chore: bump deps --- package.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index d496afc..a699257 100644 --- a/package.json +++ b/package.json @@ -28,13 +28,14 @@ "dependencies": { "acorn": "^8.8.2", "fast-glob": "^3.2.12", - "vite-plugin-dynamic-import": "^1.3.1" + "vite-plugin-dynamic-import": "^1.3.2" }, "devDependencies": { "@types/node": "^18.16.2", "typescript": "^4.9.4", "vite": "^4.3.3", - "vite-plugin-utils": "^0.4.2" + "vite-plugin-utils": "^0.4.3", + "vitest": "^0.30.1" }, "keywords": [ "vite", From e9eb5f031977a494c01ed57929aa896a4b229f37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8D=89=E9=9E=8B=E6=B2=A1=E5=8F=B7?= <308487730@qq.com> Date: Sun, 30 Apr 2023 22:14:15 +0800 Subject: [PATCH 10/11] =?UTF-8?q?refactor(test):=20integrate=20vitest=20?= =?UTF-8?q?=F0=9F=8C=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 5 ++- test/__snapshots__/cjs.js | 13 ------ test/__snapshots__/main.ts | 11 ----- test/__snapshots__/module-exports/hello.cjs | 8 ---- test/__snapshots__/module-exports/world.cjs | 8 ---- test/fixtures/__snapshots__/cjs.js | 13 ++++++ test/{ => fixtures}/__snapshots__/dynamic.tsx | 2 +- test/{ => fixtures}/__snapshots__/exports.js | 6 +-- test/fixtures/__snapshots__/main.ts | 11 +++++ .../__snapshots__/module-exports/hello.cjs | 8 ++++ .../__snapshots__/module-exports/world.cjs | 8 ++++ test/{ => fixtures}/index.html | 0 test/{ => fixtures}/src/cjs.js | 0 test/{ => fixtures}/src/dynamic.tsx | 0 test/{ => fixtures}/src/exports.js | 0 test/{ => fixtures}/src/main.ts | 0 .../src/module-exports/hello.cjs | 0 .../src/module-exports/world.cjs | 0 test/{ => fixtures}/vite.config.ts | 6 +-- test/serve.test.ts | 44 +++++++++++++++++++ 20 files changed, 93 insertions(+), 50 deletions(-) delete mode 100644 test/__snapshots__/cjs.js delete mode 100644 test/__snapshots__/main.ts delete mode 100644 test/__snapshots__/module-exports/hello.cjs delete mode 100644 test/__snapshots__/module-exports/world.cjs create mode 100644 test/fixtures/__snapshots__/cjs.js rename test/{ => fixtures}/__snapshots__/dynamic.tsx (76%) rename test/{ => fixtures}/__snapshots__/exports.js (55%) create mode 100644 test/fixtures/__snapshots__/main.ts create mode 100644 test/fixtures/__snapshots__/module-exports/hello.cjs create mode 100644 test/fixtures/__snapshots__/module-exports/world.cjs rename test/{ => fixtures}/index.html (100%) rename test/{ => fixtures}/src/cjs.js (100%) rename test/{ => fixtures}/src/dynamic.tsx (100%) rename test/{ => fixtures}/src/exports.js (100%) rename test/{ => fixtures}/src/main.ts (100%) rename test/{ => fixtures}/src/module-exports/hello.cjs (100%) rename test/{ => fixtures}/src/module-exports/world.cjs (100%) rename test/{ => fixtures}/vite.config.ts (80%) create mode 100644 test/serve.test.ts diff --git a/package.json b/package.json index a699257..fe819f3 100644 --- a/package.json +++ b/package.json @@ -21,9 +21,9 @@ "scripts": { "dev": "vite build --watch", "build": "vite build", - "test": "vite -c test/vite.config.ts", + "test": "vitest run", "types": "tsc", - "prepublishOnly": "npm run build" + "prepublishOnly": "npm run test && npm run build" }, "dependencies": { "acorn": "^8.8.2", @@ -32,6 +32,7 @@ }, "devDependencies": { "@types/node": "^18.16.2", + "node-fetch": "^3.3.1", "typescript": "^4.9.4", "vite": "^4.3.3", "vite-plugin-utils": "^0.4.3", diff --git a/test/__snapshots__/cjs.js b/test/__snapshots__/cjs.js deleted file mode 100644 index e5629f5..0000000 --- a/test/__snapshots__/cjs.js +++ /dev/null @@ -1,13 +0,0 @@ -/* export-runtime-S */ var module = { exports: {} }; var exports = module.exports; /* export-runtime-E */if (typeof exports !== 'undefined') { - if (typeof module !== 'undefined' && module.exports) { - exports = module.exports = { - cjs: 'cjs', - } - } -} -/* export-statement-S */ -const __CJS__export_default__ = (module.exports == null ? {} : module.exports).default || module.exports; -export { - __CJS__export_default__ as default, -} -/* export-statement-E */ \ No newline at end of file diff --git a/test/__snapshots__/main.ts b/test/__snapshots__/main.ts deleted file mode 100644 index 3671ed0..0000000 --- a/test/__snapshots__/main.ts +++ /dev/null @@ -1,11 +0,0 @@ -/* import-hoist-S */ import * as __CJS__import__0__ from './exports'; /* import-hoist-E */const { msg: message } = __CJS__import__0__.default || __CJS__import__0__; -import cjs from "./cjs"; -document.querySelector("#app").innerHTML = ` -
-    ${message}
-  
-
-
-    ${cjs.cjs}
-  
-`; diff --git a/test/__snapshots__/module-exports/hello.cjs b/test/__snapshots__/module-exports/hello.cjs deleted file mode 100644 index 1ac12aa..0000000 --- a/test/__snapshots__/module-exports/hello.cjs +++ /dev/null @@ -1,8 +0,0 @@ -/* export-runtime-S */ var module = { exports: {} }; var exports = module.exports; /* export-runtime-E */ -module.exports = 'module-exports/hello.cjs' -/* export-statement-S */ -const __CJS__export_default__ = (module.exports == null ? {} : module.exports).default || module.exports; -export { - __CJS__export_default__ as default, -} -/* export-statement-E */ \ No newline at end of file diff --git a/test/__snapshots__/module-exports/world.cjs b/test/__snapshots__/module-exports/world.cjs deleted file mode 100644 index 7ae0fcb..0000000 --- a/test/__snapshots__/module-exports/world.cjs +++ /dev/null @@ -1,8 +0,0 @@ -/* export-runtime-S */ var module = { exports: {} }; var exports = module.exports; /* export-runtime-E */ -module.exports = 'module-exports/world.cjs' -/* export-statement-S */ -const __CJS__export_default__ = (module.exports == null ? {} : module.exports).default || module.exports; -export { - __CJS__export_default__ as default, -} -/* export-statement-E */ \ No newline at end of file diff --git a/test/fixtures/__snapshots__/cjs.js b/test/fixtures/__snapshots__/cjs.js new file mode 100644 index 0000000..11f6a66 --- /dev/null +++ b/test/fixtures/__snapshots__/cjs.js @@ -0,0 +1,13 @@ +/* [vite-plugin-commonjs] export-runtime-S */ var module = { exports: {} }; var exports = module.exports; /* [vite-plugin-commonjs] export-runtime-E */if (typeof exports !== 'undefined') { + if (typeof module !== 'undefined' && module.exports) { + exports = module.exports = { + cjs: 'cjs', + } + } +} +/* [vite-plugin-commonjs] export-statement-S */ +const __CJS__export_default__ = (module.exports == null ? {} : module.exports).default || module.exports; +export { + __CJS__export_default__ as default, +} +/* [vite-plugin-commonjs] export-statement-E */ \ No newline at end of file diff --git a/test/__snapshots__/dynamic.tsx b/test/fixtures/__snapshots__/dynamic.tsx similarity index 76% rename from test/__snapshots__/dynamic.tsx rename to test/fixtures/__snapshots__/dynamic.tsx index d3a0c9d..42ce118 100644 --- a/test/__snapshots__/dynamic.tsx +++ b/test/fixtures/__snapshots__/dynamic.tsx @@ -1,4 +1,4 @@ -/* import-require2import-S */ import * as __dynamic_require2import__0__0 from './module-exports/hello.cjs'; import * as __dynamic_require2import__0__1 from './module-exports/world.cjs'; /* import-require2import-E */function load(name) { +/* [vite-plugin-commonjs] import-require2import-S */ import * as __dynamic_require2import__0__0 from './module-exports/hello.cjs'; import * as __dynamic_require2import__0__1 from './module-exports/world.cjs'; /* [vite-plugin-commonjs] import-require2import-E */function load(name) { const mod = function __matchRequireRuntime0__(path) { switch(path) { case '@/module-exports/hello': diff --git a/test/__snapshots__/exports.js b/test/fixtures/__snapshots__/exports.js similarity index 55% rename from test/__snapshots__/exports.js rename to test/fixtures/__snapshots__/exports.js index 2a58686..433d1ee 100644 --- a/test/__snapshots__/exports.js +++ b/test/fixtures/__snapshots__/exports.js @@ -1,4 +1,4 @@ -/* export-runtime-S */ var module = { exports: {} }; var exports = module.exports; /* export-runtime-E *//* import-hoist-S */ import * as __CJS__import__0__ from './dynamic'; /* import-hoist-E */ +/* [vite-plugin-commonjs] export-runtime-S */ var module = { exports: {} }; var exports = module.exports; /* [vite-plugin-commonjs] export-runtime-E *//* [vite-plugin-commonjs] import-hoist-S */ import * as __CJS__import__0__ from './dynamic'; /* [vite-plugin-commonjs] import-hoist-E */ const { hello, world } = __CJS__import__0__.default || __CJS__import__0__ // ❌ `exports` exported members are dynamic. @@ -21,9 +21,9 @@ import cjs from './cjs' cjs: ${JSON.stringify(cjs)} ` -/* export-statement-S */ +/* [vite-plugin-commonjs] export-statement-S */ const __CJS__export_msg__ = (module.exports == null ? {} : module.exports).msg; export { __CJS__export_msg__ as msg, } -/* export-statement-E */ \ No newline at end of file +/* [vite-plugin-commonjs] export-statement-E */ \ No newline at end of file diff --git a/test/fixtures/__snapshots__/main.ts b/test/fixtures/__snapshots__/main.ts new file mode 100644 index 0000000..317477b --- /dev/null +++ b/test/fixtures/__snapshots__/main.ts @@ -0,0 +1,11 @@ +/* [vite-plugin-commonjs] import-hoist-S */ import * as __CJS__import__0__ from './exports'; /* [vite-plugin-commonjs] import-hoist-E */const { msg: message } = __CJS__import__0__.default || __CJS__import__0__; +import cjs from "./cjs"; +document.querySelector("#app").innerHTML = ` +
+    ${message}
+  
+
+
+    ${cjs.cjs}
+  
+`; diff --git a/test/fixtures/__snapshots__/module-exports/hello.cjs b/test/fixtures/__snapshots__/module-exports/hello.cjs new file mode 100644 index 0000000..50e833f --- /dev/null +++ b/test/fixtures/__snapshots__/module-exports/hello.cjs @@ -0,0 +1,8 @@ +/* [vite-plugin-commonjs] export-runtime-S */ var module = { exports: {} }; var exports = module.exports; /* [vite-plugin-commonjs] export-runtime-E */ +module.exports = 'module-exports/hello.cjs' +/* [vite-plugin-commonjs] export-statement-S */ +const __CJS__export_default__ = (module.exports == null ? {} : module.exports).default || module.exports; +export { + __CJS__export_default__ as default, +} +/* [vite-plugin-commonjs] export-statement-E */ \ No newline at end of file diff --git a/test/fixtures/__snapshots__/module-exports/world.cjs b/test/fixtures/__snapshots__/module-exports/world.cjs new file mode 100644 index 0000000..c75802f --- /dev/null +++ b/test/fixtures/__snapshots__/module-exports/world.cjs @@ -0,0 +1,8 @@ +/* [vite-plugin-commonjs] export-runtime-S */ var module = { exports: {} }; var exports = module.exports; /* [vite-plugin-commonjs] export-runtime-E */ +module.exports = 'module-exports/world.cjs' +/* [vite-plugin-commonjs] export-statement-S */ +const __CJS__export_default__ = (module.exports == null ? {} : module.exports).default || module.exports; +export { + __CJS__export_default__ as default, +} +/* [vite-plugin-commonjs] export-statement-E */ \ No newline at end of file diff --git a/test/index.html b/test/fixtures/index.html similarity index 100% rename from test/index.html rename to test/fixtures/index.html diff --git a/test/src/cjs.js b/test/fixtures/src/cjs.js similarity index 100% rename from test/src/cjs.js rename to test/fixtures/src/cjs.js diff --git a/test/src/dynamic.tsx b/test/fixtures/src/dynamic.tsx similarity index 100% rename from test/src/dynamic.tsx rename to test/fixtures/src/dynamic.tsx diff --git a/test/src/exports.js b/test/fixtures/src/exports.js similarity index 100% rename from test/src/exports.js rename to test/fixtures/src/exports.js diff --git a/test/src/main.ts b/test/fixtures/src/main.ts similarity index 100% rename from test/src/main.ts rename to test/fixtures/src/main.ts diff --git a/test/src/module-exports/hello.cjs b/test/fixtures/src/module-exports/hello.cjs similarity index 100% rename from test/src/module-exports/hello.cjs rename to test/fixtures/src/module-exports/hello.cjs diff --git a/test/src/module-exports/world.cjs b/test/fixtures/src/module-exports/world.cjs similarity index 100% rename from test/src/module-exports/world.cjs rename to test/fixtures/src/module-exports/world.cjs diff --git a/test/vite.config.ts b/test/fixtures/vite.config.ts similarity index 80% rename from test/vite.config.ts rename to test/fixtures/vite.config.ts index a9aa9de..8506492 100644 --- a/test/vite.config.ts +++ b/test/fixtures/vite.config.ts @@ -1,9 +1,7 @@ import path from 'path' import fs from 'fs' import { defineConfig } from 'vite' -import commonjs from '..' - -fs.rmSync(path.join(__dirname, '__snapshots__'), { force: true, recursive: true }) +import commonjs from '../..' export default defineConfig({ root: __dirname, @@ -14,7 +12,7 @@ export default defineConfig({ transform(code, id) { if (/\/src\//.test(id)) { // Write transformed code to output/ - const filename = id.replace('src', '__snapshots__') + const filename = id.replace('src', 'dist') const dirname = path.dirname(filename) if (!fs.existsSync(dirname)) fs.mkdirSync(dirname) fs.writeFileSync(filename, code) diff --git a/test/serve.test.ts b/test/serve.test.ts new file mode 100644 index 0000000..d6fa4b2 --- /dev/null +++ b/test/serve.test.ts @@ -0,0 +1,44 @@ +import fs from 'node:fs' +import path from 'node:path' +import { + type ViteDevServer, + createServer, +} from 'vite' +import { + afterAll, + beforeAll, + describe, + expect, + it, +} from 'vitest' +import fetch from 'node-fetch' +import fastGlob from 'fast-glob' + +const root = path.join(__dirname, 'fixtures') +let server: ViteDevServer | null = null +const PORT = 4000 + +beforeAll(async () => { + fs.rmSync(path.join(root, 'dist'), { recursive: true, force: true }) + server = await createServer({ configFile: path.join(root, 'vite.config.ts') }) + await server.listen(PORT) +}) + +describe('vite serve', async () => { + it('__snapshots__', async () => { + const files = fastGlob.sync('__snapshots__/**/*', { cwd: root }) + for (const file of files) { + const response = await (await fetch(`http://localhost:${PORT}/${file.replace('__snapshots__', 'src')}`)).text() + const distFile = fs.readFileSync(path.join(root, file.replace('__snapshots__', 'dist')), 'utf8') + const snapFile = fs.readFileSync(path.join(root, file), 'utf8') + + expect(response).string + expect(distFile).eq(snapFile) + } + }) +}) + +afterAll(async () => { + await server?.close() + server = null +}) From ddf42eb814811da1af68647e5afe39de1643c457 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8D=89=E9=9E=8B=E6=B2=A1=E5=8F=B7?= <308487730@qq.com> Date: Sun, 30 Apr 2023 22:18:09 +0800 Subject: [PATCH 11/11] log: v0.7.0 --- CHANGELOG.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d01f4ab..8b70cd8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,16 @@ +## 0.7.0 (2023-04-30) + +- e9eb5f0 refactor(test): integrate vitest 🌱 +- 0c892f1 chore: bump deps +- 750fd4b chore: cleanup +- 4691bcc feat: `glob` files log +- 80f46f0 refactor: cleanup +- 7d6a47f chore: backup `v0.5.3` +- 782db06 docs: v0.7.0 +- d3207f3 refactor(build): better scripts +- e69a65a refactor: better support `node_modules` #23 +- 62a8cd9 chore: cleanup types + ## 0.6.2 (2023-03-12) - enhancement: support node_modules | #19