diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e337a1dd786922..410a413a823061 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -143,78 +143,6 @@ jobs: steps: - run: echo "Build & Test Failed" - test-js-plugins: - needs: changed - if: needs.changed.outputs.should_skip != 'true' - timeout-minutes: 20 - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [ubuntu-latest, macos-latest, windows-latest] - node_version: [22] - fail-fast: false - - name: "Build&Test: node-${{ matrix.node_version }}, ${{ matrix.os }} (js plugins)" - steps: - - name: Checkout - uses: actions/checkout@v6 - - - name: Install pnpm - uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0 - - - name: Set node version to ${{ matrix.node_version }} - uses: actions/setup-node@v6 - with: - node-version: ${{ matrix.node_version }} - cache: "pnpm" - - - name: Install deps - run: pnpm install - - # Install playwright's binary under custom directory to cache - - name: (non-windows) Set Playwright path and Get playwright version - if: runner.os != 'Windows' - run: | - echo "PLAYWRIGHT_BROWSERS_PATH=$HOME/.cache/playwright-bin" >> $GITHUB_ENV - PLAYWRIGHT_VERSION="$(pnpm ls --depth 0 --json -w playwright-chromium | jq --raw-output '.[0].devDependencies["playwright-chromium"].version')" - echo "PLAYWRIGHT_VERSION=$PLAYWRIGHT_VERSION" >> $GITHUB_ENV - - name: (windows) Set Playwright path and Get playwright version - if: runner.os == 'Windows' - run: | - echo "PLAYWRIGHT_BROWSERS_PATH=$HOME\.cache\playwright-bin" >> $env:GITHUB_ENV - $env:PLAYWRIGHT_VERSION="$(pnpm ls --depth 0 --json -w playwright-chromium | jq --raw-output '.[0].devDependencies["playwright-chromium"].version')" - echo "PLAYWRIGHT_VERSION=$env:PLAYWRIGHT_VERSION" >> $env:GITHUB_ENV - - - name: Cache Playwright's binary - uses: actions/cache@v5 - with: - key: ${{ runner.os }}-playwright-bin-v1-${{ env.PLAYWRIGHT_VERSION }} - path: ${{ env.PLAYWRIGHT_BROWSERS_PATH }} - restore-keys: | - ${{ runner.os }}-playwright-bin-v1- - - - name: Install Playwright - # does not need to explicitly set chromium after https://github.com/microsoft/playwright/issues/14862 is solved - run: pnpm playwright install chromium - - - name: Build - run: pnpm run build - - - name: Test unit - run: pnpm run test-unit - env: - _VITE_TEST_JS_PLUGIN: 1 - - - name: Test serve - run: pnpm run test-serve - env: - _VITE_TEST_JS_PLUGIN: 1 - - - name: Test build - run: pnpm run test-build - env: - _VITE_TEST_JS_PLUGIN: 1 - lint: timeout-minutes: 10 runs-on: ubuntu-latest diff --git a/packages/vite/src/node/__tests__/plugins/define.spec.ts b/packages/vite/src/node/__tests__/plugins/define.spec.ts index fd0c883cf55e5a..bffb92cd807ddf 100644 --- a/packages/vite/src/node/__tests__/plugins/define.spec.ts +++ b/packages/vite/src/node/__tests__/plugins/define.spec.ts @@ -6,18 +6,19 @@ import { PartialEnvironment } from '../../baseEnvironment' async function createDefinePluginTransform( define: Record = {}, - build = true, - ssr = false, + isSsrDev = false, ) { + const ssr = isSsrDev + const build = !isSsrDev const config = await resolveConfig( - { configFile: false, define }, + { configFile: false, define, environments: { ssr: {} } }, build ? 'build' : 'serve', ) const instance = definePlugin(config) const environment = new PartialEnvironment(ssr ? 'ssr' : 'client', config) return async (code: string) => { - if (process.env._VITE_TEST_JS_PLUGIN) { + if (isSsrDev) { // @ts-expect-error transform.handler should exist const result = await instance.transform.handler.call( { environment }, @@ -58,9 +59,12 @@ async function createDefinePluginTransform( } } -describe.skipIf(!process.env._VITE_TEST_JS_PLUGIN)('definePlugin', () => { +describe('definePlugin (SSR dev)', () => { + const createJsDefinePluginTransform = (define: Record = {}) => + createDefinePluginTransform(define, true) + test('replaces custom define', async () => { - const transform = await createDefinePluginTransform({ + const transform = await createJsDefinePluginTransform({ __APP_VERSION__: JSON.stringify('1.0'), }) expect(await transform('export const version = __APP_VERSION__ ;')).toBe( @@ -72,7 +76,7 @@ describe.skipIf(!process.env._VITE_TEST_JS_PLUGIN)('definePlugin', () => { }) test('should not replace if not defined', async () => { - const transform = await createDefinePluginTransform({ + const transform = await createJsDefinePluginTransform({ __APP_VERSION__: JSON.stringify('1.0'), }) expect(await transform('export const version = "1.0";')).toBe(undefined) @@ -81,55 +85,8 @@ describe.skipIf(!process.env._VITE_TEST_JS_PLUGIN)('definePlugin', () => { ).toBe(undefined) }) - test('replaces import.meta.env.SSR with false', async () => { - const transform = await createDefinePluginTransform() - expect(await transform('export const isSSR = import.meta.env.SSR;')).toBe( - 'export const isSSR = false;\n', - ) - }) - - test('preserve import.meta.hot with override', async () => { - // assert that the default behavior is to replace import.meta.hot with undefined - const transform = await createDefinePluginTransform() - expect(await transform('export const hot = import.meta.hot;')).toBe( - 'export const hot = undefined;\n', - ) - // assert that we can specify a user define to preserve import.meta.hot - const overrideTransform = await createDefinePluginTransform({ - 'import.meta.hot': 'import.meta.hot', - }) - expect(await overrideTransform('export const hot = import.meta.hot;')).toBe( - 'export const hot = import.meta.hot;\n', - ) - }) - - test('replace import.meta.env.UNKNOWN with undefined', async () => { - const transform = await createDefinePluginTransform() - expect(await transform('export const foo = import.meta.env.UNKNOWN;')).toBe( - 'export const foo = undefined ;\n', - ) - }) - - test('leave import.meta.env["UNKNOWN"] to runtime', async () => { - const transform = await createDefinePluginTransform() - expect( - await transform('export const foo = import.meta.env["UNKNOWN"];'), - ).toMatch( - /const __vite_import_meta_env__ = .*;\nexport const foo = __vite_import_meta_env__\["UNKNOWN"\];/, - ) - }) - - test('preserve import.meta.env.UNKNOWN with override', async () => { - const transform = await createDefinePluginTransform({ - 'import.meta.env.UNKNOWN': 'import.meta.env.UNKNOWN', - }) - expect(await transform('export const foo = import.meta.env.UNKNOWN;')).toBe( - 'export const foo = import.meta.env.UNKNOWN;\n', - ) - }) - test('replace import.meta.env when it is a invalid json', async () => { - const transform = await createDefinePluginTransform({ + const transform = await createJsDefinePluginTransform({ 'import.meta.env.LEGACY': '__VITE_IS_LEGACY__', }) @@ -139,47 +96,13 @@ describe.skipIf(!process.env._VITE_TEST_JS_PLUGIN)('definePlugin', () => { ), ).toMatchInlineSnapshot(` "export const isLegacy = __VITE_IS_LEGACY__; - undefined && console.log(undefined ); + import.meta.env.UNDEFINED && console.log(import.meta.env.UNDEFINED); " `) }) - - test('replace bare import.meta.env', async () => { - const transform = await createDefinePluginTransform() - expect(await transform('export const env = import.meta.env;')).toMatch( - /const __vite_import_meta_env__ = .*;\nexport const env = __vite_import_meta_env__;/, - ) - }) - - test('already has marker', async () => { - const transform = await createDefinePluginTransform() - expect( - await transform( - 'console.log(__vite_import_meta_env__);\nexport const env = import.meta.env;', - ), - ).toMatch( - /const __vite_import_meta_env__1 = .*;\nconsole.log\(__vite_import_meta_env__\);\nexport const env = __vite_import_meta_env__1;/, - ) - - expect( - await transform( - 'console.log(__vite_import_meta_env__, __vite_import_meta_env__1);\n export const env = import.meta.env;', - ), - ).toMatch( - /const __vite_import_meta_env__2 = .*;\nconsole.log\(__vite_import_meta_env__, __vite_import_meta_env__1\);\nexport const env = __vite_import_meta_env__2;/, - ) - - expect( - await transform( - 'console.log(__vite_import_meta_env__);\nexport const env = import.meta.env;\nconsole.log(import.meta.env.UNDEFINED);', - ), - ).toMatch( - /const __vite_import_meta_env__1 = .*;\nconsole.log\(__vite_import_meta_env__\);\nexport const env = __vite_import_meta_env__1;\nconsole.log\(undefined {26}\);/, - ) - }) }) -describe.skipIf(process.env._VITE_TEST_JS_PLUGIN)('native definePlugin', () => { +describe('native definePlugin', () => { test('replaces custom define', async () => { const transform = await createDefinePluginTransform({ __APP_VERSION__: JSON.stringify('1.0'), @@ -269,29 +192,4 @@ describe.skipIf(process.env._VITE_TEST_JS_PLUGIN)('native definePlugin', () => { /const env = .*;\n\nexport \{ env \};/s, ) }) - - test('already has marker', async () => { - const transform = await createDefinePluginTransform() - expect( - await transform( - 'console.log(__vite_import_meta_env__);\nexport const env = import.meta.env;', - ), - ).toMatch(/console.log\(__vite_import_meta_env__\);\nconst env = .*/) - - expect( - await transform( - 'console.log(__vite_import_meta_env__, __vite_import_meta_env__1);\n export const env = import.meta.env;', - ), - ).toMatch( - /console.log\(__vite_import_meta_env__, __vite_import_meta_env__1\);\nconst env = .*/, - ) - - expect( - await transform( - 'console.log(__vite_import_meta_env__);\nexport const env = import.meta.env;\nconsole.log(import.meta.env.UNDEFINED);', - ), - ).toMatch( - /console.log\(__vite_import_meta_env__\);\nconst env = .*;\nconsole.log\(void 0\);/s, - ) - }) }) diff --git a/packages/vite/src/node/build.ts b/packages/vite/src/node/build.ts index f31d083b0446ec..b2b5ecef6f7441 100644 --- a/packages/vite/src/node/build.ts +++ b/packages/vite/src/node/build.ts @@ -523,7 +523,7 @@ export async function resolveBuildPlugins(config: ResolvedConfig): Promise<{ ...(isBuild && !config.isWorker ? [ licensePlugin(), - manifestPlugin(config), + manifestPlugin(), ssrManifestPlugin(), buildReporterPlugin(config), ] diff --git a/packages/vite/src/node/config.ts b/packages/vite/src/node/config.ts index d45c6372f7d77b..5a7ff1d619c149 100644 --- a/packages/vite/src/node/config.ts +++ b/packages/vite/src/node/config.ts @@ -565,17 +565,6 @@ export interface ExperimentalOptions { * @default false */ hmrPartialAccept?: boolean - /** - * Enable builtin plugin that written by rust, which is faster than js plugin. - * - * - 'v1' (will be deprecated, will be removed in v8 stable): Enable the first stable set of native plugins. - * - 'v2' (will be deprecated, will be removed in v8 stable): Enable the improved dynamicImportVarsPlugin and importGlobPlugin. - * - true: Enable all native plugins (currently an alias of 'v2', it will map to a newer one in the future versions). - * - * @experimental - * @default 'v2' - */ - enableNativePlugin?: boolean | 'v1' | 'v2' /** * Enable full bundle mode. * @@ -733,8 +722,6 @@ export interface ResolvedConfig extends Readonly< /** @internal */ safeModulePaths: Set /** @internal */ - nativePluginEnabledLevel: number - /** @internal */ [SYMBOL_RESOLVED_CONFIG]: true } & PluginHookUtils > {} @@ -833,7 +820,6 @@ const configDefaults = Object.freeze({ importGlobRestoreExtension: false, renderBuiltUrl: undefined, hmrPartialAccept: false, - enableNativePlugin: process.env._VITE_TEST_JS_PLUGIN ? false : 'v2', bundledDev: false, }, future: { @@ -2005,9 +1991,6 @@ export async function resolveConfig( }, ), safeModulePaths: new Set(), - nativePluginEnabledLevel: resolveNativePluginEnabledLevel( - experimental.enableNativePlugin, - ), [SYMBOL_RESOLVED_CONFIG]: true, } resolved = { @@ -2140,26 +2123,6 @@ assetFileNames isn't equal for every build.rollupOptions.output. A single patter return resolved } -function resolveNativePluginEnabledLevel( - enableNativePlugin: Exclude< - ExperimentalOptions['enableNativePlugin'], - undefined - >, -) { - switch (enableNativePlugin) { - case 'v1': - return 1 - case 'v2': - case true: - return 2 - case false: - return -1 - default: - enableNativePlugin satisfies never - return -1 - } -} - /** * Resolve base url. Note that some users use Vite to build for non-web targets like * electron or expects to deploy diff --git a/packages/vite/src/node/plugins/define.ts b/packages/vite/src/node/plugins/define.ts index 098887f1d1859e..493697ff681c9e 100644 --- a/packages/vite/src/node/plugins/define.ts +++ b/packages/vite/src/node/plugins/define.ts @@ -7,8 +7,6 @@ import { isHTMLRequest } from './html' const nonJsRe = /\.json(?:$|\?)/ const isNonJsRequest = (request: string): boolean => nonJsRe.test(request) -const importMetaEnvMarker = '__vite_import_meta_env__' -const importMetaEnvKeyReCache = new Map() const escapedDotRE = /(?= 1) { + if (isBundled) { return { name: 'vite:define', options(option) { @@ -138,7 +133,7 @@ export function definePlugin(config: ResolvedConfig): Plugin { transform: { async handler(code, id) { - if (this.environment.config.consumer === 'client' && !isBundled) { + if (this.environment.config.consumer === 'client') { // for dev we inject actual global defines in the vite client to // avoid the transform cost. see the `clientInjection` and // `importAnalysis` plugin. @@ -155,49 +150,14 @@ export function definePlugin(config: ResolvedConfig): Plugin { return } - let [define, pattern, importMetaEnvVal] = getPattern(this.environment) + const [define, pattern] = getPattern(this.environment) if (!pattern) return // Check if our code needs any replacements before running esbuild pattern.lastIndex = 0 if (!pattern.test(code)) return - const hasDefineImportMetaEnv = 'import.meta.env' in define - let marker = importMetaEnvMarker - - if (hasDefineImportMetaEnv && code.includes(marker)) { - // append a number to the marker until it's unique, to avoid if there is a - // marker already in the code - let i = 1 - do { - marker = importMetaEnvMarker + i++ - } while (code.includes(marker)) - - if (marker !== importMetaEnvMarker) { - define = { ...define, 'import.meta.env': marker } - } - } - const result = await replaceDefine(this.environment, code, id, define) - - if (hasDefineImportMetaEnv) { - // Replace `import.meta.env.*` with undefined - result.code = result.code.replaceAll( - getImportMetaEnvKeyRe(marker), - (m) => 'undefined'.padEnd(m.length), - ) - - // If there's bare `import.meta.env` references, prepend the banner - if (result.code.includes(marker)) { - result.code = - `const ${marker} = ${importMetaEnvVal};\n` + result.code - - if (result.map) { - result.map.mappings = ';' + result.map.mappings - } - } - } - return result }, }, @@ -258,12 +218,3 @@ function handleDefineValue(value: any): string { if (typeof value === 'string') return value return JSON.stringify(value) } - -function getImportMetaEnvKeyRe(marker: string): RegExp { - let re = importMetaEnvKeyReCache.get(marker) - if (!re) { - re = new RegExp(`${marker}\\..+?\\b`, 'g') - importMetaEnvKeyReCache.set(marker, re) - } - return re -} diff --git a/packages/vite/src/node/plugins/dynamicImportVars.ts b/packages/vite/src/node/plugins/dynamicImportVars.ts index b164eab0ee46bf..32138e26de9e50 100644 --- a/packages/vite/src/node/plugins/dynamicImportVars.ts +++ b/packages/vite/src/node/plugins/dynamicImportVars.ts @@ -173,7 +173,7 @@ export function dynamicImportVarsPlugin(config: ResolvedConfig): Plugin { extensions: [], }) - if (config.isBundled && config.nativePluginEnabledLevel >= 1) { + if (config.isBundled) { return perEnvironmentPlugin('native:dynamic-import-vars', (environment) => { const { include, exclude } = environment.config.build.dynamicImportVarsOptions diff --git a/packages/vite/src/node/plugins/importMetaGlob.ts b/packages/vite/src/node/plugins/importMetaGlob.ts index ccb2dd5b547fba..816115501c0aa0 100644 --- a/packages/vite/src/node/plugins/importMetaGlob.ts +++ b/packages/vite/src/node/plugins/importMetaGlob.ts @@ -35,7 +35,7 @@ interface ParsedGeneralImportGlobOptions extends GeneralImportGlobOptions { } export function importGlobPlugin(config: ResolvedConfig): Plugin { - if (config.isBundled && config.nativePluginEnabledLevel >= 1) { + if (config.isBundled) { return nativeImportGlobPlugin({ root: config.root, sourcemap: !!config.build.sourcemap, diff --git a/packages/vite/src/node/plugins/index.ts b/packages/vite/src/node/plugins/index.ts index 5cc3e31931504e..2ec7032fae7600 100644 --- a/packages/vite/src/node/plugins/index.ts +++ b/packages/vite/src/node/plugins/index.ts @@ -50,15 +50,12 @@ export async function resolvePlugins( ? await (await import('../build')).resolveBuildPlugins(config) : { pre: [], post: [] } const { modulePreload } = config.build - const enableNativePluginV1 = config.nativePluginEnabledLevel >= 1 return [ !isBundled ? optimizedDepsPlugin() : null, !isWorker ? watchPackageDataPlugin(config.packageCache) : null, !isBundled ? preAliasPlugin(config) : null, - isBundled && - enableNativePluginV1 && - !config.resolve.alias.some((v) => v.customResolver) + isBundled && !config.resolve.alias.some((v) => v.customResolver) ? nativeAliasPlugin({ entries: config.resolve.alias.map((item) => { return { diff --git a/packages/vite/src/node/plugins/manifest.ts b/packages/vite/src/node/plugins/manifest.ts index 709c58fe6f0b2b..70ec394874663a 100644 --- a/packages/vite/src/node/plugins/manifest.ts +++ b/packages/vite/src/node/plugins/manifest.ts @@ -1,10 +1,10 @@ import path from 'node:path' -import type { OutputAsset, OutputChunk, RenderedChunk } from 'rolldown' +import type { OutputChunk, RenderedChunk } from 'rolldown' import { viteManifestPlugin as nativeManifestPlugin } from 'rolldown/experimental' import type { Plugin } from '../plugin' -import { normalizePath, sortObjectKeys } from '../utils' +import { normalizePath } from '../utils' import { perEnvironmentState } from '../environment' -import { type Environment, type ResolvedConfig, perEnvironmentPlugin } from '..' +import { type Environment, perEnvironmentPlugin } from '..' import { cssEntriesMap } from './asset' const endsWithJSRE = /\.[cm]?js$/ @@ -57,7 +57,7 @@ export interface ManifestChunk { dynamicImports?: string[] } -export function manifestPlugin(config: ResolvedConfig): Plugin { +export function manifestPlugin(): Plugin { const getState = perEnvironmentState(() => { return { manifest: {} as Manifest, @@ -68,269 +68,107 @@ export function manifestPlugin(config: ResolvedConfig): Plugin { }, } }) - if (config.build.manifest && config.nativePluginEnabledLevel >= 1) { - return perEnvironmentPlugin('native:manifest', (environment) => { - if (!environment.config.build.manifest) return false - const root = environment.config.root - const outPath = - environment.config.build.manifest === true - ? '.vite/manifest.json' - : environment.config.build.manifest - - const envs: Record = {} - function getChunkName(chunk: OutputChunk) { - return ( - getChunkOriginalFileName(chunk, root, false) ?? - `_${path.basename(chunk.fileName)}` - ) - } + return perEnvironmentPlugin('native:manifest', (environment) => { + if (!environment.config.build.manifest) return false + + const root = environment.config.root + const outPath = + environment.config.build.manifest === true + ? '.vite/manifest.json' + : environment.config.build.manifest + + const envs: Record = {} + function getChunkName(chunk: OutputChunk) { + return ( + getChunkOriginalFileName(chunk, root, false) ?? + `_${path.basename(chunk.fileName)}` + ) + } - return [ - { - name: 'native:manifest-envs', - buildStart() { - envs[environment.name] = this.environment - }, + return [ + { + name: 'native:manifest-envs', + buildStart() { + envs[environment.name] = this.environment }, - nativeManifestPlugin({ - root, - outPath, - isOutputOptionsForLegacyChunks: - environment.config.isOutputOptionsForLegacyChunks, - cssEntries() { - return Object.fromEntries( - cssEntriesMap.get(envs[environment.name])!.entries(), - ) - }, - }), - { - name: 'native:manifest-compatible', - generateBundle(_, bundle) { - const asset = bundle[outPath] - if (asset.type === 'asset') { - let manifest: Manifest | undefined - for (const output of Object.values(bundle)) { - const importedCss = output.viteMetadata?.importedCss - const importedAssets = output.viteMetadata?.importedAssets - if (!importedCss?.size && !importedAssets?.size) continue - manifest ??= JSON.parse(asset.source.toString()) as Manifest - if (output.type === 'chunk') { - const item = manifest[getChunkName(output)] + }, + nativeManifestPlugin({ + root, + outPath, + isOutputOptionsForLegacyChunks: + environment.config.isOutputOptionsForLegacyChunks, + cssEntries() { + return Object.fromEntries( + cssEntriesMap.get(envs[environment.name])!.entries(), + ) + }, + }), + { + name: 'native:manifest-compatible', + generateBundle(_, bundle) { + const asset = bundle[outPath] + if (asset.type === 'asset') { + let manifest: Manifest | undefined + for (const output of Object.values(bundle)) { + const importedCss = output.viteMetadata?.importedCss + const importedAssets = output.viteMetadata?.importedAssets + if (!importedCss?.size && !importedAssets?.size) continue + manifest ??= JSON.parse(asset.source.toString()) as Manifest + if (output.type === 'chunk') { + const item = manifest[getChunkName(output)] + if (!item) continue + if (importedCss?.size) { + item.css = [...importedCss] + } + if (importedAssets?.size) { + item.assets = [...importedAssets] + } + } else if (output.type === 'asset' && output.names.length > 0) { + // Add every unique asset to the manifest, keyed by its original name + const keys = + output.originalFileNames.length > 0 + ? output.originalFileNames + : [`_${path.basename(output.fileName)}`] + + for (const key of keys) { + const item = manifest[key] if (!item) continue - if (importedCss?.size) { - item.css = [...importedCss] - } - if (importedAssets?.size) { - item.assets = [...importedAssets] - } - } else if (output.type === 'asset' && output.names.length > 0) { - // Add every unique asset to the manifest, keyed by its original name - const keys = - output.originalFileNames.length > 0 - ? output.originalFileNames - : [`_${path.basename(output.fileName)}`] - - for (const key of keys) { - const item = manifest[key] - if (!item) continue - if (!(item.file && endsWithJSRE.test(item.file))) { - if (importedCss?.size) { - item.css = [...importedCss] - } - if (importedAssets?.size) { - item.assets = [...importedAssets] - } + if (!(item.file && endsWithJSRE.test(item.file))) { + if (importedCss?.size) { + item.css = [...importedCss] + } + if (importedAssets?.size) { + item.assets = [...importedAssets] } } } } - const output = - this.environment.config.build.rolldownOptions.output - const outputLength = Array.isArray(output) ? output.length : 1 - if (manifest && outputLength === 1) { - asset.source = JSON.stringify(manifest, undefined, 2) - return - } - - const state = getState(this) - state.outputCount++ - state.manifest = Object.assign( - state.manifest, - manifest ?? JSON.parse(asset.source.toString()), - ) - if (state.outputCount >= outputLength) { - asset.source = JSON.stringify(state.manifest, undefined, 2) - state.reset() - } else { - delete bundle[outPath] - } } - }, - }, - ] - }) - } - return { - name: 'vite:manifest', - - perEnvironmentStartEndDuringDev: true, - - applyToEnvironment(environment) { - return !!environment.config.build.manifest - }, - - generateBundle(opts, bundle) { - const state = getState(this) - const { manifest } = state - const { root } = this.environment.config - const buildOptions = this.environment.config.build - - const isLegacy = - this.environment.config.isOutputOptionsForLegacyChunks?.(opts) ?? false - function getChunkName(chunk: OutputChunk) { - return ( - getChunkOriginalFileName(chunk, root, isLegacy) ?? - `_${path.basename(chunk.fileName)}` - ) - } - - function getInternalImports(imports: string[]): string[] { - const filteredImports: string[] = [] - - for (const file of imports) { - if (bundle[file] === undefined) { - continue - } - - filteredImports.push(getChunkName(bundle[file] as OutputChunk)) - } - - return filteredImports - } - - function createChunk(chunk: OutputChunk): ManifestChunk { - const manifestChunk: ManifestChunk = { - file: chunk.fileName, - name: chunk.name, - } - - if (chunk.facadeModuleId) { - manifestChunk.src = getChunkName(chunk) - } - if (chunk.isEntry) { - manifestChunk.isEntry = true - } - if (chunk.isDynamicEntry) { - manifestChunk.isDynamicEntry = true - } - - if (chunk.imports.length) { - const internalImports = getInternalImports(chunk.imports) - if (internalImports.length > 0) { - manifestChunk.imports = internalImports - } - } - - if (chunk.dynamicImports.length) { - const internalImports = getInternalImports(chunk.dynamicImports) - if (internalImports.length > 0) { - manifestChunk.dynamicImports = internalImports - } - } - - if (chunk.viteMetadata?.importedCss.size) { - manifestChunk.css = [...chunk.viteMetadata.importedCss] - } - if (chunk.viteMetadata?.importedAssets.size) { - manifestChunk.assets = [...chunk.viteMetadata.importedAssets] - } - - return manifestChunk - } - - function createAsset( - asset: OutputAsset, - src: string, - name?: string, - ): ManifestChunk { - const manifestChunk: ManifestChunk = { - file: asset.fileName, - src, - } - if (name) { - manifestChunk.isEntry = true - manifestChunk.name = name - // @ts-expect-error keep names field for backward compatibility - manifestChunk.names = asset.names - - if (asset.viteMetadata?.importedCss.size) { - manifestChunk.css = [...asset.viteMetadata.importedCss] - } - if (asset.viteMetadata?.importedAssets.size) { - manifestChunk.assets = [...asset.viteMetadata.importedAssets] - } - } - return manifestChunk - } - - const entryCssReferenceIds = cssEntriesMap.get(this.environment)! - const entryCssAssetFileNames = new Map() - for (const [name, id] of entryCssReferenceIds) { - try { - const fileName = this.getFileName(id) - entryCssAssetFileNames.set(fileName, name) - } catch { - // The asset was generated as part of a different output option. - // It was already handled during the previous run of this plugin. - } - } - - for (const file in bundle) { - const chunk = bundle[file] - if (chunk.type === 'chunk') { - manifest[getChunkName(chunk)] = createChunk(chunk) - } else if (chunk.type === 'asset' && chunk.names.length > 0) { - // Add every unique asset to the manifest, keyed by its original name - const src = - chunk.originalFileNames.length > 0 - ? chunk.originalFileNames[0] - : `_${path.basename(chunk.fileName)}` - const name = entryCssAssetFileNames.get(chunk.fileName) - const asset = createAsset(chunk, src, name) - - // If JS chunk and asset chunk are both generated from the same source file, - // prioritize JS chunk as it contains more information - const file = manifest[src]?.file - if (!(file && endsWithJSRE.test(file))) { - manifest[src] = asset - } + const output = this.environment.config.build.rolldownOptions.output + const outputLength = Array.isArray(output) ? output.length : 1 + if (manifest && outputLength === 1) { + asset.source = JSON.stringify(manifest, undefined, 2) + return + } - for (const originalFileName of chunk.originalFileNames.slice(1)) { - const file = manifest[originalFileName]?.file - if (!(file && endsWithJSRE.test(file))) { - manifest[originalFileName] = asset + const state = getState(this) + state.outputCount++ + state.manifest = Object.assign( + state.manifest, + manifest ?? JSON.parse(asset.source.toString()), + ) + if (state.outputCount >= outputLength) { + asset.source = JSON.stringify(state.manifest, undefined, 2) + state.reset() + } else { + delete bundle[outPath] } } - } - } - - state.outputCount++ - const output = buildOptions.rollupOptions.output - const outputLength = Array.isArray(output) ? output.length : 1 - if (state.outputCount >= outputLength) { - this.emitFile({ - fileName: - typeof buildOptions.manifest === 'string' - ? buildOptions.manifest - : '.vite/manifest.json', - type: 'asset', - source: JSON.stringify(sortObjectKeys(manifest), undefined, 2), - }) - state.reset() - } - }, - } + }, + }, + ] + }) } export function getChunkOriginalFileName( diff --git a/packages/vite/src/node/plugins/oxc.ts b/packages/vite/src/node/plugins/oxc.ts index 9ad774823bb4a2..e0e30e8224d7b9 100644 --- a/packages/vite/src/node/plugins/oxc.ts +++ b/packages/vite/src/node/plugins/oxc.ts @@ -209,7 +209,7 @@ function shouldSkipWarning(warning: RolldownLog): boolean { } export function oxcPlugin(config: ResolvedConfig): Plugin { - if (config.isBundled && config.nativePluginEnabledLevel >= 1) { + if (config.isBundled) { return perEnvironmentPlugin('native:transform', (environment) => { const { jsxInject, diff --git a/packages/vite/src/node/plugins/reporter.ts b/packages/vite/src/node/plugins/reporter.ts index 540def1b5ddeb1..186d467a87861e 100644 --- a/packages/vite/src/node/plugins/reporter.ts +++ b/packages/vite/src/node/plugins/reporter.ts @@ -1,388 +1,26 @@ import path from 'node:path' -import { gzip } from 'node:zlib' -import { promisify } from 'node:util' -import colors from 'picocolors' -import type { OutputBundle } from 'rolldown' import { viteReporterPlugin as nativeReporterPlugin } from 'rolldown/experimental' import { type Plugin, perEnvironmentPlugin } from '../plugin' import type { ResolvedConfig } from '../config' -import type { Environment } from '../environment' -import { perEnvironmentState } from '../environment' -import { isDefined, isInNodeModules, normalizePath } from '../utils' import { LogLevels } from '../logger' -import { withTrailingSlash } from '../../shared/utils' - -const groups = [ - { name: 'Assets', color: colors.green }, - { name: 'CSS', color: colors.magenta }, - { name: 'JS', color: colors.cyan }, -] -type LogEntry = { - name: string - group: (typeof groups)[number]['name'] - size: number - compressedSize: number | null - mapSize: number | null -} - -const COMPRESSIBLE_ASSETS_RE = /\.(?:html|json|svg|txt|xml|xhtml|wasm)$/ export function buildReporterPlugin(config: ResolvedConfig): Plugin { - if (config.nativePluginEnabledLevel >= 1) { - return perEnvironmentPlugin('native:reporter', (env) => { - const tty = process.stdout.isTTY && !process.env.CI - const shouldLogInfo = - LogLevels[config.logLevel || 'info'] >= LogLevels.info - const assetsDir = path.join(env.config.build.assetsDir, '/') - return nativeReporterPlugin({ - root: env.config.root, - isTty: !!tty, - isLib: !!env.config.build.lib, - assetsDir, - chunkLimit: env.config.build.chunkSizeWarningLimit, - logInfo: shouldLogInfo ? (msg) => env.logger.info(msg) : undefined, - reportCompressedSize: env.config.build.reportCompressedSize, - warnLargeChunks: - env.config.build.minify && - !env.config.build.lib && - env.config.consumer === 'client', - }) + return perEnvironmentPlugin('native:reporter', (env) => { + const tty = process.stdout.isTTY && !process.env.CI + const shouldLogInfo = LogLevels[config.logLevel || 'info'] >= LogLevels.info + const assetsDir = path.join(env.config.build.assetsDir, '/') + return nativeReporterPlugin({ + root: env.config.root, + isTty: !!tty, + isLib: !!env.config.build.lib, + assetsDir, + chunkLimit: env.config.build.chunkSizeWarningLimit, + logInfo: shouldLogInfo ? (msg) => env.logger.info(msg) : undefined, + reportCompressedSize: env.config.build.reportCompressedSize, + warnLargeChunks: + env.config.build.minify && + !env.config.build.lib && + env.config.consumer === 'client', }) - } - - const compress = promisify(gzip) - - const numberFormatter = new Intl.NumberFormat('en', { - maximumFractionDigits: 2, - minimumFractionDigits: 2, }) - const displaySize = (bytes: number) => { - return `${numberFormatter.format(bytes / 1000)} kB` - } - - const tty = process.stdout.isTTY && !process.env.CI - const shouldLogInfo = LogLevels[config.logLevel || 'info'] >= LogLevels.info - - const modulesReporter = shouldLogInfo - ? perEnvironmentState((environment: Environment) => { - let hasTransformed = false - let transformedCount = 0 - - const logTransform = throttle((id: string) => { - writeLine( - `transforming (${transformedCount}) ${colors.dim( - path.relative(config.root, id), - )}`, - ) - }) - - return { - reset() { - transformedCount = 0 - }, - register(id: string) { - transformedCount++ - if (!tty) { - if (!hasTransformed) { - config.logger.info(`transforming...`) - } - } else { - if (id.includes(`?`)) return - logTransform(id) - } - hasTransformed = true - }, - log() { - if (tty) { - clearLine() - } - environment.logger.info( - `${colors.green(`✓`)} ${transformedCount} modules transformed.`, - ) - }, - } - }) - : undefined - - const chunksReporter = perEnvironmentState((environment: Environment) => { - let hasRenderedChunk = false - let hasCompressChunk = false - let chunkCount = 0 - let compressedCount = 0 - - async function getCompressedSize( - code: string | Uint8Array, - ): Promise { - if ( - environment.config.consumer !== 'client' || - !environment.config.build.reportCompressedSize - ) { - return null - } - if (shouldLogInfo && !hasCompressChunk) { - if (!tty) { - config.logger.info('computing gzip size...') - } else { - writeLine('computing gzip size (0)...') - } - hasCompressChunk = true - } - const compressed = await compress( - typeof code === 'string' ? code : Buffer.from(code), - ) - compressedCount++ - if (shouldLogInfo && tty) { - writeLine(`computing gzip size (${compressedCount})...`) - } - return compressed.length - } - - return { - reset() { - chunkCount = 0 - compressedCount = 0 - }, - register() { - chunkCount++ - if (shouldLogInfo) { - if (!tty) { - if (!hasRenderedChunk) { - environment.logger.info('rendering chunks...') - } - } else { - writeLine(`rendering chunks (${chunkCount})...`) - } - hasRenderedChunk = true - } - }, - async log(output: OutputBundle, outDir?: string) { - const chunkLimit = environment.config.build.chunkSizeWarningLimit - - let hasLargeChunks = false - - if (shouldLogInfo) { - const entries = ( - await Promise.all( - Object.values(output).map( - async (chunk): Promise => { - if (chunk.type === 'chunk') { - return { - name: chunk.fileName, - group: 'JS', - size: Buffer.byteLength(chunk.code), - compressedSize: await getCompressedSize(chunk.code), - mapSize: chunk.map - ? Buffer.byteLength(chunk.map.toString()) - : null, - } - } else { - if (chunk.fileName.endsWith('.map')) return null - const isCSS = chunk.fileName.endsWith('.css') - const isCompressible = - isCSS || COMPRESSIBLE_ASSETS_RE.test(chunk.fileName) - return { - name: chunk.fileName, - group: isCSS ? 'CSS' : 'Assets', - size: Buffer.byteLength(chunk.source), - mapSize: null, // Rollup doesn't support CSS maps? - compressedSize: isCompressible - ? await getCompressedSize(chunk.source) - : null, - } - } - }, - ), - ) - ).filter(isDefined) - if (tty) clearLine() - - let longest = 0 - let biggestSize = 0 - let biggestMap = 0 - let biggestCompressSize = 0 - for (const entry of entries) { - if (entry.name.length > longest) longest = entry.name.length - if (entry.size > biggestSize) biggestSize = entry.size - if (entry.mapSize && entry.mapSize > biggestMap) { - biggestMap = entry.mapSize - } - if ( - entry.compressedSize && - entry.compressedSize > biggestCompressSize - ) { - biggestCompressSize = entry.compressedSize - } - } - - const sizePad = displaySize(biggestSize).length - const mapPad = displaySize(biggestMap).length - const compressPad = displaySize(biggestCompressSize).length - - const relativeOutDir = normalizePath( - path.relative( - config.root, - path.resolve( - config.root, - outDir ?? environment.config.build.outDir, - ), - ), - ) - const assetsDir = path.join(environment.config.build.assetsDir, '/') - - for (const group of groups) { - const filtered = entries.filter((e) => e.group === group.name) - if (!filtered.length) continue - for (const entry of filtered.sort((a, z) => a.size - z.size)) { - const isLarge = - group.name === 'JS' && entry.size / 1000 > chunkLimit - if (isLarge) hasLargeChunks = true - const sizeColor = isLarge ? colors.yellow : colors.dim - let log = colors.dim(withTrailingSlash(relativeOutDir)) - log += - !config.build.lib && - entry.name.startsWith(withTrailingSlash(assetsDir)) - ? colors.dim(assetsDir) + - group.color( - entry.name - .slice(assetsDir.length) - .padEnd(longest + 2 - assetsDir.length), - ) - : group.color(entry.name.padEnd(longest + 2)) - log += colors.bold( - sizeColor(displaySize(entry.size).padStart(sizePad)), - ) - if (entry.compressedSize) { - log += colors.dim( - ` │ gzip: ${displaySize(entry.compressedSize).padStart( - compressPad, - )}`, - ) - } - if (entry.mapSize) { - log += colors.dim( - ` │ map: ${displaySize(entry.mapSize).padStart(mapPad)}`, - ) - } - config.logger.info(log) - } - } - } else { - hasLargeChunks = Object.values(output).some((chunk) => { - return ( - chunk.type === 'chunk' && chunk.code.length / 1000 > chunkLimit - ) - }) - } - - if ( - hasLargeChunks && - environment.config.build.minify && - !config.build.lib && - environment.config.consumer === 'client' - ) { - environment.logger.warn( - colors.yellow( - `\n(!) Some chunks are larger than ${chunkLimit} kB after minification. Consider:\n` + - `- Using dynamic import() to code-split the application\n` + - `- Use build.rollupOptions.output.manualChunks to improve chunking: https://rollupjs.org/configuration-options/#output-manualchunks\n` + - `- Adjust chunk size limit for this warning via build.chunkSizeWarningLimit.`, - ), - ) - } - }, - } - }) - - return { - name: 'vite:reporter', - sharedDuringBuild: true, - perEnvironmentStartEndDuringDev: true, - - ...(modulesReporter - ? { - transform(_, id) { - modulesReporter(this).register(id) - }, - - buildStart() { - modulesReporter(this).reset() - }, - - buildEnd() { - modulesReporter(this).log() - }, - } - : {}), - - renderStart() { - chunksReporter(this).reset() - }, - - renderChunk(_, chunk, options) { - if (options.codeSplitting !== false) { - for (const id of chunk.moduleIds) { - const module = this.getModuleInfo(id) - if (!module) continue - // When a dynamic importer shares a chunk with the imported module, - // warn that the dynamic imported module will not be moved to another chunk (#12850). - if (module.importers.length && module.dynamicImporters.length) { - // Filter out the intersection of dynamic importers and sibling modules in - // the same chunk. The intersecting dynamic importers' dynamic import is not - // expected to work. Note we're only detecting the direct ineffective - // dynamic import here. - const detectedIneffectiveDynamicImport = - module.dynamicImporters.some( - (id) => !isInNodeModules(id) && chunk.moduleIds.includes(id), - ) - if (detectedIneffectiveDynamicImport) { - this.warn( - `\n(!) ${ - module.id - } is dynamically imported by ${module.dynamicImporters.join( - ', ', - )} but also statically imported by ${module.importers.join( - ', ', - )}, dynamic import will not move module into another chunk.\n`, - ) - } - } - } - } - - chunksReporter(this).register() - }, - - generateBundle() { - if (shouldLogInfo && tty) clearLine() - }, - - async writeBundle({ dir }, output) { - await chunksReporter(this).log(output, dir) - }, - } -} - -function writeLine(output: string) { - clearLine() - if (output.length < process.stdout.columns) { - process.stdout.write(output) - } else { - process.stdout.write(output.substring(0, process.stdout.columns - 1)) - } -} - -function clearLine() { - process.stdout.clearLine(0) - process.stdout.cursorTo(0) -} - -function throttle(fn: Function) { - let timerHandle: NodeJS.Timeout | null = null - return (...args: any[]) => { - if (timerHandle) return - fn(...args) - timerHandle = setTimeout(() => { - timerHandle = null - }, 100) - } } diff --git a/packages/vite/src/node/plugins/worker.ts b/packages/vite/src/node/plugins/worker.ts index 3a042353379ad7..579cc1ad4f96d1 100644 --- a/packages/vite/src/node/plugins/worker.ts +++ b/packages/vite/src/node/plugins/worker.ts @@ -312,7 +312,7 @@ export async function workerFileToUrl( } export function webWorkerPostPlugin(config: ResolvedConfig): Plugin { - if (config.isBundled && config.nativePluginEnabledLevel >= 1) { + if (config.isBundled) { return perEnvironmentPlugin( 'native:web-worker-post-plugin', (environment) => {