diff --git a/packages/vite/src/node/index.ts b/packages/vite/src/node/index.ts index 1fc42c3ab00d78..ff8c0ebdf6abab 100644 --- a/packages/vite/src/node/index.ts +++ b/packages/vite/src/node/index.ts @@ -37,8 +37,6 @@ export type { DepOptimizationProcessing, OptimizedDepInfo, DepsOptimizer, - EsModuleLexerImportSpecifier, - EsModuleLexerParseReturnType, ExportsData } from './optimizer' export type { Plugin } from './plugin' diff --git a/packages/vite/src/node/optimizer/esbuildDepPlugin.ts b/packages/vite/src/node/optimizer/esbuildDepPlugin.ts index 4bf5c8b1c2152b..0c32af75219438 100644 --- a/packages/vite/src/node/optimizer/esbuildDepPlugin.ts +++ b/packages/vite/src/node/optimizer/esbuildDepPlugin.ts @@ -171,20 +171,15 @@ export function esbuildDepPlugin( } let contents = '' - const data = exportsData[id] - const [imports, exports] = data - if (!imports.length && !exports.length) { + const { hasImports, exports, hasReExports } = exportsData[id] + if (!hasImports && !exports.length) { // cjs contents += `export default require("${relativePath}");` } else { if (exports.includes('default')) { contents += `import d from "${relativePath}";export default d;` } - if ( - data.hasReExports || - exports.length > 1 || - exports[0] !== 'default' - ) { + if (hasReExports || exports.length > 1 || exports[0] !== 'default') { contents += `\nexport * from "${relativePath}"` } } diff --git a/packages/vite/src/node/optimizer/index.ts b/packages/vite/src/node/optimizer/index.ts index 2fbaedcc54dc55..10908caf9072b3 100644 --- a/packages/vite/src/node/optimizer/index.ts +++ b/packages/vite/src/node/optimizer/index.ts @@ -6,7 +6,6 @@ import colors from 'picocolors' import type { BuildOptions as EsbuildBuildOptions } from 'esbuild' import { build } from 'esbuild' import { init, parse } from 'es-module-lexer' -import type { ImportSpecifier as EsModuleLexerImportSpecifier } from 'types/es-module-lexer' import type { ResolvedConfig } from '../config' import { createDebugger, @@ -32,18 +31,15 @@ const isDebugEnabled = _debug('vite:deps').enabled const jsExtensionRE = /\.js$/i const jsMapExtensionRE = /\.js\.map$/i -export type { EsModuleLexerImportSpecifier } -export type EsModuleLexerParseReturnType = readonly [ - imports: ReadonlyArray, - exports: ReadonlyArray, +export type ExportsData = { + hasImports: boolean + exports: readonly string[] facade: boolean -] -export type ExportsData = EsModuleLexerParseReturnType & { // es-module-lexer has a facade detection but isn't always accurate for our // use case when the module has default export - hasReExports?: true + hasReExports?: boolean // hint if the dep requires loading as jsx - jsxLoader?: true + jsxLoader?: boolean } export interface DepsOptimizer { @@ -754,7 +750,6 @@ export async function extractExportsData( config: ResolvedConfig ): Promise { await init - let exportsData: ExportsData const esbuildOptions = config.optimizeDeps?.esbuildOptions ?? {} if (config.optimizeDeps.extensions?.some((ext) => filePath.endsWith(ext))) { @@ -767,34 +762,48 @@ export async function extractExportsData( write: false, format: 'esm' }) - exportsData = parse(result.outputFiles[0].text) as ExportsData - } else { - const entryContent = fs.readFileSync(filePath, 'utf-8') - try { - exportsData = parse(entryContent) as ExportsData - } catch { - const loader = esbuildOptions.loader?.[path.extname(filePath)] || 'jsx' - debug( - `Unable to parse: ${filePath}.\n Trying again with a ${loader} transform.` - ) - const transformed = await transformWithEsbuild(entryContent, filePath, { - loader - }) - // Ensure that optimization won't fail by defaulting '.js' to the JSX parser. - // This is useful for packages such as Gatsby. - esbuildOptions.loader = { - '.js': 'jsx', - ...esbuildOptions.loader - } - exportsData = parse(transformed.code) as ExportsData - exportsData.jsxLoader = true + const [imports, exports, facade] = parse(result.outputFiles[0].text) + return { + hasImports: imports.length > 0, + exports, + facade } - for (const { ss, se } of exportsData[0]) { - const exp = entryContent.slice(ss, se) - if (/export\s+\*\s+from/.test(exp)) { - exportsData.hasReExports = true - } + } + + let parseResult: ReturnType + let usedJsxLoader = false + + const entryContent = fs.readFileSync(filePath, 'utf-8') + try { + parseResult = parse(entryContent) + } catch { + const loader = esbuildOptions.loader?.[path.extname(filePath)] || 'jsx' + debug( + `Unable to parse: ${filePath}.\n Trying again with a ${loader} transform.` + ) + const transformed = await transformWithEsbuild(entryContent, filePath, { + loader + }) + // Ensure that optimization won't fail by defaulting '.js' to the JSX parser. + // This is useful for packages such as Gatsby. + esbuildOptions.loader = { + '.js': 'jsx', + ...esbuildOptions.loader } + parseResult = parse(transformed.code) + usedJsxLoader = true + } + + const [imports, exports, facade] = parseResult + const exportsData: ExportsData = { + hasImports: imports.length > 0, + exports, + facade, + hasReExports: imports.some(({ ss, se }) => { + const exp = entryContent.slice(ss, se) + return /export\s+\*\s+from/.test(exp) + }), + jsxLoader: usedJsxLoader } return exportsData } @@ -816,9 +825,9 @@ function needsInterop( ) { return true } - const [imports, exports] = exportsData + const { hasImports, exports } = exportsData // entry has no ESM syntax - likely CJS or UMD - if (!exports.length && !imports.length) { + if (!exports.length && !hasImports) { return true } diff --git a/packages/vite/types/es-module-lexer.d.ts b/packages/vite/types/es-module-lexer.d.ts deleted file mode 100644 index 8b405471b1e42f..00000000000000 --- a/packages/vite/types/es-module-lexer.d.ts +++ /dev/null @@ -1,90 +0,0 @@ -// Modified and inlined to avoid extra dependency -// Source: https://github.com/guybedford/es-module-lexer/blob/main/types/lexer.d.ts -// MIT Licensed https://github.com/guybedford/es-module-lexer/blob/main/LICENSE - -export interface ImportSpecifier { - /** - * Module name - * - * To handle escape sequences in specifier strings, the .n field of imported specifiers will be provided where possible. - * - * For dynamic import expressions, this field will be empty if not a valid JS string. - * - * @example - * const [imports1, exports1] = parse(String.raw`import './\u0061\u0062.js'`); - * imports1[0].n; - * // Returns "./ab.js" - * - * const [imports2, exports2] = parse(`import("./ab.js")`); - * imports2[0].n; - * // Returns "./ab.js" - * - * const [imports3, exports3] = parse(`import("./" + "ab.js")`); - * imports3[0].n; - * // Returns undefined - */ - readonly n: string | undefined - /** - * Start of module specifier - * - * @example - * const source = `import { a } from 'asdf'`; - * const [imports, exports] = parse(source); - * source.substring(imports[0].s, imports[0].e); - * // Returns "asdf" - */ - readonly s: number - /** - * End of module specifier - */ - readonly e: number - - /** - * Start of import statement - * - * @example - * const source = `import { a } from 'asdf'`; - * const [imports, exports] = parse(source); - * source.substring(imports[0].ss, imports[0].se); - * // Returns `"import { a } from 'asdf';"` - */ - readonly ss: number - /** - * End of import statement - */ - readonly se: number - - /** - * If this import statement is a dynamic import, this is the start value. - * Otherwise this is `-1`. - */ - readonly d: number - - /** - * If this import has an import assertion, this is the start value. - * Otherwise this is `-1`. - */ - readonly a: number -} - -/** - * Wait for init to resolve before calling `parse`. - */ -export const init: Promise - -/** - * Outputs the list of exports and locations of import specifiers, - * including dynamic import and import meta handling. - * - * @param source - Source code to parser - * @param name - Optional sourcename - * @returns Tuple contaning imports list and exports list. - */ -export function parse( - source: string, - name?: string -): readonly [ - imports: ReadonlyArray, - exports: ReadonlyArray, - facade: boolean -]