diff --git a/packages/vite/src/node/__tests__/plugins/json.spec.ts b/packages/vite/src/node/__tests__/plugins/json.spec.ts deleted file mode 100644 index c1e27c16a57061..00000000000000 --- a/packages/vite/src/node/__tests__/plugins/json.spec.ts +++ /dev/null @@ -1,169 +0,0 @@ -import { describe, expect, test } from 'vitest' -import { - type JsonOptions, - extractJsonErrorPosition, - jsonPlugin, -} from '../../plugins/json' - -const getErrorMessage = (input: string) => { - try { - JSON.parse(input) - throw new Error('No error happened') - } catch (e) { - return e.message - } -} - -test('can extract json error position', () => { - const cases = [ - { input: '{', expectedPosition: 0 }, - { input: '{},', expectedPosition: 1 }, - { input: '"f', expectedPosition: 1 }, - { input: '[', expectedPosition: 0 }, - ] - - for (const { input, expectedPosition } of cases) { - expect(extractJsonErrorPosition(getErrorMessage(input), input.length)).toBe( - expectedPosition, - ) - } -}) - -describe('transform', () => { - const transform = ( - input: string, - opts: Required, - isBuild: boolean, - ) => { - const plugin = jsonPlugin(opts, isBuild, false) - // @ts-expect-error transform.handler should exist - return plugin.transform.handler(input, 'test.json', { moduleType: 'json' }) - .code - } - - test("namedExports: true, stringify: 'auto' should not transformed an array input", () => { - const actualSmall = transform( - '[{"a":1,"b":2}]', - { namedExports: true, stringify: 'auto' }, - false, - ) - expect(actualSmall).toMatchInlineSnapshot(` -"export default [ - { - a: 1, - b: 2 - } -];" - `) - }) - - test('namedExports: true, stringify: true should not transformed an array input', () => { - const actualSmall = transform( - '[{"a":1,"b":2}]', - { namedExports: true, stringify: true }, - false, - ) - expect(actualSmall).toMatchInlineSnapshot( - `"export default /* #__PURE__ */ JSON.parse("[{\\"a\\":1,\\"b\\":2}]")"`, - ) - }) - - test('namedExports: true, stringify: false', () => { - const actual = transform( - '{"a":1,\n"🫠": "",\n"const": false}', - { namedExports: true, stringify: false }, - false, - ) - expect(actual).toMatchInlineSnapshot(` - "export const a = 1; - export default { - a: a, - "🫠": "", - "const": false - }; - " - `) - }) - - test('namedExports: false, stringify: false', () => { - const actual = transform( - '{"a":1,\n"🫠": "",\n"const": false}', - { namedExports: false, stringify: false }, - false, - ) - expect(actual).toMatchInlineSnapshot(` - "export default { - a: 1, - "🫠": "", - "const": false - };" - `) - }) - - test('namedExports: true, stringify: true', () => { - const actual = transform( - '{"a":1,\n"🫠": "",\n"const": false}', - { namedExports: true, stringify: true }, - false, - ) - expect(actual).toMatchInlineSnapshot(` - "export const a = 1; - export default { - a, - "🫠": "", - "const": false, - }; - " - `) - }) - - test('namedExports: false, stringify: true', () => { - const actualDev = transform( - '{"a":1,\n"🫠": "",\n"const": false}', - { namedExports: false, stringify: true }, - false, - ) - expect(actualDev).toMatchInlineSnapshot( - `"export default /* #__PURE__ */ JSON.parse("{\\"a\\":1,\\n\\"🫠\\": \\"\\",\\n\\"const\\": false}")"`, - ) - - const actualBuild = transform( - '{"a":1,\n"🫠": "",\n"const": false}', - { namedExports: false, stringify: true }, - true, - ) - expect(actualBuild).toMatchInlineSnapshot( - `"export default /* #__PURE__ */ JSON.parse("{\\"a\\":1,\\"🫠\\":\\"\\",\\"const\\":false}")"`, - ) - }) - - test("namedExports: true, stringify: 'auto'", () => { - const actualSmall = transform( - '{"a":1,\n"🫠": "",\n"const": false}', - { namedExports: true, stringify: 'auto' }, - false, - ) - expect(actualSmall).toMatchInlineSnapshot(` - "export const a = 1; - export default { - a, - "🫠": "", - "const": false, - }; - " - `) - const actualLargeNonObject = transform( - `{"a":1,\n"🫠": "${'vite'.repeat(3000)}",\n"const": false}`, - { namedExports: true, stringify: 'auto' }, - false, - ) - expect(actualLargeNonObject).not.toContain('JSON.parse(') - - const actualLarge = transform( - `{"a":1,\n"🫠": {\n"foo": "${'vite'.repeat(3000)}"\n},\n"const": false}`, - { namedExports: true, stringify: 'auto' }, - false, - ) - expect(actualLarge).toContain('JSON.parse(') - }) -}) diff --git a/packages/vite/src/node/plugins/index.ts b/packages/vite/src/node/plugins/index.ts index 8cc6d89dc977c6..bf0a2603e75f20 100644 --- a/packages/vite/src/node/plugins/index.ts +++ b/packages/vite/src/node/plugins/index.ts @@ -1,6 +1,10 @@ import aliasPlugin, { type ResolverFunction } from '@rollup/plugin-alias' import type { ObjectHook } from 'rolldown' -import { viteAliasPlugin as nativeAliasPlugin } from 'rolldown/experimental' +import { + viteAliasPlugin as nativeAliasPlugin, + viteJsonPlugin as nativeJsonPlugin, + viteWasmFallbackPlugin as nativeWasmFallbackPlugin, +} from 'rolldown/experimental' import type { PluginHookUtils, ResolvedConfig } from '../config' import { type HookHandler, @@ -8,7 +12,6 @@ import { type PluginWithRequiredHook, } from '../plugin' import { watchPackageDataPlugin } from '../packages' -import { jsonPlugin } from './json' import { oxcResolvePlugin } from './resolve' import { optimizedDepsPlugin } from './optimizedDeps' import { importAnalysisPlugin } from './importAnalysis' @@ -16,7 +19,7 @@ import { cssAnalysisPlugin, cssPlugin, cssPostPlugin } from './css' import { assetPlugin } from './asset' import { clientInjectionsPlugin } from './clientInjections' import { buildHtmlPlugin, htmlInlineProxyPlugin } from './html' -import { wasmFallbackPlugin, wasmHelperPlugin } from './wasm' +import { wasmHelperPlugin } from './wasm' import { modulePreloadPolyfillPlugin } from './modulePreloadPolyfill' import { webWorkerPlugin } from './worker' import { preAliasPlugin } from './preAlias' @@ -94,14 +97,14 @@ export async function resolvePlugins( cssPlugin(config), esbuildBannerFooterCompatPlugin(config), config.oxc !== false ? oxcPlugin(config) : null, - jsonPlugin(config.json, isBuild, enableNativePluginV1), + nativeJsonPlugin({ ...config.json, minify: isBuild }), wasmHelperPlugin(config), webWorkerPlugin(config), assetPlugin(config), ...normalPlugins, - wasmFallbackPlugin(config), + nativeWasmFallbackPlugin(), definePlugin(config), cssPostPlugin(config), isBundled && buildHtmlPlugin(config), diff --git a/packages/vite/src/node/plugins/json.ts b/packages/vite/src/node/plugins/json.ts index 99b6a328c9de6c..fcddcec9b41821 100644 --- a/packages/vite/src/node/plugins/json.ts +++ b/packages/vite/src/node/plugins/json.ts @@ -1,18 +1,3 @@ -/** - * https://github.com/rollup/plugins/blob/master/packages/json/src/index.js - * - * This source code is licensed under the MIT license found in the - * LICENSE file at - * https://github.com/rollup/plugins/blob/master/LICENSE - */ - -import { dataToEsm, makeLegalIdentifier } from '@rollup/pluginutils' -import { viteJsonPlugin as nativeJsonPlugin } from 'rolldown/experimental' -import { SPECIAL_QUERY_RE } from '../constants' -import type { Plugin } from '../plugin' -import { stripBomTag } from '../utils' -import { inlineRE, noInlineRE } from './asset' - export interface JsonOptions { /** * Generate a named export for every property of the JSON object @@ -28,135 +13,7 @@ export interface JsonOptions { stringify?: boolean | 'auto' } -// Custom json filter for vite -const jsonExtRE = /\.json(?:$|\?)(?!commonjs-(?:proxy|external))/ - -const jsonObjRE = /^\s*\{/ - const jsonLangs = `\\.(?:json|json5)(?:$|\\?)` const jsonLangRE = new RegExp(jsonLangs) export const isJSONRequest = (request: string): boolean => jsonLangRE.test(request) - -export function jsonPlugin( - options: Required, - isBuild: boolean, - enableNativePlugin: boolean, -): Plugin { - if (enableNativePlugin) { - return nativeJsonPlugin({ ...options, minify: isBuild }) - } - - return { - name: 'vite:json', - - transform: { - filter: { - id: { include: jsonExtRE, exclude: SPECIAL_QUERY_RE }, - // don't transform if the file is already transformed to a different format - moduleType: ['json'], - }, - handler(json, id) { - if (inlineRE.test(id) || noInlineRE.test(id)) { - this.warn( - `\n` + - `Using ?inline or ?no-inline for JSON imports will have no effect.\n` + - `Please use ?url&inline or ?url&no-inline to control JSON file inlining behavior.\n`, - ) - } - - json = stripBomTag(json) - - try { - if (options.stringify !== false) { - if (options.namedExports && jsonObjRE.test(json)) { - const parsed = JSON.parse(json) - const keys = Object.keys(parsed) - - let code = '' - let defaultObjectCode = '{\n' - for (const key of keys) { - if (key === makeLegalIdentifier(key)) { - code += `export const ${key} = ${serializeValue(parsed[key])};\n` - defaultObjectCode += ` ${key},\n` - } else { - defaultObjectCode += ` ${JSON.stringify(key)}: ${serializeValue(parsed[key])},\n` - } - } - defaultObjectCode += '}' - - code += `export default ${defaultObjectCode};\n` - return { - code, - map: { mappings: '' }, - moduleType: 'js', - } - } - - if ( - options.stringify === true || - // use 10kB as a threshold for 'auto' - // https://v8.dev/blog/cost-of-javascript-2019#:~:text=A%20good%20rule%20of%20thumb%20is%20to%20apply%20this%20technique%20for%20objects%20of%2010%20kB%20or%20larger - json.length > 10 * 1000 - ) { - // during build, parse then double-stringify to remove all - // unnecessary whitespaces to reduce bundle size. - if (isBuild) { - json = JSON.stringify(JSON.parse(json)) - } - - return { - code: `export default /* #__PURE__ */ JSON.parse(${JSON.stringify(json)})`, - map: { mappings: '' }, - moduleType: 'js', - } - } - } - - return { - code: dataToEsm(JSON.parse(json), { - preferConst: true, - namedExports: options.namedExports, - }), - map: { mappings: '' }, - moduleType: 'js', - } - } catch (e) { - const position = extractJsonErrorPosition(e.message, json.length) - const msg = position - ? `, invalid JSON syntax found at position ${position}` - : `.` - this.error(`Failed to parse JSON file` + msg, position) - } - }, - }, - } -} - -function serializeValue(value: unknown): string { - const valueAsString = JSON.stringify(value) - // use 10kB as a threshold - // https://v8.dev/blog/cost-of-javascript-2019#:~:text=A%20good%20rule%20of%20thumb%20is%20to%20apply%20this%20technique%20for%20objects%20of%2010%20kB%20or%20larger - if ( - typeof value === 'object' && - value != null && - valueAsString.length > 10 * 1000 - ) { - return `/* #__PURE__ */ JSON.parse(${JSON.stringify(valueAsString)})` - } - return valueAsString -} - -export function extractJsonErrorPosition( - errorMessage: string, - inputLength: number, -): number | undefined { - if (errorMessage.startsWith('Unexpected end of JSON input')) { - return inputLength - 1 - } - - const errorMessageList = /at position (\d+)/.exec(errorMessage) - return errorMessageList - ? Math.max(parseInt(errorMessageList[1], 10) - 1, 0) - : undefined -} diff --git a/packages/vite/src/node/plugins/modulePreloadPolyfill.ts b/packages/vite/src/node/plugins/modulePreloadPolyfill.ts index b7d3354a8394db..224fe3e3367ad7 100644 --- a/packages/vite/src/node/plugins/modulePreloadPolyfill.ts +++ b/packages/vite/src/node/plugins/modulePreloadPolyfill.ts @@ -2,13 +2,12 @@ import { exactRegex } from '@rolldown/pluginutils' import { viteModulePreloadPolyfillPlugin as nativeModulePreloadPolyfillPlugin } from 'rolldown/experimental' import { type ResolvedConfig, perEnvironmentPlugin } from '..' import type { Plugin } from '../plugin' -import { isModernFlag } from './importAnalysisBuild' export const modulePreloadPolyfillId = 'vite/modulepreload-polyfill' const resolvedModulePreloadPolyfillId = '\0' + modulePreloadPolyfillId + '.js' export function modulePreloadPolyfillPlugin(config: ResolvedConfig): Plugin { - if (config.isBundled && config.nativePluginEnabledLevel >= 1) { + if (config.isBundled) { return perEnvironmentPlugin( 'native:modulepreload-polyfill', (environment) => { @@ -19,8 +18,6 @@ export function modulePreloadPolyfillPlugin(config: ResolvedConfig): Plugin { ) } - let polyfillString: string | undefined - return { name: 'vite:modulepreload-polyfill', resolveId: { @@ -32,87 +29,9 @@ export function modulePreloadPolyfillPlugin(config: ResolvedConfig): Plugin { load: { filter: { id: exactRegex(resolvedModulePreloadPolyfillId) }, handler(_id) { - // `isModernFlag` is only available during build since it is resolved by `vite:build-import-analysis` - if ( - config.command !== 'build' || - this.environment.config.consumer !== 'client' - ) { - return '' - } - if (!polyfillString) { - polyfillString = `${isModernFlag}&&(${polyfill.toString()}());` - } - return { code: polyfillString, moduleSideEffects: true } + // Should resolve to an empty module in dev + return '' }, }, } } - -/** -The following polyfill function is meant to run in the browser and adapted from -https://github.com/guybedford/es-module-shims -MIT License -Copyright (C) 2018-2021 Guy Bedford -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -*/ - -declare const document: any -declare const MutationObserver: any -declare const fetch: any - -function polyfill() { - const relList = document.createElement('link').relList - if (relList && relList.supports && relList.supports('modulepreload')) { - return - } - - for (const link of document.querySelectorAll('link[rel="modulepreload"]')) { - processPreload(link) - } - - new MutationObserver((mutations: any) => { - for (const mutation of mutations) { - if (mutation.type !== 'childList') { - continue - } - for (const node of mutation.addedNodes) { - if (node.tagName === 'LINK' && node.rel === 'modulepreload') - processPreload(node) - } - } - }).observe(document, { childList: true, subtree: true }) - - function getFetchOpts(link: any) { - const fetchOpts = {} as any - if (link.integrity) fetchOpts.integrity = link.integrity - if (link.referrerPolicy) fetchOpts.referrerPolicy = link.referrerPolicy - if (link.crossOrigin === 'use-credentials') - fetchOpts.credentials = 'include' - else if (link.crossOrigin === 'anonymous') fetchOpts.credentials = 'omit' - else fetchOpts.credentials = 'same-origin' - return fetchOpts - } - - function processPreload(link: any) { - if (link.ep) - // ep marker = processed - return - link.ep = true - // prepopulate the load record - const fetchOpts = getFetchOpts(link) - fetch(link.href, fetchOpts) - } -} diff --git a/packages/vite/src/node/plugins/wasm.ts b/packages/vite/src/node/plugins/wasm.ts index 7c0ec3e706fd7b..8fa123c920686a 100644 --- a/packages/vite/src/node/plugins/wasm.ts +++ b/packages/vite/src/node/plugins/wasm.ts @@ -1,8 +1,5 @@ import { exactRegex } from '@rolldown/pluginutils' -import { - viteWasmFallbackPlugin as nativeWasmFallbackPlugin, - viteWasmHelperPlugin as nativeWasmHelperPlugin, -} from 'rolldown/experimental' +import { viteWasmHelperPlugin as nativeWasmHelperPlugin } from 'rolldown/experimental' import type { Plugin } from '../plugin' import type { ResolvedConfig } from '..' import { fileToUrl } from './asset' @@ -54,7 +51,7 @@ const wasmHelper = async (opts = {}, url: string) => { const wasmHelperCode = wasmHelper.toString() export const wasmHelperPlugin = (config: ResolvedConfig): Plugin => { - if (config.isBundled && config.nativePluginEnabledLevel >= 1) { + if (config.isBundled) { return nativeWasmHelperPlugin({ decodedBase: config.decodedBase, }) @@ -87,25 +84,3 @@ export const wasmHelperPlugin = (config: ResolvedConfig): Plugin => { }, } } - -export const wasmFallbackPlugin = (config: ResolvedConfig): Plugin => { - if (config.nativePluginEnabledLevel >= 1) { - return nativeWasmFallbackPlugin() - } - - return { - name: 'vite:wasm-fallback', - - load: { - filter: { id: /\.wasm$/ }, - handler(_id) { - throw new Error( - '"ESM integration proposal for Wasm" is not supported currently. ' + - 'Use vite-plugin-wasm or other community plugins to handle this. ' + - 'Alternatively, you can use `.wasm?init` or `.wasm?url`. ' + - 'See https://vite.dev/guide/features.html#webassembly for more details.', - ) - }, - }, - } -}