From d10dfa3fefcafc07139b455607ff9be8d574e52d Mon Sep 17 00:00:00 2001 From: sapphi-red <49056869+sapphi-red@users.noreply.github.com> Date: Wed, 11 Sep 2024 19:56:17 +0900 Subject: [PATCH 01/20] feat: use rolldown in the dep optimizer Co-authored-by: underfin --- packages/vite/package.json | 1 + packages/vite/rollup.config.ts | 1 + packages/vite/rollup.dts.config.ts | 1 + packages/vite/src/node/config.ts | 1 + .../src/node/optimizer/esbuildDepPlugin.ts | 345 ---------- packages/vite/src/node/optimizer/index.ts | 279 ++++---- .../src/node/optimizer/rolldownDepPlugin.ts | 334 +++++++++ packages/vite/src/node/optimizer/scan.ts | 642 ++++++++---------- packages/vite/src/node/plugin.ts | 11 +- .../vite/src/node/plugins/importAnalysis.ts | 12 +- playground/optimize-deps/vite.config.js | 26 +- pnpm-lock.yaml | 173 ++++- 12 files changed, 956 insertions(+), 870 deletions(-) delete mode 100644 packages/vite/src/node/optimizer/esbuildDepPlugin.ts create mode 100644 packages/vite/src/node/optimizer/rolldownDepPlugin.ts diff --git a/packages/vite/package.json b/packages/vite/package.json index 9c4250bfa20323..c928c5b8720cea 100644 --- a/packages/vite/package.json +++ b/packages/vite/package.json @@ -87,6 +87,7 @@ "dependencies": { "esbuild": "^0.24.0", "postcss": "^8.4.47", + "rolldown": "https://pkg.pr.new/rolldown@dab103f", "rollup": "^4.20.0" }, "optionalDependencies": { diff --git a/packages/vite/rollup.config.ts b/packages/vite/rollup.config.ts index ab979656ee822d..6883d9a91b0a60 100644 --- a/packages/vite/rollup.config.ts +++ b/packages/vite/rollup.config.ts @@ -99,6 +99,7 @@ const nodeConfig = defineConfig({ 'fsevents', 'lightningcss', 'rollup/parseAst', + 'rolldown/experimental', ...Object.keys(pkg.dependencies), ], plugins: [ diff --git a/packages/vite/rollup.dts.config.ts b/packages/vite/rollup.dts.config.ts index 73379bffa70a41..42c6bab09e4552 100644 --- a/packages/vite/rollup.dts.config.ts +++ b/packages/vite/rollup.dts.config.ts @@ -51,6 +51,7 @@ const identifierReplacements: Record> = { PluginContext$1: 'rollup.PluginContext', TransformPluginContext$1: 'rollup.TransformPluginContext', TransformResult$2: 'rollup.TransformResult', + RollupOptions$1: 'rollup.RollupOptions', }, esbuild: { TransformResult$1: 'esbuild_TransformResult', diff --git a/packages/vite/src/node/config.ts b/packages/vite/src/node/config.ts index af3a07c242899d..dd358d17b33fd2 100644 --- a/packages/vite/src/node/config.ts +++ b/packages/vite/src/node/config.ts @@ -812,6 +812,7 @@ function resolveDepOptimizationOptions( preserveSymlinks, ...optimizeDeps.esbuildOptions, }, + rollupOptions: optimizeDeps.rollupOptions, disabled: optimizeDeps.disabled, entries: optimizeDeps.entries, force: optimizeDeps.force ?? false, diff --git a/packages/vite/src/node/optimizer/esbuildDepPlugin.ts b/packages/vite/src/node/optimizer/esbuildDepPlugin.ts deleted file mode 100644 index 4fc5a1d53b1560..00000000000000 --- a/packages/vite/src/node/optimizer/esbuildDepPlugin.ts +++ /dev/null @@ -1,345 +0,0 @@ -import path from 'node:path' -import type { ImportKind, Plugin } from 'esbuild' -import { KNOWN_ASSET_TYPES } from '../constants' -import type { PackageCache } from '../packages' -import { - escapeRegex, - flattenId, - isBuiltin, - isExternalUrl, - moduleListContains, - normalizePath, -} from '../utils' -import { browserExternalId, optionalPeerDepId } from '../plugins/resolve' -import { isCSSRequest, isModuleCSSRequest } from '../plugins/css' -import type { Environment } from '../environment' -import { createBackCompatIdResolver } from '../idResolver' - -const externalWithConversionNamespace = - 'vite:dep-pre-bundle:external-conversion' -const convertedExternalPrefix = 'vite-dep-pre-bundle-external:' - -const cjsExternalFacadeNamespace = 'vite:cjs-external-facade' -const nonFacadePrefix = 'vite-cjs-external-facade:' - -const externalTypes = [ - 'css', - // supported pre-processor types - 'less', - 'sass', - 'scss', - 'styl', - 'stylus', - 'pcss', - 'postcss', - // wasm - 'wasm', - // known SFC types - 'vue', - 'svelte', - 'marko', - 'astro', - 'imba', - // JSX/TSX may be configured to be compiled differently from how esbuild - // handles it by default, so exclude them as well - 'jsx', - 'tsx', - ...KNOWN_ASSET_TYPES, -] - -export function esbuildDepPlugin( - environment: Environment, - qualified: Record, - external: string[], -): Plugin { - const { isProduction } = environment.config - const { extensions } = environment.config.dev.optimizeDeps - - // remove optimizable extensions from `externalTypes` list - const allExternalTypes = extensions - ? externalTypes.filter((type) => !extensions?.includes('.' + type)) - : externalTypes - - // use separate package cache for optimizer as it caches paths around node_modules - // and it's unlikely for the core Vite process to traverse into node_modules again - const esmPackageCache: PackageCache = new Map() - const cjsPackageCache: PackageCache = new Map() - - // default resolver which prefers ESM - const _resolve = createBackCompatIdResolver(environment.getTopLevelConfig(), { - asSrc: false, - scan: true, - packageCache: esmPackageCache, - }) - - // cjs resolver that prefers Node - const _resolveRequire = createBackCompatIdResolver( - environment.getTopLevelConfig(), - { - asSrc: false, - isRequire: true, - scan: true, - packageCache: cjsPackageCache, - }, - ) - - const resolve = ( - id: string, - importer: string, - kind: ImportKind, - resolveDir?: string, - ): Promise => { - let _importer: string - // explicit resolveDir - this is passed only during yarn pnp resolve for - // entries - if (resolveDir) { - _importer = normalizePath(path.join(resolveDir, '*')) - } else { - // map importer ids to file paths for correct resolution - _importer = importer in qualified ? qualified[importer] : importer - } - const resolver = kind.startsWith('require') ? _resolveRequire : _resolve - return resolver(environment, id, _importer) - } - - const resolveResult = (id: string, resolved: string) => { - if (resolved.startsWith(browserExternalId)) { - return { - path: id, - namespace: 'browser-external', - } - } - if (resolved.startsWith(optionalPeerDepId)) { - return { - path: resolved, - namespace: 'optional-peer-dep', - } - } - if (environment.config.consumer === 'server' && isBuiltin(resolved)) { - return - } - if (isExternalUrl(resolved)) { - return { - path: resolved, - external: true, - } - } - return { - path: path.resolve(resolved), - } - } - - return { - name: 'vite:dep-pre-bundle', - setup(build) { - // clear package cache when esbuild is finished - build.onEnd(() => { - esmPackageCache.clear() - cjsPackageCache.clear() - }) - - // externalize assets and commonly known non-js file types - // See #8459 for more details about this require-import conversion - build.onResolve( - { - filter: new RegExp( - `\\.(` + allExternalTypes.join('|') + `)(\\?.*)?$`, - ), - }, - async ({ path: id, importer, kind }) => { - // if the prefix exist, it is already converted to `import`, so set `external: true` - if (id.startsWith(convertedExternalPrefix)) { - return { - path: id.slice(convertedExternalPrefix.length), - external: true, - } - } - - const resolved = await resolve(id, importer, kind) - if (resolved) { - if (kind === 'require-call') { - // #16116 fix: Import the module.scss path, which is actually module.scss.js - if (resolved.endsWith('.js')) { - return { - path: resolved, - external: false, - } - } - - // here it is not set to `external: true` to convert `require` to `import` - return { - path: resolved, - namespace: externalWithConversionNamespace, - } - } - return { - path: resolved, - external: true, - } - } - }, - ) - build.onLoad( - { filter: /./, namespace: externalWithConversionNamespace }, - (args) => { - // import itself with prefix (this is the actual part of require-import conversion) - const modulePath = `"${convertedExternalPrefix}${args.path}"` - return { - contents: - isCSSRequest(args.path) && !isModuleCSSRequest(args.path) - ? `import ${modulePath};` - : `export { default } from ${modulePath};` + - `export * from ${modulePath};`, - loader: 'js', - } - }, - ) - - function resolveEntry(id: string) { - const flatId = flattenId(id) - if (flatId in qualified) { - return { - path: qualified[flatId], - } - } - } - - build.onResolve( - { filter: /^[\w@][^:]/ }, - async ({ path: id, importer, kind }) => { - if (moduleListContains(external, id)) { - return { - path: id, - external: true, - } - } - - // ensure esbuild uses our resolved entries - let entry: { path: string } | undefined - // if this is an entry, return entry namespace resolve result - if (!importer) { - if ((entry = resolveEntry(id))) return entry - // check if this is aliased to an entry - also return entry namespace - const aliased = await _resolve(environment, id, undefined, true) - if (aliased && (entry = resolveEntry(aliased))) { - return entry - } - } - - // use vite's own resolver - const resolved = await resolve(id, importer, kind) - if (resolved) { - return resolveResult(id, resolved) - } - }, - ) - - build.onLoad( - { filter: /.*/, namespace: 'browser-external' }, - ({ path }) => { - if (isProduction) { - return { - contents: 'module.exports = {}', - } - } else { - return { - // Return in CJS to intercept named imports. Use `Object.create` to - // create the Proxy in the prototype to workaround esbuild issue. Why? - // - // In short, esbuild cjs->esm flow: - // 1. Create empty object using `Object.create(Object.getPrototypeOf(module.exports))`. - // 2. Assign props of `module.exports` to the object. - // 3. Return object for ESM use. - // - // If we do `module.exports = new Proxy({}, {})`, step 1 returns empty object, - // step 2 does nothing as there's no props for `module.exports`. The final object - // is just an empty object. - // - // Creating the Proxy in the prototype satisfies step 1 immediately, which means - // the returned object is a Proxy that we can intercept. - // - // Note: Skip keys that are accessed by esbuild and browser devtools. - contents: `\ -module.exports = Object.create(new Proxy({}, { - get(_, key) { - if ( - key !== '__esModule' && - key !== '__proto__' && - key !== 'constructor' && - key !== 'splice' - ) { - console.warn(\`Module "${path}" has been externalized for browser compatibility. Cannot access "${path}.\${key}" in client code. See https://vitejs.dev/guide/troubleshooting.html#module-externalized-for-browser-compatibility for more details.\`) - } - } -}))`, - } - } - }, - ) - - build.onLoad( - { filter: /.*/, namespace: 'optional-peer-dep' }, - ({ path }) => { - if (isProduction) { - return { - contents: 'module.exports = {}', - } - } else { - const [, peerDep, parentDep] = path.split(':') - return { - contents: `throw new Error(\`Could not resolve "${peerDep}" imported by "${parentDep}". Is it installed?\`)`, - } - } - }, - ) - }, - } -} - -const matchesEntireLine = (text: string) => `^${escapeRegex(text)}$` - -// esbuild doesn't transpile `require('foo')` into `import` statements if 'foo' is externalized -// https://github.com/evanw/esbuild/issues/566#issuecomment-735551834 -export function esbuildCjsExternalPlugin( - externals: string[], - platform: 'node' | 'browser', -): Plugin { - return { - name: 'cjs-external', - setup(build) { - const filter = new RegExp(externals.map(matchesEntireLine).join('|')) - - build.onResolve({ filter: new RegExp(`^${nonFacadePrefix}`) }, (args) => { - return { - path: args.path.slice(nonFacadePrefix.length), - external: true, - } - }) - - build.onResolve({ filter }, (args) => { - // preserve `require` for node because it's more accurate than converting it to import - if (args.kind === 'require-call' && platform !== 'node') { - return { - path: args.path, - namespace: cjsExternalFacadeNamespace, - } - } - - return { - path: args.path, - external: true, - } - }) - - build.onLoad( - { filter: /.*/, namespace: cjsExternalFacadeNamespace }, - (args) => ({ - contents: - `import * as m from ${JSON.stringify( - nonFacadePrefix + args.path, - )};` + `module.exports = m;`, - }), - ) - }, - } -} diff --git a/packages/vite/src/node/optimizer/index.ts b/packages/vite/src/node/optimizer/index.ts index e038affeeca355..415f3f4f12e211 100644 --- a/packages/vite/src/node/optimizer/index.ts +++ b/packages/vite/src/node/optimizer/index.ts @@ -4,12 +4,13 @@ import path from 'node:path' import { promisify } from 'node:util' import { performance } from 'node:perf_hooks' import colors from 'picocolors' -import type { BuildContext, BuildOptions as EsbuildBuildOptions } from 'esbuild' -import esbuild, { build } from 'esbuild' +import type { BuildOptions as EsbuildBuildOptions } from 'esbuild' import { init, parse } from 'es-module-lexer' import glob from 'fast-glob' +import { type RollupOptions, type RollupOutput, rolldown } from 'rolldown' import type { ResolvedConfig } from '../config' import { + asyncFlatten, createDebugger, flattenId, getHash, @@ -21,21 +22,20 @@ import { tryStatSync, unique, } from '../utils' -import { - defaultEsbuildSupported, - transformWithEsbuild, -} from '../plugins/esbuild' +import { transformWithEsbuild } from '../plugins/esbuild' import { ESBUILD_MODULES_TARGET, METADATA_FILENAME } from '../constants' import { isWindows } from '../../shared/utils' import type { Environment } from '../environment' -import { esbuildCjsExternalPlugin, esbuildDepPlugin } from './esbuildDepPlugin' import { ScanEnvironment, scanImports } from './scan' import { createOptimizeDepsIncludeResolver, expandGlobIds } from './resolve' +import { + rolldownCjsExternalPlugin, + rolldownDepPlugin, +} from './rolldownDepPlugin' const debug = createDebugger('vite:deps') const jsExtensionRE = /\.js$/i -const jsMapExtensionRE = /\.js\.map$/i export type ExportsData = { hasModuleSyntax: boolean @@ -103,6 +103,7 @@ export interface DepOptimizationConfig { | 'outExtension' | 'metafile' > + rollupOptions?: RollupOptions /** * List of file extensions that can be optimized. A corresponding esbuild * plugin must exist to handle the specific extension. @@ -201,6 +202,7 @@ export interface OptimizedDepInfo { * data used both to define if interop is needed and when pre-bundling */ exportsData?: Promise + isDynamicEntry?: boolean } export interface DepOptimizationMetadata { @@ -594,7 +596,7 @@ export function runOptimizeDeps( const start = performance.now() - const preparedRun = prepareEsbuildOptimizerRun( + const preparedRun = prepareRolldownOptimizerRun( environment, depsInfo, processingCacheDir, @@ -602,63 +604,44 @@ export function runOptimizeDeps( ) const runResult = preparedRun.then(({ context, idToExports }) => { - function disposeContext() { - return context?.dispose().catch((e) => { - environment.logger.error('Failed to dispose esbuild context', { - error: e, - }) - }) - } if (!context || optimizerContext.cancelled) { - disposeContext() return cancelledResult } return context - .rebuild() + .build() .then((result) => { - const meta = result.metafile! - - // the paths in `meta.outputs` are relative to `process.cwd()` - const processingCacheDirOutputPath = path.relative( - process.cwd(), - processingCacheDir, - ) - - for (const id in depsInfo) { - const output = esbuildOutputFromId( - meta.outputs, - id, - processingCacheDir, - ) - - const { exportsData, ...info } = depsInfo[id] - addOptimizedDepInfo(metadata, 'optimized', { - ...info, - // We only need to hash the output.imports in to check for stability, but adding the hash - // and file path gives us a unique hash that may be useful for other things in the future - fileHash: getHash( - metadata.hash + - depsInfo[id].file + - JSON.stringify(output.imports), - ), - browserHash: metadata.browserHash, - // After bundling we have more information and can warn the user about legacy packages - // that require manual configuration - needsInterop: needsInterop( - environment, - id, - idToExports[id], - output, - ), - }) - } + for (const chunk of result.output) { + if (chunk.type !== 'chunk') continue - for (const o of Object.keys(meta.outputs)) { - if (!jsMapExtensionRE.test(o)) { - const id = path - .relative(processingCacheDirOutputPath, o) - .replace(jsExtensionRE, '') + if (chunk.isEntry) { + // One chunk maybe corresponding multiply entry + const deps = Object.values(depsInfo).filter( + (d) => d.src === normalizePath(chunk.facadeModuleId!), + ) + for (const { exportsData, file, id, ...info } of deps) { + addOptimizedDepInfo(metadata, 'optimized', { + id, + file, + ...info, + // We only need to hash the output.imports in to check for stability, but adding the hash + // and file path gives us a unique hash that may be useful for other things in the future + fileHash: getHash( + metadata.hash + file + JSON.stringify(chunk.modules), + ), + browserHash: metadata.browserHash, + // After bundling we have more information and can warn the user about legacy packages + // that require manual configuration + needsInterop: needsInterop( + environment, + id, + idToExports[id], + chunk, + ), + }) + } + } else { + const id = chunk.fileName.replace(jsExtensionRE, '') const file = getOptimizedDepPath(environment, id) if ( !findOptimizedDepInfoInRecord( @@ -671,6 +654,7 @@ export function runOptimizeDeps( file, needsInterop: false, browserHash: metadata.browserHash, + isDynamicEntry: chunk.isDynamicEntry, }) } } @@ -682,18 +666,14 @@ export function runOptimizeDeps( return successfulResult }) - .catch((e) => { if (e.errors && e.message.includes('The build was canceled')) { - // esbuild logs an error when cancelling, but this is expected so + // an error happens when cancelling, but this is expected so // return an empty result instead return cancelledResult } throw e }) - .finally(() => { - return disposeContext() - }) }) runResult.catch(() => { @@ -704,20 +684,20 @@ export function runOptimizeDeps( async cancel() { optimizerContext.cancelled = true const { context } = await preparedRun - await context?.cancel() + context?.cancel() cleanUp() }, result: runResult, } } -async function prepareEsbuildOptimizerRun( +async function prepareRolldownOptimizerRun( environment: Environment, depsInfo: Record, processingCacheDir: string, optimizerContext: { cancelled: boolean }, ): Promise<{ - context?: BuildContext + context?: { build: () => Promise; cancel: () => void } idToExports: Record }> { // esbuild generates nested directory output with lowest common ancestor base @@ -731,21 +711,19 @@ async function prepareEsbuildOptimizerRun( const { optimizeDeps } = environment.config.dev - const { plugins: pluginsFromConfig = [], ...esbuildOptions } = - optimizeDeps?.esbuildOptions ?? {} + const { plugins: pluginsFromConfig = [], ...rollupOptions } = + optimizeDeps?.rollupOptions ?? {} + let jsxLoader = false await Promise.all( Object.keys(depsInfo).map(async (id) => { const src = depsInfo[id].src! const exportsData = await (depsInfo[id].exportsData ?? extractExportsData(environment, src)) - if (exportsData.jsxLoader && !esbuildOptions.loader?.['.js']) { + if (exportsData.jsxLoader) { // 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, - } + jsxLoader = true } const flatId = flattenId(id) flatIdDeps[flatId] = src @@ -765,46 +743,68 @@ async function prepareEsbuildOptimizerRun( const external = [...(optimizeDeps?.exclude ?? [])] - const plugins = [...pluginsFromConfig] + const plugins = await asyncFlatten(pluginsFromConfig) if (external.length) { - plugins.push(esbuildCjsExternalPlugin(external, platform)) + plugins.push(rolldownCjsExternalPlugin(external, platform)) } - plugins.push(esbuildDepPlugin(environment, flatIdDeps, external)) - - const context = await esbuild.context({ - absWorkingDir: process.cwd(), - entryPoints: Object.keys(flatIdDeps), - bundle: true, - // We can't use platform 'neutral', as esbuild has custom handling - // when the platform is 'node' or 'browser' that can't be emulated - // by using mainFields and conditions - platform, - define, - format: 'esm', - // See https://github.com/evanw/esbuild/issues/1921#issuecomment-1152991694 - banner: - platform === 'node' - ? { - js: `import { createRequire } from 'module';const require = createRequire(import.meta.url);`, - } - : undefined, - target: ESBUILD_MODULES_TARGET, - external, - logLevel: 'error', - splitting: true, - sourcemap: true, - outdir: processingCacheDir, - ignoreAnnotations: true, - metafile: true, - plugins, - charset: 'utf8', - ...esbuildOptions, - supported: { - ...defaultEsbuildSupported, - ...esbuildOptions.supported, + plugins.push(rolldownDepPlugin(environment, flatIdDeps, external)) + plugins.push({ + name: 'optimizer-transform', + async transform(code, id) { + if (/\.(?:m?[jt]s|[jt]sx)$/.test(id)) { + const result = await transformWithEsbuild(code, id, { + sourcemap: true, + sourcefile: id, + loader: jsxLoader && /\.js$/.test(id) ? 'jsx' : undefined, + define, + target: ESBUILD_MODULES_TARGET, + }) + return { + code: result.code, + map: result.map, + } + } }, }) - return { context, idToExports } + + let canceled = false + async function build() { + const bundle = await rolldown({ + input: flatIdDeps, + logLevel: 'warn', + plugins, + resolve: { + // TODO: set aliasFields, conditionNames depending on `platform` + mainFields: ['module', 'main'], + aliasFields: [['browser']], + extensions: ['.js', '.css'], + conditionNames: ['browser'], + }, + ...rollupOptions, + }) + if (canceled) { + await bundle.close() + throw new Error('The build was canceled') + } + const result = await bundle.write({ + format: 'esm', + sourcemap: true, + dir: processingCacheDir, + banner: + platform === 'node' + ? `import { createRequire } from 'module';const require = createRequire(import.meta.url);` + : undefined, + ...rollupOptions.output, + }) + await bundle.close() + return result + } + + function cancel() { + canceled = true + } + + return { context: { build, cancel }, idToExports } } export async function addManuallyIncludedOptimizeDeps( @@ -1003,19 +1003,23 @@ function stringifyDepsOptimizerMetadata( browserHash, optimized: Object.fromEntries( Object.values(optimized).map( - ({ id, src, file, fileHash, needsInterop }) => [ + ({ id, src, file, fileHash, needsInterop, isDynamicEntry }) => [ id, { src, file, fileHash, needsInterop, + isDynamicEntry, }, ], ), ), chunks: Object.fromEntries( - Object.values(chunks).map(({ id, file }) => [id, { file }]), + Object.values(chunks).map(({ id, file, isDynamicEntry }) => [ + id, + { file, isDynamicEntry }, + ]), ), }, (key: string, value: string) => { @@ -1030,29 +1034,6 @@ function stringifyDepsOptimizerMetadata( ) } -function esbuildOutputFromId( - outputs: Record, - id: string, - cacheDirOutputPath: string, -): any { - const cwd = process.cwd() - const flatId = flattenId(id) + '.js' - const normalizedOutputPath = normalizePath( - path.relative(cwd, path.join(cacheDirOutputPath, flatId)), - ) - const output = outputs[normalizedOutputPath] - if (output) { - return output - } - // If the root dir was symlinked, esbuild could return output keys as `../cwd/` - // Normalize keys to support this case too - for (const [key, value] of Object.entries(outputs)) { - if (normalizePath(path.relative(cwd, key)) === normalizedOutputPath) { - return value - } - } -} - export async function extractExportsData( environment: Environment, filePath: string, @@ -1061,18 +1042,32 @@ export async function extractExportsData( const { optimizeDeps } = environment.config.dev - const esbuildOptions = optimizeDeps?.esbuildOptions ?? {} + const rollupOptions = optimizeDeps?.rollupOptions ?? {} if (optimizeDeps.extensions?.some((ext) => filePath.endsWith(ext))) { // For custom supported extensions, build the entry file to transform it into JS, // and then parse with es-module-lexer. Note that the `bundle` option is not `true`, // so only the entry file is being transformed. - const result = await build({ - ...esbuildOptions, - entryPoints: [filePath], - write: false, + const { plugins: pluginsFromConfig = [], ...remainingRollupOptions } = + rollupOptions + const plugins = await asyncFlatten(pluginsFromConfig) + plugins.unshift({ + name: 'externalize', + resolveId(id, importer) { + if (importer !== undefined) { + return { id, external: true } + } + }, + }) + const build = await rolldown({ + ...remainingRollupOptions, + plugins, + input: [filePath], + }) + const result = await build.generate({ + ...rollupOptions.output, format: 'esm', }) - const [, exports, , hasModuleSyntax] = parse(result.outputFiles[0].text) + const [, exports, , hasModuleSyntax] = parse(result.output[0].code) return { hasModuleSyntax, exports: exports.map((e) => e.n), @@ -1086,7 +1081,7 @@ export async function extractExportsData( try { parseResult = parse(entryContent) } catch { - const loader = esbuildOptions.loader?.[path.extname(filePath)] || 'jsx' + const loader = rollupOptions.moduleTypes?.[path.extname(filePath)] || 'jsx' debug?.( `Unable to parse: ${filePath}.\n Trying again with a ${loader} transform.`, ) diff --git a/packages/vite/src/node/optimizer/rolldownDepPlugin.ts b/packages/vite/src/node/optimizer/rolldownDepPlugin.ts new file mode 100644 index 00000000000000..46db9e4c92b0a8 --- /dev/null +++ b/packages/vite/src/node/optimizer/rolldownDepPlugin.ts @@ -0,0 +1,334 @@ +import path from 'node:path' +import type { ImportKind, Plugin } from 'rolldown' +import { KNOWN_ASSET_TYPES } from '../constants' +import type { PackageCache } from '../packages' +import { + escapeRegex, + flattenId, + isBuiltin, + isExternalUrl, + moduleListContains, + normalizePath, +} from '../utils' +import { browserExternalId, optionalPeerDepId } from '../plugins/resolve' +import { isCSSRequest, isModuleCSSRequest } from '../plugins/css' +import type { Environment } from '../environment' +import { createBackCompatIdResolver } from '../idResolver' + +const externalWithConversionNamespace = + 'vite:dep-pre-bundle:external-conversion' +const convertedExternalPrefix = 'vite-dep-pre-bundle-external:' + +const cjsExternalFacadeNamespace = 'vite:cjs-external-facade' +const nonFacadePrefix = 'vite-cjs-external-facade:' + +const externalTypes = [ + 'css', + // supported pre-processor types + 'less', + 'sass', + 'scss', + 'styl', + 'stylus', + 'pcss', + 'postcss', + // wasm + 'wasm', + // known SFC types + 'vue', + 'svelte', + 'marko', + 'astro', + 'imba', + // JSX/TSX may be configured to be compiled differently from how esbuild + // handles it by default, so exclude them as well + 'jsx', + 'tsx', + ...KNOWN_ASSET_TYPES, +] + +const optionalPeerDepNamespace = 'optional-peer-dep:' +const browserExternalNamespace = 'browser-external:' + +export function rolldownDepPlugin( + environment: Environment, + qualified: Record, + external: string[], +): Plugin { + const { isProduction } = environment.config + const { extensions } = environment.config.dev.optimizeDeps + + // remove optimizable extensions from `externalTypes` list + const allExternalTypes = extensions + ? externalTypes.filter((type) => !extensions?.includes('.' + type)) + : externalTypes + + // use separate package cache for optimizer as it caches paths around node_modules + // and it's unlikely for the core Vite process to traverse into node_modules again + const esmPackageCache: PackageCache = new Map() + const cjsPackageCache: PackageCache = new Map() + + // default resolver which prefers ESM + const _resolve = createBackCompatIdResolver(environment.getTopLevelConfig(), { + asSrc: false, + scan: true, + packageCache: esmPackageCache, + }) + + // cjs resolver that prefers Node + const _resolveRequire = createBackCompatIdResolver( + environment.getTopLevelConfig(), + { + asSrc: false, + isRequire: true, + scan: true, + packageCache: cjsPackageCache, + }, + ) + + const resolve = ( + id: string, + importer: string | undefined, + kind: ImportKind, + resolveDir?: string, + ): Promise => { + let _importer: string | undefined + // explicit resolveDir - this is passed only during yarn pnp resolve for + // entries + if (resolveDir) { + _importer = normalizePath(path.join(resolveDir, '*')) + } else if (importer) { + // map importer ids to file paths for correct resolution + _importer = importer in qualified ? qualified[importer] : importer + } + const resolver = kind.startsWith('require') ? _resolveRequire : _resolve + return resolver(environment, id, _importer) + } + + const resolveResult = (id: string, resolved: string) => { + if (resolved.startsWith(browserExternalId)) { + return { + id: browserExternalNamespace + id, + } + } + if (resolved.startsWith(optionalPeerDepId)) { + return { + id: optionalPeerDepNamespace + resolved, + } + } + if (environment.config.consumer === 'server' && isBuiltin(resolved)) { + return + } + if (isExternalUrl(resolved)) { + return { + id: resolved, + external: true, + } + } + return { + id: path.resolve(resolved), + } + } + + const allExternalTypesReg = new RegExp( + `\\.(` + allExternalTypes.join('|') + `)(\\?.*)?$`, + ) + + function resolveEntry(id: string) { + const flatId = flattenId(id) + if (flatId in qualified) { + return { + id: qualified[flatId], + } + } + } + + return { + name: 'vite:dep-pre-bundle', + // clear package cache when build is finished + buildEnd() { + esmPackageCache.clear() + cjsPackageCache.clear() + }, + resolveId: async function (id, importer, options) { + const kind = options.kind + // externalize assets and commonly known non-js file types + // See #8459 for more details about this require-import conversion + if (allExternalTypesReg.test(id)) { + // if the prefix exist, it is already converted to `import`, so set `external: true` + if (id.startsWith(convertedExternalPrefix)) { + return { + id: id.slice(convertedExternalPrefix.length), + external: true, + } + } + + const resolved = await resolve(id, importer, kind) + if (resolved) { + if (kind === 'require-call') { + // #16116 fix: Import the module.scss path, which is actually module.scss.js + if (resolved.endsWith('.js')) { + return { + id: resolved, + external: false, + } + } + + // here it is not set to `external: true` to convert `require` to `import` + return { + id: externalWithConversionNamespace + resolved, + } + } + return { + id: resolved, + external: true, + } + } + } + + if (/^[\w@][^:]/.test(id)) { + if (moduleListContains(external, id)) { + return { + id: id, + external: true, + } + } + + // ensure esbuild uses our resolved entries + let entry: { id: string } | undefined + // if this is an entry, return entry namespace resolve result + if (!importer) { + if ((entry = resolveEntry(id))) return entry + // check if this is aliased to an entry - also return entry namespace + const aliased = await _resolve(environment, id, undefined, true) + if (aliased && (entry = resolveEntry(aliased))) { + return entry + } + } + + // use vite's own resolver + const resolved = await resolve(id, importer, kind) + if (resolved) { + return resolveResult(id, resolved) + } + } + }, + load(id) { + if (id.startsWith(externalWithConversionNamespace)) { + const path = id.slice(externalWithConversionNamespace.length) + // import itself with prefix (this is the actual part of require-import conversion) + const modulePath = `"${convertedExternalPrefix}${path}"` + return { + code: + isCSSRequest(path) && !isModuleCSSRequest(path) + ? `import ${modulePath};` + : `export { default } from ${modulePath};` + + `export * from ${modulePath};`, + } + } + + if (id.startsWith(browserExternalNamespace)) { + const path = id.slice(browserExternalNamespace.length) + if (isProduction) { + return { + code: 'module.exports = {}', + } + } else { + return { + // Return in CJS to intercept named imports. Use `Object.create` to + // create the Proxy in the prototype to workaround esbuild issue. Why? + // + // In short, esbuild cjs->esm flow: + // 1. Create empty object using `Object.create(Object.getPrototypeOf(module.exports))`. + // 2. Assign props of `module.exports` to the object. + // 3. Return object for ESM use. + // + // If we do `module.exports = new Proxy({}, {})`, step 1 returns empty object, + // step 2 does nothing as there's no props for `module.exports`. The final object + // is just an empty object. + // + // Creating the Proxy in the prototype satisfies step 1 immediately, which means + // the returned object is a Proxy that we can intercept. + // + // Note: Skip keys that are accessed by esbuild and browser devtools. + code: `\ +module.exports = Object.create(new Proxy({}, { + get(_, key) { + if ( + key !== '__esModule' && + key !== '__proto__' && + key !== 'constructor' && + key !== 'splice' + ) { + console.warn(\`Module "${path}" has been externalized for browser compatibility. Cannot access "${path}.\${key}" in client code. See http://vitejs.dev/guide/troubleshooting.html#module-externalized-for-browser-compatibility for more details.\`) + } + } +}))`, + } + } + } + + if (id.startsWith(optionalPeerDepNamespace)) { + if (isProduction) { + return { + code: 'module.exports = {}', + } + } else { + const path = id.slice(externalWithConversionNamespace.length) + const [, peerDep, parentDep] = path.split(':') + return { + code: `throw new Error(\`Could not resolve "${peerDep}" imported by "${parentDep}". Is it installed?\`)`, + } + } + } + }, + } +} + +const matchesEntireLine = (text: string) => `^${escapeRegex(text)}$` + +// esbuild doesn't transpile `require('foo')` into `import` statements if 'foo' is externalized +// https://github.com/evanw/esbuild/issues/566#issuecomment-735551834 +export function rolldownCjsExternalPlugin( + externals: string[], + platform: 'node' | 'browser', +): Plugin { + const filter = new RegExp(externals.map(matchesEntireLine).join('|')) + + return { + name: 'cjs-external', + resolveId(id, _importer, options) { + if (id.startsWith(nonFacadePrefix)) { + return { + id: id.slice(nonFacadePrefix.length), + external: true, + } + } + + if (filter.test(id)) { + const kind = options.kind + // preserve `require` for node because it's more accurate than converting it to import + if (kind === 'require-call' && platform !== 'node') { + return { + id: cjsExternalFacadeNamespace + id, + } + } + + return { + id, + external: true, + } + } + }, + load(id) { + if (id.startsWith(cjsExternalFacadeNamespace)) { + return { + code: + `import * as m from ${JSON.stringify( + nonFacadePrefix + id.slice(cjsExternalFacadeNamespace.length), + )};` + `module.exports = m;`, + } + } + }, + } +} diff --git a/packages/vite/src/node/optimizer/scan.ts b/packages/vite/src/node/optimizer/scan.ts index 90298b8d21070a..13f85cd7612de1 100644 --- a/packages/vite/src/node/optimizer/scan.ts +++ b/packages/vite/src/node/optimizer/scan.ts @@ -3,16 +3,12 @@ import fsp from 'node:fs/promises' import path from 'node:path' import { performance } from 'node:perf_hooks' import glob from 'fast-glob' -import type { - BuildContext, - Loader, - OnLoadArgs, - OnLoadResult, - Plugin, -} from 'esbuild' -import esbuild, { formatMessages, transform } from 'esbuild' +import type { Loader } from 'esbuild' +import { transform } from 'esbuild' import type { PartialResolvedId } from 'rollup' import colors from 'picocolors' +import type { Plugin } from 'rolldown' +import { scan } from 'rolldown/experimental' import { CSS_LANGS_RE, JS_TYPES_RE, @@ -21,6 +17,7 @@ import { } from '../constants' import { arraify, + asyncFlatten, createDebugger, dataUrlRE, externalRE, @@ -112,7 +109,7 @@ type ResolveIdOptions = Omit< const debug = createDebugger('vite:deps') -const htmlTypesRE = /\.(html|vue|svelte|astro|imba)$/ +const htmlTypesRE = /\.(?:html|vue|svelte|astro|imba)$/ // A simple regex to detect import sources. This is only used on // + const filePath = id.replace(normalizePath(config.root), '') + addToHTMLProxyCache(config, filePath, inlineModuleIndex, { + code: contents, + }) + js += `\nimport "${id}?html-proxy&index=${inlineModuleIndex}.js"` + shouldRemove = true + } - if (isModule) { - inlineModuleIndex++ - if (url && !isExcludedUrl(url) && !isPublicFile) { - // - const filePath = id.replace(normalizePath(config.root), '') - addToHTMLProxyCache(config, filePath, inlineModuleIndex, { - code: contents, - }) - js += `\nimport "${id}?html-proxy&index=${inlineModuleIndex}.js"` - shouldRemove = true - } - - everyScriptIsAsync &&= isAsync - someScriptsAreAsync ||= isAsync - someScriptsAreDefer ||= !isAsync - } else if (url && !isPublicFile) { - if (!isExcludedUrl(url)) { - config.logger.warn( - ` asset - for (const { start, end, url } of scriptUrls) { - if (checkPublicFile(url, config)) { - s.update( - start, - end, - partialEncodeURIPath(toOutputPublicFilePath(url)), - ) - } else if (!isExcludedUrl(url)) { - s.update( - start, - end, - partialEncodeURIPath(await urlToBuiltUrl(this, url, id)), - ) + // emit asset + for (const { start, end, url } of scriptUrls) { + if (checkPublicFile(url, config)) { + s.update( + start, + end, + partialEncodeURIPath(toOutputPublicFilePath(url)), + ) + } else if (!isExcludedUrl(url)) { + s.update( + start, + end, + partialEncodeURIPath(await urlToBuiltUrl(this, url, id)), + ) + } } - } - // ignore if its url can't be resolved - const resolvedStyleUrls = await Promise.all( - styleUrls.map(async (styleUrl) => ({ - ...styleUrl, - resolved: await this.resolve(styleUrl.url, id), - })), - ) - for (const { start, end, url, resolved } of resolvedStyleUrls) { - if (resolved == null) { - config.logger.warnOnce( - `\n${url} doesn't exist at build time, it will remain unchanged to be resolved at runtime`, - ) - const importExpression = `\nimport ${JSON.stringify(url)}` - js = js.replace(importExpression, '') - } else { - s.remove(start, end) + // ignore if its url can't be resolved + const resolvedStyleUrls = await Promise.all( + styleUrls.map(async (styleUrl) => ({ + ...styleUrl, + resolved: await this.resolve(styleUrl.url, id), + })), + ) + for (const { start, end, url, resolved } of resolvedStyleUrls) { + if (resolved == null) { + config.logger.warnOnce( + `\n${url} doesn't exist at build time, it will remain unchanged to be resolved at runtime`, + ) + const importExpression = `\nimport ${JSON.stringify(url)}` + js = js.replace(importExpression, '') + } else { + s.remove(start, end) + } } - } - processedHtml.set(id, s.toString()) + processedHtml.set(id, s.toString()) - // inject module preload polyfill only when configured and needed - const { modulePreload } = this.environment.config.build - if ( - modulePreload !== false && - modulePreload.polyfill && - (someScriptsAreAsync || someScriptsAreDefer) - ) { - js = `import "${modulePreloadPolyfillId}";\n${js}` - } + // inject module preload polyfill only when configured and needed + const { modulePreload } = this.environment.config.build + if ( + modulePreload !== false && + modulePreload.polyfill && + (someScriptsAreAsync || someScriptsAreDefer) + ) { + js = `import "${modulePreloadPolyfillId}";\n${js}` + } - // Force rollup to keep this module from being shared between other entry points. - // If the resulting chunk is empty, it will be removed in generateBundle. - return { code: js, moduleSideEffects: 'no-treeshake' } - } + // Force rollup to keep this module from being shared between other entry points. + // If the resulting chunk is empty, it will be removed in generateBundle. + return { code: js, moduleSideEffects: 'no-treeshake' } + } + }, }, async generateBundle(options, bundle) { diff --git a/packages/vite/src/node/plugins/worker.ts b/packages/vite/src/node/plugins/worker.ts index d1b28ffe5e2cbf..1c41934d674920 100644 --- a/packages/vite/src/node/plugins/worker.ts +++ b/packages/vite/src/node/plugins/worker.ts @@ -1,6 +1,6 @@ import path from 'node:path' import MagicString from 'magic-string' -import type { OutputChunk } from 'rolldown' +import type { OutputChunk, RolldownPlugin } from 'rolldown' import type { ResolvedConfig } from '../config' import type { Plugin } from '../plugin' import { ENV_ENTRY, ENV_PUBLIC_PATH } from '../constants' @@ -221,7 +221,7 @@ export function webWorkerPostPlugin(): Plugin { } } -export function webWorkerPlugin(config: ResolvedConfig): Plugin { +export function webWorkerPlugin(config: ResolvedConfig): RolldownPlugin { const isBuild = config.command === 'build' const isWorker = config.isWorker @@ -239,10 +239,17 @@ export function webWorkerPlugin(config: ResolvedConfig): Plugin { }) }, - load(id) { - if (isBuild && workerOrSharedWorkerRE.test(id)) { - return '' - } + load: { + filter: { + id: { + include: [workerOrSharedWorkerRE], + }, + }, + handler(id) { + if (isBuild && workerOrSharedWorkerRE.test(id)) { + return '' + } + }, }, // shouldTransformCachedModule({ id }) { @@ -251,147 +258,154 @@ export function webWorkerPlugin(config: ResolvedConfig): Plugin { // } // }, - async transform(raw, id) { - const workerFileMatch = workerFileRE.exec(id) - if (workerFileMatch) { - // if import worker by worker constructor will have query.type - // other type will be import worker by esm - const workerType = workerFileMatch[1] as WorkerType - let injectEnv = '' - - const scriptPath = JSON.stringify( - path.posix.join(config.base, ENV_PUBLIC_PATH), - ) + transform: { + filter: { + id: { + include: [workerOrSharedWorkerRE, workerFileRE], + }, + }, + async handler(raw, id) { + const workerFileMatch = workerFileRE.exec(id) + if (workerFileMatch) { + // if import worker by worker constructor will have query.type + // other type will be import worker by esm + const workerType = workerFileMatch[1] as WorkerType + let injectEnv = '' + + const scriptPath = JSON.stringify( + path.posix.join(config.base, ENV_PUBLIC_PATH), + ) - if (workerType === 'classic') { - injectEnv = `importScripts(${scriptPath})\n` - } else if (workerType === 'module') { - injectEnv = `import ${scriptPath}\n` - } else if (workerType === 'ignore') { - if (isBuild) { - injectEnv = '' - } else { - // dynamic worker type we can't know how import the env - // so we copy /@vite/env code of server transform result into file header - const environment = this.environment - const moduleGraph = - environment.mode === 'dev' ? environment.moduleGraph : undefined - const module = moduleGraph?.getModuleById(ENV_ENTRY) - injectEnv = module?.transformResult?.code || '' + if (workerType === 'classic') { + injectEnv = `importScripts(${scriptPath})\n` + } else if (workerType === 'module') { + injectEnv = `import ${scriptPath}\n` + } else if (workerType === 'ignore') { + if (isBuild) { + injectEnv = '' + } else { + // dynamic worker type we can't know how import the env + // so we copy /@vite/env code of server transform result into file header + const environment = this.environment + const moduleGraph = + environment.mode === 'dev' ? environment.moduleGraph : undefined + const module = moduleGraph?.getModuleById(ENV_ENTRY) + injectEnv = module?.transformResult?.code || '' + } } - } - if (injectEnv) { - const s = new MagicString(raw) - s.prepend(injectEnv + ';\n') - return { - code: s.toString(), - map: s.generateMap({ hires: 'boundary' }), + if (injectEnv) { + const s = new MagicString(raw) + s.prepend(injectEnv + ';\n') + return { + code: s.toString(), + map: s.generateMap({ hires: 'boundary' }), + } } + return } - return - } - const workerMatch = workerOrSharedWorkerRE.exec(id) - if (!workerMatch) return - - const { format } = config.worker - const workerConstructor = - workerMatch[1] === 'sharedworker' ? 'SharedWorker' : 'Worker' - const workerType = isBuild - ? format === 'es' - ? 'module' - : 'classic' - : 'module' - const workerTypeOption = `{ - ${workerType === 'module' ? `type: "module",` : ''} - name: options?.name - }` - - let urlCode: string - if (isBuild) { - if (isWorker && config.bundleChain.at(-1) === cleanUrl(id)) { - urlCode = 'self.location.href' - } else if (inlineRE.test(id)) { - const chunk = await bundleWorkerEntry(config, id) - const encodedJs = `const encodedJs = "${Buffer.from( - chunk.code, - ).toString('base64')}";` - - const code = - // Using blob URL for SharedWorker results in multiple instances of a same worker - workerConstructor === 'Worker' - ? `${encodedJs} - const decodeBase64 = (base64) => Uint8Array.from(atob(base64), c => c.charCodeAt(0)); - const blob = typeof self !== "undefined" && self.Blob && new Blob([${ - workerType === 'classic' - ? '' - : // `URL` is always available, in `Worker[type="module"]` - `'URL.revokeObjectURL(import.meta.url);',` - }decodeBase64(encodedJs)], { type: "text/javascript;charset=utf-8" }); - export default function WorkerWrapper(options) { - let objURL; - try { - objURL = blob && (self.URL || self.webkitURL).createObjectURL(blob); - if (!objURL) throw '' - const worker = new ${workerConstructor}(objURL, ${workerTypeOption}); - worker.addEventListener("error", () => { - (self.URL || self.webkitURL).revokeObjectURL(objURL); - }); - return worker; - } catch(e) { + const workerMatch = workerOrSharedWorkerRE.exec(id) + if (!workerMatch) return + + const { format } = config.worker + const workerConstructor = + workerMatch[1] === 'sharedworker' ? 'SharedWorker' : 'Worker' + const workerType = isBuild + ? format === 'es' + ? 'module' + : 'classic' + : 'module' + const workerTypeOption = `{ + ${workerType === 'module' ? `type: "module",` : ''} + name: options?.name + }` + + let urlCode: string + if (isBuild) { + if (isWorker && config.bundleChain.at(-1) === cleanUrl(id)) { + urlCode = 'self.location.href' + } else if (inlineRE.test(id)) { + const chunk = await bundleWorkerEntry(config, id) + const encodedJs = `const encodedJs = "${Buffer.from( + chunk.code, + ).toString('base64')}";` + + const code = + // Using blob URL for SharedWorker results in multiple instances of a same worker + workerConstructor === 'Worker' + ? `${encodedJs} + const decodeBase64 = (base64) => Uint8Array.from(atob(base64), c => c.charCodeAt(0)); + const blob = typeof self !== "undefined" && self.Blob && new Blob([${ + workerType === 'classic' + ? '' + : // `URL` is always available, in `Worker[type="module"]` + `'URL.revokeObjectURL(import.meta.url);',` + }decodeBase64(encodedJs)], { type: "text/javascript;charset=utf-8" }); + export default function WorkerWrapper(options) { + let objURL; + try { + objURL = blob && (self.URL || self.webkitURL).createObjectURL(blob); + if (!objURL) throw '' + const worker = new ${workerConstructor}(objURL, ${workerTypeOption}); + worker.addEventListener("error", () => { + (self.URL || self.webkitURL).revokeObjectURL(objURL); + }); + return worker; + } catch(e) { + return new ${workerConstructor}( + "data:text/javascript;base64," + encodedJs, + ${workerTypeOption} + ); + }${ + // For module workers, we should not revoke the URL until the worker runs, + // otherwise the worker fails to run + workerType === 'classic' + ? ` finally { + objURL && (self.URL || self.webkitURL).revokeObjectURL(objURL); + }` + : '' + } + }` + : `${encodedJs} + export default function WorkerWrapper(options) { return new ${workerConstructor}( "data:text/javascript;base64," + encodedJs, ${workerTypeOption} ); - }${ - // For module workers, we should not revoke the URL until the worker runs, - // otherwise the worker fails to run - workerType === 'classic' - ? ` finally { - objURL && (self.URL || self.webkitURL).revokeObjectURL(objURL); - }` - : '' } - }` - : `${encodedJs} - export default function WorkerWrapper(options) { - return new ${workerConstructor}( - "data:text/javascript;base64," + encodedJs, - ${workerTypeOption} - ); + ` + + return { + code, + // Empty sourcemap to suppress Rollup warning + map: { mappings: '' }, + } + } else { + urlCode = JSON.stringify(await workerFileToUrl(config, id)) } - ` + } else { + let url = await fileToUrl(this, cleanUrl(id)) + url = injectQuery(url, `${WORKER_FILE_ID}&type=${workerType}`) + urlCode = JSON.stringify(url) + } + if (urlRE.test(id)) { return { - code, - // Empty sourcemap to suppress Rollup warning - map: { mappings: '' }, + code: `export default ${urlCode}`, + map: { mappings: '' }, // Empty sourcemap to suppress Rollup warning } - } else { - urlCode = JSON.stringify(await workerFileToUrl(config, id)) } - } else { - let url = await fileToUrl(this, cleanUrl(id)) - url = injectQuery(url, `${WORKER_FILE_ID}&type=${workerType}`) - urlCode = JSON.stringify(url) - } - if (urlRE.test(id)) { return { - code: `export default ${urlCode}`, + code: `export default function WorkerWrapper(options) { + return new ${workerConstructor}( + ${urlCode}, + ${workerTypeOption} + ); + }`, map: { mappings: '' }, // Empty sourcemap to suppress Rollup warning } - } - - return { - code: `export default function WorkerWrapper(options) { - return new ${workerConstructor}( - ${urlCode}, - ${workerTypeOption} - ); - }`, - map: { mappings: '' }, // Empty sourcemap to suppress Rollup warning - } + }, }, renderChunk(code, chunk, outputOptions) { diff --git a/packages/vite/src/node/plugins/workerImportMetaUrl.ts b/packages/vite/src/node/plugins/workerImportMetaUrl.ts index 54f83324303399..d1e484acd54891 100644 --- a/packages/vite/src/node/plugins/workerImportMetaUrl.ts +++ b/packages/vite/src/node/plugins/workerImportMetaUrl.ts @@ -1,9 +1,8 @@ import path from 'node:path' import MagicString from 'magic-string' -import type { RollupError } from 'rolldown' +import type { RolldownPlugin, RollupError } from 'rolldown' import { stripLiteral } from 'strip-literal' import type { ResolvedConfig } from '../config' -import type { Plugin } from '../plugin' import { evalValue, injectQuery, transformStableResult } from '../utils' import { createBackCompatIdResolver } from '../idResolver' import type { ResolveIdFn } from '../idResolver' @@ -104,7 +103,9 @@ function isIncludeWorkerImportMetaUrl(code: string): boolean { return false } -export function workerImportMetaUrlPlugin(config: ResolvedConfig): Plugin { +export function workerImportMetaUrlPlugin( + config: ResolvedConfig, +): RolldownPlugin { const isBuild = config.command === 'build' let workerResolver: ResolveIdFn @@ -126,82 +127,89 @@ export function workerImportMetaUrlPlugin(config: ResolvedConfig): Plugin { // } // }, - async transform(code, id) { - if ( - this.environment.config.consumer === 'client' && - isIncludeWorkerImportMetaUrl(code) - ) { - let s: MagicString | undefined - const cleanString = stripLiteral(code) - const workerImportMetaUrlRE = - /\bnew\s+(?:Worker|SharedWorker)\s*\(\s*(new\s+URL\s*\(\s*('[^']+'|"[^"]+"|`[^`]+`)\s*,\s*import\.meta\.url\s*\))/dg - - let match: RegExpExecArray | null - while ((match = workerImportMetaUrlRE.exec(cleanString))) { - const [[, endIndex], [expStart, expEnd], [urlStart, urlEnd]] = - match.indices! - - const rawUrl = code.slice(urlStart, urlEnd) - - // potential dynamic template string - if (rawUrl[0] === '`' && rawUrl.includes('${')) { - this.error( - `\`new URL(url, import.meta.url)\` is not supported in dynamic template string.`, - expStart, - ) - } + transform: { + filter: { + code: { + include: [/(?:new Worker|new SharedWorker)/], + }, + }, + async handler(code, id) { + if ( + this.environment.config.consumer === 'client' && + isIncludeWorkerImportMetaUrl(code) + ) { + let s: MagicString | undefined + const cleanString = stripLiteral(code) + const workerImportMetaUrlRE = + /\bnew\s+(?:Worker|SharedWorker)\s*\(\s*(new\s+URL\s*\(\s*('[^']+'|"[^"]+"|`[^`]+`)\s*,\s*import\.meta\.url\s*\))/dg + + let match: RegExpExecArray | null + while ((match = workerImportMetaUrlRE.exec(cleanString))) { + const [[, endIndex], [expStart, expEnd], [urlStart, urlEnd]] = + match.indices! + + const rawUrl = code.slice(urlStart, urlEnd) + + // potential dynamic template string + if (rawUrl[0] === '`' && rawUrl.includes('${')) { + this.error( + `\`new URL(url, import.meta.url)\` is not supported in dynamic template string.`, + expStart, + ) + } - s ||= new MagicString(code) - const workerType = getWorkerType(code, cleanString, endIndex) - const url = rawUrl.slice(1, -1) - let file: string | undefined - if (url[0] === '.') { - file = path.resolve(path.dirname(id), url) - file = tryFsResolve(file, fsResolveOptions) ?? file - } else { - workerResolver ??= createBackCompatIdResolver(config, { - extensions: [], - tryIndex: false, - preferRelative: true, - }) - file = await workerResolver(this.environment, url, id) - file ??= - url[0] === '/' - ? slash(path.join(config.publicDir, url)) - : slash(path.resolve(path.dirname(id), url)) - } + s ||= new MagicString(code) + const workerType = getWorkerType(code, cleanString, endIndex) + const url = rawUrl.slice(1, -1) + let file: string | undefined + if (url[0] === '.') { + file = path.resolve(path.dirname(id), url) + file = tryFsResolve(file, fsResolveOptions) ?? file + } else { + workerResolver ??= createBackCompatIdResolver(config, { + extensions: [], + tryIndex: false, + preferRelative: true, + }) + file = await workerResolver(this.environment, url, id) + file ??= + url[0] === '/' + ? slash(path.join(config.publicDir, url)) + : slash(path.resolve(path.dirname(id), url)) + } - if ( - isBuild && - config.isWorker && - config.bundleChain.at(-1) === cleanUrl(file) - ) { - s.update(expStart, expEnd, 'self.location.href') - } else { - let builtUrl: string - if (isBuild) { - builtUrl = await workerFileToUrl(config, file) + if ( + isBuild && + config.isWorker && + config.bundleChain.at(-1) === cleanUrl(file) + ) { + s.update(expStart, expEnd, 'self.location.href') } else { - builtUrl = await fileToUrl(this, cleanUrl(file)) - builtUrl = injectQuery( - builtUrl, - `${WORKER_FILE_ID}&type=${workerType}`, + let builtUrl: string + if (isBuild) { + builtUrl = await workerFileToUrl(config, file) + } else { + builtUrl = await fileToUrl(this, cleanUrl(file)) + builtUrl = injectQuery( + builtUrl, + `${WORKER_FILE_ID}&type=${workerType}`, + ) + } + s.update( + expStart, + expEnd, + `new URL(/* @vite-ignore */ ${JSON.stringify(builtUrl)}, import.meta.url)`, ) } - s.update( - expStart, - expEnd, - `new URL(/* @vite-ignore */ ${JSON.stringify(builtUrl)}, import.meta.url)`, - ) } - } - if (s) { - return transformStableResult(s, id, config) - } + if (s) { + return transformStableResult(s, id, config) + } - return null - } + return null + } + }, }, } } From 98d77723ed5764a7c1d277d32f93e363968a10ad Mon Sep 17 00:00:00 2001 From: IWANABETHATGUY Date: Sat, 14 Sep 2024 18:59:59 +0800 Subject: [PATCH 13/20] =?UTF-8?q?perf:=20=E2=9A=A1=EF=B8=8F=20use=20defaul?= =?UTF-8?q?t=20resolver=20(#56)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/vite/src/node/plugins/index.ts | 28 +++++++++++++------------ 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/packages/vite/src/node/plugins/index.ts b/packages/vite/src/node/plugins/index.ts index 351827adc6c714..731f78f4237984 100644 --- a/packages/vite/src/node/plugins/index.ts +++ b/packages/vite/src/node/plugins/index.ts @@ -82,19 +82,21 @@ export async function resolvePlugins( }) : modulePreloadPolyfillPlugin(config) : null, - resolvePlugin( - { - root: config.root, - isProduction: config.isProduction, - isBuild, - packageCache: config.packageCache, - asSrc: true, - fsUtils: getFsUtils(config), - optimizeDeps: true, - externalize: isBuild && !!config.build.ssr, // TODO: should we do this for all environments? - }, - config.environments, - ), + enableNativePlugin + ? null + : resolvePlugin( + { + root: config.root, + isProduction: config.isProduction, + isBuild, + packageCache: config.packageCache, + asSrc: true, + fsUtils: getFsUtils(config), + optimizeDeps: true, + externalize: isBuild && !!config.build.ssr, // TODO: should we do this for all environments? + }, + config.environments, + ), htmlInlineProxyPlugin(config), cssPlugin(config), config.esbuild !== false From be93d0ef9521ea9c6853ece2a3a3cdab63f6575c Mon Sep 17 00:00:00 2001 From: sapphi-red <49056869+sapphi-red@users.noreply.github.com> Date: Thu, 19 Sep 2024 15:43:29 +0900 Subject: [PATCH 14/20] chore: fix native manifest plugin --- packages/vite/src/node/build.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/vite/src/node/build.ts b/packages/vite/src/node/build.ts index 18cbc208b0e74e..d12f65f5e5bfe4 100644 --- a/packages/vite/src/node/build.ts +++ b/packages/vite/src/node/build.ts @@ -497,7 +497,13 @@ export async function resolveBuildPlugins(config: ResolvedConfig): Promise<{ ? [ config.build.manifest && enableNativePlugin ? // TODO: make this environment-specific - nativeManifestPlugin() + nativeManifestPlugin({ + root: config.root, + outPath: + config.build.manifest === true + ? '.vite/manifest.json' + : config.build.manifest, + }) : manifestPlugin(), ssrManifestPlugin(), ...(enableBuildReport ? [buildReporterPlugin(config)] : []), From 6c2a5d466abee1c12322a807b2598a240f8e7005 Mon Sep 17 00:00:00 2001 From: sapphi-red <49056869+sapphi-red@users.noreply.github.com> Date: Thu, 19 Sep 2024 18:14:00 +0900 Subject: [PATCH 15/20] feat: handle non-relative paths by Vite's resolver for enableNativePlugin --- packages/vite/src/node/plugins/index.ts | 16 ++++++++-- packages/vite/src/node/plugins/resolve.ts | 32 +++++++++++++++++++- playground/resolve/browser-field/relative.js | 3 +- 3 files changed, 47 insertions(+), 4 deletions(-) diff --git a/packages/vite/src/node/plugins/index.ts b/packages/vite/src/node/plugins/index.ts index 731f78f4237984..4bf0c449012860 100644 --- a/packages/vite/src/node/plugins/index.ts +++ b/packages/vite/src/node/plugins/index.ts @@ -16,7 +16,7 @@ import type { HookHandler, Plugin, PluginWithRequiredHook } from '../plugin' import { watchPackageDataPlugin } from '../packages' import { getFsUtils } from '../fsUtils' import { jsonPlugin } from './json' -import { resolvePlugin } from './resolve' +import { filteredResolvePlugin, resolvePlugin } from './resolve' import { optimizedDepsPlugin } from './optimizedDeps' import { esbuildPlugin } from './esbuild' import { importAnalysisPlugin } from './importAnalysis' @@ -83,7 +83,19 @@ export async function resolvePlugins( : modulePreloadPolyfillPlugin(config) : null, enableNativePlugin - ? null + ? filteredResolvePlugin( + { + root: config.root, + isProduction: config.isProduction, + isBuild, + packageCache: config.packageCache, + asSrc: true, + fsUtils: getFsUtils(config), + optimizeDeps: true, + externalize: isBuild && !!config.build.ssr, // TODO: should we do this for all environments? + }, + config.environments, + ) : resolvePlugin( { root: config.root, diff --git a/packages/vite/src/node/plugins/resolve.ts b/packages/vite/src/node/plugins/resolve.ts index cee370624c6e6a..3c5cd3898826c3 100644 --- a/packages/vite/src/node/plugins/resolve.ts +++ b/packages/vite/src/node/plugins/resolve.ts @@ -1,7 +1,7 @@ import fs from 'node:fs' import path from 'node:path' import colors from 'picocolors' -import type { PartialResolvedId } from 'rolldown' +import type { PartialResolvedId, RolldownPlugin } from 'rolldown' import { exports, imports } from 'resolve.exports' import { hasESMSyntax } from 'mlly' import type { Plugin } from '../plugin' @@ -180,6 +180,36 @@ export interface ResolvePluginOptionsWithOverrides extends ResolveOptions, ResolvePluginOptions {} +export function filteredResolvePlugin( + resolveOptions: ResolvePluginOptionsWithOverrides, + environmentsOptions?: Record, +): RolldownPlugin { + const originalPlugin = resolvePlugin(resolveOptions, environmentsOptions) + + return { + name: 'vite:resolve', + options(option) { + option.resolve ??= {} + option.resolve.extensions = this.environment.config.resolve.extensions + }, + resolveId: { + filter: { + id: { + exclude: [ + // relative paths without query + // also exclude path ending with .[cm]?jsx? (for typescript moduleResolution=nodenext) + /^\.\.?[/\\](?!.*\.[cm]?jsx?$)[^?]+$/, + /^(?:\0|\/?virtual:)/, + ], + }, + }, + // @ts-expect-error the options is incompatible + handler: originalPlugin.resolveId!, + }, + load: originalPlugin.load, + } +} + export function resolvePlugin( resolveOptions: ResolvePluginOptionsWithOverrides, /** diff --git a/playground/resolve/browser-field/relative.js b/playground/resolve/browser-field/relative.js index 660d6be578a728..3be4d7452be75d 100644 --- a/playground/resolve/browser-field/relative.js +++ b/playground/resolve/browser-field/relative.js @@ -4,7 +4,8 @@ import rb from './no-ext.js' // no substitution import rc from './ext' import rd from './ext.js' import re from './ext-index/index.js' -import rf from './ext-index' +// import rf from './ext-index' +const rf = 'FIXME' import rg from './no-ext-index/index.js' // no substitution export { ra, rb, rc, rd, re, rf, rg } From 326bcae4d48e78c94472185cb06de5eecc271c15 Mon Sep 17 00:00:00 2001 From: IWANABETHATGUY Date: Fri, 20 Sep 2024 21:00:05 +0800 Subject: [PATCH 16/20] =?UTF-8?q?fix:=20=F0=9F=90=9B=20lint=20(#58)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/vite/src/node/build.ts | 66 ++++++++++++++++----------------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/packages/vite/src/node/build.ts b/packages/vite/src/node/build.ts index d12f65f5e5bfe4..c179b7dc4ceada 100644 --- a/packages/vite/src/node/build.ts +++ b/packages/vite/src/node/build.ts @@ -1037,10 +1037,10 @@ export function resolveBuildOutputs( } const warningIgnoreList = [`CIRCULAR_DEPENDENCY`, `THIS_IS_UNDEFINED`] -const dynamicImportWarningIgnoreList = [ - `Unsupported expression`, - `statically analyzed`, -] +// const dynamicImportWarningIgnoreList = [ +// `Unsupported expression`, +// `statically analyzed`, +// ] function clearLine() { const tty = process.stdout.isTTY && !process.env.CI @@ -1065,41 +1065,41 @@ export function onRollupWarning( } if (typeof warning === 'object') { - if (warning.code === 'UNRESOLVED_IMPORT') { - const id = warning.id - const exporter = warning.exporter - // throw unless it's commonjs external... - if (!id || !id.endsWith('?commonjs-external')) { - throw new Error( - `[vite]: Rollup failed to resolve import "${exporter}" from "${id}".\n` + - `This is most likely unintended because it can break your application at runtime.\n` + - `If you do want to externalize this module explicitly add it to\n` + - `\`build.rollupOptions.external\``, - ) - } - } + // if (warning.code === 'UNRESOLVED_IMPORT') { + // const id = warning.id + // const exporter = warning.exporter + // // throw unless it's commonjs external... + // if (!id || !id.endsWith('?commonjs-external')) { + // throw new Error( + // `[vite]: Rollup failed to resolve import "${exporter}" from "${id}".\n` + + // `This is most likely unintended because it can break your application at runtime.\n` + + // `If you do want to externalize this module explicitly add it to\n` + + // `\`build.rollupOptions.external\``, + // ) + // } + // } - if ( - warning.plugin === 'rollup-plugin-dynamic-import-variables' && - dynamicImportWarningIgnoreList.some((msg) => - warning.message.includes(msg), - ) - ) { - return - } + // if ( + // warning.plugin === 'rollup-plugin-dynamic-import-variables' && + // dynamicImportWarningIgnoreList.some((msg) => + // warning.message.includes(msg), + // ) + // ) { + // return + // } if (warningIgnoreList.includes(warning.code!)) { return } - if (warning.code === 'PLUGIN_WARNING') { - environment.logger.warn( - `${colors.bold( - colors.yellow(`[plugin:${warning.plugin}]`), - )} ${colors.yellow(warning.message)}`, - ) - return - } + // if (warning.code === 'PLUGIN_WARNING') { + // environment.logger.warn( + // `${colors.bold( + // colors.yellow(`[plugin:${warning.plugin}]`), + // )} ${colors.yellow(warning.message)}`, + // ) + // return + // } } warn(warnLog) From 5adb440046cb330dbfb664f9ae35fcfbce952e6d Mon Sep 17 00:00:00 2001 From: sapphi-red <49056869+sapphi-red@users.noreply.github.com> Date: Tue, 24 Sep 2024 13:47:21 +0900 Subject: [PATCH 17/20] chore: bump rolldown --- packages/vite/package.json | 2 +- packages/vite/src/node/build.ts | 5 + packages/vite/src/node/optimizer/index.ts | 10 ++ packages/vite/src/node/plugins/worker.ts | 5 + pnpm-lock.yaml | 106 +++++++++++----------- 5 files changed, 74 insertions(+), 54 deletions(-) diff --git a/packages/vite/package.json b/packages/vite/package.json index c928c5b8720cea..4ea9dc540b7a68 100644 --- a/packages/vite/package.json +++ b/packages/vite/package.json @@ -87,7 +87,7 @@ "dependencies": { "esbuild": "^0.24.0", "postcss": "^8.4.47", - "rolldown": "https://pkg.pr.new/rolldown@dab103f", + "rolldown": "https://pkg.pr.new/rolldown@824315d", "rollup": "^4.20.0" }, "optionalDependencies": { diff --git a/packages/vite/src/node/build.ts b/packages/vite/src/node/build.ts index c179b7dc4ceada..42b5e8b9e9195a 100644 --- a/packages/vite/src/node/build.ts +++ b/packages/vite/src/node/build.ts @@ -646,6 +646,11 @@ export async function buildEnvironment( onwarn(warning, warn) { onRollupWarning(warning, warn, environment) }, + // TODO: remove this and enable rolldown's CSS support later + moduleTypes: { + ...options.rollupOptions.moduleTypes, + '.css': 'js', + }, } /** diff --git a/packages/vite/src/node/optimizer/index.ts b/packages/vite/src/node/optimizer/index.ts index 415f3f4f12e211..99dad1a07e668a 100644 --- a/packages/vite/src/node/optimizer/index.ts +++ b/packages/vite/src/node/optimizer/index.ts @@ -781,6 +781,11 @@ async function prepareRolldownOptimizerRun( conditionNames: ['browser'], }, ...rollupOptions, + // TODO: remove this and enable rolldown's CSS support later + moduleTypes: { + '.css': 'js', + ...rollupOptions.moduleTypes, + }, }) if (canceled) { await bundle.close() @@ -1062,6 +1067,11 @@ export async function extractExportsData( ...remainingRollupOptions, plugins, input: [filePath], + // TODO: remove this and enable rolldown's CSS support later + moduleTypes: { + '.css': 'js', + ...remainingRollupOptions.moduleTypes, + }, }) const result = await build.generate({ ...rollupOptions.output, diff --git a/packages/vite/src/node/plugins/worker.ts b/packages/vite/src/node/plugins/worker.ts index 1c41934d674920..debfa4610f512f 100644 --- a/packages/vite/src/node/plugins/worker.ts +++ b/packages/vite/src/node/plugins/worker.ts @@ -85,6 +85,11 @@ async function bundleWorkerEntry( onwarn(warning, warn) { onRollupWarning(warning, warn, workerEnvironment) }, + // TODO: remove this and enable rolldown's CSS support later + moduleTypes: { + '.css': 'js', + ...rollupOptions.moduleTypes, + }, // preserveEntrySignatures: false, }) let chunk: OutputChunk diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0c540e297dcbf4..2852b1c5b73efa 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -231,8 +231,8 @@ importers: specifier: ^8.4.47 version: 8.4.47 rolldown: - specifier: https://pkg.pr.new/rolldown@dab103f - version: https://pkg.pr.new/rolldown@dab103f + specifier: https://pkg.pr.new/rolldown@824315d + version: https://pkg.pr.new/rolldown@824315d rollup: specifier: ^4.20.0 version: 4.20.0 @@ -3059,63 +3059,63 @@ packages: '@polka/url@1.0.0-next.24': resolution: {integrity: sha512-2LuNTFBIO0m7kKIQvvPHN6UE63VjpmL9rnEEaOOaiSPbZK+zUOYIzBAWcED+3XYzhYsd/0mD57VdxAEqqV52CQ==} - '@rolldown/binding-darwin-arm64@https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-darwin-arm64@dab103f': - resolution: {tarball: https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-darwin-arm64@dab103f} + '@rolldown/binding-darwin-arm64@https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-darwin-arm64@824315d': + resolution: {tarball: https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-darwin-arm64@824315d} version: 0.13.2 os: [darwin] - '@rolldown/binding-darwin-x64@https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-darwin-x64@dab103f': - resolution: {tarball: https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-darwin-x64@dab103f} + '@rolldown/binding-darwin-x64@https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-darwin-x64@824315d': + resolution: {tarball: https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-darwin-x64@824315d} version: 0.13.2 os: [darwin] - '@rolldown/binding-freebsd-x64@https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-freebsd-x64@dab103f': - resolution: {tarball: https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-freebsd-x64@dab103f} + '@rolldown/binding-freebsd-x64@https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-freebsd-x64@824315d': + resolution: {tarball: https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-freebsd-x64@824315d} version: 0.13.2 os: [freebsd] - '@rolldown/binding-linux-arm-gnueabihf@https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-linux-arm-gnueabihf@dab103f': - resolution: {tarball: https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-linux-arm-gnueabihf@dab103f} + '@rolldown/binding-linux-arm-gnueabihf@https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-linux-arm-gnueabihf@824315d': + resolution: {tarball: https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-linux-arm-gnueabihf@824315d} version: 0.13.2 os: [linux] - '@rolldown/binding-linux-arm64-gnu@https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-linux-arm64-gnu@dab103f': - resolution: {tarball: https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-linux-arm64-gnu@dab103f} + '@rolldown/binding-linux-arm64-gnu@https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-linux-arm64-gnu@824315d': + resolution: {tarball: https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-linux-arm64-gnu@824315d} version: 0.13.2 os: [linux] - '@rolldown/binding-linux-arm64-musl@https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-linux-arm64-musl@dab103f': - resolution: {tarball: https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-linux-arm64-musl@dab103f} + '@rolldown/binding-linux-arm64-musl@https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-linux-arm64-musl@824315d': + resolution: {tarball: https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-linux-arm64-musl@824315d} version: 0.13.2 os: [linux] - '@rolldown/binding-linux-x64-gnu@https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-linux-x64-gnu@dab103f': - resolution: {tarball: https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-linux-x64-gnu@dab103f} + '@rolldown/binding-linux-x64-gnu@https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-linux-x64-gnu@824315d': + resolution: {tarball: https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-linux-x64-gnu@824315d} version: 0.13.2 os: [linux] - '@rolldown/binding-linux-x64-musl@https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-linux-x64-musl@dab103f': - resolution: {tarball: https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-linux-x64-musl@dab103f} + '@rolldown/binding-linux-x64-musl@https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-linux-x64-musl@824315d': + resolution: {tarball: https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-linux-x64-musl@824315d} version: 0.13.2 os: [linux] - '@rolldown/binding-wasm32-wasi@https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-wasm32-wasi@dab103f': - resolution: {tarball: https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-wasm32-wasi@dab103f} + '@rolldown/binding-wasm32-wasi@https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-wasm32-wasi@824315d': + resolution: {tarball: https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-wasm32-wasi@824315d} version: 0.13.2 engines: {node: '>=14.21.3'} - '@rolldown/binding-win32-arm64-msvc@https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-win32-arm64-msvc@dab103f': - resolution: {tarball: https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-win32-arm64-msvc@dab103f} + '@rolldown/binding-win32-arm64-msvc@https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-win32-arm64-msvc@824315d': + resolution: {tarball: https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-win32-arm64-msvc@824315d} version: 0.13.2 os: [win32] - '@rolldown/binding-win32-ia32-msvc@https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-win32-ia32-msvc@dab103f': - resolution: {tarball: https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-win32-ia32-msvc@dab103f} + '@rolldown/binding-win32-ia32-msvc@https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-win32-ia32-msvc@824315d': + resolution: {tarball: https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-win32-ia32-msvc@824315d} version: 0.13.2 os: [win32] - '@rolldown/binding-win32-x64-msvc@https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-win32-x64-msvc@dab103f': - resolution: {tarball: https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-win32-x64-msvc@dab103f} + '@rolldown/binding-win32-x64-msvc@https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-win32-x64-msvc@824315d': + resolution: {tarball: https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-win32-x64-msvc@824315d} version: 0.13.2 os: [win32] @@ -6312,8 +6312,8 @@ packages: resolution: {integrity: sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==} hasBin: true - rolldown@https://pkg.pr.new/rolldown@dab103f: - resolution: {tarball: https://pkg.pr.new/rolldown@dab103f} + rolldown@https://pkg.pr.new/rolldown@824315d: + resolution: {tarball: https://pkg.pr.new/rolldown@824315d} version: 0.13.2 hasBin: true @@ -8701,42 +8701,42 @@ snapshots: '@polka/url@1.0.0-next.24': {} - '@rolldown/binding-darwin-arm64@https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-darwin-arm64@dab103f': + '@rolldown/binding-darwin-arm64@https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-darwin-arm64@824315d': optional: true - '@rolldown/binding-darwin-x64@https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-darwin-x64@dab103f': + '@rolldown/binding-darwin-x64@https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-darwin-x64@824315d': optional: true - '@rolldown/binding-freebsd-x64@https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-freebsd-x64@dab103f': + '@rolldown/binding-freebsd-x64@https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-freebsd-x64@824315d': optional: true - '@rolldown/binding-linux-arm-gnueabihf@https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-linux-arm-gnueabihf@dab103f': + '@rolldown/binding-linux-arm-gnueabihf@https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-linux-arm-gnueabihf@824315d': optional: true - '@rolldown/binding-linux-arm64-gnu@https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-linux-arm64-gnu@dab103f': + '@rolldown/binding-linux-arm64-gnu@https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-linux-arm64-gnu@824315d': optional: true - '@rolldown/binding-linux-arm64-musl@https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-linux-arm64-musl@dab103f': + '@rolldown/binding-linux-arm64-musl@https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-linux-arm64-musl@824315d': optional: true - '@rolldown/binding-linux-x64-gnu@https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-linux-x64-gnu@dab103f': + '@rolldown/binding-linux-x64-gnu@https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-linux-x64-gnu@824315d': optional: true - '@rolldown/binding-linux-x64-musl@https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-linux-x64-musl@dab103f': + '@rolldown/binding-linux-x64-musl@https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-linux-x64-musl@824315d': optional: true - '@rolldown/binding-wasm32-wasi@https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-wasm32-wasi@dab103f': + '@rolldown/binding-wasm32-wasi@https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-wasm32-wasi@824315d': dependencies: '@napi-rs/wasm-runtime': 0.2.4 optional: true - '@rolldown/binding-win32-arm64-msvc@https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-win32-arm64-msvc@dab103f': + '@rolldown/binding-win32-arm64-msvc@https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-win32-arm64-msvc@824315d': optional: true - '@rolldown/binding-win32-ia32-msvc@https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-win32-ia32-msvc@dab103f': + '@rolldown/binding-win32-ia32-msvc@https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-win32-ia32-msvc@824315d': optional: true - '@rolldown/binding-win32-x64-msvc@https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-win32-x64-msvc@dab103f': + '@rolldown/binding-win32-x64-msvc@https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-win32-x64-msvc@824315d': optional: true '@rollup/plugin-alias@5.1.0(rollup@3.29.4)': @@ -12269,22 +12269,22 @@ snapshots: dependencies: glob: 10.4.5 - rolldown@https://pkg.pr.new/rolldown@dab103f: + rolldown@https://pkg.pr.new/rolldown@824315d: dependencies: zod: 3.23.8 optionalDependencies: - '@rolldown/binding-darwin-arm64': https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-darwin-arm64@dab103f - '@rolldown/binding-darwin-x64': https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-darwin-x64@dab103f - '@rolldown/binding-freebsd-x64': https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-freebsd-x64@dab103f - '@rolldown/binding-linux-arm-gnueabihf': https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-linux-arm-gnueabihf@dab103f - '@rolldown/binding-linux-arm64-gnu': https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-linux-arm64-gnu@dab103f - '@rolldown/binding-linux-arm64-musl': https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-linux-arm64-musl@dab103f - '@rolldown/binding-linux-x64-gnu': https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-linux-x64-gnu@dab103f - '@rolldown/binding-linux-x64-musl': https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-linux-x64-musl@dab103f - '@rolldown/binding-wasm32-wasi': https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-wasm32-wasi@dab103f - '@rolldown/binding-win32-arm64-msvc': https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-win32-arm64-msvc@dab103f - '@rolldown/binding-win32-ia32-msvc': https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-win32-ia32-msvc@dab103f - '@rolldown/binding-win32-x64-msvc': https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-win32-x64-msvc@dab103f + '@rolldown/binding-darwin-arm64': https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-darwin-arm64@824315d + '@rolldown/binding-darwin-x64': https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-darwin-x64@824315d + '@rolldown/binding-freebsd-x64': https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-freebsd-x64@824315d + '@rolldown/binding-linux-arm-gnueabihf': https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-linux-arm-gnueabihf@824315d + '@rolldown/binding-linux-arm64-gnu': https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-linux-arm64-gnu@824315d + '@rolldown/binding-linux-arm64-musl': https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-linux-arm64-musl@824315d + '@rolldown/binding-linux-x64-gnu': https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-linux-x64-gnu@824315d + '@rolldown/binding-linux-x64-musl': https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-linux-x64-musl@824315d + '@rolldown/binding-wasm32-wasi': https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-wasm32-wasi@824315d + '@rolldown/binding-win32-arm64-msvc': https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-win32-arm64-msvc@824315d + '@rolldown/binding-win32-ia32-msvc': https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-win32-ia32-msvc@824315d + '@rolldown/binding-win32-x64-msvc': https://pkg.pr.new/rolldown/rolldown/@rolldown/binding-win32-x64-msvc@824315d rollup-plugin-dts@6.1.1(rollup@3.29.4)(typescript@5.5.3): dependencies: From 04c717fef8016d07410b5e739500a2abb9700d80 Mon Sep 17 00:00:00 2001 From: sapphi-red <49056869+sapphi-red@users.noreply.github.com> Date: Tue, 24 Sep 2024 15:16:46 +0900 Subject: [PATCH 18/20] chore: skip data uri by load fallback plugin for native data uri handling --- .../vite/src/node/plugins/loadFallback.ts | 33 +++++++++++-------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/packages/vite/src/node/plugins/loadFallback.ts b/packages/vite/src/node/plugins/loadFallback.ts index f221ce56bdd2fb..6771d303d71ef1 100644 --- a/packages/vite/src/node/plugins/loadFallback.ts +++ b/packages/vite/src/node/plugins/loadFallback.ts @@ -1,24 +1,31 @@ import fsp from 'node:fs/promises' +import type { RolldownPlugin } from 'rolldown' import { cleanUrl } from '../../shared/utils' -import type { Plugin } from '../plugin' /** * A plugin to provide build load fallback for arbitrary request with queries. */ -export function buildLoadFallbackPlugin(): Plugin { +export function buildLoadFallbackPlugin(): RolldownPlugin { return { name: 'vite:load-fallback', - async load(id) { - try { - const cleanedId = cleanUrl(id) - const content = await fsp.readFile(cleanedId, 'utf-8') - this.addWatchFile(cleanedId) - return content - } catch { - const content = await fsp.readFile(id, 'utf-8') - this.addWatchFile(id) - return content - } + load: { + filter: { + id: { + exclude: [/^data:/], + }, + }, + async handler(id) { + try { + const cleanedId = cleanUrl(id) + const content = await fsp.readFile(cleanedId, 'utf-8') + this.addWatchFile(cleanedId) + return content + } catch { + const content = await fsp.readFile(id, 'utf-8') + this.addWatchFile(id) + return content + } + }, }, } } From 8be28c10188c8bb71a4c4456043621736ee14b8d Mon Sep 17 00:00:00 2001 From: sapphi-red <49056869+sapphi-red@users.noreply.github.com> Date: Wed, 25 Sep 2024 15:57:33 +0900 Subject: [PATCH 19/20] chore: remove browser field edge case test --- playground/resolve/browser-field/relative.js | 6 ++---- playground/resolve/index.html | 5 ++--- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/playground/resolve/browser-field/relative.js b/playground/resolve/browser-field/relative.js index 3be4d7452be75d..6b45c5758d37dd 100644 --- a/playground/resolve/browser-field/relative.js +++ b/playground/resolve/browser-field/relative.js @@ -4,8 +4,6 @@ import rb from './no-ext.js' // no substitution import rc from './ext' import rd from './ext.js' import re from './ext-index/index.js' -// import rf from './ext-index' -const rf = 'FIXME' -import rg from './no-ext-index/index.js' // no substitution +import rf from './no-ext-index/index.js' // no substitution -export { ra, rb, rc, rd, re, rf, rg } +export { ra, rb, rc, rd, re, rf } diff --git a/playground/resolve/index.html b/playground/resolve/index.html index 5badd9bf57bb6e..861d2a8e562ee8 100644 --- a/playground/resolve/index.html +++ b/playground/resolve/index.html @@ -322,11 +322,10 @@

resolve non normalized absolute path

rd, re, rf, - rg, } from '@vitejs/test-resolve-browser-field/relative' - const success = [main, a, c, d, e, f, h, i, ra, rc, rd, re, rf] - const noSuccess = [b, g, rb, rg] + const success = [main, a, c, d, e, f, h, i, ra, rc, rd, re] + const noSuccess = [b, g, rb, rf] if ( [...success, ...noSuccess].filter((text) => text.includes('[success]')) From 1f97ea1060301dd10010f029ccef4c8feb3b3861 Mon Sep 17 00:00:00 2001 From: sapphi-red <49056869+sapphi-red@users.noreply.github.com> Date: Thu, 26 Sep 2024 12:04:08 +0900 Subject: [PATCH 20/20] wip: use builtin css handling --- packages/vite/src/node/build.ts | 14 +++++++++++++- packages/vite/src/node/optimizer/index.ts | 10 ---------- packages/vite/src/node/plugins/css.ts | 2 +- .../vite/src/node/plugins/importAnalysisBuild.ts | 3 ++- packages/vite/src/node/plugins/worker.ts | 5 ----- 5 files changed, 16 insertions(+), 18 deletions(-) diff --git a/packages/vite/src/node/build.ts b/packages/vite/src/node/build.ts index 42b5e8b9e9195a..7f2465deedaee6 100644 --- a/packages/vite/src/node/build.ts +++ b/packages/vite/src/node/build.ts @@ -649,7 +649,19 @@ export async function buildEnvironment( // TODO: remove this and enable rolldown's CSS support later moduleTypes: { ...options.rollupOptions.moduleTypes, - '.css': 'js', + // https://github.com/rolldown/rolldown/blob/4020de442a8ab0f7973794ead3b8aa04e316d558/crates/rolldown/src/module_loader/module_task.rs#L120 + // @ts-expect-error css + '.sass': 'css', + // @ts-expect-error css + '.scss': 'css', + // @ts-expect-error css + '.sss': 'css', + // @ts-expect-error css + '.styl': 'css', + // @ts-expect-error css + '.stylus': 'css', + // @ts-expect-error css + '.less': 'css', }, } diff --git a/packages/vite/src/node/optimizer/index.ts b/packages/vite/src/node/optimizer/index.ts index 99dad1a07e668a..415f3f4f12e211 100644 --- a/packages/vite/src/node/optimizer/index.ts +++ b/packages/vite/src/node/optimizer/index.ts @@ -781,11 +781,6 @@ async function prepareRolldownOptimizerRun( conditionNames: ['browser'], }, ...rollupOptions, - // TODO: remove this and enable rolldown's CSS support later - moduleTypes: { - '.css': 'js', - ...rollupOptions.moduleTypes, - }, }) if (canceled) { await bundle.close() @@ -1067,11 +1062,6 @@ export async function extractExportsData( ...remainingRollupOptions, plugins, input: [filePath], - // TODO: remove this and enable rolldown's CSS support later - moduleTypes: { - '.css': 'js', - ...remainingRollupOptions.moduleTypes, - }, }) const result = await build.generate({ ...rollupOptions.output, diff --git a/packages/vite/src/node/plugins/css.ts b/packages/vite/src/node/plugins/css.ts index 98445624a74142..5b97ed252d5ef7 100644 --- a/packages/vite/src/node/plugins/css.ts +++ b/packages/vite/src/node/plugins/css.ts @@ -577,7 +577,7 @@ export function cssPostPlugin(config: ResolvedConfig): RolldownPlugin { code = `export default ${JSON.stringify(content)}` } else { // empty module when it's not a CSS module nor `?inline` - code = '' + code = css } return { diff --git a/packages/vite/src/node/plugins/importAnalysisBuild.ts b/packages/vite/src/node/plugins/importAnalysisBuild.ts index bb24f9610194dd..e1f684d8a7d739 100644 --- a/packages/vite/src/node/plugins/importAnalysisBuild.ts +++ b/packages/vite/src/node/plugins/importAnalysisBuild.ts @@ -221,7 +221,8 @@ export function buildImportAnalysisPlugin(config: ResolvedConfig): [Plugin] { } }, - async transform(source, importer) { + async transform(source, importer, opts) { + if (opts?.moduleType === 'css') return if (isInNodeModules(importer) && !dynamicImportPrefixRE.test(source)) { return } diff --git a/packages/vite/src/node/plugins/worker.ts b/packages/vite/src/node/plugins/worker.ts index debfa4610f512f..1c41934d674920 100644 --- a/packages/vite/src/node/plugins/worker.ts +++ b/packages/vite/src/node/plugins/worker.ts @@ -85,11 +85,6 @@ async function bundleWorkerEntry( onwarn(warning, warn) { onRollupWarning(warning, warn, workerEnvironment) }, - // TODO: remove this and enable rolldown's CSS support later - moduleTypes: { - '.css': 'js', - ...rollupOptions.moduleTypes, - }, // preserveEntrySignatures: false, }) let chunk: OutputChunk