From de0a56e1277128e9b06346b58d913973df25b8f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=87=8E=E9=B8=A1=E6=B2=A1=E5=90=8D?= Date: Fri, 22 Sep 2023 10:11:01 +0800 Subject: [PATCH] feat: supports full module interop --- src/dynamic-require.ts | 4 ++-- src/generate-import.ts | 28 +++++++++++++++------------- src/index.ts | 25 +++++++++++++++---------- 3 files changed, 32 insertions(+), 25 deletions(-) diff --git a/src/dynamic-require.ts b/src/dynamic-require.ts index 30fd1d8..26beeb6 100644 --- a/src/dynamic-require.ts +++ b/src/dynamic-require.ts @@ -7,7 +7,7 @@ import { } from 'vite-plugin-dynamic-import' import { normalizePath, COLOURS } from 'vite-plugin-utils/function' import { - type Options, + type CommonjsOptions, TAG, } from '.' import type { Analyzed } from './analyze' @@ -27,7 +27,7 @@ export class DynaimcRequire { constructor( private config: ResolvedConfig, - private options: Options & { extensions: string[] }, + private options: CommonjsOptions & { extensions: string[] }, private resolve = new Resolve(config), ) { } diff --git a/src/generate-import.ts b/src/generate-import.ts index 80d43c1..59894a6 100644 --- a/src/generate-import.ts +++ b/src/generate-import.ts @@ -1,13 +1,14 @@ -import type { ImportType } from 'src' +import type { CommonjsOptions, ImportInteropType } from 'src' import type { Analyzed } from './analyze' export interface ImportRecord { node: AcornNode importExpression?: string - importedName?: string + importInterop?: string } -export function generateImport(analyzed: Analyzed, id: string, rules?: ImportType | ((id: string) => ImportType)) { +export function generateImport(analyzed: Analyzed, id: string, options: CommonjsOptions) { + const { importRules } = options.advanced ?? {} const imports: ImportRecord[] = [] let count = 0 @@ -36,26 +37,27 @@ export function generateImport(analyzed: Analyzed, id: string, rules?: ImportTyp } // This is probably less accurate, but is much cheaper than a full AST parse. - let importType: ImportType = 'defaultFirst' - if (typeof rules === 'string') { - importType = rules - } else if (typeof rules === 'function') { - importType = rules(id) || 'defaultFirst' + let importInterop: ImportInteropType | string = 'defaultFirst' + if (typeof importRules === 'string') { + importInterop = importRules + } else if (typeof importRules === 'function') { + importInterop = importRules(id) } impt.importExpression = `import * as ${importName} from "${requireId}"` - switch (importType) { + switch (importInterop) { case 'defaultFirst': - impt.importedName = `${importName}.default || ${importName}` + impt.importInterop = `${importName}.default || ${importName}` break case 'namedFirst': - impt.importedName = `Object.keys(${importName}).join('') !== 'default' ? ${importName} : ${importName}.default` + impt.importInterop = `Object.keys(${importName}).join('') !== "default" ? ${importName} : ${importName}.default` break case 'merge': - impt.importedName = `${importName}.default ? Object.assign(${importName}.default, ${importName}) : ${importName}` + impt.importInterop = `${importName}.default ? Object.assign(${importName}.default, ${importName}) : ${importName}` break default: - throw new Error(`Unknown import type: ${importType} for ${id}`) + // User-defined module interop. + impt.importInterop = importInterop // string | undefined } imports.push(impt) diff --git a/src/index.ts b/src/index.ts index a4b6794..c5b2e7c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -20,9 +20,9 @@ import { DynaimcRequire } from './dynamic-require' export const TAG = '[vite-plugin-commonjs]' -export type ImportType = 'defaultFirst' | 'namedFirst' | 'merge' +export type ImportInteropType = 'defaultFirst' | 'namedFirst' | 'merge' -export interface Options { +export interface CommonjsOptions { filter?: (id: string) => boolean | undefined dynamic?: { /** @@ -45,12 +45,17 @@ export interface Options { onFiles?: (files: string[], id: string) => typeof files | undefined } advanced?: { - /** Custom import behavior */ - importRules?: ImportType | ((id: string) => ImportType) + /** + * Custom import module interop behavior. + * + * If you want to fully customize the interop behavior, + * you can pass a function and return the interop code snippet. + */ + importRules?: ImportInteropType | ((id: string) => ImportInteropType | string) } } -export default function commonjs(options: Options = {}): Plugin { +export default function commonjs(options: CommonjsOptions = {}): Plugin { let config: ResolvedConfig let extensions = DEFAULT_EXTENSIONS let dynaimcRequire: DynaimcRequire @@ -122,7 +127,7 @@ async function transformCommonjs({ extensions, dynaimcRequire, }: { - options: Options, + options: CommonjsOptions, code: string, id: string, extensions: string[], @@ -146,7 +151,7 @@ async function transformCommonjs({ } const analyzed = analyzer(ast, code, id) - const imports = generateImport(analyzed, id, options.advanced?.importRules) + const imports = generateImport(analyzed, id, options) const exportRuntime = id.includes('node_modules/.vite') // Bypass Pre-build ? null @@ -161,12 +166,12 @@ async function transformCommonjs({ const { node, importExpression, - importedName, + importInterop, } = impt - if (importExpression && importedName) { + if (importExpression != null && importInterop != null) { // TODO: Merge duplicated require id hoistImports.push(importExpression + ';') - ms.overwrite(node.start, node.end, importedName) + ms.overwrite(node.start, node.end, importInterop) } }