Good')
- })
- const content = await page.waitForSelector('text=Good Html')
- expect(content).toBeTruthy()
- })
- })
-}
diff --git a/packages/playground/html/index.html b/packages/playground/html/index.html
deleted file mode 100644
index 7320ff2b097db0..00000000000000
--- a/packages/playground/html/index.html
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
diff --git a/packages/playground/html/invalid.html b/packages/playground/html/invalid.html
deleted file mode 100644
index 5b5cf429687466..00000000000000
--- a/packages/playground/html/invalid.html
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/packages/playground/html/nested/index.html b/packages/playground/html/nested/index.html
deleted file mode 100644
index 4fb855b783c890..00000000000000
--- a/packages/playground/html/nested/index.html
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
Nested
-
diff --git a/packages/playground/html/package.json b/packages/playground/html/package.json
deleted file mode 100644
index a101033f8d3470..00000000000000
--- a/packages/playground/html/package.json
+++ /dev/null
@@ -1,11 +0,0 @@
-{
- "name": "test-html",
- "private": true,
- "version": "0.0.0",
- "scripts": {
- "dev": "vite",
- "build": "vite build",
- "debug": "node --inspect-brk ../../vite/bin/vite",
- "preview": "vite preview"
- }
-}
diff --git "a/packages/playground/html/unicode-path/\344\270\255\346\226\207-\343\201\253\343\201\273\343\202\223\343\201\224-\355\225\234\352\270\200-\360\237\214\225\360\237\214\226\360\237\214\227/index.html" "b/packages/playground/html/unicode-path/\344\270\255\346\226\207-\343\201\253\343\201\273\343\202\223\343\201\224-\355\225\234\352\270\200-\360\237\214\225\360\237\214\226\360\237\214\227/index.html"
deleted file mode 100644
index f3c55befe1f315..00000000000000
--- "a/packages/playground/html/unicode-path/\344\270\255\346\226\207-\343\201\253\343\201\273\343\202\223\343\201\224-\355\225\234\352\270\200-\360\237\214\225\360\237\214\226\360\237\214\227/index.html"
+++ /dev/null
@@ -1 +0,0 @@
-
unicode-path
diff --git a/packages/playground/html/vite.config.js b/packages/playground/html/vite.config.js
deleted file mode 100644
index 1703e02cc05366..00000000000000
--- a/packages/playground/html/vite.config.js
+++ /dev/null
@@ -1,162 +0,0 @@
-const { resolve } = require('path')
-
-/**
- * @type {import('vite').UserConfig}
- */
-module.exports = {
- build: {
- rollupOptions: {
- input: {
- main: resolve(__dirname, 'index.html'),
- nested: resolve(__dirname, 'nested/index.html'),
- scriptAsync: resolve(__dirname, 'scriptAsync.html'),
- scriptMixed: resolve(__dirname, 'scriptMixed.html'),
- emptyAttr: resolve(__dirname, 'emptyAttr.html'),
- link: resolve(__dirname, 'link.html'),
- 'link/target': resolve(__dirname, 'index.html'),
- zeroJS: resolve(__dirname, 'zeroJS.html'),
- noHead: resolve(__dirname, 'noHead.html'),
- noBody: resolve(__dirname, 'noBody.html'),
- inline1: resolve(__dirname, 'inline/shared-1.html'),
- inline2: resolve(__dirname, 'inline/shared-2.html'),
- inline3: resolve(__dirname, 'inline/unique.html'),
- unicodePath: resolve(
- __dirname,
- 'unicode-path/中文-にほんご-한글-🌕🌖🌗/index.html'
- )
- }
- }
- },
-
- plugins: [
- {
- name: 'pre-transform',
- transformIndexHtml: {
- enforce: 'pre',
- transform(html, { filename }) {
- if (html.includes('/@vite/client')) {
- throw new Error('pre transform applied at wrong time!')
- }
- const head = `
-
-
-
-
{{ title }}
- `
- return `
-${filename.includes('noHead') ? '' : head}
-${
- filename.includes('noBody')
- ? html
- : `
- ${html}
-`
-}
-
- `
- }
- }
- },
- {
- name: 'string-transform',
- transformIndexHtml(html) {
- return html.replace('Hello', 'Transformed')
- }
- },
- {
- name: 'tags-transform',
- transformIndexHtml() {
- return [
- {
- tag: 'meta',
- attrs: { name: 'description', content: 'a vite app' }
- // default injection is head-prepend
- },
- {
- tag: 'meta',
- attrs: { name: 'keywords', content: 'es modules' },
- injectTo: 'head'
- }
- ]
- }
- },
- {
- name: 'combined-transform',
- transformIndexHtml(html) {
- return {
- html: html.replace('{{ title }}', 'Test HTML transforms'),
- tags: [
- {
- tag: 'p',
- attrs: { class: 'inject' },
- children: 'This is injected',
- injectTo: 'body'
- }
- ]
- }
- }
- },
- {
- name: 'serve-only-transform',
- transformIndexHtml(_, ctx) {
- if (ctx.server) {
- return [
- {
- tag: 'p',
- attrs: { class: 'server' },
- children: 'This is injected only during dev',
- injectTo: 'body'
- }
- ]
- }
- }
- },
- {
- name: 'build-only-transform',
- transformIndexHtml(_, ctx) {
- if (ctx.bundle) {
- return [
- {
- tag: 'p',
- attrs: { class: 'build' },
- children: 'This is injected only during build',
- injectTo: 'body'
- }
- ]
- }
- }
- },
- {
- name: 'path-conditional-transform',
- transformIndexHtml(_, ctx) {
- if (ctx.path.includes('nested')) {
- return [
- {
- tag: 'p',
- attrs: { class: 'conditional' },
- children: 'This is injected only for /nested/index.html',
- injectTo: 'body'
- }
- ]
- }
- }
- },
- {
- name: 'body-prepend-transform',
- transformIndexHtml() {
- return [
- {
- tag: 'noscript',
- children: '',
- injectTo: 'body'
- },
- {
- tag: 'noscript',
- children: '',
- injectTo: 'body-prepend'
- }
- ]
- }
- }
- ]
-}
diff --git a/packages/playground/json/__tests__/json.spec.ts b/packages/playground/json/__tests__/json.spec.ts
deleted file mode 100644
index 2897ee22332e44..00000000000000
--- a/packages/playground/json/__tests__/json.spec.ts
+++ /dev/null
@@ -1,46 +0,0 @@
-import { isBuild } from '../../testUtils'
-
-const json = require('../test.json')
-const deepJson = require('vue/package.json')
-const stringified = JSON.stringify(json)
-const deepStringified = JSON.stringify(deepJson)
-
-test('default import', async () => {
- expect(await page.textContent('.full')).toBe(stringified)
-})
-
-test('named import', async () => {
- expect(await page.textContent('.named')).toBe(json.hello)
-})
-
-test('deep import', async () => {
- expect(await page.textContent('.deep-full')).toBe(deepStringified)
-})
-
-test('named deep import', async () => {
- expect(await page.textContent('.deep-named')).toBe(deepJson.name)
-})
-
-test('dynamic import', async () => {
- expect(await page.textContent('.dynamic')).toBe(stringified)
-})
-
-test('dynamic import, named', async () => {
- expect(await page.textContent('.dynamic-named')).toBe(json.hello)
-})
-
-test('fetch', async () => {
- expect(await page.textContent('.fetch')).toBe(stringified)
-})
-
-test('?url', async () => {
- expect(await page.textContent('.url')).toMatch(
- isBuild ? 'data:application/json' : '/test.json'
- )
-})
-
-test('?raw', async () => {
- expect(await page.textContent('.raw')).toBe(
- require('fs').readFileSync(require.resolve('../test.json'), 'utf-8')
- )
-})
diff --git a/packages/playground/json/index.html b/packages/playground/json/index.html
deleted file mode 100644
index cf16636f91cb68..00000000000000
--- a/packages/playground/json/index.html
+++ /dev/null
@@ -1,58 +0,0 @@
-
Normal Import
-
-
-
-
Deep Import
-
-
-
-
Dynamic Import
-
-
-
-
fetch
-
-
-
Importing as URL
-
-
-
Raw Import
-
-
-
JSON Module
-
-
-
diff --git a/packages/playground/json/json-module/package.json b/packages/playground/json/json-module/package.json
deleted file mode 100644
index 17db87793a74e3..00000000000000
--- a/packages/playground/json/json-module/package.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "name": "json-module",
- "version": "0.0.0"
-}
diff --git a/packages/playground/json/package.json b/packages/playground/json/package.json
deleted file mode 100644
index 203846bab73b48..00000000000000
--- a/packages/playground/json/package.json
+++ /dev/null
@@ -1,15 +0,0 @@
-{
- "name": "test-json",
- "private": true,
- "version": "0.0.0",
- "scripts": {
- "dev": "vite",
- "build": "vite build",
- "debug": "node --inspect-brk ../../vite/bin/vite",
- "preview": "vite preview"
- },
- "devDependencies": {
- "vue": "^3.2.25",
- "json-module": "file:./json-module"
- }
-}
diff --git a/packages/playground/legacy/__tests__/legacy.spec.ts b/packages/playground/legacy/__tests__/legacy.spec.ts
deleted file mode 100644
index b8025694437502..00000000000000
--- a/packages/playground/legacy/__tests__/legacy.spec.ts
+++ /dev/null
@@ -1,86 +0,0 @@
-import {
- listAssets,
- findAssetFile,
- isBuild,
- readManifest,
- untilUpdated,
- getColor
-} from '../../testUtils'
-
-test('should work', async () => {
- expect(await page.textContent('#app')).toMatch('Hello')
-})
-
-test('import.meta.env.LEGACY', async () => {
- expect(await page.textContent('#env')).toMatch(isBuild ? 'true' : 'false')
-})
-
-// https://github.com/vitejs/vite/issues/3400
-test('transpiles down iterators correctly', async () => {
- expect(await page.textContent('#iterators')).toMatch('hello')
-})
-
-test('wraps with iife', async () => {
- expect(await page.textContent('#babel-helpers')).toMatch(
- 'exposed babel helpers: false'
- )
-})
-
-test('generates assets', async () => {
- await untilUpdated(
- () => page.textContent('#assets'),
- isBuild
- ? [
- 'index: 404',
- 'index-legacy: 404',
- 'chunk-async: 404',
- 'chunk-async-legacy: 404',
- 'immutable-chunk: 200',
- 'immutable-chunk-legacy: 200',
- 'polyfills-legacy: 404'
- ].join('\n')
- : [
- 'index: 404',
- 'index-legacy: 404',
- 'chunk-async: 404',
- 'chunk-async-legacy: 404',
- 'immutable-chunk: 404',
- 'immutable-chunk-legacy: 404',
- 'polyfills-legacy: 404'
- ].join('\n'),
- true
- )
-})
-
-test('correctly emits styles', async () => {
- expect(await getColor('#app')).toBe('red')
-})
-
-if (isBuild) {
- test('should generate correct manifest', async () => {
- const manifest = readManifest()
- expect(manifest['../../../vite/legacy-polyfills']).toBeDefined()
- expect(manifest['../../../vite/legacy-polyfills'].src).toBe(
- '../../../vite/legacy-polyfills'
- )
- })
-
- test('should minify legacy chunks with terser', async () => {
- // This is a ghetto heuristic, but terser output seems to reliably start
- // with one of the following, and non-terser output (including unminified or
- // ebuild-minified) does not!
- const terserPatt = /^(?:!function|System.register)/
-
- expect(findAssetFile(/chunk-async-legacy/)).toMatch(terserPatt)
- expect(findAssetFile(/chunk-async\./)).not.toMatch(terserPatt)
- expect(findAssetFile(/immutable-chunk-legacy/)).toMatch(terserPatt)
- expect(findAssetFile(/immutable-chunk\./)).not.toMatch(terserPatt)
- expect(findAssetFile(/index-legacy/)).toMatch(terserPatt)
- expect(findAssetFile(/index\./)).not.toMatch(terserPatt)
- expect(findAssetFile(/polyfills-legacy/)).toMatch(terserPatt)
- })
-
- test('should emit css file', async () => {
- expect(listAssets().some((filename) => filename.endsWith('.css')))
- })
-}
diff --git a/packages/playground/legacy/__tests__/ssr/legacy-ssr.spec.ts b/packages/playground/legacy/__tests__/ssr/legacy-ssr.spec.ts
deleted file mode 100644
index dad9b94d83509e..00000000000000
--- a/packages/playground/legacy/__tests__/ssr/legacy-ssr.spec.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-import { isBuild } from '../../../testUtils'
-import { port } from './serve'
-
-const url = `http://localhost:${port}`
-
-if (isBuild) {
- test('should work', async () => {
- await page.goto(url)
- expect(await page.textContent('#app')).toMatch('Hello')
- })
-
- test('import.meta.env.LEGACY', async () => {
- // SSR build is always modern
- expect(await page.textContent('#env')).toMatch('false')
- })
-} else {
- // this test doesn't support serve mode
- // must contain at least one test
- test('should work', () => void 0)
-}
diff --git a/packages/playground/legacy/__tests__/ssr/serve.js b/packages/playground/legacy/__tests__/ssr/serve.js
deleted file mode 100644
index df43f180afb188..00000000000000
--- a/packages/playground/legacy/__tests__/ssr/serve.js
+++ /dev/null
@@ -1,52 +0,0 @@
-// @ts-check
-// this is automtically detected by scripts/jestPerTestSetup.ts and will replace
-// the default e2e test serve behavior
-const path = require('path')
-
-const port = (exports.port = 9527)
-
-/**
- * @param {string} root
- * @param {boolean} _isProd
- */
-exports.serve = async function serve(root, _isProd) {
- const { build } = require('vite')
- await build({
- root,
- logLevel: 'silent',
- build: {
- target: 'esnext',
- ssr: 'entry-server.js',
- outDir: 'dist/server'
- }
- })
-
- const express = require('express')
- const app = express()
-
- app.use('/', async (_req, res) => {
- const { render } = require(path.resolve(
- root,
- './dist/server/entry-server.js'
- ))
- const html = await render()
- res.status(200).set({ 'Content-Type': 'text/html' }).end(html)
- })
-
- return new Promise((resolve, reject) => {
- try {
- const server = app.listen(port, () => {
- resolve({
- // for test teardown
- async close() {
- await new Promise((resolve) => {
- server.close(resolve)
- })
- }
- })
- })
- } catch (e) {
- reject(e)
- }
- })
-}
diff --git a/packages/playground/legacy/immutable-chunk.js b/packages/playground/legacy/immutable-chunk.js
deleted file mode 100644
index 4227b718b98309..00000000000000
--- a/packages/playground/legacy/immutable-chunk.js
+++ /dev/null
@@ -1,18 +0,0 @@
-const chunks = [
- 'index',
- 'index-legacy',
- 'chunk-async',
- 'chunk-async-legacy',
- 'immutable-chunk',
- 'immutable-chunk-legacy',
- 'polyfills-legacy'
-]
-
-export function fn() {
- return Promise.all(
- chunks.map(async (name) => {
- const response = await fetch(`/assets/${name}.js`)
- return `${name}: ${response.status}`
- })
- )
-}
diff --git a/packages/playground/legacy/index.html b/packages/playground/legacy/index.html
deleted file mode 100644
index bdc2feac6b4fbe..00000000000000
--- a/packages/playground/legacy/index.html
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
diff --git a/packages/playground/legacy/main.js b/packages/playground/legacy/main.js
deleted file mode 100644
index b05acf439bdff8..00000000000000
--- a/packages/playground/legacy/main.js
+++ /dev/null
@@ -1,41 +0,0 @@
-import './style.css'
-
-async function run() {
- const { fn } = await import('./async.js')
- fn()
-}
-
-run()
-
-let isLegacy
-
-// make sure that branching works despite esbuild's constant folding (#1999)
-if (import.meta.env.LEGACY) {
- if (import.meta.env.LEGACY === true) isLegacy = true
-} else {
- if (import.meta.env.LEGACY === false) isLegacy = false
-}
-
-text('#env', `is legacy: ${isLegacy}`)
-
-// Iterators
-text('#iterators', [...new Set(['hello'])].join(''))
-
-// babel-helpers
-// Using `String.raw` to inject `@babel/plugin-transform-template-literals`
-// helpers.
-text(
- '#babel-helpers',
- String.raw`exposed babel helpers: ${window._templateObject != null}`
-)
-
-// dynamic chunk names
-import('./immutable-chunk.js')
- .then(({ fn }) => fn())
- .then((assets) => {
- text('#assets', assets.join('\n'))
- })
-
-function text(el, text) {
- document.querySelector(el).textContent = text
-}
diff --git a/packages/playground/legacy/package.json b/packages/playground/legacy/package.json
deleted file mode 100644
index 3a3315c42aa832..00000000000000
--- a/packages/playground/legacy/package.json
+++ /dev/null
@@ -1,16 +0,0 @@
-{
- "name": "test-legacy",
- "private": true,
- "version": "0.0.0",
- "scripts": {
- "dev": "vite",
- "build": "vite build --debug legacy",
- "build:custom-filename": "vite --config ./vite.config-custom-filename.js build --debug legacy",
- "debug": "node --inspect-brk ../../vite/bin/vite",
- "preview": "vite preview"
- },
- "devDependencies": {
- "@vitejs/plugin-legacy": "workspace:*",
- "express": "^4.17.1"
- }
-}
diff --git a/packages/playground/legacy/vite.config-custom-filename.js b/packages/playground/legacy/vite.config-custom-filename.js
deleted file mode 100644
index 9a96133b015588..00000000000000
--- a/packages/playground/legacy/vite.config-custom-filename.js
+++ /dev/null
@@ -1,15 +0,0 @@
-const legacy = require('@vitejs/plugin-legacy').default
-
-module.exports = {
- plugins: [legacy()],
- build: {
- manifest: true,
- minify: false,
- rollupOptions: {
- output: {
- entryFileNames: `assets/[name].js`,
- chunkFileNames: `assets/[name].js`
- }
- }
- }
-}
diff --git a/packages/playground/legacy/vite.config.js b/packages/playground/legacy/vite.config.js
deleted file mode 100644
index 90d3be7f7c56a0..00000000000000
--- a/packages/playground/legacy/vite.config.js
+++ /dev/null
@@ -1,39 +0,0 @@
-const fs = require('fs')
-const path = require('path')
-const legacy = require('@vitejs/plugin-legacy').default
-
-module.exports = {
- plugins: [
- legacy({
- targets: 'IE 11'
- })
- ],
-
- build: {
- cssCodeSplit: false,
- manifest: true,
- rollupOptions: {
- output: {
- chunkFileNames(chunkInfo) {
- if (chunkInfo.name === 'immutable-chunk') {
- return `assets/${chunkInfo.name}.js`
- }
-
- return `assets/chunk-[name].[hash].js`
- }
- }
- }
- },
-
- // special test only hook
- // for tests, remove `
-
-
-
-
-
-
-
-
diff --git a/packages/playground/lib/package.json b/packages/playground/lib/package.json
deleted file mode 100644
index 2c3ae4be3d4bcb..00000000000000
--- a/packages/playground/lib/package.json
+++ /dev/null
@@ -1,11 +0,0 @@
-{
- "name": "@example/my-lib",
- "private": true,
- "version": "0.0.0",
- "scripts": {
- "dev": "vite",
- "build": "vite build",
- "debug": "node --inspect-brk ../../vite/bin/vite",
- "preview": "vite preview"
- }
-}
diff --git a/packages/playground/lib/src/main.js b/packages/playground/lib/src/main.js
deleted file mode 100644
index 2422edf5829a0e..00000000000000
--- a/packages/playground/lib/src/main.js
+++ /dev/null
@@ -1,3 +0,0 @@
-export default function myLib(sel) {
- document.querySelector(sel).textContent = 'It works'
-}
diff --git a/packages/playground/lib/src/main2.js b/packages/playground/lib/src/main2.js
deleted file mode 100644
index 0c729fad8d165c..00000000000000
--- a/packages/playground/lib/src/main2.js
+++ /dev/null
@@ -1,4 +0,0 @@
-export default async function message(sel) {
- const message = await import('./message.js')
- document.querySelector(sel).textContent = message.default
-}
diff --git a/packages/playground/lib/vite.config.js b/packages/playground/lib/vite.config.js
deleted file mode 100644
index 50cd188b1a40cc..00000000000000
--- a/packages/playground/lib/vite.config.js
+++ /dev/null
@@ -1,31 +0,0 @@
-const fs = require('fs')
-const path = require('path')
-
-/**
- * @type {import('vite').UserConfig}
- */
-module.exports = {
- build: {
- lib: {
- entry: path.resolve(__dirname, 'src/main.js'),
- name: 'MyLib',
- formats: ['es', 'umd', 'iife'],
- fileName: (format) => `my-lib-custom-filename.${format}.js`
- }
- },
- plugins: [
- {
- name: 'emit-index',
- generateBundle() {
- this.emitFile({
- type: 'asset',
- fileName: 'index.html',
- source: fs.readFileSync(
- path.resolve(__dirname, 'index.dist.html'),
- 'utf-8'
- )
- })
- }
- }
- ]
-}
diff --git a/packages/playground/lib/vite.dyimport.config.js b/packages/playground/lib/vite.dyimport.config.js
deleted file mode 100644
index 76311f4b8ba138..00000000000000
--- a/packages/playground/lib/vite.dyimport.config.js
+++ /dev/null
@@ -1,18 +0,0 @@
-const fs = require('fs')
-const path = require('path')
-
-/**
- * @type {import('vite').UserConfig}
- */
-module.exports = {
- build: {
- minify: false,
- lib: {
- entry: path.resolve(__dirname, 'src/main2.js'),
- formats: ['es'],
- name: 'message',
- fileName: () => 'dynamic-import-message.js'
- },
- outDir: 'dist/lib'
- }
-}
diff --git a/packages/playground/multiple-entrypoints/__tests__/multiple-entrypoints.spec.ts b/packages/playground/multiple-entrypoints/__tests__/multiple-entrypoints.spec.ts
deleted file mode 100644
index 56c0b46c8a3e6f..00000000000000
--- a/packages/playground/multiple-entrypoints/__tests__/multiple-entrypoints.spec.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-import { getColor, untilUpdated } from '../../testUtils'
-
-test('should have css applied on second dynamic import', async () => {
- await untilUpdated(() => page.textContent('.content'), 'Initial', true)
- await page.click('.b')
-
- await untilUpdated(() => page.textContent('.content'), 'Reference', true)
- expect(await getColor('.content')).toBe('red')
-})
diff --git a/packages/playground/multiple-entrypoints/package.json b/packages/playground/multiple-entrypoints/package.json
deleted file mode 100644
index 6c338a64518ddb..00000000000000
--- a/packages/playground/multiple-entrypoints/package.json
+++ /dev/null
@@ -1,15 +0,0 @@
-{
- "name": "multiple-entrypoints",
- "private": true,
- "version": "0.0.0",
- "scripts": {
- "dev": "vite",
- "build": "vite build",
- "debug": "node --inspect-brk ../../vite/bin/vite",
- "preview": "vite preview"
- },
- "devDependencies": {
- "fast-glob": "^3.2.11",
- "sass": "^1.43.4"
- }
-}
diff --git a/packages/playground/multiple-entrypoints/vite.config.js b/packages/playground/multiple-entrypoints/vite.config.js
deleted file mode 100644
index c2a44858a3ce6b..00000000000000
--- a/packages/playground/multiple-entrypoints/vite.config.js
+++ /dev/null
@@ -1,40 +0,0 @@
-const { resolve } = require('path')
-const fs = require('fs')
-
-module.exports = {
- build: {
- outDir: './dist',
- emptyOutDir: true,
- rollupOptions: {
- preserveEntrySignatures: 'strict',
- input: {
- a0: resolve(__dirname, 'entrypoints/a0.js'),
- a1: resolve(__dirname, 'entrypoints/a1.js'),
- a2: resolve(__dirname, 'entrypoints/a2.js'),
- a3: resolve(__dirname, 'entrypoints/a3.js'),
- a4: resolve(__dirname, 'entrypoints/a4.js'),
- a5: resolve(__dirname, 'entrypoints/a5.js'),
- a6: resolve(__dirname, 'entrypoints/a6.js'),
- a7: resolve(__dirname, 'entrypoints/a7.js'),
- a8: resolve(__dirname, 'entrypoints/a8.js'),
- a9: resolve(__dirname, 'entrypoints/a9.js'),
- a10: resolve(__dirname, 'entrypoints/a10.js'),
- a11: resolve(__dirname, 'entrypoints/a11.js'),
- a12: resolve(__dirname, 'entrypoints/a12.js'),
- a13: resolve(__dirname, 'entrypoints/a13.js'),
- a14: resolve(__dirname, 'entrypoints/a14.js'),
- a15: resolve(__dirname, 'entrypoints/a15.js'),
- a16: resolve(__dirname, 'entrypoints/a16.js'),
- a17: resolve(__dirname, 'entrypoints/a17.js'),
- a18: resolve(__dirname, 'entrypoints/a18.js'),
- a19: resolve(__dirname, 'entrypoints/a19.js'),
- a20: resolve(__dirname, 'entrypoints/a20.js'),
- a21: resolve(__dirname, 'entrypoints/a21.js'),
- a22: resolve(__dirname, 'entrypoints/a22.js'),
- a23: resolve(__dirname, 'entrypoints/a23.js'),
- a24: resolve(__dirname, 'entrypoints/a24.js'),
- index: resolve(__dirname, './index.html')
- }
- }
- }
-}
diff --git a/packages/playground/nested-deps/__tests__/nested-deps.spec.ts b/packages/playground/nested-deps/__tests__/nested-deps.spec.ts
deleted file mode 100644
index 2ef0e191da7b50..00000000000000
--- a/packages/playground/nested-deps/__tests__/nested-deps.spec.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-test('handle nested package', async () => {
- expect(await page.textContent('.a')).toBe('A@2.0.0')
- expect(await page.textContent('.b')).toBe('B@1.0.0')
- expect(await page.textContent('.nested-a')).toBe('A@1.0.0')
- const c = await page.textContent('.c')
- expect(c).toBe('es-C@1.0.0')
- expect(await page.textContent('.side-c')).toBe(c)
- expect(await page.textContent('.d')).toBe('D@1.0.0')
- expect(await page.textContent('.nested-d')).toBe('D-nested@1.0.0')
- expect(await page.textContent('.nested-e')).toBe('1')
-})
diff --git a/packages/playground/nested-deps/index.html b/packages/playground/nested-deps/index.html
deleted file mode 100644
index 3243c1689bf0cd..00000000000000
--- a/packages/playground/nested-deps/index.html
+++ /dev/null
@@ -1,48 +0,0 @@
-
direct dependency A
-
-
-
direct dependency B
-
-
-
nested dependency A
-
-
-
direct dependency C
-
-
-
side dependency C
-
-
-
direct dependency D
-
-
-
nested dependency nested-D (dep of D)
-
-
-
exclude dependency of pre-bundled dependency
-
nested module instance count:
-
-
diff --git a/packages/playground/nested-deps/package.json b/packages/playground/nested-deps/package.json
deleted file mode 100644
index d7450d0545fcb4..00000000000000
--- a/packages/playground/nested-deps/package.json
+++ /dev/null
@@ -1,18 +0,0 @@
-{
- "name": "@test/nested-deps",
- "private": true,
- "version": "0.0.0",
- "scripts": {
- "dev": "vite",
- "build": "vite build",
- "debug": "node --inspect-brk ../../vite/bin/vite",
- "preview": "vite preview"
- },
- "dependencies": {
- "test-package-a": "link:./test-package-a",
- "test-package-b": "link:./test-package-b",
- "test-package-c": "link:./test-package-c",
- "test-package-d": "link:./test-package-d",
- "test-package-e": "link:./test-package-e"
- }
-}
diff --git a/packages/playground/nested-deps/test-package-a/package.json b/packages/playground/nested-deps/test-package-a/package.json
deleted file mode 100644
index 688fab78bab766..00000000000000
--- a/packages/playground/nested-deps/test-package-a/package.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "name": "test-package-a",
- "private": true,
- "version": "2.0.0",
- "main": "index.js"
-}
diff --git a/packages/playground/nested-deps/test-package-b/package.json b/packages/playground/nested-deps/test-package-b/package.json
deleted file mode 100644
index 6e32e8eba153a1..00000000000000
--- a/packages/playground/nested-deps/test-package-b/package.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "name": "test-package-b",
- "private": true,
- "version": "1.0.0",
- "main": "index.js"
-}
diff --git a/packages/playground/nested-deps/test-package-c/package.json b/packages/playground/nested-deps/test-package-c/package.json
deleted file mode 100644
index 47672d07b3881f..00000000000000
--- a/packages/playground/nested-deps/test-package-c/package.json
+++ /dev/null
@@ -1,7 +0,0 @@
-{
- "name": "test-package-c",
- "private": true,
- "version": "1.0.0",
- "main": "index.js",
- "module": "index-es.js"
-}
diff --git a/packages/playground/nested-deps/test-package-c/side.js b/packages/playground/nested-deps/test-package-c/side.js
deleted file mode 100644
index 4d46da7c26e02e..00000000000000
--- a/packages/playground/nested-deps/test-package-c/side.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default as C } from 'test-package-c'
diff --git a/packages/playground/nested-deps/test-package-d/index.js b/packages/playground/nested-deps/test-package-d/index.js
deleted file mode 100644
index f5b35d06fd5001..00000000000000
--- a/packages/playground/nested-deps/test-package-d/index.js
+++ /dev/null
@@ -1,3 +0,0 @@
-export { default as nestedD } from 'test-package-d-nested'
-
-export default 'D@1.0.0'
diff --git a/packages/playground/nested-deps/test-package-d/package.json b/packages/playground/nested-deps/test-package-d/package.json
deleted file mode 100644
index e9f522c2c5ddf6..00000000000000
--- a/packages/playground/nested-deps/test-package-d/package.json
+++ /dev/null
@@ -1,9 +0,0 @@
-{
- "name": "test-package-d",
- "private": true,
- "version": "1.0.0",
- "main": "index.js",
- "dependencies": {
- "test-package-d-nested": "link:./test-package-d-nested"
- }
-}
diff --git a/packages/playground/nested-deps/test-package-d/test-package-d-nested/package.json b/packages/playground/nested-deps/test-package-d/test-package-d-nested/package.json
deleted file mode 100644
index 50f1123b6f7ff7..00000000000000
--- a/packages/playground/nested-deps/test-package-d/test-package-d-nested/package.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "name": "test-package-d-nested",
- "private": true,
- "version": "1.0.0",
- "main": "index.js"
-}
diff --git a/packages/playground/nested-deps/test-package-e/index.js b/packages/playground/nested-deps/test-package-e/index.js
deleted file mode 100644
index 3d9c38b0ab3886..00000000000000
--- a/packages/playground/nested-deps/test-package-e/index.js
+++ /dev/null
@@ -1,2 +0,0 @@
-export { testIncluded } from 'test-package-e-included'
-export { testExcluded } from 'test-package-e-excluded'
diff --git a/packages/playground/nested-deps/test-package-e/package.json b/packages/playground/nested-deps/test-package-e/package.json
deleted file mode 100644
index 45779d1a7676ad..00000000000000
--- a/packages/playground/nested-deps/test-package-e/package.json
+++ /dev/null
@@ -1,10 +0,0 @@
-{
- "name": "test-package-e",
- "private": true,
- "version": "0.1.0",
- "main": "index.js",
- "dependencies": {
- "test-package-e-excluded": "link:./test-package-e-excluded",
- "test-package-e-included": "link:./test-package-e-included"
- }
-}
diff --git a/packages/playground/nested-deps/test-package-e/test-package-e-excluded/package.json b/packages/playground/nested-deps/test-package-e/test-package-e-excluded/package.json
deleted file mode 100644
index 8722324da53499..00000000000000
--- a/packages/playground/nested-deps/test-package-e/test-package-e-excluded/package.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "name": "test-package-e-excluded",
- "private": true,
- "version": "0.1.0",
- "main": "index.js"
-}
diff --git a/packages/playground/nested-deps/test-package-e/test-package-e-included/index.js b/packages/playground/nested-deps/test-package-e/test-package-e-included/index.js
deleted file mode 100644
index 23239f157bfa38..00000000000000
--- a/packages/playground/nested-deps/test-package-e/test-package-e-included/index.js
+++ /dev/null
@@ -1,5 +0,0 @@
-import { testExcluded } from 'test-package-e-excluded'
-
-export function testIncluded() {
- return testExcluded()
-}
diff --git a/packages/playground/nested-deps/test-package-e/test-package-e-included/package.json b/packages/playground/nested-deps/test-package-e/test-package-e-included/package.json
deleted file mode 100644
index 37198ee7d6a7c7..00000000000000
--- a/packages/playground/nested-deps/test-package-e/test-package-e-included/package.json
+++ /dev/null
@@ -1,9 +0,0 @@
-{
- "name": "test-package-e-included",
- "private": true,
- "version": "0.1.0",
- "main": "index.js",
- "dependencies": {
- "test-package-e-excluded": "link:../test-package-e-excluded"
- }
-}
diff --git a/packages/playground/nested-deps/vite.config.js b/packages/playground/nested-deps/vite.config.js
deleted file mode 100644
index 015598af64b016..00000000000000
--- a/packages/playground/nested-deps/vite.config.js
+++ /dev/null
@@ -1,16 +0,0 @@
-/**
- * @type {import('vite').UserConfig}
- */
-module.exports = {
- optimizeDeps: {
- include: [
- 'test-package-a',
- 'test-package-b',
- 'test-package-c',
- 'test-package-c/side',
- 'test-package-d > test-package-d-nested',
- 'test-package-e-included'
- ],
- exclude: ['test-package-d', 'test-package-e-excluded']
- }
-}
diff --git a/packages/playground/optimize-deps/__tests__/optimize-deps.spec.ts b/packages/playground/optimize-deps/__tests__/optimize-deps.spec.ts
deleted file mode 100644
index d95a6d984cd9aa..00000000000000
--- a/packages/playground/optimize-deps/__tests__/optimize-deps.spec.ts
+++ /dev/null
@@ -1,107 +0,0 @@
-import { getColor, isBuild } from '../../testUtils'
-
-test('default + named imports from cjs dep (react)', async () => {
- expect(await page.textContent('.cjs button')).toBe('count is 0')
- await page.click('.cjs button')
- expect(await page.textContent('.cjs button')).toBe('count is 1')
-})
-
-test('named imports from webpacked cjs (phoenix)', async () => {
- expect(await page.textContent('.cjs-phoenix')).toBe('ok')
-})
-
-test('default import from webpacked cjs (clipboard)', async () => {
- expect(await page.textContent('.cjs-clipboard')).toBe('ok')
-})
-
-test('dynamic imports from cjs dep (react)', async () => {
- expect(await page.textContent('.cjs-dynamic button')).toBe('count is 0')
- await page.click('.cjs-dynamic button')
- expect(await page.textContent('.cjs-dynamic button')).toBe('count is 1')
-})
-
-test('dynamic named imports from webpacked cjs (phoenix)', async () => {
- expect(await page.textContent('.cjs-dynamic-phoenix')).toBe('ok')
-})
-
-test('dynamic default import from webpacked cjs (clipboard)', async () => {
- expect(await page.textContent('.cjs-dynamic-clipboard')).toBe('ok')
-})
-
-test('dynamic default import from cjs (cjs-dynamic-dep-cjs-compiled-from-esm)', async () => {
- expect(await page.textContent('.cjs-dynamic-dep-cjs-compiled-from-esm')).toBe(
- 'ok'
- )
-})
-
-test('dynamic default import from cjs (cjs-dynamic-dep-cjs-compiled-from-cjs)', async () => {
- expect(await page.textContent('.cjs-dynamic-dep-cjs-compiled-from-cjs')).toBe(
- 'ok'
- )
-})
-
-test('dedupe', async () => {
- expect(await page.textContent('.dedupe button')).toBe('count is 0')
- await page.click('.dedupe button')
- expect(await page.textContent('.dedupe button')).toBe('count is 1')
-})
-
-test('cjs browser field (axios)', async () => {
- expect(await page.textContent('.cjs-browser-field')).toBe('pong')
-})
-
-test('dep from linked dep (lodash-es)', async () => {
- expect(await page.textContent('.deps-linked')).toBe('fooBarBaz')
-})
-
-test('forced include', async () => {
- expect(await page.textContent('.force-include')).toMatch(`[success]`)
-})
-
-test('import * from optimized dep', async () => {
- expect(await page.textContent('.import-star')).toMatch(`[success]`)
-})
-
-test('import from dep with .notjs files', async () => {
- expect(await page.textContent('.not-js')).toMatch(`[success]`)
-})
-
-test('dep with dynamic import', async () => {
- expect(await page.textContent('.dep-with-dynamic-import')).toMatch(
- `[success]`
- )
-})
-
-test('dep with css import', async () => {
- expect(await getColor('h1')).toBe('red')
-})
-
-test('dep w/ non-js files handled via plugin', async () => {
- expect(await page.textContent('.plugin')).toMatch(`[success]`)
-})
-
-test('vue + vuex', async () => {
- expect(await page.textContent('.vue')).toMatch(`[success]`)
-})
-
-test('esbuild-plugin', async () => {
- expect(await page.textContent('.esbuild-plugin')).toMatch(
- isBuild ? `Hello from a package` : `Hello from an esbuild plugin`
- )
-})
-
-test('import from hidden dir', async () => {
- expect(await page.textContent('.hidden-dir')).toBe('hello!')
-})
-
-test('import optimize-excluded package that imports optimized-included package', async () => {
- expect(await page.textContent('.nested-include')).toBe('nested-include')
-})
-
-test('import aliased package with colon', async () => {
- expect(await page.textContent('.url')).toBe('vitejs.dev')
-})
-
-test('variable names are reused in different scripts', async () => {
- expect(await page.textContent('.reused-variable-names')).toBe('reused')
-})
diff --git a/packages/playground/optimize-deps/cjs-dynamic.js b/packages/playground/optimize-deps/cjs-dynamic.js
deleted file mode 100644
index 91dc5a964d5481..00000000000000
--- a/packages/playground/optimize-deps/cjs-dynamic.js
+++ /dev/null
@@ -1,53 +0,0 @@
-// test dynamic import to cjs deps
-// mostly ensuring consistency between dev server behavior and build behavior
-// of @rollup/plugin-commonjs
-;(async () => {
- const { useState } = await import('react')
- const React = (await import('react')).default
- const ReactDOM = await import('react-dom')
-
- const clip = await import('clipboard')
- if (typeof clip.default === 'function') {
- text('.cjs-dynamic-clipboard', 'ok')
- }
-
- const { Socket } = await import('phoenix')
- if (typeof Socket === 'function') {
- text('.cjs-dynamic-phoenix', 'ok')
- }
-
- const cjsFromESM = await import('dep-cjs-compiled-from-esm')
- console.log('cjsFromESM', cjsFromESM)
- if (typeof cjsFromESM.default === 'function') {
- text('.cjs-dynamic-dep-cjs-compiled-from-esm', 'ok')
- }
-
- const cjsFromCJS = await import('dep-cjs-compiled-from-cjs')
- console.log('cjsFromCJS', cjsFromCJS)
- if (typeof cjsFromCJS.default === 'function') {
- text('.cjs-dynamic-dep-cjs-compiled-from-cjs', 'ok')
- }
-
- function App() {
- const [count, setCount] = useState(0)
-
- return React.createElement(
- 'button',
- {
- onClick() {
- setCount(count + 1)
- }
- },
- `count is ${count}`
- )
- }
-
- ReactDOM.render(
- React.createElement(App),
- document.querySelector('.cjs-dynamic')
- )
-
- function text(el, text) {
- document.querySelector(el).textContent = text
- }
-})()
diff --git a/packages/playground/optimize-deps/cjs.js b/packages/playground/optimize-deps/cjs.js
deleted file mode 100644
index 9dc613c4b3ba71..00000000000000
--- a/packages/playground/optimize-deps/cjs.js
+++ /dev/null
@@ -1,35 +0,0 @@
-// test importing both default and named exports from a CommonJS module
-// React is the ultimate test of this because its dynamic exports assignments
-// are not statically detectable by @rollup/plugin-commonjs.
-import React, { useState } from 'react'
-import ReactDOM from 'react-dom'
-import { Socket } from 'phoenix'
-import clip from 'clipboard'
-
-if (typeof clip === 'function') {
- text('.cjs-clipboard', 'ok')
-}
-
-if (typeof Socket === 'function') {
- text('.cjs-phoenix', 'ok')
-}
-
-function App() {
- const [count, setCount] = useState(0)
-
- return React.createElement(
- 'button',
- {
- onClick() {
- setCount(count + 1)
- }
- },
- `count is ${count}`
- )
-}
-
-ReactDOM.render(React.createElement(App), document.querySelector('.cjs'))
-
-function text(el, text) {
- document.querySelector(el).textContent = text
-}
diff --git a/packages/playground/optimize-deps/dedupe.js b/packages/playground/optimize-deps/dedupe.js
deleted file mode 100644
index d04726330a6138..00000000000000
--- a/packages/playground/optimize-deps/dedupe.js
+++ /dev/null
@@ -1,23 +0,0 @@
-import React from 'react'
-import ReactDOM from 'react-dom'
-
-// #1302: The linked package has a different version of React in its deps
-// and is itself optimized. Without `dedupe`, the linked package is optimized
-// with a separate copy of React included, and results in runtime errors.
-import { useCount } from 'dep-linked-include/index.mjs'
-
-function App() {
- const [count, setCount] = useCount()
-
- return React.createElement(
- 'button',
- {
- onClick() {
- setCount(count + 1)
- }
- },
- `count is ${count}`
- )
-}
-
-ReactDOM.render(React.createElement(App), document.querySelector('.dedupe'))
diff --git a/packages/playground/optimize-deps/dep-cjs-compiled-from-cjs/index.js b/packages/playground/optimize-deps/dep-cjs-compiled-from-cjs/index.js
deleted file mode 100644
index 38d4c85c7c33ce..00000000000000
--- a/packages/playground/optimize-deps/dep-cjs-compiled-from-cjs/index.js
+++ /dev/null
@@ -1,5 +0,0 @@
-'use strict'
-function foo() {
- return 'foo'
-}
-module.exports = foo
diff --git a/packages/playground/optimize-deps/dep-cjs-compiled-from-cjs/package.json b/packages/playground/optimize-deps/dep-cjs-compiled-from-cjs/package.json
deleted file mode 100644
index 8fbd661730eafd..00000000000000
--- a/packages/playground/optimize-deps/dep-cjs-compiled-from-cjs/package.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "name": "dep-cjs-compiled-from-cjs",
- "private": true,
- "version": "0.0.0",
- "main": "index.js"
-}
diff --git a/packages/playground/optimize-deps/dep-cjs-compiled-from-esm/package.json b/packages/playground/optimize-deps/dep-cjs-compiled-from-esm/package.json
deleted file mode 100644
index 27ba12a1ec6f7b..00000000000000
--- a/packages/playground/optimize-deps/dep-cjs-compiled-from-esm/package.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "name": "dep-cjs-compiled-from-esm",
- "private": true,
- "version": "0.0.0",
- "main": "index.js"
-}
diff --git a/packages/playground/optimize-deps/dep-esbuild-plugin-transform/package.json b/packages/playground/optimize-deps/dep-esbuild-plugin-transform/package.json
deleted file mode 100644
index 4adb0e7032ae20..00000000000000
--- a/packages/playground/optimize-deps/dep-esbuild-plugin-transform/package.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "name": "dep-esbuild-plugin-transform",
- "private": true,
- "version": "0.0.0",
- "main": "index.js"
-}
diff --git a/packages/playground/optimize-deps/dep-linked-include/index.mjs b/packages/playground/optimize-deps/dep-linked-include/index.mjs
deleted file mode 100644
index 81c43abc0387ac..00000000000000
--- a/packages/playground/optimize-deps/dep-linked-include/index.mjs
+++ /dev/null
@@ -1,21 +0,0 @@
-export { msg } from './foo.js'
-
-import { useState } from 'react'
-
-export function useCount() {
- return useState(0)
-}
-
-// test dep with css/asset imports
-import './test.css'
-
-// test importing node built-ins
-import fs from 'fs'
-
-if (false) {
- fs.readFileSync()
-} else {
- console.log('ok')
-}
-
-export { default as VueSFC } from './Test.vue'
diff --git a/packages/playground/optimize-deps/dep-linked-include/package.json b/packages/playground/optimize-deps/dep-linked-include/package.json
deleted file mode 100644
index 0cfabdb9a2f2b9..00000000000000
--- a/packages/playground/optimize-deps/dep-linked-include/package.json
+++ /dev/null
@@ -1,9 +0,0 @@
-{
- "name": "dep-linked-include",
- "private": true,
- "version": "0.0.0",
- "main": "index.mjs",
- "dependencies": {
- "react": "17.0.2"
- }
-}
diff --git a/packages/playground/optimize-deps/dep-linked-include/test.css b/packages/playground/optimize-deps/dep-linked-include/test.css
deleted file mode 100644
index 60f1eab97137f7..00000000000000
--- a/packages/playground/optimize-deps/dep-linked-include/test.css
+++ /dev/null
@@ -1,3 +0,0 @@
-body {
- color: red;
-}
diff --git a/packages/playground/optimize-deps/dep-linked/package.json b/packages/playground/optimize-deps/dep-linked/package.json
deleted file mode 100644
index 915340f10ae4a0..00000000000000
--- a/packages/playground/optimize-deps/dep-linked/package.json
+++ /dev/null
@@ -1,9 +0,0 @@
-{
- "name": "dep-linked",
- "private": true,
- "version": "0.0.0",
- "main": "index.js",
- "dependencies": {
- "lodash-es": "^4.17.21"
- }
-}
diff --git a/packages/playground/optimize-deps/dep-not-js/package.json b/packages/playground/optimize-deps/dep-not-js/package.json
deleted file mode 100644
index 39ebafb6217b6e..00000000000000
--- a/packages/playground/optimize-deps/dep-not-js/package.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "name": "dep-not-js",
- "private": true,
- "version": "1.0.0",
- "main": "index.notjs"
-}
diff --git a/packages/playground/optimize-deps/dep-with-dynamic-import/package.json b/packages/playground/optimize-deps/dep-with-dynamic-import/package.json
deleted file mode 100644
index 81c5d2dda6a62a..00000000000000
--- a/packages/playground/optimize-deps/dep-with-dynamic-import/package.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "name": "dep-with-dynamic-import",
- "private": true,
- "version": "0.0.0",
- "main": "index.js"
-}
diff --git a/packages/playground/optimize-deps/glob/foo.js b/packages/playground/optimize-deps/glob/foo.js
deleted file mode 100644
index 7f2da6bfb1c276..00000000000000
--- a/packages/playground/optimize-deps/glob/foo.js
+++ /dev/null
@@ -1,5 +0,0 @@
-import axios from 'axios'
-
-axios.get('/ping').then((res) => {
- document.querySelector('.cjs-browser-field').textContent = res.data
-})
diff --git a/packages/playground/optimize-deps/index.html b/packages/playground/optimize-deps/index.html
deleted file mode 100644
index 2be896d00acba9..00000000000000
--- a/packages/playground/optimize-deps/index.html
+++ /dev/null
@@ -1,124 +0,0 @@
-
Optimize Deps
-
-
CommonJS w/ named imports (react)
-
-
CommonJS w/ named imports (phoenix)
-
fail
-
CommonJS w/ default export (clipboard)
-
fail
-
-
-
-
CommonJS dynamic import default + named (react)
-
-
CommonJS dynamic import named (phoenix)
-
-
CommonJS dynamic import default (clipboard)
-
-
CommonJS dynamic import default (dep-cjs-compiled-from-esm)
-
-
CommonJS dynamic import default (dep-cjs-compiled-from-cjs)
-
-
-
-
-
Dedupe (dep in linked & optimized package)
-
-
-
-
CommonJS w/ browser field mapping (axios)
-
This should show pong:
-
-
Detecting linked src package and optimizing its deps (lodash-es)
-
This should show fooBarBaz:
-
-
Optimizing force included dep even when it's linked
-
-
-
import * as ...
-
-
-
Import from dependency with .notjs files
-
-
-
Import from dependency with dynamic import
-
-
-
Dep w/ special file format supported via plugins
-
-
-
Vue & Vuex
-
-
-
Dep with changes from esbuild plugin
-
This should show a greeting:
-
-
Dep from hidden dir
-
This should show hello!:
-
-
Nested include
-
Module path:
-
-
Alias with colon
-
URL:
-
-
Reused variable names
-
This should show reused:
-
-
-
-
-
-
-
diff --git a/packages/playground/optimize-deps/nested-exclude/index.js b/packages/playground/optimize-deps/nested-exclude/index.js
deleted file mode 100644
index 3910458ef4d26d..00000000000000
--- a/packages/playground/optimize-deps/nested-exclude/index.js
+++ /dev/null
@@ -1,3 +0,0 @@
-export { default as nestedInclude } from 'nested-include'
-
-export default 'nested-exclude'
diff --git a/packages/playground/optimize-deps/nested-exclude/nested-include/package.json b/packages/playground/optimize-deps/nested-exclude/nested-include/package.json
deleted file mode 100644
index 581ef4dada69ce..00000000000000
--- a/packages/playground/optimize-deps/nested-exclude/nested-include/package.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "name": "nested-include",
- "private": true,
- "version": "1.0.0",
- "main": "index.js"
-}
diff --git a/packages/playground/optimize-deps/nested-exclude/package.json b/packages/playground/optimize-deps/nested-exclude/package.json
deleted file mode 100644
index 57dfc20ea1f801..00000000000000
--- a/packages/playground/optimize-deps/nested-exclude/package.json
+++ /dev/null
@@ -1,9 +0,0 @@
-{
- "name": "nested-exclude",
- "private": true,
- "version": "1.0.0",
- "main": "index.js",
- "dependencies": {
- "nested-include": "link:./nested-include"
- }
-}
diff --git a/packages/playground/optimize-deps/package.json b/packages/playground/optimize-deps/package.json
deleted file mode 100644
index 2752e691da6fb2..00000000000000
--- a/packages/playground/optimize-deps/package.json
+++ /dev/null
@@ -1,35 +0,0 @@
-{
- "name": "test-optimize-deps",
- "private": true,
- "version": "0.0.0",
- "scripts": {
- "dev": "vite",
- "build": "vite build",
- "debug": "node --inspect-brk ../../vite/bin/vite",
- "preview": "vite preview",
- "postinstall": "ts-node ../../../scripts/patchFileDeps.ts"
- },
- "dependencies": {
- "axios": "^0.24.0",
- "clipboard": "^2.0.8",
- "dep-cjs-compiled-from-cjs": "file:./dep-cjs-compiled-from-cjs",
- "dep-cjs-compiled-from-esm": "file:./dep-cjs-compiled-from-esm",
- "dep-esbuild-plugin-transform": "file:./dep-esbuild-plugin-transform",
- "dep-linked": "link:./dep-linked",
- "dep-linked-include": "link:./dep-linked-include",
- "dep-not-js": "file:./dep-not-js",
- "dep-with-dynamic-import": "file:./dep-with-dynamic-import",
- "lodash-es": "^4.17.21",
- "nested-exclude": "file:./nested-exclude",
- "phoenix": "^1.6.2",
- "react": "^17.0.2",
- "react-dom": "^17.0.2",
- "resolve-linked": "workspace:0.0.0",
- "url": "^0.11.0",
- "vue": "^3.2.25",
- "vuex": "^4.0.0"
- },
- "devDependencies": {
- "@vitejs/plugin-vue": "workspace:*"
- }
-}
diff --git a/packages/playground/optimize-deps/vite.config.js b/packages/playground/optimize-deps/vite.config.js
deleted file mode 100644
index a989cf1961de11..00000000000000
--- a/packages/playground/optimize-deps/vite.config.js
+++ /dev/null
@@ -1,91 +0,0 @@
-const fs = require('fs')
-const vue = require('@vitejs/plugin-vue')
-
-/**
- * @type {import('vite').UserConfig}
- */
-module.exports = {
- resolve: {
- dedupe: ['react'],
- alias: {
- 'node:url': 'url'
- }
- },
-
- optimizeDeps: {
- include: ['dep-linked-include', 'nested-exclude > nested-include'],
- exclude: ['nested-exclude'],
- esbuildOptions: {
- plugins: [
- {
- name: 'replace-a-file',
- setup(build) {
- build.onLoad(
- { filter: /dep-esbuild-plugin-transform(\\|\/)index\.js$/ },
- () => ({
- contents: `export const hello = () => 'Hello from an esbuild plugin'`,
- loader: 'js'
- })
- )
- }
- }
- ]
- }
- },
-
- build: {
- // to make tests faster
- minify: false
- },
-
- plugins: [
- vue(),
- notjs(),
- // for axios request test
- {
- name: 'mock',
- configureServer({ middlewares }) {
- middlewares.use('/ping', (_, res) => {
- res.statusCode = 200
- res.end('pong')
- })
- }
- }
- ]
-}
-
-// Handles .notjs file, basically remove wrapping
and tags
-function notjs() {
- return {
- name: 'notjs',
- config() {
- return {
- optimizeDeps: {
- extensions: ['.notjs'],
- esbuildOptions: {
- plugins: [
- {
- name: 'esbuild-notjs',
- setup(build) {
- build.onLoad({ filter: /\.notjs$/ }, ({ path }) => {
- let contents = fs.readFileSync(path, 'utf-8')
- contents = contents
- .replace('
', '')
- .replace(' ', '')
- return { contents, loader: 'js' }
- })
- }
- }
- ]
- }
- }
- }
- },
- transform(code, id) {
- if (id.endsWith('.notjs')) {
- code = code.replace('
', '').replace(' ', '')
- return { code }
- }
- }
- }
-}
diff --git a/packages/playground/optimize-missing-deps/__test__/optimize-missing-deps.spec.ts b/packages/playground/optimize-missing-deps/__test__/optimize-missing-deps.spec.ts
deleted file mode 100644
index dd776daeceadbf..00000000000000
--- a/packages/playground/optimize-missing-deps/__test__/optimize-missing-deps.spec.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-import { port } from './serve'
-import fetch from 'node-fetch'
-import { untilUpdated } from '../../testUtils'
-
-const url = `http://localhost:${port}`
-
-test('*', async () => {
- await page.goto(url)
- // reload page to get optimized missing deps
- await page.reload()
- await untilUpdated(() => page.textContent('div'), 'Client')
-
- // raw http request
- const aboutHtml = await (await fetch(url)).text()
- expect(aboutHtml).toMatch('Server')
-})
diff --git a/packages/playground/optimize-missing-deps/__test__/serve.js b/packages/playground/optimize-missing-deps/__test__/serve.js
deleted file mode 100644
index 9f293024f83913..00000000000000
--- a/packages/playground/optimize-missing-deps/__test__/serve.js
+++ /dev/null
@@ -1,36 +0,0 @@
-// @ts-check
-// this is automtically detected by scripts/jestPerTestSetup.ts and will replace
-// the default e2e test serve behavior
-
-const path = require('path')
-
-const port = (exports.port = 9529)
-
-/**
- * @param {string} root
- * @param {boolean} isProd
- */
-exports.serve = async function serve(root, isProd) {
- const { createServer } = require(path.resolve(root, 'server.js'))
- const { app, vite } = await createServer(root, isProd)
-
- return new Promise((resolve, reject) => {
- try {
- const server = app.listen(port, () => {
- resolve({
- // for test teardown
- async close() {
- await new Promise((resolve) => {
- server.close(resolve)
- })
- if (vite) {
- await vite.close()
- }
- }
- })
- })
- } catch (e) {
- reject(e)
- }
- })
-}
diff --git a/packages/playground/optimize-missing-deps/index.html b/packages/playground/optimize-missing-deps/index.html
deleted file mode 100644
index 13e9831870aa18..00000000000000
--- a/packages/playground/optimize-missing-deps/index.html
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
-
-
-
Vite App
-
-
-
-
-
diff --git a/packages/playground/optimize-missing-deps/main.js b/packages/playground/optimize-missing-deps/main.js
deleted file mode 100644
index 93f3e1221298bf..00000000000000
--- a/packages/playground/optimize-missing-deps/main.js
+++ /dev/null
@@ -1,3 +0,0 @@
-import { sayName } from 'missing-dep'
-
-export const name = sayName()
diff --git a/packages/playground/optimize-missing-deps/missing-dep/index.js b/packages/playground/optimize-missing-deps/missing-dep/index.js
deleted file mode 100644
index f5d61c545d080a..00000000000000
--- a/packages/playground/optimize-missing-deps/missing-dep/index.js
+++ /dev/null
@@ -1,5 +0,0 @@
-import { name } from 'multi-entry-dep'
-
-export function sayName() {
- return name
-}
diff --git a/packages/playground/optimize-missing-deps/missing-dep/package.json b/packages/playground/optimize-missing-deps/missing-dep/package.json
deleted file mode 100644
index bbfc8ba2c87a57..00000000000000
--- a/packages/playground/optimize-missing-deps/missing-dep/package.json
+++ /dev/null
@@ -1,9 +0,0 @@
-{
- "name": "missing-dep",
- "private": true,
- "version": "0.0.0",
- "main": "index.js",
- "dependencies": {
- "multi-entry-dep": "file:../multi-entry-dep"
- }
-}
diff --git a/packages/playground/optimize-missing-deps/multi-entry-dep/index.js b/packages/playground/optimize-missing-deps/multi-entry-dep/index.js
deleted file mode 100644
index 0717b87c27c2d8..00000000000000
--- a/packages/playground/optimize-missing-deps/multi-entry-dep/index.js
+++ /dev/null
@@ -1,3 +0,0 @@
-const path = require('path')
-
-exports.name = path.normalize('./Server')
diff --git a/packages/playground/optimize-missing-deps/multi-entry-dep/package.json b/packages/playground/optimize-missing-deps/multi-entry-dep/package.json
deleted file mode 100644
index ac4f3e542d152b..00000000000000
--- a/packages/playground/optimize-missing-deps/multi-entry-dep/package.json
+++ /dev/null
@@ -1,9 +0,0 @@
-{
- "name": "multi-entry-dep",
- "private": true,
- "version": "0.0.0",
- "main": "index.js",
- "browser": {
- "./index.js": "./index.browser.js"
- }
-}
diff --git a/packages/playground/optimize-missing-deps/package.json b/packages/playground/optimize-missing-deps/package.json
deleted file mode 100644
index 431cf3b33c3847..00000000000000
--- a/packages/playground/optimize-missing-deps/package.json
+++ /dev/null
@@ -1,16 +0,0 @@
-{
- "name": "optimize-missing-deps",
- "private": true,
- "version": "0.0.0",
- "scripts": {
- "dev": "node server",
- "postinstall": "ts-node ../../../scripts/patchFileDeps.ts"
- },
- "dependencies": {
- "missing-dep": "file:./missing-dep",
- "multi-entry-dep": "file:./multi-entry-dep"
- },
- "devDependencies": {
- "express": "^4.17.1"
- }
-}
diff --git a/packages/playground/optimize-missing-deps/server.js b/packages/playground/optimize-missing-deps/server.js
deleted file mode 100644
index b9422feb622584..00000000000000
--- a/packages/playground/optimize-missing-deps/server.js
+++ /dev/null
@@ -1,60 +0,0 @@
-// @ts-check
-const fs = require('fs')
-const path = require('path')
-const express = require('express')
-
-const isTest = process.env.NODE_ENV === 'test' || !!process.env.VITE_TEST_BUILD
-
-async function createServer(root = process.cwd()) {
- const resolve = (p) => path.resolve(__dirname, p)
-
- const app = express()
-
- /**
- * @type {import('vite').ViteDevServer}
- */
- const vite = await require('vite').createServer({
- root,
- logLevel: isTest ? 'error' : 'info',
- server: { middlewareMode: 'ssr' }
- })
- app.use(vite.middlewares)
-
- app.use('*', async (req, res) => {
- try {
- let template = fs.readFileSync(resolve('index.html'), 'utf-8')
- template = await vite.transformIndexHtml(req.originalUrl, template)
-
- // this will import missing deps nest built-in deps that should not be optimized
- const { name } = await vite.ssrLoadModule('./main.js')
-
- // this will import missing deps that should be optimized correctly
- const appHtml = `
${name}
-`
-
- const html = template.replace(``, appHtml)
-
- res.status(200).set({ 'Content-Type': 'text/html' }).end(html)
- } catch (e) {
- vite.ssrFixStacktrace(e)
- console.log(e.stack)
- res.status(500).end(e.stack)
- }
- })
-
- return { app, vite }
-}
-
-if (!isTest) {
- createServer().then(({ app }) =>
- app.listen(3000, () => {
- console.log('http://localhost:3000')
- })
- )
-}
-
-// for test use
-exports.createServer = createServer
diff --git a/packages/playground/package.json b/packages/playground/package.json
deleted file mode 100644
index 58ef368099e82f..00000000000000
--- a/packages/playground/package.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
- "name": "vite-playground",
- "private": true,
- "version": "1.0.0",
- "devDependencies": {
- "css-color-names": "^1.0.1"
- }
-}
diff --git a/packages/playground/preload/__tests__/preload.spec.ts b/packages/playground/preload/__tests__/preload.spec.ts
deleted file mode 100644
index 27a64930487797..00000000000000
--- a/packages/playground/preload/__tests__/preload.spec.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-import { isBuild } from '../../testUtils'
-
-test('should have no 404s', () => {
- browserLogs.forEach((msg) => {
- expect(msg).not.toMatch('404')
- })
-})
-
-if (isBuild) {
- test('dynamic import', async () => {
- const appHtml = await page.content()
- expect(appHtml).toMatch('This is
home page.')
- })
-
- test('dynamic import with comments', async () => {
- await page.goto(viteTestUrl + '/#/hello')
- const html = await page.content()
- expect(html).toMatch(
- /link rel="modulepreload".*?href="\/assets\/Hello\.\w{8}\.js"/
- )
- expect(html).toMatch(
- /link rel="stylesheet".*?href="\/assets\/Hello\.\w{8}\.css"/
- )
- })
-}
diff --git a/packages/playground/preload/index.html b/packages/playground/preload/index.html
deleted file mode 100644
index affed21f9791cf..00000000000000
--- a/packages/playground/preload/index.html
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
diff --git a/packages/playground/preload/package.json b/packages/playground/preload/package.json
deleted file mode 100644
index 5e65dafc8099c4..00000000000000
--- a/packages/playground/preload/package.json
+++ /dev/null
@@ -1,18 +0,0 @@
-{
- "name": "test-preload",
- "private": true,
- "version": "0.0.0",
- "scripts": {
- "dev": "vite",
- "build": "vite build",
- "debug": "node --inspect-brk ../../vite/bin/vite",
- "preview": "vite preview"
- },
- "dependencies": {
- "vue": "^3.2.25",
- "vue-router": "^4.0.0"
- },
- "devDependencies": {
- "@vitejs/plugin-vue": "workspace:*"
- }
-}
diff --git a/packages/playground/preload/router.js b/packages/playground/preload/router.js
deleted file mode 100644
index d3d5afdc99f6a3..00000000000000
--- a/packages/playground/preload/router.js
+++ /dev/null
@@ -1,21 +0,0 @@
-import { createRouter, createWebHashHistory } from 'vue-router'
-import Home from './src/components/Home.vue'
-
-const routes = [
- { path: '/', name: 'Home', component: Home },
- {
- path: '/hello',
- name: 'Hello',
- component: () => import(/* a comment */ './src/components/Hello.vue')
- },
- {
- path: '/about',
- name: 'About',
- component: () => import('./src/components/About.vue')
- } // Lazy load route component
-]
-
-export default createRouter({
- routes,
- history: createWebHashHistory()
-})
diff --git a/packages/playground/preload/src/App.vue b/packages/playground/preload/src/App.vue
deleted file mode 100644
index 3582faf75e1216..00000000000000
--- a/packages/playground/preload/src/App.vue
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
diff --git a/packages/playground/preload/src/components/About.vue b/packages/playground/preload/src/components/About.vue
deleted file mode 100644
index 2e73e80099446b..00000000000000
--- a/packages/playground/preload/src/components/About.vue
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
- This is about page.
-
- Go to Home page
-
-
-
-
diff --git a/packages/playground/preload/src/components/Hello.vue b/packages/playground/preload/src/components/Hello.vue
deleted file mode 100644
index 33b44d278d305d..00000000000000
--- a/packages/playground/preload/src/components/Hello.vue
+++ /dev/null
@@ -1,18 +0,0 @@
-
- {{ msg }}
-
- This is hello page.
-
- Go to Home page
-
-
-
-
-
-
diff --git a/packages/playground/preload/src/components/Home.vue b/packages/playground/preload/src/components/Home.vue
deleted file mode 100644
index 20f6b4948ac30a..00000000000000
--- a/packages/playground/preload/src/components/Home.vue
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
- This is home page.
-
- Go to About page
-
- Go to Hello page
-
-
-
-
diff --git a/packages/playground/preload/vite.config.js b/packages/playground/preload/vite.config.js
deleted file mode 100644
index 96fb82f51ed349..00000000000000
--- a/packages/playground/preload/vite.config.js
+++ /dev/null
@@ -1,15 +0,0 @@
-const vuePlugin = require('@vitejs/plugin-vue')
-
-module.exports = {
- plugins: [vuePlugin()],
- build: {
- terserOptions: {
- format: {
- beautify: true
- },
- compress: {
- passes: 3
- }
- }
- }
-}
diff --git a/packages/playground/preserve-symlinks/__tests__/preserve-symlinks.spec.ts b/packages/playground/preserve-symlinks/__tests__/preserve-symlinks.spec.ts
deleted file mode 100644
index 7e0b546d7dbdbb..00000000000000
--- a/packages/playground/preserve-symlinks/__tests__/preserve-symlinks.spec.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-test('should have no 404s', () => {
- browserLogs.forEach((msg) => {
- expect(msg).not.toMatch('404')
- })
-})
-
-test('not-preserve-symlinks', async () => {
- expect(await page.textContent('#root')).toBe('hello vite')
-})
diff --git a/packages/playground/preserve-symlinks/index.html b/packages/playground/preserve-symlinks/index.html
deleted file mode 100644
index 07f82cb05c3eec..00000000000000
--- a/packages/playground/preserve-symlinks/index.html
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-
-
-
-
-
Vite App
-
-
-
-
-
-
diff --git a/packages/playground/preserve-symlinks/moduleA/package.json b/packages/playground/preserve-symlinks/moduleA/package.json
deleted file mode 100644
index 3df68a0a78a164..00000000000000
--- a/packages/playground/preserve-symlinks/moduleA/package.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "name": "@symlinks/moduleA",
- "private": true,
- "version": "0.0.0",
- "main": "linked.js"
-}
diff --git a/packages/playground/preserve-symlinks/moduleA/src/data.js b/packages/playground/preserve-symlinks/moduleA/src/data.js
deleted file mode 100644
index e1bc98ec67da12..00000000000000
--- a/packages/playground/preserve-symlinks/moduleA/src/data.js
+++ /dev/null
@@ -1,3 +0,0 @@
-export const data = {
- msg: 'hello vite'
-}
diff --git a/packages/playground/preserve-symlinks/package.json b/packages/playground/preserve-symlinks/package.json
deleted file mode 100644
index 00a8ef23a3b05e..00000000000000
--- a/packages/playground/preserve-symlinks/package.json
+++ /dev/null
@@ -1,13 +0,0 @@
-{
- "name": "preserve-symlinks",
- "private": true,
- "version": "0.0.0",
- "scripts": {
- "dev": "vite --force",
- "build": "vite build",
- "preview": "vite preview"
- },
- "dependencies": {
- "@symlinks/moduleA": "link:./moduleA"
- }
-}
diff --git a/packages/playground/preserve-symlinks/src/main.js b/packages/playground/preserve-symlinks/src/main.js
deleted file mode 100644
index 7257c44f1ba83f..00000000000000
--- a/packages/playground/preserve-symlinks/src/main.js
+++ /dev/null
@@ -1,3 +0,0 @@
-import { sayHi } from '@symlinks/moduleA'
-
-document.getElementById('root').innerText = sayHi().msg
diff --git a/packages/playground/react-emotion/App.jsx b/packages/playground/react-emotion/App.jsx
deleted file mode 100644
index b3715369614530..00000000000000
--- a/packages/playground/react-emotion/App.jsx
+++ /dev/null
@@ -1,56 +0,0 @@
-import { useState } from 'react'
-import { css } from '@emotion/react'
-
-import _Switch from 'react-switch'
-const Switch = _Switch.default || _Switch
-
-export function Counter() {
- const [count, setCount] = useState(0)
-
- return (
-
setCount((count) => count + 1)}
- >
- count is: {count}
-
- )
-}
-
-function FragmentTest() {
- const [checked, setChecked] = useState(false)
- return (
- <>
-
-
-
-
- >
- )
-}
-
-function App() {
- return (
-
- )
-}
-
-export default App
diff --git a/packages/playground/react-emotion/__tests__/react.spec.ts b/packages/playground/react-emotion/__tests__/react.spec.ts
deleted file mode 100644
index 49a66b9e103374..00000000000000
--- a/packages/playground/react-emotion/__tests__/react.spec.ts
+++ /dev/null
@@ -1,45 +0,0 @@
-import { editFile, untilUpdated } from '../../testUtils'
-
-test('should render', async () => {
- expect(await page.textContent('h1')).toMatch(
- 'Hello Vite + React + @emotion/react'
- )
-})
-
-test('should update', async () => {
- expect(await page.textContent('button')).toMatch('count is: 0')
- await page.click('button')
- expect(await page.textContent('button')).toMatch('count is: 1')
-})
-
-test('should hmr', async () => {
- editFile('App.jsx', (code) =>
- code.replace('Vite + React + @emotion/react', 'Updated')
- )
- await untilUpdated(() => page.textContent('h1'), 'Hello Updated')
- // preserve state
- expect(await page.textContent('button')).toMatch('count is: 1')
-})
-
-test('should update button style', async () => {
- function getButtonBorderStyle() {
- return page.evaluate(() => {
- return window.getComputedStyle(document.querySelector('button')).border
- })
- }
-
- const styles = await page.evaluate(() => {
- return document.querySelector('button').style
- })
-
- expect(await getButtonBorderStyle()).toMatch('2px solid rgb(0, 0, 0)')
-
- editFile('App.jsx', (code) =>
- code.replace('border: 2px solid #000', 'border: 4px solid red')
- )
-
- await untilUpdated(getButtonBorderStyle, '4px solid rgb(255, 0, 0)')
-
- // preserve state
- expect(await page.textContent('button')).toMatch('count is: 1')
-})
diff --git a/packages/playground/react-emotion/index.html b/packages/playground/react-emotion/index.html
deleted file mode 100644
index ce1b9213c82763..00000000000000
--- a/packages/playground/react-emotion/index.html
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
diff --git a/packages/playground/react-emotion/package.json b/packages/playground/react-emotion/package.json
deleted file mode 100644
index fc78ac30b34a8d..00000000000000
--- a/packages/playground/react-emotion/package.json
+++ /dev/null
@@ -1,27 +0,0 @@
-{
- "name": "test-react-emotion",
- "private": true,
- "version": "0.0.0",
- "scripts": {
- "dev": "vite",
- "build": "vite build",
- "debug": "node --inspect-brk ../../vite/bin/vite",
- "preview": "vite preview"
- },
- "dependencies": {
- "@emotion/react": "^11.5.0",
- "react": "^17.0.2",
- "react-dom": "^17.0.2",
- "react-switch": "^6.0.0"
- },
- "devDependencies": {
- "@babel/plugin-proposal-pipeline-operator": "^7.16.0",
- "@emotion/babel-plugin": "^11.3.0",
- "@vitejs/plugin-react": "workspace:*"
- },
- "babel": {
- "presets": [
- "@babel/preset-env"
- ]
- }
-}
diff --git a/packages/playground/react-emotion/vite.config.ts b/packages/playground/react-emotion/vite.config.ts
deleted file mode 100644
index 9364c8f616c2f5..00000000000000
--- a/packages/playground/react-emotion/vite.config.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-import react from '@vitejs/plugin-react'
-import type { UserConfig } from 'vite'
-
-const config: UserConfig = {
- plugins: [
- react({
- jsxImportSource: '@emotion/react',
- babel: {
- plugins: ['@emotion/babel-plugin']
- }
- })
- ],
- clearScreen: false,
- build: {
- // to make tests faster
- minify: false
- }
-}
-
-export default config
diff --git a/packages/playground/react/App.jsx b/packages/playground/react/App.jsx
deleted file mode 100644
index 70d1f961b8cce9..00000000000000
--- a/packages/playground/react/App.jsx
+++ /dev/null
@@ -1,33 +0,0 @@
-import { useState } from 'react'
-import Dummy from './components/Dummy?qs-should-not-break-plugin-react'
-
-function App() {
- const [count, setCount] = useState(0)
- return (
-
-
- Hello Vite + React
-
- setCount((count) => count + 1)}>
- count is: {count}
-
-
-
- Edit App.jsx and save to test HMR updates.
-
-
- Learn React
-
-
-
-
-
- )
-}
-
-export default App
diff --git a/packages/playground/react/__tests__/react.spec.ts b/packages/playground/react/__tests__/react.spec.ts
deleted file mode 100644
index 46eb752924f801..00000000000000
--- a/packages/playground/react/__tests__/react.spec.ts
+++ /dev/null
@@ -1,39 +0,0 @@
-import { editFile, untilUpdated, isBuild } from '../../testUtils'
-
-test('should render', async () => {
- expect(await page.textContent('h1')).toMatch('Hello Vite + React')
-})
-
-test('should update', async () => {
- expect(await page.textContent('button')).toMatch('count is: 0')
- await page.click('button')
- expect(await page.textContent('button')).toMatch('count is: 1')
-})
-
-test('should hmr', async () => {
- editFile('App.jsx', (code) => code.replace('Vite + React', 'Updated'))
- await untilUpdated(() => page.textContent('h1'), 'Hello Updated')
- // preserve state
- expect(await page.textContent('button')).toMatch('count is: 1')
-})
-
-test('should have annotated jsx with file location metadata', async () => {
- // we're not annotating in prod,
- // so we skip this test when isBuild is true
- if (isBuild) return
-
- const meta = await page.evaluate(() => {
- const button = document.querySelector('button')
- const key = Object.keys(button).find(
- (key) => key.indexOf('__reactFiber') === 0
- )
- return button[key]._debugSource
- })
- // If the evaluate call doesn't crash, and the returned metadata has
- // the expected fields, we're good.
- expect(Object.keys(meta).sort()).toEqual([
- 'columnNumber',
- 'fileName',
- 'lineNumber'
- ])
-})
diff --git a/packages/playground/react/components/Dummy.jsx b/packages/playground/react/components/Dummy.jsx
deleted file mode 100644
index 27ec3c21de30fd..00000000000000
--- a/packages/playground/react/components/Dummy.jsx
+++ /dev/null
@@ -1,3 +0,0 @@
-export default function Dummy() {
- return <>>
-}
diff --git a/packages/playground/react/index.html b/packages/playground/react/index.html
deleted file mode 100644
index ce1b9213c82763..00000000000000
--- a/packages/playground/react/index.html
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
diff --git a/packages/playground/react/package.json b/packages/playground/react/package.json
deleted file mode 100644
index fc9b8e69d3999e..00000000000000
--- a/packages/playground/react/package.json
+++ /dev/null
@@ -1,23 +0,0 @@
-{
- "name": "test-react",
- "private": true,
- "version": "0.0.0",
- "scripts": {
- "dev": "vite",
- "build": "vite build",
- "debug": "node --inspect-brk ../../vite/bin/vite",
- "preview": "vite preview"
- },
- "dependencies": {
- "react": "^17.0.2",
- "react-dom": "^17.0.2"
- },
- "devDependencies": {
- "@vitejs/plugin-react": "workspace:*"
- },
- "babel": {
- "presets": [
- "@babel/preset-env"
- ]
- }
-}
diff --git a/packages/playground/react/vite.config.ts b/packages/playground/react/vite.config.ts
deleted file mode 100644
index c6955a131d375f..00000000000000
--- a/packages/playground/react/vite.config.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-import react from '@vitejs/plugin-react'
-import type { UserConfig } from 'vite'
-
-const config: UserConfig = {
- plugins: [react()],
- build: {
- // to make tests faster
- minify: false
- }
-}
-
-export default config
diff --git a/packages/playground/resolve-config/__tests__/resolve-config.spec.ts b/packages/playground/resolve-config/__tests__/resolve-config.spec.ts
deleted file mode 100644
index 13ea5ea6f59a4f..00000000000000
--- a/packages/playground/resolve-config/__tests__/resolve-config.spec.ts
+++ /dev/null
@@ -1,54 +0,0 @@
-import fs from 'fs'
-import path from 'path'
-import { commandSync } from 'execa'
-import { isBuild, testDir, workspaceRoot } from '../../testUtils'
-
-const viteBin = path.join(workspaceRoot, 'packages', 'vite', 'bin', 'vite.js')
-
-const fromTestDir = (...p: string[]) => path.resolve(testDir, ...p)
-
-const build = (configName: string) => {
- commandSync(`${viteBin} build`, { cwd: fromTestDir(configName) })
-}
-const getDistFile = (configName: string) => {
- return fs.readFileSync(fromTestDir(`${configName}/dist/index.es.js`), 'utf8')
-}
-
-if (isBuild) {
- it('loads vite.config.js', () => {
- build('js')
- expect(getDistFile('js')).toContain('console.log(true)')
- })
- it('loads vite.config.js with package#type module', () => {
- build('js-module')
- expect(getDistFile('js-module')).toContain('console.log(true)')
- })
- it('loads vite.config.cjs', () => {
- build('cjs')
- expect(getDistFile('cjs')).toContain('console.log(true)')
- })
- it('loads vite.config.cjs with package#type module', () => {
- build('cjs-module')
- expect(getDistFile('cjs-module')).toContain('console.log(true)')
- })
- it('loads vite.config.mjs', () => {
- build('mjs')
- expect(getDistFile('mjs')).toContain('console.log(true)')
- })
- it('loads vite.config.mjs with package#type module', () => {
- build('mjs-module')
- expect(getDistFile('mjs-module')).toContain('console.log(true)')
- })
- it('loads vite.config.ts', () => {
- build('ts')
- expect(getDistFile('ts')).toContain('console.log(true)')
- })
- it('loads vite.config.ts with package#type module', () => {
- build('ts-module')
- expect(getDistFile('ts-module')).toContain('console.log(true)')
- })
-} else {
- // this test doesn't support serve mode
- // must contain at least one test
- test('should work', () => void 0)
-}
diff --git a/packages/playground/resolve-config/__tests__/serve.js b/packages/playground/resolve-config/__tests__/serve.js
deleted file mode 100644
index bd451d4cf6f6bc..00000000000000
--- a/packages/playground/resolve-config/__tests__/serve.js
+++ /dev/null
@@ -1,39 +0,0 @@
-// @ts-check
-// this is automtically detected by scripts/jestPerTestSetup.ts and will replace
-// the default e2e test serve behavior
-
-const path = require('path')
-const fs = require('fs-extra')
-const { testDir } = require('../../testUtils')
-
-const fromTestDir = (/** @type{string[]} */ ...p) => path.resolve(testDir, ...p)
-
-const configNames = ['js', 'cjs', 'mjs', 'ts']
-
-/** @param {string} root @param {boolean} isProd */
-exports.serve = async function serve(root, isProd) {
- if (!isProd) return
-
- // create separate directories for all config types:
- // ./{js,cjs,mjs,ts} and ./{js,cjs,mjs,ts}-module (with package#type)
- for (const configName of configNames) {
- const pathToConf = fromTestDir(configName, `vite.config.${configName}`)
-
- await fs.copy(fromTestDir('root'), fromTestDir(configName))
- await fs.rename(fromTestDir(configName, 'vite.config.js'), pathToConf)
-
- if (configName === 'cjs') {
- const conf = await fs.readFile(pathToConf, 'utf8')
- await fs.writeFile(
- pathToConf,
- conf.replace('export default', 'module.exports = ')
- )
- }
-
- // copy directory and add package.json with "type": "module"
- await fs.copy(fromTestDir(configName), fromTestDir(`${configName}-module`))
- await fs.writeJSON(fromTestDir(`${configName}-module`, 'package.json'), {
- type: 'module'
- })
- }
-}
diff --git a/packages/playground/resolve-config/package.json b/packages/playground/resolve-config/package.json
deleted file mode 100644
index 5459dad99003cd..00000000000000
--- a/packages/playground/resolve-config/package.json
+++ /dev/null
@@ -1,10 +0,0 @@
-{
- "name": "resolve-config",
- "private": true,
- "version": "0.0.0",
- "scripts": {
- "dev": "vite --force",
- "build": "vite build",
- "preview": "vite preview"
- }
-}
diff --git a/packages/playground/resolve-config/root/index.js b/packages/playground/resolve-config/root/index.js
deleted file mode 100644
index a3f8f13f20f96e..00000000000000
--- a/packages/playground/resolve-config/root/index.js
+++ /dev/null
@@ -1 +0,0 @@
-console.log(__CONFIG_LOADED__)
diff --git a/packages/playground/resolve-config/root/vite.config.js b/packages/playground/resolve-config/root/vite.config.js
deleted file mode 100644
index ed72046f940d59..00000000000000
--- a/packages/playground/resolve-config/root/vite.config.js
+++ /dev/null
@@ -1,9 +0,0 @@
-export default {
- define: { __CONFIG_LOADED__: true },
- logLevel: 'silent',
- build: {
- minify: false,
- sourcemap: false,
- lib: { entry: 'index.js', fileName: 'index', formats: ['es'] }
- }
-}
diff --git a/packages/playground/resolve-linked/package.json b/packages/playground/resolve-linked/package.json
deleted file mode 100644
index 204cfd931c63ab..00000000000000
--- a/packages/playground/resolve-linked/package.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "name": "resolve-linked",
- "private": true,
- "version": "0.0.0",
- "main": "src/index.js"
-}
diff --git a/packages/playground/resolve/__tests__/resolve.spec.ts b/packages/playground/resolve/__tests__/resolve.spec.ts
deleted file mode 100644
index b64da138033fc0..00000000000000
--- a/packages/playground/resolve/__tests__/resolve.spec.ts
+++ /dev/null
@@ -1,109 +0,0 @@
-import { isBuild } from '../../testUtils'
-
-test('bom import', async () => {
- expect(await page.textContent('.utf8-bom')).toMatch('[success]')
-})
-
-test('deep import', async () => {
- expect(await page.textContent('.deep-import')).toMatch('[2,4]')
-})
-
-test('entry with exports field', async () => {
- expect(await page.textContent('.exports-entry')).toMatch('[success]')
-})
-
-test('deep import with exports field', async () => {
- expect(await page.textContent('.exports-deep')).toMatch('[success]')
-})
-
-test('deep import with query with exports field', async () => {
- expect(await page.textContent('.exports-deep-query')).not.toMatch('fail')
-})
-
-test('deep import with exports field + exposed dir', async () => {
- expect(await page.textContent('.exports-deep-exposed-dir')).toMatch(
- '[success]'
- )
-})
-
-test('deep import with exports field + mapped dir', async () => {
- expect(await page.textContent('.exports-deep-mapped-dir')).toMatch(
- '[success]'
- )
-})
-
-test('Respect exports field env key priority', async () => {
- expect(await page.textContent('.exports-env')).toMatch('[success]')
-})
-
-test('Respect production/development conditionals', async () => {
- expect(await page.textContent('.exports-env')).toMatch(
- isBuild ? `browser.prod.mjs` : `browser.mjs`
- )
-})
-
-test('implicit dir/index.js', async () => {
- expect(await page.textContent('.index')).toMatch('[success]')
-})
-
-test('implicit dir/index.js vs explicit file', async () => {
- expect(await page.textContent('.dir-vs-file')).toMatch('[success]')
-})
-
-test('exact extension vs. duplicated (.js.js)', async () => {
- expect(await page.textContent('.exact-extension')).toMatch('[success]')
-})
-
-test('dont add extension to directory name (./dir-with-ext.js/index.js)', async () => {
- expect(await page.textContent('.dir-with-ext')).toMatch('[success]')
-})
-
-test('a ts module can import another ts module using its corresponding js file name', async () => {
- expect(await page.textContent('.ts-extension')).toMatch('[success]')
-})
-
-test('filename with dot', async () => {
- expect(await page.textContent('.dot')).toMatch('[success]')
-})
-
-test('browser field', async () => {
- expect(await page.textContent('.browser')).toMatch('[success]')
-})
-
-test('css entry', async () => {
- expect(await page.textContent('.css')).toMatch('[success]')
-})
-
-test('monorepo linked dep', async () => {
- expect(await page.textContent('.monorepo')).toMatch('[success]')
-})
-
-test('plugin resolved virtual file', async () => {
- expect(await page.textContent('.virtual')).toMatch('[success]')
-})
-
-test('plugin resolved custom virtual file', async () => {
- expect(await page.textContent('.custom-virtual')).toMatch('[success]')
-})
-
-test('resolve inline package', async () => {
- expect(await page.textContent('.inline-pkg')).toMatch('[success]')
-})
-
-test('resolve.extensions', async () => {
- expect(await page.textContent('.custom-ext')).toMatch('[success]')
-})
-
-test('resolve.mainFields', async () => {
- expect(await page.textContent('.custom-main-fields')).toMatch('[success]')
-})
-
-test('resolve.conditions', async () => {
- expect(await page.textContent('.custom-condition')).toMatch('[success]')
-})
-
-test('resolve package that contains # in path', async () => {
- expect(await page.textContent('.path-contains-sharp-symbol')).toMatch(
- '[success]'
- )
-})
diff --git a/packages/playground/resolve/browser-field/multiple.dot.path.js b/packages/playground/resolve/browser-field/multiple.dot.path.js
deleted file mode 100644
index b4022f73daaf6e..00000000000000
--- a/packages/playground/resolve/browser-field/multiple.dot.path.js
+++ /dev/null
@@ -1,2 +0,0 @@
-const fs = require('fs')
-console.log('this should not run in the browser')
diff --git a/packages/playground/resolve/browser-field/no-ext-index/index.js b/packages/playground/resolve/browser-field/no-ext-index/index.js
deleted file mode 100644
index d3f4967324ffb7..00000000000000
--- a/packages/playground/resolve/browser-field/no-ext-index/index.js
+++ /dev/null
@@ -1,2 +0,0 @@
-import jsdom from 'jsdom' // should be redireted to empty module
-export default ''
diff --git a/packages/playground/resolve/browser-field/no-ext.js b/packages/playground/resolve/browser-field/no-ext.js
deleted file mode 100644
index d3f4967324ffb7..00000000000000
--- a/packages/playground/resolve/browser-field/no-ext.js
+++ /dev/null
@@ -1,2 +0,0 @@
-import jsdom from 'jsdom' // should be redireted to empty module
-export default ''
diff --git a/packages/playground/resolve/browser-field/not-browser.js b/packages/playground/resolve/browser-field/not-browser.js
deleted file mode 100644
index b4022f73daaf6e..00000000000000
--- a/packages/playground/resolve/browser-field/not-browser.js
+++ /dev/null
@@ -1,2 +0,0 @@
-const fs = require('fs')
-console.log('this should not run in the browser')
diff --git a/packages/playground/resolve/browser-field/out/esm.browser.js b/packages/playground/resolve/browser-field/out/esm.browser.js
deleted file mode 100644
index bddf03df0b0c22..00000000000000
--- a/packages/playground/resolve/browser-field/out/esm.browser.js
+++ /dev/null
@@ -1,2 +0,0 @@
-import jsdom from 'jsdom' // should be redireted to empty module
-export default '[success] resolve browser field'
diff --git a/packages/playground/resolve/browser-field/package.json b/packages/playground/resolve/browser-field/package.json
deleted file mode 100644
index 006f9b4b5f4fc6..00000000000000
--- a/packages/playground/resolve/browser-field/package.json
+++ /dev/null
@@ -1,17 +0,0 @@
-{
- "name": "resolve-browser-field",
- "private": true,
- "version": "1.0.0",
- "//": "real world example: https://github.com/axios/axios/blob/3f2ef030e001547eb06060499f8a2e3f002b5a14/package.json#L71-L73",
- "main": "out/cjs.node.js",
- "browser": {
- "./out/cjs.node.js": "./out/esm.browser.js",
- "./no-ext": "./out/esm.browser.js",
- "./ext.js": "./out/esm.browser.js",
- "./ext-index/index.js": "./out/esm.browser.js",
- "./no-ext-index": "./out/esm.browser.js",
- "./not-browser.js": false,
- "./multiple.dot.path.js": false,
- "jsdom": false
- }
-}
diff --git a/packages/playground/resolve/browser-field/relative.js b/packages/playground/resolve/browser-field/relative.js
deleted file mode 100644
index bbf6a2c74a10b5..00000000000000
--- a/packages/playground/resolve/browser-field/relative.js
+++ /dev/null
@@ -1,9 +0,0 @@
-import ra from './no-ext'
-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 rg from './no-ext-index/index.js' // no substitution
-
-export { ra, rb, rc, rd, re, rf, rg }
diff --git a/packages/playground/resolve/config-dep.js b/packages/playground/resolve/config-dep.js
deleted file mode 100644
index 8bc3563c743bcd..00000000000000
--- a/packages/playground/resolve/config-dep.js
+++ /dev/null
@@ -1,3 +0,0 @@
-module.exports = {
- a: 1
-}
diff --git a/packages/playground/resolve/custom-condition/package.json b/packages/playground/resolve/custom-condition/package.json
deleted file mode 100644
index 490a420fe2dbfc..00000000000000
--- a/packages/playground/resolve/custom-condition/package.json
+++ /dev/null
@@ -1,13 +0,0 @@
-{
- "name": "resolve-custom-condition",
- "private": true,
- "version": "1.0.0",
- "main": "index.js",
- "exports": {
- ".": {
- "custom": "./index.custom.js",
- "import": "./index.js",
- "require": "./index.js"
- }
- }
-}
diff --git a/packages/playground/resolve/custom-main-field/package.json b/packages/playground/resolve/custom-main-field/package.json
deleted file mode 100644
index bb948c3261eb1c..00000000000000
--- a/packages/playground/resolve/custom-main-field/package.json
+++ /dev/null
@@ -1,7 +0,0 @@
-{
- "name": "resolve-custom-main-field",
- "private": true,
- "version": "1.0.0",
- "main": "index.js",
- "custom": "index.custom.js"
-}
diff --git a/packages/playground/resolve/exports-env/package.json b/packages/playground/resolve/exports-env/package.json
deleted file mode 100644
index f9e635b5a19c24..00000000000000
--- a/packages/playground/resolve/exports-env/package.json
+++ /dev/null
@@ -1,15 +0,0 @@
-{
- "name": "resolve-exports-env",
- "private": true,
- "version": "1.0.0",
- "exports": {
- "import": {
- "browser": {
- "production": "./browser.prod.mjs",
- "development": "./browser.mjs"
- }
- },
- "browser": "./browser.js",
- "default": "./fallback.umd.js"
- }
-}
diff --git a/packages/playground/resolve/exports-path/package.json b/packages/playground/resolve/exports-path/package.json
deleted file mode 100644
index 7355da2f63f616..00000000000000
--- a/packages/playground/resolve/exports-path/package.json
+++ /dev/null
@@ -1,18 +0,0 @@
-{
- "name": "resolve-exports-path",
- "private": true,
- "version": "1.0.0",
- "exports": {
- ".": {
- "import": "./main.js",
- "require": "./cjs.js"
- },
- "./deep.js": "./deep.js",
- "./deep.json": "./deep.json",
- "./dir/": "./dir/",
- "./dir-mapped/*": {
- "import": "./dir/*",
- "require": "./dir-cjs/*"
- }
- }
-}
diff --git a/packages/playground/resolve/index.html b/packages/playground/resolve/index.html
deleted file mode 100644
index c0569345d86837..00000000000000
--- a/packages/playground/resolve/index.html
+++ /dev/null
@@ -1,227 +0,0 @@
-
Resolve
-
-
Utf8-bom import
-
fail
-
-
Deep import
-
Should show [2,4]:fail
-
-
Entry resolving with exports field
-
fail
-
-
Deep import with exports field
-
fail
-
-
Deep import with query with exports field
-
fail
-
-
Deep import with exports field + exposed directory
-
fail
-
-
Deep import with exports field + mapped directory
-
fail
-
-
Exports field env priority
-
fail
-
-
Resolve /index.*
-
fail
-
-
Resolve dir and file of the same name (should prioritize file)
-
fail
-
-
Resolve to non-duplicated file extension
-
fail
-
-
Don't add extensions to directory names
-
fail
-
-
- A ts module can import another ts module using its corresponding js file name
-
-
fail
-
-
- A ts module can import another tsx module using its corresponding jsx file
- name
-
-
fail
-
-
- A ts module can import another tsx module using its corresponding js file name
-
-
fail
-
-
Resolve file name containing dot
-
fail
-
-
Browser Field
-
fail
-
-
CSS Entry
-
-
-
Monorepo linked dep
-
-
-
Plugin resolved virtual file
-
-
-
Plugin resolved custom virtual file
-
-
-
Inline package
-
-
-
resolve.extensions
-
-
-
resolve.mainFields
-
-
-
resolve.conditions
-
-
-
resolve package that contains # in path
-
-
-
-
-
diff --git a/packages/playground/resolve/inline-package/package.json b/packages/playground/resolve/inline-package/package.json
deleted file mode 100644
index bbbb6b0b0381d8..00000000000000
--- a/packages/playground/resolve/inline-package/package.json
+++ /dev/null
@@ -1,7 +0,0 @@
-{
- "name": "inline-package",
- "private": true,
- "version": "0.0.0",
- "sideEffects": false,
- "main": "./inline"
-}
diff --git a/packages/playground/resolve/package.json b/packages/playground/resolve/package.json
deleted file mode 100644
index 5e0f53b4c8468a..00000000000000
--- a/packages/playground/resolve/package.json
+++ /dev/null
@@ -1,22 +0,0 @@
-{
- "name": "test-resolve",
- "private": true,
- "version": "0.0.0",
- "scripts": {
- "dev": "vite",
- "build": "vite build",
- "debug": "node --inspect-brk ../../vite/bin/vite",
- "preview": "vite preview"
- },
- "dependencies": {
- "@babel/runtime": "^7.16.0",
- "es5-ext": "0.10.53",
- "normalize.css": "^8.0.1",
- "resolve-browser-field": "link:./browser-field",
- "resolve-custom-condition": "link:./custom-condition",
- "resolve-custom-main-field": "link:./custom-main-field",
- "resolve-exports-env": "link:./exports-env",
- "resolve-exports-path": "link:./exports-path",
- "resolve-linked": "workspace:*"
- }
-}
diff --git a/packages/playground/resolve/ts-extension/index.ts b/packages/playground/resolve/ts-extension/index.ts
deleted file mode 100644
index bdb326f8778e64..00000000000000
--- a/packages/playground/resolve/ts-extension/index.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-import { msg } from './hello.js'
-import { msgJsx } from './hellojsx.jsx'
-import { msgTsx } from './hellotsx.js'
-
-export { msg, msgJsx, msgTsx }
diff --git a/packages/playground/resolve/vite.config.js b/packages/playground/resolve/vite.config.js
deleted file mode 100644
index be1b75e431383a..00000000000000
--- a/packages/playground/resolve/vite.config.js
+++ /dev/null
@@ -1,44 +0,0 @@
-const virtualFile = '@virtual-file'
-const virtualId = '\0' + virtualFile
-
-const customVirtualFile = '@custom-virtual-file'
-const { a } = require('./config-dep')
-
-module.exports = {
- resolve: {
- extensions: ['.mjs', '.js', '.es', '.ts'],
- mainFields: ['custom', 'module'],
- conditions: ['custom']
- },
- define: {
- VITE_CONFIG_DEP_TEST: a
- },
- plugins: [
- {
- name: 'virtual-module',
- resolveId(id) {
- if (id === virtualFile) {
- return virtualId
- }
- },
- load(id) {
- if (id === virtualId) {
- return `export const msg = "[success] from conventional virtual file"`
- }
- }
- },
- {
- name: 'custom-resolve',
- resolveId(id) {
- if (id === customVirtualFile) {
- return id
- }
- },
- load(id) {
- if (id === customVirtualFile) {
- return `export const msg = "[success] from custom virtual file"`
- }
- }
- }
- ]
-}
diff --git a/packages/playground/shims.d.ts b/packages/playground/shims.d.ts
deleted file mode 100644
index ced8fb1ad585ae..00000000000000
--- a/packages/playground/shims.d.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-declare module 'css-color-names' {
- const colors: Record
- export default colors
-}
-
-declare module '*.vue' {
- import type { ComponentOptions } from 'vue'
- const component: ComponentOptions
- export default component
-}
diff --git a/packages/playground/ssr-deps/__tests__/serve.js b/packages/playground/ssr-deps/__tests__/serve.js
deleted file mode 100644
index 5ba5724f2b7a94..00000000000000
--- a/packages/playground/ssr-deps/__tests__/serve.js
+++ /dev/null
@@ -1,36 +0,0 @@
-// @ts-check
-// this is automtically detected by scripts/jestPerTestSetup.ts and will replace
-// the default e2e test serve behavior
-
-const path = require('path')
-
-const port = (exports.port = 9530)
-
-/**
- * @param {string} root
- * @param {boolean} isProd
- */
-exports.serve = async function serve(root, isProd) {
- const { createServer } = require(path.resolve(root, 'server.js'))
- const { app, vite } = await createServer(root, isProd)
-
- return new Promise((resolve, reject) => {
- try {
- const server = app.listen(port, () => {
- resolve({
- // for test teardown
- async close() {
- await new Promise((resolve) => {
- server.close(resolve)
- })
- if (vite) {
- await vite.close()
- }
- }
- })
- })
- } catch (e) {
- reject(e)
- }
- })
-}
diff --git a/packages/playground/ssr-deps/__tests__/ssr-deps.spec.ts b/packages/playground/ssr-deps/__tests__/ssr-deps.spec.ts
deleted file mode 100644
index 8a201c9eb87455..00000000000000
--- a/packages/playground/ssr-deps/__tests__/ssr-deps.spec.ts
+++ /dev/null
@@ -1,73 +0,0 @@
-import { port } from './serve'
-
-const url = `http://localhost:${port}`
-
-/**
- * test for #5809
- *
- * NOTE: This test will always succeed now, unless the temporary workaround for Jest can be removed
- * See https://github.com/vitejs/vite/pull/5197#issuecomment-938054077
- */
-test('msg should be encrypted', async () => {
- await page.goto(url)
- expect(await page.textContent('.encrypted-msg')).not.toMatch(
- 'Secret Message!'
- )
-})
-
-test('msg read by fs/promises', async () => {
- await page.goto(url)
- expect(await page.textContent('.file-message')).toMatch('File Content!')
-})
-
-test('msg from primitive export', async () => {
- await page.goto(url)
- expect(await page.textContent('.primitive-export-message')).toMatch(
- 'Hello World!'
- )
-})
-
-test('msg from TS transpiled exports', async () => {
- await page.goto(url)
- expect(await page.textContent('.ts-default-export-message')).toMatch(
- 'Hello World!'
- )
- expect(await page.textContent('.ts-named-export-message')).toMatch(
- 'Hello World!'
- )
-})
-
-test('msg from Object.assign exports', async () => {
- await page.goto(url)
- expect(await page.textContent('.object-assigned-exports-message')).toMatch(
- 'Hello World!'
- )
-})
-
-test('msg from forwarded exports', async () => {
- await page.goto(url)
- expect(await page.textContent('.forwarded-export-message')).toMatch(
- 'Hello World!'
- )
-})
-
-test('msg from define properties exports', async () => {
- await page.goto(url)
- expect(await page.textContent('.define-properties-exports-msg')).toMatch(
- 'Hello World!'
- )
-})
-
-test('msg from define property exports', async () => {
- await page.goto(url)
- expect(await page.textContent('.define-property-exports-msg')).toMatch(
- 'Hello World!'
- )
-})
-
-test('msg from only object assigned exports', async () => {
- await page.goto(url)
- expect(await page.textContent('.only-object-assigned-exports-msg')).toMatch(
- 'Hello World!'
- )
-})
diff --git a/packages/playground/ssr-deps/define-properties-exports/package.json b/packages/playground/ssr-deps/define-properties-exports/package.json
deleted file mode 100644
index 3cf10f8cced539..00000000000000
--- a/packages/playground/ssr-deps/define-properties-exports/package.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "name": "define-properties-exports",
- "private": true,
- "version": "0.0.0"
-}
diff --git a/packages/playground/ssr-deps/define-property-exports/index.js b/packages/playground/ssr-deps/define-property-exports/index.js
deleted file mode 100644
index 4506dd6200051e..00000000000000
--- a/packages/playground/ssr-deps/define-property-exports/index.js
+++ /dev/null
@@ -1,5 +0,0 @@
-Object.defineProperty(exports, 'hello', {
- value() {
- return 'Hello World!'
- }
-})
diff --git a/packages/playground/ssr-deps/define-property-exports/package.json b/packages/playground/ssr-deps/define-property-exports/package.json
deleted file mode 100644
index 38ef7fdf5f410a..00000000000000
--- a/packages/playground/ssr-deps/define-property-exports/package.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "name": "define-property-exports",
- "private": true,
- "version": "0.0.0"
-}
diff --git a/packages/playground/ssr-deps/forwarded-export/package.json b/packages/playground/ssr-deps/forwarded-export/package.json
deleted file mode 100644
index 1a0a62e0b4472d..00000000000000
--- a/packages/playground/ssr-deps/forwarded-export/package.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "name": "forwarded-export",
- "private": true,
- "version": "0.0.0"
-}
diff --git a/packages/playground/ssr-deps/index.html b/packages/playground/ssr-deps/index.html
deleted file mode 100644
index b1e884efaab01a..00000000000000
--- a/packages/playground/ssr-deps/index.html
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
-
-
- SSR Dependencies
-
-
- SSR Dependencies
-
-
-
diff --git a/packages/playground/ssr-deps/object-assigned-exports/index.js b/packages/playground/ssr-deps/object-assigned-exports/index.js
deleted file mode 100644
index d6510e38f3a36f..00000000000000
--- a/packages/playground/ssr-deps/object-assigned-exports/index.js
+++ /dev/null
@@ -1,9 +0,0 @@
-Object.defineProperty(exports, '__esModule', { value: true })
-
-const obj = {
- hello() {
- return 'Hello World!'
- }
-}
-
-Object.assign(exports, obj)
diff --git a/packages/playground/ssr-deps/object-assigned-exports/package.json b/packages/playground/ssr-deps/object-assigned-exports/package.json
deleted file mode 100644
index a385dc9b7ec1b7..00000000000000
--- a/packages/playground/ssr-deps/object-assigned-exports/package.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "name": "object-assigned-exports",
- "private": true,
- "version": "0.0.0"
-}
diff --git a/packages/playground/ssr-deps/only-object-assigned-exports/index.js b/packages/playground/ssr-deps/only-object-assigned-exports/index.js
deleted file mode 100644
index b6a4ab368b133d..00000000000000
--- a/packages/playground/ssr-deps/only-object-assigned-exports/index.js
+++ /dev/null
@@ -1,5 +0,0 @@
-Object.assign(exports, {
- hello() {
- return 'Hello World!'
- }
-})
diff --git a/packages/playground/ssr-deps/only-object-assigned-exports/package.json b/packages/playground/ssr-deps/only-object-assigned-exports/package.json
deleted file mode 100644
index 22a071b59e411d..00000000000000
--- a/packages/playground/ssr-deps/only-object-assigned-exports/package.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "name": "only-object-assigned-exports",
- "private": true,
- "version": "0.0.0"
-}
diff --git a/packages/playground/ssr-deps/package.json b/packages/playground/ssr-deps/package.json
deleted file mode 100644
index 7af243c3b4769a..00000000000000
--- a/packages/playground/ssr-deps/package.json
+++ /dev/null
@@ -1,27 +0,0 @@
-{
- "name": "test-ssr-deps",
- "private": true,
- "version": "0.0.0",
- "scripts": {
- "dev": "node server",
- "serve": "cross-env NODE_ENV=production node server",
- "debug": "node --inspect-brk server",
- "postinstall": "ts-node ../../../scripts/patchFileDeps.ts"
- },
- "dependencies": {
- "bcrypt": "^5.0.1",
- "define-properties-exports": "file:./define-properties-exports",
- "define-property-exports": "file:./define-property-exports",
- "forwarded-export": "file:./forwarded-export",
- "object-assigned-exports": "file:./object-assigned-exports",
- "only-object-assigned-exports": "file:./only-object-assigned-exports",
- "primitive-export": "file:./primitive-export",
- "read-file-content": "file:./read-file-content",
- "require-absolute": "file:./require-absolute",
- "ts-transpiled-exports": "file:./ts-transpiled-exports"
- },
- "devDependencies": {
- "cross-env": "^7.0.3",
- "express": "^4.17.1"
- }
-}
diff --git a/packages/playground/ssr-deps/primitive-export/package.json b/packages/playground/ssr-deps/primitive-export/package.json
deleted file mode 100644
index d86685f6b9a8f1..00000000000000
--- a/packages/playground/ssr-deps/primitive-export/package.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "name": "primitive-export",
- "private": true,
- "version": "0.0.0"
-}
diff --git a/packages/playground/ssr-deps/read-file-content/index.js b/packages/playground/ssr-deps/read-file-content/index.js
deleted file mode 100644
index c8761b3b4734c1..00000000000000
--- a/packages/playground/ssr-deps/read-file-content/index.js
+++ /dev/null
@@ -1,9 +0,0 @@
-const path = require('path')
-
-module.exports = async function readFileContent(filePath) {
- const fs =
- process.versions.node.split('.')[0] >= '14'
- ? require('fs/promises')
- : require('fs').promises
- return await fs.readFile(path.resolve(filePath), 'utf-8')
-}
diff --git a/packages/playground/ssr-deps/read-file-content/package.json b/packages/playground/ssr-deps/read-file-content/package.json
deleted file mode 100644
index 6145e7821f7067..00000000000000
--- a/packages/playground/ssr-deps/read-file-content/package.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "name": "read-file-content",
- "private": true,
- "version": "0.0.0"
-}
diff --git a/packages/playground/ssr-deps/require-absolute/index.js b/packages/playground/ssr-deps/require-absolute/index.js
deleted file mode 100644
index c2f844f3e2f6ed..00000000000000
--- a/packages/playground/ssr-deps/require-absolute/index.js
+++ /dev/null
@@ -1,3 +0,0 @@
-const path = require('path')
-
-module.exports.hello = () => require(path.resolve(__dirname, './foo.js')).hello
diff --git a/packages/playground/ssr-deps/require-absolute/package.json b/packages/playground/ssr-deps/require-absolute/package.json
deleted file mode 100644
index 352f550e184745..00000000000000
--- a/packages/playground/ssr-deps/require-absolute/package.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "name": "require-absolute",
- "private": true,
- "version": "0.0.0"
-}
diff --git a/packages/playground/ssr-deps/server.js b/packages/playground/ssr-deps/server.js
deleted file mode 100644
index 89a64ae51fdc94..00000000000000
--- a/packages/playground/ssr-deps/server.js
+++ /dev/null
@@ -1,68 +0,0 @@
-// @ts-check
-const fs = require('fs')
-const path = require('path')
-const express = require('express')
-
-const isTest = process.env.NODE_ENV === 'test' || !!process.env.VITE_TEST_BUILD
-
-async function createServer(
- root = process.cwd(),
- isProd = process.env.NODE_ENV === 'production'
-) {
- const resolve = (p) => path.resolve(__dirname, p)
-
- const app = express()
-
- /**
- * @type {import('vite').ViteDevServer}
- */
- const vite = await require('vite').createServer({
- root,
- logLevel: isTest ? 'error' : 'info',
- server: {
- middlewareMode: 'ssr',
- watch: {
- // During tests we edit the files too fast and sometimes chokidar
- // misses change events, so enforce polling for consistency
- usePolling: true,
- interval: 100
- }
- }
- })
- // use vite's connect instance as middleware
- app.use(vite.middlewares)
-
- app.use('*', async (req, res) => {
- try {
- const url = req.originalUrl
-
- let template
- template = fs.readFileSync(resolve('index.html'), 'utf-8')
- template = await vite.transformIndexHtml(url, template)
- const render = (await vite.ssrLoadModule('/src/app.js')).render
-
- const appHtml = await render(url, __dirname)
-
- const html = template.replace(``, appHtml)
-
- res.status(200).set({ 'Content-Type': 'text/html' }).end(html)
- } catch (e) {
- vite && vite.ssrFixStacktrace(e)
- console.log(e.stack)
- res.status(500).end(e.stack)
- }
- })
-
- return { app, vite }
-}
-
-if (!isTest) {
- createServer().then(({ app }) =>
- app.listen(3000, () => {
- console.log('http://localhost:3000')
- })
- )
-}
-
-// for test use
-exports.createServer = createServer
diff --git a/packages/playground/ssr-deps/src/app.js b/packages/playground/ssr-deps/src/app.js
deleted file mode 100644
index 9646cdcf2bf688..00000000000000
--- a/packages/playground/ssr-deps/src/app.js
+++ /dev/null
@@ -1,49 +0,0 @@
-import path from 'path'
-import readFileContent from 'read-file-content'
-import primitiveExport from 'primitive-export'
-import tsDefaultExport, { hello as tsNamedExport } from 'ts-transpiled-exports'
-import objectAssignedExports from 'object-assigned-exports'
-import forwardedExport from 'forwarded-export'
-import bcrypt from 'bcrypt'
-import definePropertiesExports from 'define-properties-exports'
-import definePropertyExports from 'define-property-exports'
-import onlyObjectAssignedExports from 'only-object-assigned-exports'
-import requireAbsolute from 'require-absolute'
-
-export async function render(url, rootDir) {
- let html = ''
-
- const encryptedMsg = await bcrypt.hash('Secret Message!', 10)
- html += `\nencrypted message: ${encryptedMsg}
`
-
- const fileContent = await readFileContent(path.resolve(rootDir, 'message'))
- html += `\nmsg read via fs/promises: ${fileContent}
`
-
- html += `\nmessage from primitive export: ${primitiveExport}
`
-
- const tsDefaultExportMessage = tsDefaultExport()
- html += `\nmessage from ts-default-export: ${tsDefaultExportMessage}
`
-
- const tsNamedExportMessage = tsNamedExport()
- html += `\nmessage from ts-named-export: ${tsNamedExportMessage}
`
-
- const objectAssignedExportsMessage = objectAssignedExports.hello()
- html += `\nmessage from object-assigned-exports: ${objectAssignedExportsMessage}
`
-
- const forwardedExportMessage = forwardedExport.hello()
- html += `\nmessage from forwarded-export: ${forwardedExportMessage}
`
-
- const definePropertiesExportsMsg = definePropertiesExports.hello()
- html += `\nmessage from define-properties-exports: ${definePropertiesExportsMsg}
`
-
- const definePropertyExportsMsg = definePropertyExports.hello()
- html += `\nmessage from define-property-exports: ${definePropertyExportsMsg}
`
-
- const onlyObjectAssignedExportsMessage = onlyObjectAssignedExports.hello()
- html += `\nmessage from only-object-assigned-exports: ${onlyObjectAssignedExportsMessage}
`
-
- const requireAbsoluteMessage = requireAbsolute.hello()
- html += `\nmessage from require-absolute: ${requireAbsoluteMessage}
`
-
- return html + '\n'
-}
diff --git a/packages/playground/ssr-deps/ts-transpiled-exports/package.json b/packages/playground/ssr-deps/ts-transpiled-exports/package.json
deleted file mode 100644
index 7dbeff43974e42..00000000000000
--- a/packages/playground/ssr-deps/ts-transpiled-exports/package.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "name": "ts-transpiled-exports",
- "private": true,
- "version": "0.0.0"
-}
diff --git a/packages/playground/ssr-html/__tests__/serve.js b/packages/playground/ssr-html/__tests__/serve.js
deleted file mode 100644
index 5ba5724f2b7a94..00000000000000
--- a/packages/playground/ssr-html/__tests__/serve.js
+++ /dev/null
@@ -1,36 +0,0 @@
-// @ts-check
-// this is automtically detected by scripts/jestPerTestSetup.ts and will replace
-// the default e2e test serve behavior
-
-const path = require('path')
-
-const port = (exports.port = 9530)
-
-/**
- * @param {string} root
- * @param {boolean} isProd
- */
-exports.serve = async function serve(root, isProd) {
- const { createServer } = require(path.resolve(root, 'server.js'))
- const { app, vite } = await createServer(root, isProd)
-
- return new Promise((resolve, reject) => {
- try {
- const server = app.listen(port, () => {
- resolve({
- // for test teardown
- async close() {
- await new Promise((resolve) => {
- server.close(resolve)
- })
- if (vite) {
- await vite.close()
- }
- }
- })
- })
- } catch (e) {
- reject(e)
- }
- })
-}
diff --git a/packages/playground/ssr-html/__tests__/ssr-html.spec.ts b/packages/playground/ssr-html/__tests__/ssr-html.spec.ts
deleted file mode 100644
index e34b8a91fc3421..00000000000000
--- a/packages/playground/ssr-html/__tests__/ssr-html.spec.ts
+++ /dev/null
@@ -1,39 +0,0 @@
-import { port } from './serve'
-import fetch from 'node-fetch'
-
-const url = `http://localhost:${port}`
-
-describe('injected inline scripts', () => {
- test('no injected inline scripts are present', async () => {
- await page.goto(url)
- const inlineScripts = await page.$$eval('script', (nodes) =>
- nodes.filter((n) => !n.getAttribute('src') && n.innerHTML)
- )
- expect(inlineScripts).toHaveLength(0)
- })
-
- test('injected script proxied correctly', async () => {
- await page.goto(url)
- const proxiedScripts = await page.$$eval('script', (nodes) =>
- nodes
- .filter((n) => {
- const src = n.getAttribute('src')
- if (!src) return false
- return src.includes('?html-proxy&index')
- })
- .map((n) => n.getAttribute('src'))
- )
-
- // assert at least 1 proxied script exists
- expect(proxiedScripts).not.toHaveLength(0)
-
- const scriptContents = await Promise.all(
- proxiedScripts.map((src) => fetch(url + src).then((res) => res.text()))
- )
-
- // all proxied scripts return code
- for (const code of scriptContents) {
- expect(code).toBeTruthy()
- }
- })
-})
diff --git a/packages/playground/ssr-html/index.html b/packages/playground/ssr-html/index.html
deleted file mode 100644
index c37dcc7e366ae8..00000000000000
--- a/packages/playground/ssr-html/index.html
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
-
-
- SSR HTML
-
-
- SSR Dynamic HTML
-
-
diff --git a/packages/playground/ssr-html/package.json b/packages/playground/ssr-html/package.json
deleted file mode 100644
index a14756422a8b28..00000000000000
--- a/packages/playground/ssr-html/package.json
+++ /dev/null
@@ -1,15 +0,0 @@
-{
- "name": "test-ssr-html",
- "private": true,
- "version": "0.0.0",
- "scripts": {
- "dev": "node server",
- "serve": "cross-env NODE_ENV=production node server",
- "debug": "node --inspect-brk server"
- },
- "dependencies": {},
- "devDependencies": {
- "cross-env": "^7.0.3",
- "express": "^4.17.1"
- }
-}
diff --git a/packages/playground/ssr-html/server.js b/packages/playground/ssr-html/server.js
deleted file mode 100644
index ad115f1be01163..00000000000000
--- a/packages/playground/ssr-html/server.js
+++ /dev/null
@@ -1,75 +0,0 @@
-// @ts-check
-const fs = require('fs')
-const path = require('path')
-const express = require('express')
-
-const isTest = process.env.NODE_ENV === 'test' || !!process.env.VITE_TEST_BUILD
-
-const DYNAMIC_SCRIPTS = `
-
-
-`
-
-async function createServer(
- root = process.cwd(),
- isProd = process.env.NODE_ENV === 'production'
-) {
- const resolve = (p) => path.resolve(__dirname, p)
-
- const app = express()
-
- /**
- * @type {import('vite').ViteDevServer}
- */
- let vite
- vite = await require('vite').createServer({
- root,
- logLevel: isTest ? 'error' : 'info',
- server: {
- middlewareMode: 'ssr',
- watch: {
- // During tests we edit the files too fast and sometimes chokidar
- // misses change events, so enforce polling for consistency
- usePolling: true,
- interval: 100
- }
- }
- })
- // use vite's connect instance as middleware
- app.use(vite.middlewares)
-
- app.use('*', async (req, res) => {
- try {
- let [url] = req.originalUrl.split('?')
- if (url.endsWith('/')) url += 'index.html'
-
- const htmlLoc = resolve(`.${url}`)
- let html = fs.readFileSync(htmlLoc, 'utf8')
- html = html.replace('', `${DYNAMIC_SCRIPTS}`)
- html = await vite.transformIndexHtml(url, html)
-
- res.status(200).set({ 'Content-Type': 'text/html' }).end(html)
- } catch (e) {
- vite && vite.ssrFixStacktrace(e)
- console.log(e.stack)
- res.status(500).end(e.stack)
- }
- })
-
- return { app, vite }
-}
-
-if (!isTest) {
- createServer().then(({ app }) =>
- app.listen(3000, () => {
- console.log('http://localhost:3000')
- })
- )
-}
-
-// for test use
-exports.createServer = createServer
diff --git a/packages/playground/ssr-pug/__tests__/serve.js b/packages/playground/ssr-pug/__tests__/serve.js
deleted file mode 100644
index 5ba5724f2b7a94..00000000000000
--- a/packages/playground/ssr-pug/__tests__/serve.js
+++ /dev/null
@@ -1,36 +0,0 @@
-// @ts-check
-// this is automtically detected by scripts/jestPerTestSetup.ts and will replace
-// the default e2e test serve behavior
-
-const path = require('path')
-
-const port = (exports.port = 9530)
-
-/**
- * @param {string} root
- * @param {boolean} isProd
- */
-exports.serve = async function serve(root, isProd) {
- const { createServer } = require(path.resolve(root, 'server.js'))
- const { app, vite } = await createServer(root, isProd)
-
- return new Promise((resolve, reject) => {
- try {
- const server = app.listen(port, () => {
- resolve({
- // for test teardown
- async close() {
- await new Promise((resolve) => {
- server.close(resolve)
- })
- if (vite) {
- await vite.close()
- }
- }
- })
- })
- } catch (e) {
- reject(e)
- }
- })
-}
diff --git a/packages/playground/ssr-pug/package.json b/packages/playground/ssr-pug/package.json
deleted file mode 100644
index e2282b20565c1b..00000000000000
--- a/packages/playground/ssr-pug/package.json
+++ /dev/null
@@ -1,15 +0,0 @@
-{
- "name": "test-ssr-pug",
- "private": true,
- "version": "0.0.0",
- "scripts": {
- "dev": "node server",
- "serve": "cross-env NODE_ENV=production node server",
- "debug": "node --inspect-brk server"
- },
- "devDependencies": {
- "cross-env": "^7.0.3",
- "express": "^4.17.1",
- "pug": "^3.0.2"
- }
-}
diff --git a/packages/playground/ssr-pug/server.js b/packages/playground/ssr-pug/server.js
deleted file mode 100644
index 3cea5c48dde00b..00000000000000
--- a/packages/playground/ssr-pug/server.js
+++ /dev/null
@@ -1,76 +0,0 @@
-// @ts-check
-const path = require('path')
-const pug = require('pug')
-const express = require('express')
-
-const isTest = process.env.NODE_ENV === 'test' || !!process.env.VITE_TEST_BUILD
-
-const DYNAMIC_SCRIPTS = `
-
-
-`
-
-async function createServer(
- root = process.cwd(),
- isProd = process.env.NODE_ENV === 'production'
-) {
- const resolve = (p) => path.resolve(__dirname, p)
-
- const app = express()
-
- /**
- * @type {import('vite').ViteDevServer}
- */
- let vite
- vite = await require('vite').createServer({
- root,
- logLevel: isTest ? 'error' : 'info',
- server: {
- middlewareMode: 'ssr',
- watch: {
- // During tests we edit the files too fast and sometimes chokidar
- // misses change events, so enforce polling for consistency
- usePolling: true,
- interval: 100
- }
- }
- })
- // use vite's connect instance as middleware
- app.use(vite.middlewares)
-
- app.use('*', async (req, res) => {
- try {
- let [url] = req.originalUrl.split('?')
- url = url.replace(/\.html$/, '.pug')
- if (url.endsWith('/')) url += 'index.pug'
-
- const htmlLoc = resolve(`.${url}`)
- let html = pug.renderFile(htmlLoc)
- html = html.replace('', `${DYNAMIC_SCRIPTS}`)
- html = await vite.transformIndexHtml(url, html)
-
- res.status(200).set({ 'Content-Type': 'text/html' }).end(html)
- } catch (e) {
- vite && vite.ssrFixStacktrace(e)
- console.log(e.stack)
- res.status(500).end(e.stack)
- }
- })
-
- return { app, vite }
-}
-
-if (!isTest) {
- createServer().then(({ app }) =>
- app.listen(3000, () => {
- console.log('http://localhost:3000')
- })
- )
-}
-
-// for test use
-exports.createServer = createServer
diff --git a/packages/playground/ssr-pug/src/app.js b/packages/playground/ssr-pug/src/app.js
deleted file mode 100644
index 5b0175bb863d70..00000000000000
--- a/packages/playground/ssr-pug/src/app.js
+++ /dev/null
@@ -1,3 +0,0 @@
-const p = document.createElement('p')
-p.innerHTML = '✅ Dynamically injected script from file'
-document.body.appendChild(p)
diff --git a/packages/playground/ssr-react/__tests__/serve.js b/packages/playground/ssr-react/__tests__/serve.js
deleted file mode 100644
index 1bc028c03dc27c..00000000000000
--- a/packages/playground/ssr-react/__tests__/serve.js
+++ /dev/null
@@ -1,62 +0,0 @@
-// @ts-check
-// this is automtically detected by scripts/jestPerTestSetup.ts and will replace
-// the default e2e test serve behavior
-
-const path = require('path')
-
-const port = (exports.port = 9528)
-
-/**
- * @param {string} root
- * @param {boolean} isProd
- */
-exports.serve = async function serve(root, isProd) {
- if (isProd) {
- // build first
- const { build } = require('vite')
- // client build
- await build({
- root,
- logLevel: 'silent', // exceptions are logged by Jest
- build: {
- target: 'esnext',
- minify: false,
- ssrManifest: true,
- outDir: 'dist/client'
- }
- })
- // server build
- await build({
- root,
- logLevel: 'silent',
- build: {
- target: 'esnext',
- ssr: 'src/entry-server.jsx',
- outDir: 'dist/server'
- }
- })
- }
-
- const { createServer } = require(path.resolve(root, 'server.js'))
- const { app, vite } = await createServer(root, isProd)
-
- return new Promise((resolve, reject) => {
- try {
- const server = app.listen(port, () => {
- resolve({
- // for test teardown
- async close() {
- await new Promise((resolve) => {
- server.close(resolve)
- })
- if (vite) {
- await vite.close()
- }
- }
- })
- })
- } catch (e) {
- reject(e)
- }
- })
-}
diff --git a/packages/playground/ssr-react/__tests__/ssr-react.spec.ts b/packages/playground/ssr-react/__tests__/ssr-react.spec.ts
deleted file mode 100644
index 2235d4ae4d0edf..00000000000000
--- a/packages/playground/ssr-react/__tests__/ssr-react.spec.ts
+++ /dev/null
@@ -1,64 +0,0 @@
-import { editFile, untilUpdated } from '../../testUtils'
-import { port } from './serve'
-import fetch from 'node-fetch'
-
-const url = `http://localhost:${port}`
-
-test('/env', async () => {
- await page.goto(url + '/env')
- expect(await page.textContent('h1')).toMatch('default message here')
-
- // raw http request
- const envHtml = await (await fetch(url + '/env')).text()
- expect(envHtml).toMatch('API_KEY_qwertyuiop')
-})
-
-test('/about', async () => {
- await page.goto(url + '/about')
- expect(await page.textContent('h1')).toMatch('About')
- // should not have hydration mismatch
- browserLogs.forEach((msg) => {
- expect(msg).not.toMatch('Expected server HTML')
- })
-
- // raw http request
- const aboutHtml = await (await fetch(url + '/about')).text()
- expect(aboutHtml).toMatch('About')
-})
-
-test('/', async () => {
- await page.goto(url)
- expect(await page.textContent('h1')).toMatch('Home')
- // should not have hydration mismatch
- browserLogs.forEach((msg) => {
- expect(msg).not.toMatch('Expected server HTML')
- })
-
- // raw http request
- const html = await (await fetch(url)).text()
- expect(html).toMatch('Home')
-})
-
-test('hmr', async () => {
- editFile('src/pages/Home.jsx', (code) =>
- code.replace('Home', 'changed')
- )
- await untilUpdated(() => page.textContent('h1'), 'changed')
-})
-
-test('client navigation', async () => {
- await untilUpdated(() => page.textContent('a[href="/about"]'), 'About')
- await page.click('a[href="/about"]')
- await untilUpdated(() => page.textContent('h1'), 'About')
- editFile('src/pages/About.jsx', (code) =>
- code.replace('About', 'changed')
- )
- await untilUpdated(() => page.textContent('h1'), 'changed')
-})
-
-test(`circular dependecies modules doesn't throw`, async () => {
- await page.goto(url)
- expect(await page.textContent('.circ-dep-init')).toMatch(
- 'circ-dep-init-a circ-dep-init-b'
- )
-})
diff --git a/packages/playground/ssr-react/index.html b/packages/playground/ssr-react/index.html
deleted file mode 100644
index 1c891c04355068..00000000000000
--- a/packages/playground/ssr-react/index.html
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
-
-
- Vite App
-
-
-
-
-
-
diff --git a/packages/playground/ssr-react/package.json b/packages/playground/ssr-react/package.json
deleted file mode 100644
index a05bcc08806f3b..00000000000000
--- a/packages/playground/ssr-react/package.json
+++ /dev/null
@@ -1,27 +0,0 @@
-{
- "name": "test-ssr-react",
- "private": true,
- "version": "0.0.0",
- "scripts": {
- "dev": "node server",
- "build": "npm run build:client && npm run build:server",
- "build:client": "vite build --outDir dist/client",
- "build:server": "vite build --ssr src/entry-server.jsx --outDir dist/server",
- "generate": "vite build --outDir dist/static && npm run build:server && node prerender",
- "serve": "cross-env NODE_ENV=production node server",
- "debug": "node --inspect-brk server"
- },
- "dependencies": {
- "react": "^17.0.2",
- "react-dom": "^17.0.2",
- "react-router": "^5.2.1",
- "react-router-dom": "^5.3.0"
- },
- "devDependencies": {
- "@vitejs/plugin-react": "workspace:*",
- "compression": "^1.7.4",
- "cross-env": "^7.0.3",
- "express": "^4.17.1",
- "serve-static": "^1.14.1"
- }
-}
diff --git a/packages/playground/ssr-react/prerender.js b/packages/playground/ssr-react/prerender.js
deleted file mode 100644
index ac88ef632ec6f5..00000000000000
--- a/packages/playground/ssr-react/prerender.js
+++ /dev/null
@@ -1,32 +0,0 @@
-// Pre-render the app into static HTML.
-// run `yarn generate` and then `dist/static` can be served as a static site.
-
-const fs = require('fs')
-const path = require('path')
-
-const toAbsolute = (p) => path.resolve(__dirname, p)
-
-const template = fs.readFileSync(toAbsolute('dist/static/index.html'), 'utf-8')
-const { render } = require('./dist/server/entry-server.js')
-
-// determine routes to pre-render from src/pages
-const routesToPrerender = fs
- .readdirSync(toAbsolute('src/pages'))
- .map((file) => {
- const name = file.replace(/\.jsx$/, '').toLowerCase()
- return name === 'home' ? `/` : `/${name}`
- })
-
-;(async () => {
- // pre-render each route...
- for (const url of routesToPrerender) {
- const context = {}
- const appHtml = await render(url, context)
-
- const html = template.replace(``, appHtml)
-
- const filePath = `dist/static${url === '/' ? '/index' : url}.html`
- fs.writeFileSync(toAbsolute(filePath), html)
- console.log('pre-rendered:', filePath)
- }
-})()
diff --git a/packages/playground/ssr-react/server.js b/packages/playground/ssr-react/server.js
deleted file mode 100644
index 1876439c18fa88..00000000000000
--- a/packages/playground/ssr-react/server.js
+++ /dev/null
@@ -1,96 +0,0 @@
-// @ts-check
-const fs = require('fs')
-const path = require('path')
-const express = require('express')
-
-const isTest = process.env.NODE_ENV === 'test' || !!process.env.VITE_TEST_BUILD
-
-process.env.MY_CUSTOM_SECRET = 'API_KEY_qwertyuiop'
-
-async function createServer(
- root = process.cwd(),
- isProd = process.env.NODE_ENV === 'production'
-) {
- const resolve = (p) => path.resolve(__dirname, p)
-
- const indexProd = isProd
- ? fs.readFileSync(resolve('dist/client/index.html'), 'utf-8')
- : ''
-
- const app = express()
-
- /**
- * @type {import('vite').ViteDevServer}
- */
- let vite
- if (!isProd) {
- vite = await require('vite').createServer({
- root,
- logLevel: isTest ? 'error' : 'info',
- server: {
- middlewareMode: 'ssr',
- watch: {
- // During tests we edit the files too fast and sometimes chokidar
- // misses change events, so enforce polling for consistency
- usePolling: true,
- interval: 100
- }
- }
- })
- // use vite's connect instance as middleware
- app.use(vite.middlewares)
- } else {
- app.use(require('compression')())
- app.use(
- require('serve-static')(resolve('dist/client'), {
- index: false
- })
- )
- }
-
- app.use('*', async (req, res) => {
- try {
- const url = req.originalUrl
-
- let template, render
- if (!isProd) {
- // always read fresh template in dev
- template = fs.readFileSync(resolve('index.html'), 'utf-8')
- template = await vite.transformIndexHtml(url, template)
- render = (await vite.ssrLoadModule('/src/entry-server.jsx')).render
- } else {
- template = indexProd
- render = require('./dist/server/entry-server.js').render
- }
-
- const context = {}
- const appHtml = render(url, context)
-
- if (context.url) {
- // Somewhere a `` was rendered
- return res.redirect(301, context.url)
- }
-
- const html = template.replace(``, appHtml)
-
- res.status(200).set({ 'Content-Type': 'text/html' }).end(html)
- } catch (e) {
- !isProd && vite.ssrFixStacktrace(e)
- console.log(e.stack)
- res.status(500).end(e.stack)
- }
- })
-
- return { app, vite }
-}
-
-if (!isTest) {
- createServer().then(({ app }) =>
- app.listen(3000, () => {
- console.log('http://localhost:3000')
- })
- )
-}
-
-// for test use
-exports.createServer = createServer
diff --git a/packages/playground/ssr-react/src/App.jsx b/packages/playground/ssr-react/src/App.jsx
deleted file mode 100644
index 1c598add666efb..00000000000000
--- a/packages/playground/ssr-react/src/App.jsx
+++ /dev/null
@@ -1,41 +0,0 @@
-import { Link, Route, Switch } from 'react-router-dom'
-
-// Auto generates routes from files under ./pages
-// https://vitejs.dev/guide/features.html#glob-import
-const pages = import.meta.globEager('./pages/*.jsx')
-
-const routes = Object.keys(pages).map((path) => {
- const name = path.match(/\.\/pages\/(.*)\.jsx$/)[1]
- return {
- name,
- path: name === 'Home' ? '/' : `/${name.toLowerCase()}`,
- component: pages[path].default
- }
-})
-
-export function App() {
- return (
- <>
-
-
- {routes.map(({ name, path }) => {
- return (
-
- {name}
-
- )
- })}
-
-
-
- {routes.map(({ path, component: RouteComp }) => {
- return (
-
-
-
- )
- })}
-
- >
- )
-}
diff --git a/packages/playground/ssr-react/src/add.js b/packages/playground/ssr-react/src/add.js
deleted file mode 100644
index a0e419e9cfcacf..00000000000000
--- a/packages/playground/ssr-react/src/add.js
+++ /dev/null
@@ -1,9 +0,0 @@
-import { multiply } from './multiply'
-
-export function add(a, b) {
- return a + b
-}
-
-export function addAndMultiply(a, b, c) {
- return multiply(add(a, b), c)
-}
diff --git a/packages/playground/ssr-react/src/entry-client.jsx b/packages/playground/ssr-react/src/entry-client.jsx
deleted file mode 100644
index 8757bdc929d0e4..00000000000000
--- a/packages/playground/ssr-react/src/entry-client.jsx
+++ /dev/null
@@ -1,10 +0,0 @@
-import ReactDOM from 'react-dom'
-import { BrowserRouter } from 'react-router-dom'
-import { App } from './App'
-
-ReactDOM.hydrate(
-
-
- ,
- document.getElementById('app')
-)
diff --git a/packages/playground/ssr-react/src/entry-server.jsx b/packages/playground/ssr-react/src/entry-server.jsx
deleted file mode 100644
index 56d4810d11ba3c..00000000000000
--- a/packages/playground/ssr-react/src/entry-server.jsx
+++ /dev/null
@@ -1,11 +0,0 @@
-import ReactDOMServer from 'react-dom/server'
-import { StaticRouter } from 'react-router-dom'
-import { App } from './App'
-
-export function render(url, context) {
- return ReactDOMServer.renderToString(
-
-
-
- )
-}
diff --git a/packages/playground/ssr-react/src/forked-deadlock/README.md b/packages/playground/ssr-react/src/forked-deadlock/README.md
deleted file mode 100644
index 798c8c429ee9e4..00000000000000
--- a/packages/playground/ssr-react/src/forked-deadlock/README.md
+++ /dev/null
@@ -1,51 +0,0 @@
-This test aim to check for a particular type of circular dependency that causes tricky deadlocks, **deadlocks with forked imports stack**
-
-```
-A -> B means: B is imported by A and B has A in its stack
-A ... B means: A is waiting for B to ssrLoadModule()
-
-H -> X ... Y
-H -> X -> Y ... B
-H -> A ... B
-H -> A -> B ... X
-```
-
-### Forked deadlock description:
-
-```
-[X] is waiting for [Y] to resolve
- ↑ ↳ is waiting for [A] to resolve
- │ ↳ is waiting for [B] to resolve
- │ ↳ is waiting for [X] to resolve
- └────────────────────────────────────────────────────────────────────────┘
-```
-
-This may seems a traditional deadlock, but the thing that makes this special is the import stack of each module:
-
-```
-[X] stack:
- [H]
-```
-
-```
-[Y] stack:
- [X]
- [H]
-```
-
-```
-[A] stack:
- [H]
-```
-
-```
-[B] stack:
- [A]
- [H]
-```
-
-Even if `[X]` is imported by `[B]`, `[B]` is not in `[X]`'s stack because it's imported by `[H]` in first place then it's stack is only composed by `[H]`. `[H]` **forks** the imports **stack** and this make hard to be found.
-
-### Fix description
-
-Vite, when imports `[X]`, should check whether `[X]` is already pending and if it is, it must check that, when it was imported in first place, the stack of `[X]` doesn't have any module in common with the current module; in this case `[B]` has the module `[H]` is common with `[X]` and i can assume that a deadlock is going to happen.
diff --git a/packages/playground/ssr-react/src/multiply.js b/packages/playground/ssr-react/src/multiply.js
deleted file mode 100644
index 94f43efbff58bd..00000000000000
--- a/packages/playground/ssr-react/src/multiply.js
+++ /dev/null
@@ -1,9 +0,0 @@
-import { add } from './add'
-
-export function multiply(a, b) {
- return a * b
-}
-
-export function multiplyAndAdd(a, b, c) {
- return add(multiply(a, b), c)
-}
diff --git a/packages/playground/ssr-react/src/pages/About.jsx b/packages/playground/ssr-react/src/pages/About.jsx
deleted file mode 100644
index 0fe4de69078504..00000000000000
--- a/packages/playground/ssr-react/src/pages/About.jsx
+++ /dev/null
@@ -1,12 +0,0 @@
-import { addAndMultiply } from '../add'
-import { multiplyAndAdd } from '../multiply'
-
-export default function About() {
- return (
- <>
- About
- {addAndMultiply(1, 2, 3)}
- {multiplyAndAdd(1, 2, 3)}
- >
- )
-}
diff --git a/packages/playground/ssr-react/src/pages/Env.jsx b/packages/playground/ssr-react/src/pages/Env.jsx
deleted file mode 100644
index 1102990f11c8cb..00000000000000
--- a/packages/playground/ssr-react/src/pages/Env.jsx
+++ /dev/null
@@ -1,7 +0,0 @@
-export default function Env() {
- let msg = 'default message here'
- try {
- msg = process.env.MY_CUSTOM_SECRET || msg
- } catch {}
- return {msg}
-}
diff --git a/packages/playground/ssr-react/src/pages/Home.jsx b/packages/playground/ssr-react/src/pages/Home.jsx
deleted file mode 100644
index d1f4944810cc98..00000000000000
--- a/packages/playground/ssr-react/src/pages/Home.jsx
+++ /dev/null
@@ -1,17 +0,0 @@
-import { addAndMultiply } from '../add'
-import { multiplyAndAdd } from '../multiply'
-import { commonModuleExport } from '../forked-deadlock/common-module'
-import { getValueAB } from '../circular-dep-init/circular-dep-init'
-
-export default function Home() {
- commonModuleExport()
-
- return (
- <>
- Home
- {addAndMultiply(1, 2, 3)}
- {multiplyAndAdd(1, 2, 3)}
- {getValueAB()}
- >
- )
-}
diff --git a/packages/playground/ssr-react/vite.config.js b/packages/playground/ssr-react/vite.config.js
deleted file mode 100644
index bcc1369313cc5a..00000000000000
--- a/packages/playground/ssr-react/vite.config.js
+++ /dev/null
@@ -1,11 +0,0 @@
-const react = require('@vitejs/plugin-react')
-
-/**
- * @type {import('vite').UserConfig}
- */
-module.exports = {
- plugins: [react()],
- build: {
- minify: false
- }
-}
diff --git a/packages/playground/ssr-vue/__tests__/serve.js b/packages/playground/ssr-vue/__tests__/serve.js
deleted file mode 100644
index 1e220fed9630e4..00000000000000
--- a/packages/playground/ssr-vue/__tests__/serve.js
+++ /dev/null
@@ -1,62 +0,0 @@
-// @ts-check
-// this is automtically detected by scripts/jestPerTestSetup.ts and will replace
-// the default e2e test serve behavior
-
-const path = require('path')
-
-const port = (exports.port = 9527)
-
-/**
- * @param {string} root
- * @param {boolean} isProd
- */
-exports.serve = async function serve(root, isProd) {
- if (isProd) {
- // build first
- const { build } = require('vite')
- // client build
- await build({
- root,
- logLevel: 'silent', // exceptions are logged by Jest
- build: {
- target: 'esnext',
- minify: false,
- ssrManifest: true,
- outDir: 'dist/client'
- }
- })
- // server build
- await build({
- root,
- logLevel: 'silent',
- build: {
- target: 'esnext',
- ssr: 'src/entry-server.js',
- outDir: 'dist/server'
- }
- })
- }
-
- const { createServer } = require(path.resolve(root, 'server.js'))
- const { app, vite } = await createServer(root, isProd)
-
- return new Promise((resolve, reject) => {
- try {
- const server = app.listen(port, () => {
- resolve({
- // for test teardown
- async close() {
- await new Promise((resolve) => {
- server.close(resolve)
- })
- if (vite) {
- await vite.close()
- }
- }
- })
- })
- } catch (e) {
- reject(e)
- }
- })
-}
diff --git a/packages/playground/ssr-vue/__tests__/ssr-vue.spec.ts b/packages/playground/ssr-vue/__tests__/ssr-vue.spec.ts
deleted file mode 100644
index 952e287a7f12aa..00000000000000
--- a/packages/playground/ssr-vue/__tests__/ssr-vue.spec.ts
+++ /dev/null
@@ -1,182 +0,0 @@
-import { editFile, getColor, isBuild, untilUpdated } from '../../testUtils'
-import { port } from './serve'
-import fetch from 'node-fetch'
-import { resolve } from 'path'
-
-const url = `http://localhost:${port}`
-
-test('vuex can be import succeed by named import', async () => {
- await page.goto(url + '/store')
- expect(await page.textContent('h1')).toMatch('bar')
-
- // raw http request
- const storeHtml = await (await fetch(url + '/store')).text()
- expect(storeHtml).toMatch('bar')
-})
-
-test('/about', async () => {
- await page.goto(url + '/about')
- expect(await page.textContent('h1')).toMatch('About')
- // should not have hydration mismatch
- browserLogs.forEach((msg) => {
- expect(msg).not.toMatch('mismatch')
- })
-
- // fetch sub route
- const aboutHtml = await (await fetch(url + '/about')).text()
- expect(aboutHtml).toMatch('About')
- if (isBuild) {
- // assert correct preload directive generation for async chunks and CSS
- expect(aboutHtml).not.toMatch(
- /link rel="modulepreload".*?href="\/assets\/Home\.\w{8}\.js"/
- )
- expect(aboutHtml).not.toMatch(
- /link rel="stylesheet".*?href="\/assets\/Home\.\w{8}\.css"/
- )
- expect(aboutHtml).toMatch(
- /link rel="modulepreload".*?href="\/assets\/About\.\w{8}\.js"/
- )
- expect(aboutHtml).toMatch(
- /link rel="stylesheet".*?href="\/assets\/About\.\w{8}\.css"/
- )
- }
-})
-
-test('/external', async () => {
- await page.goto(url + '/external')
- expect(await page.textContent('div')).toMatch(
- 'Example external component content'
- )
- // should not have hydration mismatch
- browserLogs.forEach((msg) => {
- expect(msg).not.toMatch('mismatch')
- })
-
- // fetch sub route
- const externalHtml = await (await fetch(url + '/external')).text()
- expect(externalHtml).toMatch('Example external component content')
- if (isBuild) {
- // assert correct preload directive generation for async chunks and CSS
- expect(externalHtml).not.toMatch(
- /link rel="modulepreload".*?href="\/assets\/Home\.\w{8}\.js"/
- )
- expect(externalHtml).not.toMatch(
- /link rel="stylesheet".*?href="\/assets\/Home\.\w{8}\.css"/
- )
- expect(externalHtml).toMatch(
- /link rel="modulepreload".*?href="\/assets\/External\.\w{8}\.js"/
- )
- }
-})
-
-test('/', async () => {
- await page.goto(url)
- expect(await page.textContent('h1')).toMatch('Home')
- // should not have hydration mismatch
- browserLogs.forEach((msg) => {
- expect(msg).not.toMatch('mismatch')
- })
-
- const html = await (await fetch(url)).text()
- expect(html).toMatch('Home')
- if (isBuild) {
- // assert correct preload directive generation for async chunks and CSS
- expect(html).toMatch(
- /link rel="modulepreload".*?href="\/assets\/Home\.\w{8}\.js"/
- )
- expect(html).toMatch(
- /link rel="stylesheet".*?href="\/assets\/Home\.\w{8}\.css"/
- )
- // JSX component preload registration
- expect(html).toMatch(
- /link rel="modulepreload".*?href="\/assets\/Foo\.\w{8}\.js"/
- )
- expect(html).toMatch(
- /link rel="stylesheet".*?href="\/assets\/Foo\.\w{8}\.css"/
- )
- expect(html).not.toMatch(
- /link rel="modulepreload".*?href="\/assets\/About\.\w{8}\.js"/
- )
- expect(html).not.toMatch(
- /link rel="stylesheet".*?href="\/assets\/About\.\w{8}\.css"/
- )
- }
-})
-
-test('css', async () => {
- if (isBuild) {
- expect(await getColor('h1')).toBe('green')
- expect(await getColor('.jsx')).toBe('blue')
- } else {
- // During dev, the CSS is loaded from async chunk and we may have to wait
- // when the test runs concurrently.
- await untilUpdated(() => getColor('h1'), 'green')
- await untilUpdated(() => getColor('.jsx'), 'blue')
- }
-})
-
-test('asset', async () => {
- // should have no 404s
- browserLogs.forEach((msg) => {
- expect(msg).not.toMatch('404')
- })
- const img = await page.$('img')
- expect(await img.getAttribute('src')).toMatch(
- isBuild ? /\/assets\/logo\.\w{8}\.png/ : '/src/assets/logo.png'
- )
-})
-
-test('jsx', async () => {
- expect(await page.textContent('.jsx')).toMatch('from JSX')
-})
-
-test('virtual module', async () => {
- expect(await page.textContent('.virtual')).toMatch('hi')
-})
-
-test('nested virtual module', async () => {
- expect(await page.textContent('.nested-virtual')).toMatch('[success]')
-})
-
-test('hydration', async () => {
- expect(await page.textContent('button')).toMatch('0')
- await page.click('button')
- expect(await page.textContent('button')).toMatch('1')
-})
-
-test('hmr', async () => {
- editFile('src/pages/Home.vue', (code) => code.replace('Home', 'changed'))
- await untilUpdated(() => page.textContent('h1'), 'changed')
-})
-
-test('client navigation', async () => {
- await untilUpdated(() => page.textContent('a[href="/about"]'), 'About')
- await page.click('a[href="/about"]')
- await untilUpdated(() => page.textContent('h1'), 'About')
- editFile('src/pages/About.vue', (code) => code.replace('About', 'changed'))
- await untilUpdated(() => page.textContent('h1'), 'changed')
- await page.click('a[href="/"]')
- await untilUpdated(() => page.textContent('a[href="/"]'), 'Home')
-})
-
-test('import.meta.url', async () => {
- await page.goto(url)
- expect(await page.textContent('.protocol')).toEqual('file:')
-})
-
-test('dynamic css file should be preloaded', async () => {
- if (isBuild) {
- await page.goto(url)
- const homeHtml = await (await fetch(url)).text()
- const re = /link rel="modulepreload".*?href="\/assets\/(Home\.\w{8}\.js)"/
- const filename = re.exec(homeHtml)[1]
- const manifest = require(resolve(
- process.cwd(),
- './packages/temp/ssr-vue/dist/client/ssr-manifest.json'
- ))
- const depFile = manifest[filename]
- for (const file of depFile) {
- expect(homeHtml).toMatch(file)
- }
- }
-})
diff --git a/packages/playground/ssr-vue/dep-import-type/deep/index.d.ts b/packages/playground/ssr-vue/dep-import-type/deep/index.d.ts
deleted file mode 100644
index 39df3b83f7e9b8..00000000000000
--- a/packages/playground/ssr-vue/dep-import-type/deep/index.d.ts
+++ /dev/null
@@ -1 +0,0 @@
-export interface Foo {}
diff --git a/packages/playground/ssr-vue/dep-import-type/package.json b/packages/playground/ssr-vue/dep-import-type/package.json
deleted file mode 100644
index 935f28eb7f7157..00000000000000
--- a/packages/playground/ssr-vue/dep-import-type/package.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "name": "dep-import-type",
- "private": true,
- "version": "0.0.0",
- "main": "index.js"
-}
diff --git a/packages/playground/ssr-vue/example-external-component/ExampleExternalComponent.vue b/packages/playground/ssr-vue/example-external-component/ExampleExternalComponent.vue
deleted file mode 100644
index 55f3cea40e0399..00000000000000
--- a/packages/playground/ssr-vue/example-external-component/ExampleExternalComponent.vue
+++ /dev/null
@@ -1,3 +0,0 @@
-
- Example external component content
-
diff --git a/packages/playground/ssr-vue/example-external-component/index.js b/packages/playground/ssr-vue/example-external-component/index.js
deleted file mode 100644
index 8fc72c3aee0652..00000000000000
--- a/packages/playground/ssr-vue/example-external-component/index.js
+++ /dev/null
@@ -1,3 +0,0 @@
-import ExampleExternalComponent from './ExampleExternalComponent.vue'
-
-export default ExampleExternalComponent
diff --git a/packages/playground/ssr-vue/example-external-component/package.json b/packages/playground/ssr-vue/example-external-component/package.json
deleted file mode 100644
index 302e7fd4d9ff05..00000000000000
--- a/packages/playground/ssr-vue/example-external-component/package.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "name": "example-external-component",
- "private": true,
- "version": "0.0.0",
- "main": "index.js"
-}
diff --git a/packages/playground/ssr-vue/index.html b/packages/playground/ssr-vue/index.html
deleted file mode 100644
index 17b46a2f7a2267..00000000000000
--- a/packages/playground/ssr-vue/index.html
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-
-
-
- Vite App
-
-
-
-
-
-
-
diff --git a/packages/playground/ssr-vue/package.json b/packages/playground/ssr-vue/package.json
deleted file mode 100644
index 4a385336a97603..00000000000000
--- a/packages/playground/ssr-vue/package.json
+++ /dev/null
@@ -1,31 +0,0 @@
-{
- "name": "test-ssr-vue",
- "private": true,
- "version": "0.0.0",
- "scripts": {
- "dev": "node server",
- "build": "npm run build:client && npm run build:server",
- "build:noExternal": "npm run build:client && npm run build:server:noExternal",
- "build:client": "vite build --ssrManifest --outDir dist/client",
- "build:server": "vite build --ssr src/entry-server.js --outDir dist/server",
- "build:server:noExternal": "vite build --config vite.config.noexternal.js --ssr src/entry-server.js --outDir dist/server",
- "generate": "vite build --ssrManifest --outDir dist/static && npm run build:server && node prerender",
- "serve": "cross-env NODE_ENV=production node server",
- "debug": "node --inspect-brk server"
- },
- "dependencies": {
- "example-external-component": "file:example-external-component",
- "vue": "^3.2.25",
- "vue-router": "^4.0.0",
- "vuex": "^4.0.2"
- },
- "devDependencies": {
- "@vitejs/plugin-vue": "workspace:*",
- "@vitejs/plugin-vue-jsx": "workspace:*",
- "compression": "^1.7.4",
- "cross-env": "^7.0.3",
- "dep-import-type": "link:./dep-import-type",
- "express": "^4.17.1",
- "serve-static": "^1.14.1"
- }
-}
diff --git a/packages/playground/ssr-vue/prerender.js b/packages/playground/ssr-vue/prerender.js
deleted file mode 100644
index c4158dbe3357a9..00000000000000
--- a/packages/playground/ssr-vue/prerender.js
+++ /dev/null
@@ -1,37 +0,0 @@
-// Pre-render the app into static HTML.
-// run `npm run generate` and then `dist/static` can be served as a static site.
-
-const fs = require('fs')
-const path = require('path')
-
-const toAbsolute = (p) => path.resolve(__dirname, p)
-
-const manifest = require('./dist/static/ssr-manifest.json')
-const template = fs.readFileSync(toAbsolute('dist/static/index.html'), 'utf-8')
-const { render } = require('./dist/server/entry-server.js')
-
-// determine routes to pre-render from src/pages
-const routesToPrerender = fs
- .readdirSync(toAbsolute('src/pages'))
- .map((file) => {
- const name = file.replace(/\.vue$/, '').toLowerCase()
- return name === 'home' ? `/` : `/${name}`
- })
-
-;(async () => {
- // pre-render each route...
- for (const url of routesToPrerender) {
- const [appHtml, preloadLinks] = await render(url, manifest)
-
- const html = template
- .replace(``, preloadLinks)
- .replace(``, appHtml)
-
- const filePath = `dist/static${url === '/' ? '/index' : url}.html`
- fs.writeFileSync(toAbsolute(filePath), html)
- console.log('pre-rendered:', filePath)
- }
-
- // done, delete ssr manifest
- fs.unlinkSync(toAbsolute('dist/static/ssr-manifest.json'))
-})()
diff --git a/packages/playground/ssr-vue/server.js b/packages/playground/ssr-vue/server.js
deleted file mode 100644
index 642f274647294f..00000000000000
--- a/packages/playground/ssr-vue/server.js
+++ /dev/null
@@ -1,95 +0,0 @@
-// @ts-check
-const fs = require('fs')
-const path = require('path')
-const express = require('express')
-
-const isTest = process.env.NODE_ENV === 'test' || !!process.env.VITE_TEST_BUILD
-
-async function createServer(
- root = process.cwd(),
- isProd = process.env.NODE_ENV === 'production'
-) {
- const resolve = (p) => path.resolve(__dirname, p)
-
- const indexProd = isProd
- ? fs.readFileSync(resolve('dist/client/index.html'), 'utf-8')
- : ''
-
- const manifest = isProd
- ? // @ts-ignore
- require('./dist/client/ssr-manifest.json')
- : {}
-
- const app = express()
-
- /**
- * @type {import('vite').ViteDevServer}
- */
- let vite
- if (!isProd) {
- vite = await require('vite').createServer({
- root,
- logLevel: isTest ? 'error' : 'info',
- server: {
- middlewareMode: 'ssr',
- watch: {
- // During tests we edit the files too fast and sometimes chokidar
- // misses change events, so enforce polling for consistency
- usePolling: true,
- interval: 100
- }
- }
- })
- // use vite's connect instance as middleware
- app.use(vite.middlewares)
- } else {
- app.use(require('compression')())
- app.use(
- require('serve-static')(resolve('dist/client'), {
- index: false
- })
- )
- }
-
- app.use('*', async (req, res) => {
- try {
- const url = req.originalUrl
-
- let template, render
- if (!isProd) {
- // always read fresh template in dev
- template = fs.readFileSync(resolve('index.html'), 'utf-8')
- template = await vite.transformIndexHtml(url, template)
- render = (await vite.ssrLoadModule('/src/entry-server.js')).render
- } else {
- template = indexProd
- render = require('./dist/server/entry-server.js').render
- }
-
- const [appHtml, preloadLinks] = await render(url, manifest)
-
- const html = template
- .replace(``, preloadLinks)
- .replace(``, appHtml)
-
- res.status(200).set({ 'Content-Type': 'text/html' }).end(html)
- } catch (e) {
- vite && vite.ssrFixStacktrace(e)
- console.log(e.stack)
- res.status(500).end(e.stack)
- }
- })
-
- return { app, vite }
-}
-
-if (!isTest) {
- createServer().then(({ app }) =>
- app.listen(3000, () => {
- console.log('http://localhost:3000')
- })
- )
-}
-
-// for test use
-exports.createServer = createServer
diff --git a/packages/playground/ssr-vue/src/App.vue b/packages/playground/ssr-vue/src/App.vue
deleted file mode 100644
index dc8bfca16a59ab..00000000000000
--- a/packages/playground/ssr-vue/src/App.vue
+++ /dev/null
@@ -1,33 +0,0 @@
-
-
- Home |
- About
-
-
-
-
-
-
-
-
-
diff --git a/packages/playground/ssr-vue/src/assets/button.css b/packages/playground/ssr-vue/src/assets/button.css
deleted file mode 100644
index 8e1ebc58c0891f..00000000000000
--- a/packages/playground/ssr-vue/src/assets/button.css
+++ /dev/null
@@ -1,15 +0,0 @@
-.btn {
- background-color: #65b587;
- border-radius: 8px;
- border-style: none;
- box-sizing: border-box;
- cursor: pointer;
- display: inline-block;
- font-size: 14px;
- font-weight: 500;
- height: 40px;
- line-height: 20px;
- list-style: none;
- outline: none;
- padding: 10px 16px;
-}
diff --git a/packages/playground/ssr-vue/src/assets/fonts/Inter-Italic.woff b/packages/playground/ssr-vue/src/assets/fonts/Inter-Italic.woff
deleted file mode 100644
index e7da6663fe5e47..00000000000000
Binary files a/packages/playground/ssr-vue/src/assets/fonts/Inter-Italic.woff and /dev/null differ
diff --git a/packages/playground/ssr-vue/src/assets/fonts/Inter-Italic.woff2 b/packages/playground/ssr-vue/src/assets/fonts/Inter-Italic.woff2
deleted file mode 100644
index 8559dfde38986e..00000000000000
Binary files a/packages/playground/ssr-vue/src/assets/fonts/Inter-Italic.woff2 and /dev/null differ
diff --git a/packages/playground/ssr-vue/src/assets/logo.png b/packages/playground/ssr-vue/src/assets/logo.png
deleted file mode 100644
index f3d2503fc2a44b..00000000000000
Binary files a/packages/playground/ssr-vue/src/assets/logo.png and /dev/null differ
diff --git a/packages/playground/ssr-vue/src/components/Foo.jsx b/packages/playground/ssr-vue/src/components/Foo.jsx
deleted file mode 100644
index 427815b2d252d2..00000000000000
--- a/packages/playground/ssr-vue/src/components/Foo.jsx
+++ /dev/null
@@ -1,10 +0,0 @@
-import { defineComponent } from 'vue'
-import './foo.css'
-
-// named exports w/ variable declaration: ok
-export const Foo = defineComponent({
- name: 'foo',
- setup() {
- return () => from JSX
- }
-})
diff --git a/packages/playground/ssr-vue/src/components/ImportType.vue b/packages/playground/ssr-vue/src/components/ImportType.vue
deleted file mode 100644
index 144d36bc34e7ec..00000000000000
--- a/packages/playground/ssr-vue/src/components/ImportType.vue
+++ /dev/null
@@ -1,8 +0,0 @@
-
- import type should be removed without side-effect
-
-
-
diff --git a/packages/playground/ssr-vue/src/components/button.js b/packages/playground/ssr-vue/src/components/button.js
deleted file mode 100644
index 3b39f53fd96c47..00000000000000
--- a/packages/playground/ssr-vue/src/components/button.js
+++ /dev/null
@@ -1,16 +0,0 @@
-import { createVNode, defineComponent } from 'vue'
-import '../assets/button.css'
-
-export default defineComponent({
- setup() {
- return () => {
- return createVNode(
- 'div',
- {
- class: 'btn'
- },
- 'dynamicBtn'
- )
- }
- }
-})
diff --git a/packages/playground/ssr-vue/src/components/foo.css b/packages/playground/ssr-vue/src/components/foo.css
deleted file mode 100644
index f8baa0d15b90d3..00000000000000
--- a/packages/playground/ssr-vue/src/components/foo.css
+++ /dev/null
@@ -1,3 +0,0 @@
-.jsx {
- color: blue;
-}
diff --git a/packages/playground/ssr-vue/src/entry-client.js b/packages/playground/ssr-vue/src/entry-client.js
deleted file mode 100644
index 842acce7dc685b..00000000000000
--- a/packages/playground/ssr-vue/src/entry-client.js
+++ /dev/null
@@ -1,8 +0,0 @@
-import { createApp } from './main'
-
-const { app, router } = createApp()
-
-// wait until router is ready before mounting to ensure hydration match
-router.isReady().then(() => {
- app.mount('#app')
-})
diff --git a/packages/playground/ssr-vue/src/entry-server.js b/packages/playground/ssr-vue/src/entry-server.js
deleted file mode 100644
index 0f4e47711c17a1..00000000000000
--- a/packages/playground/ssr-vue/src/entry-server.js
+++ /dev/null
@@ -1,69 +0,0 @@
-import { createApp } from './main'
-import { renderToString } from 'vue/server-renderer'
-import path, { basename } from 'path'
-
-export async function render(url, manifest) {
- const { app, router } = createApp()
-
- // set the router to the desired URL before rendering
- router.push(url)
- await router.isReady()
-
- // passing SSR context object which will be available via useSSRContext()
- // @vitejs/plugin-vue injects code into a component's setup() that registers
- // itself on ctx.modules. After the render, ctx.modules would contain all the
- // components that have been instantiated during this render call.
- const ctx = {}
- const html = await renderToString(app, ctx)
-
- // the SSR manifest generated by Vite contains module -> chunk/asset mapping
- // which we can then use to determine what files need to be preloaded for this
- // request.
- const preloadLinks = renderPreloadLinks(ctx.modules, manifest)
- return [html, preloadLinks]
-}
-
-function renderPreloadLinks(modules, manifest) {
- let links = ''
- const seen = new Set()
- modules.forEach((id) => {
- const files = manifest[id]
- if (files) {
- files.forEach((file) => {
- if (!seen.has(file)) {
- seen.add(file)
- const filename = basename(file)
- if (manifest[filename]) {
- for (const depFile of manifest[filename]) {
- links += renderPreloadLink(depFile)
- seen.add(depFile)
- }
- }
- links += renderPreloadLink(file)
- }
- })
- }
- })
- return links
-}
-
-function renderPreloadLink(file) {
- if (file.endsWith('.js')) {
- return ` `
- } else if (file.endsWith('.css')) {
- return ` `
- } else if (file.endsWith('.woff')) {
- return ` `
- } else if (file.endsWith('.woff2')) {
- return ` `
- } else if (file.endsWith('.gif')) {
- return ` `
- } else if (file.endsWith('.jpg') || file.endsWith('.jpeg')) {
- return ` `
- } else if (file.endsWith('.png')) {
- return ` `
- } else {
- // TODO
- return ''
- }
-}
diff --git a/packages/playground/ssr-vue/src/main.js b/packages/playground/ssr-vue/src/main.js
deleted file mode 100644
index dbf4287b0baf3c..00000000000000
--- a/packages/playground/ssr-vue/src/main.js
+++ /dev/null
@@ -1,13 +0,0 @@
-import App from './App.vue'
-import { createSSRApp } from 'vue'
-import { createRouter } from './router'
-
-// SSR requires a fresh app instance per request, therefore we export a function
-// that creates a fresh app instance. If using Vuex, we'd also be creating a
-// fresh store here.
-export function createApp() {
- const app = createSSRApp(App)
- const router = createRouter()
- app.use(router)
- return { app, router }
-}
diff --git a/packages/playground/ssr-vue/src/pages/About.vue b/packages/playground/ssr-vue/src/pages/About.vue
deleted file mode 100644
index 2c8589f7ff109a..00000000000000
--- a/packages/playground/ssr-vue/src/pages/About.vue
+++ /dev/null
@@ -1,30 +0,0 @@
-
- {{ msg }}
- {{ url }}
- CommonButton
-
-
-
-
-
diff --git a/packages/playground/ssr-vue/src/pages/External.vue b/packages/playground/ssr-vue/src/pages/External.vue
deleted file mode 100644
index ffdcd03b85be84..00000000000000
--- a/packages/playground/ssr-vue/src/pages/External.vue
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-
-
-
diff --git a/packages/playground/ssr-vue/src/pages/Home.vue b/packages/playground/ssr-vue/src/pages/Home.vue
deleted file mode 100644
index 32a33882cc2324..00000000000000
--- a/packages/playground/ssr-vue/src/pages/Home.vue
+++ /dev/null
@@ -1,51 +0,0 @@
-
- Home
-
-
-
- count is: {{ state.count }}
-
- msg from virtual module: {{ foo.msg }}
- this will be styled with a font-face
- {{ state.url }}
- {{ state.protocol }}
- msg from nested virtual module: {{ virtualMsg }}
- CommonButton
-
-
-
-
-
-
-
-
diff --git a/packages/playground/ssr-vue/src/pages/Store.vue b/packages/playground/ssr-vue/src/pages/Store.vue
deleted file mode 100644
index df4d6b302d8474..00000000000000
--- a/packages/playground/ssr-vue/src/pages/Store.vue
+++ /dev/null
@@ -1,24 +0,0 @@
-
- {{ foo }}
-
-
-
-
-
diff --git a/packages/playground/ssr-vue/src/router.js b/packages/playground/ssr-vue/src/router.js
deleted file mode 100644
index b80b76b0bf4e2a..00000000000000
--- a/packages/playground/ssr-vue/src/router.js
+++ /dev/null
@@ -1,26 +0,0 @@
-import {
- createMemoryHistory,
- createRouter as _createRouter,
- createWebHistory
-} from 'vue-router'
-
-// Auto generates routes from vue files under ./pages
-// https://vitejs.dev/guide/features.html#glob-import
-const pages = import.meta.glob('./pages/*.vue')
-
-const routes = Object.keys(pages).map((path) => {
- const name = path.match(/\.\/pages(.*)\.vue$/)[1].toLowerCase()
- return {
- path: name === '/home' ? '/' : name,
- component: pages[path] // () => import('./pages/*.vue')
- }
-})
-
-export function createRouter() {
- return _createRouter({
- // use appropriate history implementation for server/client
- // import.meta.env.SSR is injected by Vite.
- history: import.meta.env.SSR ? createMemoryHistory() : createWebHistory(),
- routes
- })
-}
diff --git a/packages/playground/ssr-vue/vite.config.js b/packages/playground/ssr-vue/vite.config.js
deleted file mode 100644
index 0adfa551b3b134..00000000000000
--- a/packages/playground/ssr-vue/vite.config.js
+++ /dev/null
@@ -1,49 +0,0 @@
-const vuePlugin = require('@vitejs/plugin-vue')
-const vueJsx = require('@vitejs/plugin-vue-jsx')
-const virtualFile = '@virtual-file'
-const virtualId = '\0' + virtualFile
-const nestedVirtualFile = '@nested-virtual-file'
-const nestedVirtualId = '\0' + nestedVirtualFile
-
-/**
- * @type {import('vite').UserConfig}
- */
-module.exports = {
- plugins: [
- vuePlugin(),
- vueJsx(),
- {
- name: 'virtual',
- resolveId(id) {
- if (id === '@foo') {
- return id
- }
- },
- load(id) {
- if (id === '@foo') {
- return `export default { msg: 'hi' }`
- }
- }
- },
- {
- name: 'virtual-module',
- resolveId(id) {
- if (id === virtualFile) {
- return virtualId
- } else if (id === nestedVirtualFile) {
- return nestedVirtualId
- }
- },
- load(id) {
- if (id === virtualId) {
- return `export { msg } from "@nested-virtual-file";`
- } else if (id === nestedVirtualId) {
- return `export const msg = "[success] from conventional virtual file"`
- }
- }
- }
- ],
- build: {
- minify: false
- }
-}
diff --git a/packages/playground/ssr-vue/vite.config.noexternal.js b/packages/playground/ssr-vue/vite.config.noexternal.js
deleted file mode 100644
index ac74bf1430e94e..00000000000000
--- a/packages/playground/ssr-vue/vite.config.noexternal.js
+++ /dev/null
@@ -1,22 +0,0 @@
-const config = require('./vite.config.js')
-/**
- * @type {import('vite').UserConfig}
- */
-module.exports = Object.assign(config, {
- ssr: {
- noExternal: /./
- },
- resolve: {
- // necessary because vue.ssrUtils is only exported on cjs modules
- alias: [
- {
- find: '@vue/runtime-dom',
- replacement: '@vue/runtime-dom/dist/runtime-dom.cjs.js'
- },
- {
- find: '@vue/runtime-core',
- replacement: '@vue/runtime-core/dist/runtime-core.cjs.js'
- }
- ]
- }
-})
diff --git a/packages/playground/ssr-webworker/__tests__/serve.js b/packages/playground/ssr-webworker/__tests__/serve.js
deleted file mode 100644
index f4f207b85026c6..00000000000000
--- a/packages/playground/ssr-webworker/__tests__/serve.js
+++ /dev/null
@@ -1,48 +0,0 @@
-// @ts-check
-// this is automtically detected by scripts/jestPerTestSetup.ts and will replace
-// the default e2e test serve behavior
-
-const path = require('path')
-
-const port = (exports.port = 9528)
-
-/**
- * @param {string} root
- * @param {boolean} isProd
- */
-exports.serve = async function serve(root, isProd) {
- // we build first, regardless of whether it's prod/build mode
- // because Vite doesn't support the concept of a "webworker server"
- const { build } = require('vite')
-
- // worker build
- await build({
- root,
- logLevel: 'silent',
- build: {
- target: 'esnext',
- ssr: 'src/entry-worker.jsx',
- outDir: 'dist/worker'
- }
- })
-
- const { createServer } = require(path.resolve(root, 'worker.js'))
- const { app } = await createServer(root, isProd)
-
- return new Promise((resolve, reject) => {
- try {
- const server = app.listen(port, () => {
- resolve({
- // for test teardown
- async close() {
- await new Promise((resolve) => {
- server.close(resolve)
- })
- }
- })
- })
- } catch (e) {
- reject(e)
- }
- })
-}
diff --git a/packages/playground/ssr-webworker/__tests__/ssr-webworker.spec.ts b/packages/playground/ssr-webworker/__tests__/ssr-webworker.spec.ts
deleted file mode 100644
index 30d2bb93e495b1..00000000000000
--- a/packages/playground/ssr-webworker/__tests__/ssr-webworker.spec.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-import { port } from './serve'
-
-const url = `http://localhost:${port}`
-
-test('/', async () => {
- await page.goto(url + '/')
- expect(await page.textContent('h1')).toMatch('hello from webworker')
- expect(await page.textContent('.linked')).toMatch('dep from upper directory')
- expect(await page.textContent('.external')).toMatch('object')
-})
diff --git a/packages/playground/ssr-webworker/package.json b/packages/playground/ssr-webworker/package.json
deleted file mode 100644
index a7ebdf27ea22aa..00000000000000
--- a/packages/playground/ssr-webworker/package.json
+++ /dev/null
@@ -1,16 +0,0 @@
-{
- "name": "test-ssr-webworker",
- "private": true,
- "version": "0.0.0",
- "scripts": {
- "dev": "DEV=1 node worker",
- "build:worker": "vite build --ssr src/entry-worker.jsx --outDir dist/worker"
- },
- "dependencies": {
- "react": "^17.0.2"
- },
- "devDependencies": {
- "miniflare": "^1.4.1",
- "resolve-linked": "workspace:*"
- }
-}
diff --git a/packages/playground/ssr-webworker/src/entry-worker.jsx b/packages/playground/ssr-webworker/src/entry-worker.jsx
deleted file mode 100644
index c885657b18a6d3..00000000000000
--- a/packages/playground/ssr-webworker/src/entry-worker.jsx
+++ /dev/null
@@ -1,19 +0,0 @@
-import { msg as linkedMsg } from 'resolve-linked'
-import React from 'react'
-
-addEventListener('fetch', function (event) {
- return event.respondWith(
- new Response(
- `
- hello from webworker
- ${linkedMsg}
- ${typeof React}
- `,
- {
- headers: {
- 'content-type': 'text/html'
- }
- }
- )
- )
-})
diff --git a/packages/playground/ssr-webworker/vite.config.js b/packages/playground/ssr-webworker/vite.config.js
deleted file mode 100644
index 80cc1784cdc565..00000000000000
--- a/packages/playground/ssr-webworker/vite.config.js
+++ /dev/null
@@ -1,26 +0,0 @@
-/**
- * @type {import('vite').UserConfig}
- */
-module.exports = {
- build: {
- minify: false
- },
- resolve: {
- dedupe: ['react']
- },
- ssr: {
- target: 'webworker',
- noExternal: true
- },
- plugins: [
- {
- config() {
- return {
- ssr: {
- noExternal: ['this-should-not-replace-the-boolean']
- }
- }
- }
- }
- ]
-}
diff --git a/packages/playground/ssr-webworker/worker.js b/packages/playground/ssr-webworker/worker.js
deleted file mode 100644
index 09725aaa9d71bb..00000000000000
--- a/packages/playground/ssr-webworker/worker.js
+++ /dev/null
@@ -1,26 +0,0 @@
-// @ts-check
-const path = require('path')
-const { Miniflare } = require('miniflare')
-
-const isDev = process.env.DEV
-
-async function createServer(root = process.cwd()) {
- const mf = new Miniflare({
- scriptPath: path.resolve(root, 'dist/worker/entry-worker.js')
- })
-
- const app = mf.createServer()
-
- return { app }
-}
-
-if (isDev) {
- createServer().then(({ app }) =>
- app.listen(3000, () => {
- console.log('http://localhost:3000')
- })
- )
-}
-
-// for test use
-exports.createServer = createServer
diff --git a/packages/playground/tailwind/__test__/tailwind.spec.ts b/packages/playground/tailwind/__test__/tailwind.spec.ts
deleted file mode 100644
index 47f6b7ccf49037..00000000000000
--- a/packages/playground/tailwind/__test__/tailwind.spec.ts
+++ /dev/null
@@ -1,60 +0,0 @@
-import { isBuild, editFile, untilUpdated, getColor } from '../../testUtils'
-
-test('should render', async () => {
- expect(await page.textContent('#pagetitle')).toBe('|Page title|')
-})
-
-if (!isBuild) {
- test('regenerate CSS and HMR (glob pattern)', async () => {
- browserLogs.length = 0
- const el = await page.$('#pagetitle')
- const el2 = await page.$('#helloroot')
-
- expect(await getColor(el)).toBe('rgb(11, 22, 33)')
-
- editFile('src/views/Page.vue', (code) =>
- code.replace('|Page title|', '|Page title updated|')
- )
- await untilUpdated(() => el.textContent(), '|Page title updated|')
-
- expect(browserLogs).toMatchObject([
- '[vite] css hot updated: /index.css',
- '[vite] hot updated: /src/views/Page.vue'
- ])
-
- browserLogs.length = 0
-
- editFile('src/components/HelloWorld.vue', (code) =>
- code.replace('text-gray-800', 'text-[rgb(10,20,30)]')
- )
-
- await untilUpdated(() => getColor(el2), 'rgb(10, 20, 30)')
-
- expect(browserLogs).toMatchObject([
- '[vite] css hot updated: /index.css',
- '[vite] hot updated: /src/components/HelloWorld.vue'
- ])
-
- browserLogs.length = 0
- })
-
- test('regenerate CSS and HMR (relative path)', async () => {
- browserLogs.length = 0
- const el = await page.$('h1')
-
- expect(await getColor(el)).toBe('black')
-
- editFile('src/App.vue', (code) =>
- code.replace('text-black', 'text-[rgb(11,22,33)]')
- )
-
- await untilUpdated(() => getColor(el), 'rgb(11, 22, 33)')
-
- expect(browserLogs).toMatchObject([
- '[vite] css hot updated: /index.css',
- '[vite] hot updated: /src/App.vue'
- ])
-
- browserLogs.length = 0
- })
-}
diff --git a/packages/playground/tailwind/index.html b/packages/playground/tailwind/index.html
deleted file mode 100644
index 5165c9f6325cde..00000000000000
--- a/packages/playground/tailwind/index.html
+++ /dev/null
@@ -1,14 +0,0 @@
-
-
-
-
-
-
- Vite App
-
-
-
-
-
-
-
diff --git a/packages/playground/tailwind/package.json b/packages/playground/tailwind/package.json
deleted file mode 100644
index ff79908d386e96..00000000000000
--- a/packages/playground/tailwind/package.json
+++ /dev/null
@@ -1,20 +0,0 @@
-{
- "name": "test-tailwind",
- "private": true,
- "version": "0.0.0",
- "scripts": {
- "dev": "vite",
- "build": "vite build",
- "debug": "node --inspect-brk ../../vite/bin/vite",
- "preview": "vite preview"
- },
- "dependencies": {
- "autoprefixer": "^10.4.0",
- "tailwindcss": "^2.2.19",
- "vue": "^3.2.25",
- "vue-router": "^4.0.0"
- },
- "devDependencies": {
- "@vitejs/plugin-vue": "workspace:*"
- }
-}
diff --git a/packages/playground/tailwind/postcss.config.js b/packages/playground/tailwind/postcss.config.js
deleted file mode 100644
index b73493f7f96fae..00000000000000
--- a/packages/playground/tailwind/postcss.config.js
+++ /dev/null
@@ -1,7 +0,0 @@
-// postcss.config.js
-module.exports = {
- plugins: {
- tailwindcss: { config: __dirname + '/tailwind.config.js' },
- autoprefixer: {}
- }
-}
diff --git a/packages/playground/tailwind/public/favicon.ico b/packages/playground/tailwind/public/favicon.ico
deleted file mode 100644
index df36fcfb72584e..00000000000000
Binary files a/packages/playground/tailwind/public/favicon.ico and /dev/null differ
diff --git a/packages/playground/tailwind/src/App.vue b/packages/playground/tailwind/src/App.vue
deleted file mode 100644
index 25835fc414a06f..00000000000000
--- a/packages/playground/tailwind/src/App.vue
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
Tailwind app
- {{ foo }}
-
-
-
-
-
diff --git a/packages/playground/tailwind/src/assets/logo.png b/packages/playground/tailwind/src/assets/logo.png
deleted file mode 100644
index f3d2503fc2a44b..00000000000000
Binary files a/packages/playground/tailwind/src/assets/logo.png and /dev/null differ
diff --git a/packages/playground/tailwind/src/components/HelloWorld.vue b/packages/playground/tailwind/src/components/HelloWorld.vue
deleted file mode 100644
index 1ea9fdf2573697..00000000000000
--- a/packages/playground/tailwind/src/components/HelloWorld.vue
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
- HelloWorld - {{ count }}
-
-
-
-
diff --git a/packages/playground/tailwind/src/main.js b/packages/playground/tailwind/src/main.js
deleted file mode 100644
index 78494e75b4741d..00000000000000
--- a/packages/playground/tailwind/src/main.js
+++ /dev/null
@@ -1,6 +0,0 @@
-import { createApp } from 'vue'
-import App from './App.vue'
-import router from './router'
-// import '../index.css';
-
-createApp(App).use(router).mount('#app')
diff --git a/packages/playground/tailwind/src/router.ts b/packages/playground/tailwind/src/router.ts
deleted file mode 100644
index 32f1a47b40540d..00000000000000
--- a/packages/playground/tailwind/src/router.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-import { createWebHistory, createRouter } from 'vue-router'
-import Page from './views/Page.vue'
-
-const history = createWebHistory()
-
-const routeur = createRouter({
- history: history,
- routes: [
- {
- path: '/',
- component: Page
- }
- ]
-})
-
-export default routeur
diff --git a/packages/playground/tailwind/src/views/Page.vue b/packages/playground/tailwind/src/views/Page.vue
deleted file mode 100644
index 764a2a18e54fdb..00000000000000
--- a/packages/playground/tailwind/src/views/Page.vue
+++ /dev/null
@@ -1,28 +0,0 @@
-
-
-
|Page title|
-
{{ val }}
-
- Tailwind style
-
-
-
-
-
-
diff --git a/packages/playground/tailwind/tailwind.config.js b/packages/playground/tailwind/tailwind.config.js
deleted file mode 100644
index 64a2b1a2499bb4..00000000000000
--- a/packages/playground/tailwind/tailwind.config.js
+++ /dev/null
@@ -1,17 +0,0 @@
-module.exports = {
- mode: 'jit',
- purge: [
- // Before editing this section, make sure no paths are matching with `/src/App.vue`
- // Look https://github.com/vitejs/vite/pull/6959 for more details
- __dirname + '/src/{components,views}/**/*.vue',
- __dirname + '/src/App.vue'
- ],
- darkMode: false, // or 'media' or 'class'
- theme: {
- extend: {}
- },
- variants: {
- extend: {}
- },
- plugins: []
-}
diff --git a/packages/playground/tailwind/vite.config.ts b/packages/playground/tailwind/vite.config.ts
deleted file mode 100644
index e7c50bf3c3ae78..00000000000000
--- a/packages/playground/tailwind/vite.config.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-import { defineConfig } from 'vite'
-import vue from '@vitejs/plugin-vue'
-
-export default defineConfig({
- resolve: {
- alias: {
- '/@': __dirname
- }
- },
- plugins: [vue()],
- build: {
- // to make tests faster
- minify: false
- },
- server: {
- // This option caused issues with HMR,
- // although it should not affect the build
- origin: 'http://localhost:8080/'
- }
-})
diff --git a/packages/playground/testUtils.ts b/packages/playground/testUtils.ts
deleted file mode 100644
index 0c8186d4ed121d..00000000000000
--- a/packages/playground/testUtils.ts
+++ /dev/null
@@ -1,140 +0,0 @@
-// test utils used in e2e tests for playgrounds.
-// this can be directly imported in any playground tests as 'testUtils', e.g.
-// `import { getColor } from 'testUtils'`
-
-import fs from 'fs'
-import path from 'path'
-import colors from 'css-color-names'
-import type { ElementHandle } from 'playwright-chromium'
-import type { Manifest } from 'vite'
-
-export function slash(p: string): string {
- return p.replace(/\\/g, '/')
-}
-
-export const isBuild = !!process.env.VITE_TEST_BUILD
-
-const testPath = expect.getState().testPath
-const testName = slash(testPath).match(/playground\/([\w-]+)\//)?.[1]
-export const testDir = path.resolve(__dirname, '../../packages/temp', testName)
-export const workspaceRoot = path.resolve(__dirname, '../../')
-
-const hexToNameMap: Record = {}
-Object.keys(colors).forEach((color) => {
- hexToNameMap[colors[color]] = color
-})
-
-function componentToHex(c: number): string {
- const hex = c.toString(16)
- return hex.length === 1 ? '0' + hex : hex
-}
-
-function rgbToHex(rgb: string): string {
- const match = rgb.match(/rgb\((\d+),\s*(\d+),\s*(\d+)\)/)
- if (match) {
- const [_, rs, gs, bs] = match
- return (
- '#' +
- componentToHex(parseInt(rs, 10)) +
- componentToHex(parseInt(gs, 10)) +
- componentToHex(parseInt(bs, 10))
- )
- } else {
- return '#000000'
- }
-}
-
-const timeout = (n: number) => new Promise((r) => setTimeout(r, n))
-
-async function toEl(el: string | ElementHandle): Promise {
- if (typeof el === 'string') {
- return await page.$(el)
- }
- return el
-}
-
-export async function getColor(el: string | ElementHandle): Promise {
- el = await toEl(el)
- const rgb = await el.evaluate((el) => getComputedStyle(el as Element).color)
- return hexToNameMap[rgbToHex(rgb)] ?? rgb
-}
-
-export async function getBg(el: string | ElementHandle): Promise {
- el = await toEl(el)
- return el.evaluate((el) => getComputedStyle(el as Element).backgroundImage)
-}
-
-export async function getBgColor(el: string | ElementHandle): Promise {
- el = await toEl(el)
- return el.evaluate((el) => getComputedStyle(el as Element).backgroundColor)
-}
-
-export function readFile(filename: string): string {
- return fs.readFileSync(path.resolve(testDir, filename), 'utf-8')
-}
-
-export function editFile(
- filename: string,
- replacer: (str: string) => string,
- runInBuild: boolean = false
-): void {
- if (isBuild && !runInBuild) return
- filename = path.resolve(testDir, filename)
- const content = fs.readFileSync(filename, 'utf-8')
- const modified = replacer(content)
- fs.writeFileSync(filename, modified)
-}
-
-export function addFile(filename: string, content: string): void {
- fs.writeFileSync(path.resolve(testDir, filename), content)
-}
-
-export function removeFile(filename: string): void {
- fs.unlinkSync(path.resolve(testDir, filename))
-}
-
-export function listAssets(base = ''): string[] {
- const assetsDir = path.join(testDir, 'dist', base, 'assets')
- return fs.readdirSync(assetsDir)
-}
-
-export function findAssetFile(match: string | RegExp, base = ''): string {
- const assetsDir = path.join(testDir, 'dist', base, 'assets')
- const files = fs.readdirSync(assetsDir)
- const file = files.find((file) => {
- return file.match(match)
- })
- return file ? fs.readFileSync(path.resolve(assetsDir, file), 'utf-8') : ''
-}
-
-export function readManifest(base = ''): Manifest {
- return JSON.parse(
- fs.readFileSync(path.join(testDir, 'dist', base, 'manifest.json'), 'utf-8')
- )
-}
-
-/**
- * Poll a getter until the value it returns includes the expected value.
- */
-export async function untilUpdated(
- poll: () => string | Promise,
- expected: string,
- runInBuild = false
-): Promise {
- if (isBuild && !runInBuild) return
- const maxTries = process.env.CI ? 100 : 50
- for (let tries = 0; tries < maxTries; tries++) {
- const actual = (await poll()) ?? ''
- if (actual.indexOf(expected) > -1 || tries === maxTries - 1) {
- expect(actual).toMatch(expected)
- break
- } else {
- await timeout(50)
- }
- }
-}
-
-/**
- * Send the rebuild complete message in build watch
- */
-export { notifyRebuildComplete } from '../../scripts/jestPerTestSetup'
diff --git a/packages/playground/tsconfig-json-load-error/__tests__/tsconfig-json-load-error.spec.ts b/packages/playground/tsconfig-json-load-error/__tests__/tsconfig-json-load-error.spec.ts
deleted file mode 100644
index 699f658da6a255..00000000000000
--- a/packages/playground/tsconfig-json-load-error/__tests__/tsconfig-json-load-error.spec.ts
+++ /dev/null
@@ -1,50 +0,0 @@
-import { editFile, isBuild, readFile, untilUpdated } from '../../testUtils'
-
-if (isBuild) {
- test('should throw an error on build', () => {
- const buildError = beforeAllError
- expect(buildError).toBeTruthy()
- expect(buildError.message).toMatch(
- /^parsing .* failed: SyntaxError: Unexpected token } in JSON at position \d+$/
- )
- beforeAllError = null // got expected error, null it here so testsuite does not fail from rethrow in afterAll
- })
-
- test('should not output files to dist', () => {
- let err
- try {
- readFile('dist/index.html')
- } catch (e) {
- err = e
- }
- expect(err).toBeTruthy()
- expect(err.code).toBe('ENOENT')
- })
-} else {
- test('should log 500 error in browser for malformed tsconfig', () => {
- // don't test for actual complete message as this might be locale dependant. chrome does log 500 consistently though
- expect(browserLogs.find((x) => x.includes('500'))).toBeTruthy()
- expect(browserLogs).not.toContain('tsconfig error fixed, file loaded')
- })
-
- test('should show error overlay for tsconfig error', async () => {
- const errorOverlay = await page.waitForSelector('vite-error-overlay')
- expect(errorOverlay).toBeTruthy()
- const message = await errorOverlay.$$eval('.message-body', (m) => {
- return m[0].innerHTML
- })
- // use regex with variable filename and position values because they are different on win
- expect(message).toMatch(
- /^parsing .* failed: SyntaxError: Unexpected token } in JSON at position \d+$/
- )
- })
-
- test('should reload when tsconfig is changed', async () => {
- await editFile('has-error/tsconfig.json', (content) => {
- return content.replace('"compilerOptions":', '"compilerOptions":{}')
- })
- await untilUpdated(() => {
- return browserLogs.find((x) => x === 'tsconfig error fixed, file loaded')
- }, 'tsconfig error fixed, file loaded')
- })
-}
diff --git a/packages/playground/tsconfig-json-load-error/index.html b/packages/playground/tsconfig-json-load-error/index.html
deleted file mode 100644
index a59cd7cce619a7..00000000000000
--- a/packages/playground/tsconfig-json-load-error/index.html
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
-
-
- Vite App
-
-
-
-
-
-
diff --git a/packages/playground/tsconfig-json-load-error/package.json b/packages/playground/tsconfig-json-load-error/package.json
deleted file mode 100644
index b02c6e5ee5ab53..00000000000000
--- a/packages/playground/tsconfig-json-load-error/package.json
+++ /dev/null
@@ -1,11 +0,0 @@
-{
- "name": "tsconfig-json-load-error",
- "private": true,
- "version": "0.0.0",
- "scripts": {
- "dev": "vite",
- "build": "vite build",
- "debug": "node --inspect-brk ../../vite/bin/vite",
- "preview": "vite preview"
- }
-}
diff --git a/packages/playground/tsconfig-json-load-error/tsconfig.json b/packages/playground/tsconfig-json-load-error/tsconfig.json
deleted file mode 100644
index e91cdec493e28f..00000000000000
--- a/packages/playground/tsconfig-json-load-error/tsconfig.json
+++ /dev/null
@@ -1,20 +0,0 @@
-{
- "compilerOptions": {
- "target": "ESNext",
- "module": "ESNext",
- "lib": ["ESNext", "DOM"],
- "moduleResolution": "Node",
- "strict": true,
- "sourceMap": true,
- "resolveJsonModule": true,
- "esModuleInterop": true,
- "noEmit": true,
- "noUnusedLocals": true,
- "noUnusedParameters": true,
- "noImplicitReturns": true,
-
- "useDefineForClassFields": true,
- "importsNotUsedAsValues": "preserve"
- },
- "include": ["./src"]
-}
diff --git a/packages/playground/tsconfig-json/__tests__/tsconfig-json.spec.ts b/packages/playground/tsconfig-json/__tests__/tsconfig-json.spec.ts
deleted file mode 100644
index 0cd6af909f045b..00000000000000
--- a/packages/playground/tsconfig-json/__tests__/tsconfig-json.spec.ts
+++ /dev/null
@@ -1,68 +0,0 @@
-import path from 'path'
-import fs from 'fs'
-import { transformWithEsbuild } from 'vite'
-
-test('should respected each `tsconfig.json`s compilerOptions', () => {
- // main side effect should be called (because of `"importsNotUsedAsValues": "preserve"`)
- expect(browserLogs).toContain('main side effect')
- // main base setter should not be called (because of `"useDefineForClassFields": true"`)
- expect(browserLogs).not.toContain('data setter in MainBase')
-
- // nested side effect should not be called (because "importsNotUsedAsValues" is not set, defaults to "remove")
- expect(browserLogs).not.toContain('nested side effect')
- // nested base setter should be called (because of `"useDefineForClassFields": false"`)
- expect(browserLogs).toContain('data setter in NestedBase')
-
- // nested-with-extends side effect should be called (because "importsNotUsedAsValues" is extended from the main tsconfig.json, which is "preserve")
- expect(browserLogs).toContain('nested-with-extends side effect')
- // nested-with-extends base setter should be called (because of `"useDefineForClassFields": false"`)
- expect(browserLogs).toContain('data setter in NestedWithExtendsBase')
-})
-
-describe('transformWithEsbuild', () => {
- test('merge tsconfigRaw object', async () => {
- const main = path.resolve(__dirname, '../src/main.ts')
- const mainContent = fs.readFileSync(main, 'utf-8')
- const result = await transformWithEsbuild(mainContent, main, {
- tsconfigRaw: {
- compilerOptions: {
- useDefineForClassFields: false
- }
- }
- })
- // "importsNotUsedAsValues": "preserve" from tsconfig.json should still work
- expect(result.code).toContain('import "./not-used-type";')
- })
-
- test('overwrite tsconfigRaw string', async () => {
- const main = path.resolve(__dirname, '../src/main.ts')
- const mainContent = fs.readFileSync(main, 'utf-8')
- const result = await transformWithEsbuild(mainContent, main, {
- tsconfigRaw: `{
- "compilerOptions": {
- "useDefineForClassFields": false
- }
- }`
- })
- // "importsNotUsedAsValues": "preserve" from tsconfig.json should not be read
- // and defaults to "remove"
- expect(result.code).not.toContain('import "./not-used-type";')
- })
-
- test('preserveValueImports', async () => {
- const main = path.resolve(__dirname, '../src/main.ts')
- const mainContent = fs.readFileSync(main, 'utf-8')
- const result = await transformWithEsbuild(mainContent, main, {
- tsconfigRaw: {
- compilerOptions: {
- useDefineForClassFields: false,
- preserveValueImports: true
- }
- }
- })
- // "importsNotUsedAsValues": "preserve" from tsconfig.json should still work
- expect(result.code).toContain(
- 'import { MainTypeOnlyClass } from "./not-used-type";'
- )
- })
-})
diff --git a/packages/playground/tsconfig-json/index.html b/packages/playground/tsconfig-json/index.html
deleted file mode 100644
index a59cd7cce619a7..00000000000000
--- a/packages/playground/tsconfig-json/index.html
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
-
-
- Vite App
-
-
-
-
-
-
diff --git a/packages/playground/tsconfig-json/nested/tsconfig.json b/packages/playground/tsconfig-json/nested/tsconfig.json
deleted file mode 100644
index 23b6b8779f649a..00000000000000
--- a/packages/playground/tsconfig-json/nested/tsconfig.json
+++ /dev/null
@@ -1,21 +0,0 @@
-// prettier-ignore
-{
- "include": ["./"],
- "compilerOptions": {
- "target": "ESNext",
- "module": "ESNext",
- "lib": ["ESNext", "DOM"],
- "moduleResolution": "Node",
- "strict": true,
- "sourceMap": true,
- "resolveJsonModule": true,
- "esModuleInterop": true,
- "noEmit": true,
- "noUnusedLocals": true,
- "noUnusedParameters": true,
- "noImplicitReturns": true,
-
- /* tsconfig.json should support comments and trailing comma */
- "useDefineForClassFields": false,
- }
-}
diff --git a/packages/playground/tsconfig-json/package.json b/packages/playground/tsconfig-json/package.json
deleted file mode 100644
index c4248463facdb9..00000000000000
--- a/packages/playground/tsconfig-json/package.json
+++ /dev/null
@@ -1,11 +0,0 @@
-{
- "name": "tsconfig-json",
- "private": true,
- "version": "0.0.0",
- "scripts": {
- "dev": "vite",
- "build": "vite build",
- "debug": "node --inspect-brk ../../vite/bin/vite",
- "preview": "vite preview"
- }
-}
diff --git a/packages/playground/tsconfig-json/src/main.ts b/packages/playground/tsconfig-json/src/main.ts
deleted file mode 100644
index 6ae1fe03b7d023..00000000000000
--- a/packages/playground/tsconfig-json/src/main.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-// @ts-nocheck
-import '../nested/main'
-import '../nested-with-extends/main'
-
-// eslint-disable-next-line @typescript-eslint/consistent-type-imports
-import { MainTypeOnlyClass } from './not-used-type'
-
-class MainBase {
- set data(value: string) {
- console.log('data setter in MainBase')
- }
-}
-class MainDerived extends MainBase {
- // No longer triggers a 'console.log'
- // when using 'useDefineForClassFields'.
- data = 10
-
- foo?: MainTypeOnlyClass
-}
-
-const d = new MainDerived()
diff --git a/packages/playground/tsconfig-json/tsconfig.json b/packages/playground/tsconfig-json/tsconfig.json
deleted file mode 100644
index e91cdec493e28f..00000000000000
--- a/packages/playground/tsconfig-json/tsconfig.json
+++ /dev/null
@@ -1,20 +0,0 @@
-{
- "compilerOptions": {
- "target": "ESNext",
- "module": "ESNext",
- "lib": ["ESNext", "DOM"],
- "moduleResolution": "Node",
- "strict": true,
- "sourceMap": true,
- "resolveJsonModule": true,
- "esModuleInterop": true,
- "noEmit": true,
- "noUnusedLocals": true,
- "noUnusedParameters": true,
- "noImplicitReturns": true,
-
- "useDefineForClassFields": true,
- "importsNotUsedAsValues": "preserve"
- },
- "include": ["./src"]
-}
diff --git a/packages/playground/tsconfig.json b/packages/playground/tsconfig.json
deleted file mode 100644
index d60edb9f78c801..00000000000000
--- a/packages/playground/tsconfig.json
+++ /dev/null
@@ -1,14 +0,0 @@
-{
- "include": ["."],
- "exclude": ["**/dist/**"],
- "compilerOptions": {
- "target": "es2019",
- "outDir": "dist",
- "allowJs": true,
- "esModuleInterop": true,
- "moduleResolution": "node",
- "baseUrl": ".",
- "jsx": "preserve",
- "types": ["vite/client", "jest", "node"]
- }
-}
diff --git a/packages/playground/vue-jsx/Comp.tsx b/packages/playground/vue-jsx/Comp.tsx
deleted file mode 100644
index fe8add4d428a2c..00000000000000
--- a/packages/playground/vue-jsx/Comp.tsx
+++ /dev/null
@@ -1,14 +0,0 @@
-import { defineComponent, ref } from 'vue'
-
-const Default = defineComponent(() => {
- const count = ref(3)
- const inc = () => count.value++
-
- return () => (
-
- default tsx {count.value}
-
- )
-})
-
-export default Default
diff --git a/packages/playground/vue-jsx/Comps.jsx b/packages/playground/vue-jsx/Comps.jsx
deleted file mode 100644
index e5cc405a77581b..00000000000000
--- a/packages/playground/vue-jsx/Comps.jsx
+++ /dev/null
@@ -1,35 +0,0 @@
-import { defineComponent, ref } from 'vue'
-
-export const Named = defineComponent(() => {
- const count = ref(0)
- const inc = () => count.value++
-
- return () => (
-
- named {count.value}
-
- )
-})
-
-const NamedSpec = defineComponent(() => {
- const count = ref(1)
- const inc = () => count.value++
-
- return () => (
-
- named specifier {count.value}
-
- )
-})
-export { NamedSpec }
-
-export default defineComponent(() => {
- const count = ref(2)
- const inc = () => count.value++
-
- return () => (
-
- default {count.value}
-
- )
-})
diff --git a/packages/playground/vue-jsx/OtherExt.tesx b/packages/playground/vue-jsx/OtherExt.tesx
deleted file mode 100644
index 7ae585a014c566..00000000000000
--- a/packages/playground/vue-jsx/OtherExt.tesx
+++ /dev/null
@@ -1,9 +0,0 @@
-import { defineComponent } from 'vue'
-
-const Default = defineComponent(() => {
- return () => (
- Other Ext
- )
-})
-
-export default Default
diff --git a/packages/playground/vue-jsx/Query.jsx b/packages/playground/vue-jsx/Query.jsx
deleted file mode 100644
index 60de93eafb7b1c..00000000000000
--- a/packages/playground/vue-jsx/Query.jsx
+++ /dev/null
@@ -1,12 +0,0 @@
-import { defineComponent, ref } from 'vue'
-
-export default defineComponent(() => {
- const count = ref(6)
- const inc = () => count.value++
-
- return () => (
-
- import with query transform fail
-
- )
-})
diff --git a/packages/playground/vue-jsx/Script.vue b/packages/playground/vue-jsx/Script.vue
deleted file mode 100644
index 2689ed2dfe6ffb..00000000000000
--- a/packages/playground/vue-jsx/Script.vue
+++ /dev/null
@@ -1,14 +0,0 @@
-
diff --git a/packages/playground/vue-jsx/SrcImport.jsx b/packages/playground/vue-jsx/SrcImport.jsx
deleted file mode 100644
index dc775be205af73..00000000000000
--- a/packages/playground/vue-jsx/SrcImport.jsx
+++ /dev/null
@@ -1,12 +0,0 @@
-import { defineComponent, ref } from 'vue'
-
-export default defineComponent(() => {
- const count = ref(5)
- const inc = () => count.value++
-
- return () => (
-
- src import {count.value}
-
- )
-})
diff --git a/packages/playground/vue-jsx/SrcImport.vue b/packages/playground/vue-jsx/SrcImport.vue
deleted file mode 100644
index 89f6fb3eb77e2b..00000000000000
--- a/packages/playground/vue-jsx/SrcImport.vue
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/packages/playground/vue-jsx/__tests__/vue-jsx.spec.ts b/packages/playground/vue-jsx/__tests__/vue-jsx.spec.ts
deleted file mode 100644
index 999fdc19af51ec..00000000000000
--- a/packages/playground/vue-jsx/__tests__/vue-jsx.spec.ts
+++ /dev/null
@@ -1,115 +0,0 @@
-import { editFile, isBuild, untilUpdated } from 'testUtils'
-
-test('should render', async () => {
- expect(await page.textContent('.named')).toMatch('0')
- expect(await page.textContent('.named-specifier')).toMatch('1')
- expect(await page.textContent('.default')).toMatch('2')
- expect(await page.textContent('.default-tsx')).toMatch('3')
- expect(await page.textContent('.script')).toMatch('4')
- expect(await page.textContent('.src-import')).toMatch('5')
- expect(await page.textContent('.jsx-with-query')).toMatch('6')
- expect(await page.textContent('.other-ext')).toMatch('Other Ext')
-})
-
-test('should update', async () => {
- await page.click('.named')
- expect(await page.textContent('.named')).toMatch('1')
- await page.click('.named-specifier')
- expect(await page.textContent('.named-specifier')).toMatch('2')
- await page.click('.default')
- expect(await page.textContent('.default')).toMatch('3')
- await page.click('.default-tsx')
- expect(await page.textContent('.default-tsx')).toMatch('4')
- await page.click('.script')
- expect(await page.textContent('.script')).toMatch('5')
- await page.click('.src-import')
- expect(await page.textContent('.src-import')).toMatch('6')
- await page.click('.jsx-with-query')
- expect(await page.textContent('.jsx-with-query')).toMatch('7')
-})
-
-if (!isBuild) {
- test('hmr: named export', async () => {
- editFile('Comps.jsx', (code) =>
- code.replace('named {count', 'named updated {count')
- )
- await untilUpdated(() => page.textContent('.named'), 'named updated 0')
-
- // affect all components in same file
- expect(await page.textContent('.named-specifier')).toMatch('1')
- expect(await page.textContent('.default')).toMatch('2')
- // should not affect other components from different file
- expect(await page.textContent('.default-tsx')).toMatch('4')
- })
-
- test('hmr: named export via specifier', async () => {
- editFile('Comps.jsx', (code) =>
- code.replace('named specifier {count', 'named specifier updated {count')
- )
- await untilUpdated(
- () => page.textContent('.named-specifier'),
- 'named specifier updated 1'
- )
-
- // affect all components in same file
- expect(await page.textContent('.default')).toMatch('2')
- // should not affect other components on the page
- expect(await page.textContent('.default-tsx')).toMatch('4')
- })
-
- test('hmr: default export', async () => {
- editFile('Comps.jsx', (code) =>
- code.replace('default {count', 'default updated {count')
- )
- await untilUpdated(() => page.textContent('.default'), 'default updated 2')
-
- // should not affect other components on the page
- expect(await page.textContent('.default-tsx')).toMatch('4')
- })
-
- test('hmr: named export via specifier', async () => {
- // update another component
- await page.click('.named')
- expect(await page.textContent('.named')).toMatch('1')
-
- editFile('Comp.tsx', (code) =>
- code.replace('default tsx {count', 'default tsx updated {count')
- )
- await untilUpdated(
- () => page.textContent('.default-tsx'),
- 'default tsx updated 3'
- )
-
- // should not affect other components on the page
- expect(await page.textContent('.named')).toMatch('1')
- })
-
- test('hmr: script in .vue', async () => {
- editFile('Script.vue', (code) =>
- code.replace('script {count', 'script updated {count')
- )
- await untilUpdated(() => page.textContent('.script'), 'script updated 4')
-
- expect(await page.textContent('.src-import')).toMatch('6')
- })
-
- test('hmr: src import in .vue', async () => {
- await page.click('.script')
- editFile('SrcImport.jsx', (code) =>
- code.replace('src import {count', 'src import updated {count')
- )
- await untilUpdated(
- () => page.textContent('.src-import'),
- 'src import updated 5'
- )
-
- expect(await page.textContent('.script')).toMatch('5')
- })
-
- test('hmr: setup jsx in .vue', async () => {
- editFile('setup-syntax-jsx.vue', (code) =>
- code.replace('let count = ref(100)', 'let count = ref(1000)')
- )
- await untilUpdated(() => page.textContent('.setup-jsx'), '1000')
- })
-}
diff --git a/packages/playground/vue-jsx/index.html b/packages/playground/vue-jsx/index.html
deleted file mode 100644
index a285a008c13a9e..00000000000000
--- a/packages/playground/vue-jsx/index.html
+++ /dev/null
@@ -1,2 +0,0 @@
-
-
diff --git a/packages/playground/vue-jsx/main.jsx b/packages/playground/vue-jsx/main.jsx
deleted file mode 100644
index e304e7788e49e7..00000000000000
--- a/packages/playground/vue-jsx/main.jsx
+++ /dev/null
@@ -1,27 +0,0 @@
-import { createApp } from 'vue'
-import { Named, NamedSpec, default as Default } from './Comps'
-import { default as TsxDefault } from './Comp'
-import OtherExt from './OtherExt.tesx'
-import JsxScript from './Script.vue'
-import JsxSrcImport from './SrcImport.vue'
-import JsxSetupSyntax from './setup-syntax-jsx.vue'
-// eslint-disable-next-line
-import JsxWithQuery from './Query.jsx?query=true'
-
-function App() {
- return (
- <>
-
-
-
-
-
-
-
-
-
- >
- )
-}
-
-createApp(App).mount('#app')
diff --git a/packages/playground/vue-jsx/package.json b/packages/playground/vue-jsx/package.json
deleted file mode 100644
index 4b2135906b2833..00000000000000
--- a/packages/playground/vue-jsx/package.json
+++ /dev/null
@@ -1,18 +0,0 @@
-{
- "name": "test-vue-jsx",
- "private": true,
- "version": "0.0.0",
- "scripts": {
- "dev": "vite",
- "build": "vite build",
- "debug": "node --inspect-brk ../../vite/bin/vite",
- "preview": "vite preview"
- },
- "dependencies": {
- "vue": "^3.2.25"
- },
- "devDependencies": {
- "@vitejs/plugin-vue": "workspace:*",
- "@vitejs/plugin-vue-jsx": "workspace:*"
- }
-}
diff --git a/packages/playground/vue-jsx/setup-syntax-jsx.vue b/packages/playground/vue-jsx/setup-syntax-jsx.vue
deleted file mode 100644
index 0b16be7e773280..00000000000000
--- a/packages/playground/vue-jsx/setup-syntax-jsx.vue
+++ /dev/null
@@ -1,17 +0,0 @@
-
-
-
- {{ count }}
-
-
diff --git a/packages/playground/vue-jsx/vite.config.js b/packages/playground/vue-jsx/vite.config.js
deleted file mode 100644
index d6eb84e05f4e4a..00000000000000
--- a/packages/playground/vue-jsx/vite.config.js
+++ /dev/null
@@ -1,39 +0,0 @@
-const vueJsxPlugin = require('@vitejs/plugin-vue-jsx')
-const vuePlugin = require('@vitejs/plugin-vue')
-
-/**
- * @type {import('vite').UserConfig}
- */
-module.exports = {
- plugins: [
- vueJsxPlugin({
- include: [/\.tesx$/, /\.[jt]sx$/]
- }),
- vuePlugin(),
- {
- name: 'jsx-query-plugin',
- transform(code, id) {
- if (id.includes('?query=true')) {
- return `
-import { createVNode as _createVNode } from "vue";
-import { defineComponent, ref } from 'vue';
-export default defineComponent(() => {
- const count = ref(6);
-
- const inc = () => count.value++;
-
- return () => _createVNode("button", {
- "class": "jsx-with-query",
- "onClick": inc
- }, [count.value]);
-});
-`
- }
- }
- }
- ],
- build: {
- // to make tests faster
- minify: false
- }
-}
diff --git a/packages/playground/vue-lib/__tests__/serve.js b/packages/playground/vue-lib/__tests__/serve.js
deleted file mode 100644
index 73f89eee44ea3e..00000000000000
--- a/packages/playground/vue-lib/__tests__/serve.js
+++ /dev/null
@@ -1,7 +0,0 @@
-// @ts-check
-// this is automtically detected by scripts/jestPerTestSetup.ts and will replace
-// the default e2e test serve behavior
-
-exports.serve = async function serve() {
- // do nothing, skip default behavior
-}
diff --git a/packages/playground/vue-lib/__tests__/vue-lib.spec.ts b/packages/playground/vue-lib/__tests__/vue-lib.spec.ts
deleted file mode 100644
index 0504160f17d2f0..00000000000000
--- a/packages/playground/vue-lib/__tests__/vue-lib.spec.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-import { build } from 'vite'
-import path from 'path'
-import type { OutputChunk, RollupOutput } from 'rollup'
-
-describe('vue component library', () => {
- test('should output tree shakeable css module code', async () => {
- // Build lib
- await build({
- logLevel: 'silent',
- configFile: path.resolve(__dirname, '../vite.config.lib.ts')
- })
- // Build app
- const { output } = (await build({
- logLevel: 'silent',
- configFile: path.resolve(__dirname, '../vite.config.consumer.ts')
- })) as RollupOutput
- const { code } = output.find(
- (e) => e.type === 'chunk' && e.isEntry
- ) as OutputChunk
- // Unused css module should be treeshaked
- expect(code).toContain('styleA') // styleA is used by CompA
- expect(code).not.toContain('styleB') // styleB is not used
- })
-})
diff --git a/packages/playground/vue-lib/index.html b/packages/playground/vue-lib/index.html
deleted file mode 100644
index e016cf7d760797..00000000000000
--- a/packages/playground/vue-lib/index.html
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
diff --git a/packages/playground/vue-lib/package.json b/packages/playground/vue-lib/package.json
deleted file mode 100644
index df82cf48b62a9c..00000000000000
--- a/packages/playground/vue-lib/package.json
+++ /dev/null
@@ -1,16 +0,0 @@
-{
- "name": "test-vue-lib",
- "private": true,
- "version": "0.0.0",
- "scripts": {
- "dev-consumer": "vite --config ./vite.config.consumer.ts",
- "build-lib": "vite build --config ./vite.config.lib.ts",
- "build-consumer": "vite build --config ./vite.config.consumer.ts"
- },
- "dependencies": {
- "vue": "^3.2.25"
- },
- "devDependencies": {
- "@vitejs/plugin-vue": "workspace:*"
- }
-}
diff --git a/packages/playground/vue-lib/src-consumer/index.ts b/packages/playground/vue-lib/src-consumer/index.ts
deleted file mode 100644
index ac0f65e2a3ed9d..00000000000000
--- a/packages/playground/vue-lib/src-consumer/index.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-// @ts-ignore
-/* eslint-disable node/no-missing-import */
-import { CompA } from '../dist/lib/my-vue-lib.es'
-import '../dist/lib/style.css'
-import { createApp } from 'vue'
-
-const app = createApp(CompA)
-app.mount('#app')
diff --git a/packages/playground/vue-lib/src-lib/CompA.vue b/packages/playground/vue-lib/src-lib/CompA.vue
deleted file mode 100644
index dac9298b3bedf4..00000000000000
--- a/packages/playground/vue-lib/src-lib/CompA.vue
+++ /dev/null
@@ -1,8 +0,0 @@
-
- CompA
-
-
diff --git a/packages/playground/vue-lib/src-lib/CompB.vue b/packages/playground/vue-lib/src-lib/CompB.vue
deleted file mode 100644
index cca30168fb6753..00000000000000
--- a/packages/playground/vue-lib/src-lib/CompB.vue
+++ /dev/null
@@ -1,8 +0,0 @@
-
- CompB
-
-
diff --git a/packages/playground/vue-lib/src-lib/index.ts b/packages/playground/vue-lib/src-lib/index.ts
deleted file mode 100644
index f83abd4ec72118..00000000000000
--- a/packages/playground/vue-lib/src-lib/index.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-export { default as CompA } from './CompA.vue'
-export { default as CompB } from './CompB.vue'
diff --git a/packages/playground/vue-lib/vite.config.consumer.ts b/packages/playground/vue-lib/vite.config.consumer.ts
deleted file mode 100644
index 9e75b5cfbeabcb..00000000000000
--- a/packages/playground/vue-lib/vite.config.consumer.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-import { defineConfig } from 'vite'
-import vue from '@vitejs/plugin-vue'
-
-export default defineConfig({
- root: __dirname,
- build: {
- outDir: 'dist/consumer'
- },
- plugins: [vue()]
-})
diff --git a/packages/playground/vue-lib/vite.config.lib.ts b/packages/playground/vue-lib/vite.config.lib.ts
deleted file mode 100644
index a888382d008a8c..00000000000000
--- a/packages/playground/vue-lib/vite.config.lib.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-import path from 'path'
-import { defineConfig } from 'vite'
-import vue from '@vitejs/plugin-vue'
-
-export default defineConfig({
- root: __dirname,
- build: {
- outDir: 'dist/lib',
- lib: {
- entry: path.resolve(__dirname, 'src-lib/index.ts'),
- name: 'MyVueLib',
- formats: ['es'],
- fileName: (format) => `my-vue-lib.${format}.js`
- },
- rollupOptions: {
- external: ['vue'],
- output: {
- globals: { vue: 'Vue' }
- }
- }
- },
- plugins: [vue()]
-})
diff --git a/packages/playground/vue/Assets.vue b/packages/playground/vue/Assets.vue
deleted file mode 100644
index 875ac1b243b393..00000000000000
--- a/packages/playground/vue/Assets.vue
+++ /dev/null
@@ -1,42 +0,0 @@
-
- Template Static Asset Reference
-
- Relative
-
-
-
- Absolute
-
-
-
- Absolute import from public dir
-
-
-
- Relative URL in style
-
-
-
- SVG Fragment reference
-
-
-
-
-
diff --git a/packages/playground/vue/AsyncComponent.vue b/packages/playground/vue/AsyncComponent.vue
deleted file mode 100644
index 4e66630c4d2edd..00000000000000
--- a/packages/playground/vue/AsyncComponent.vue
+++ /dev/null
@@ -1,15 +0,0 @@
-
- Async Component
- Testing TLA and for await compatibility with esbuild
- ab == {{ test }}
-
-
-
diff --git a/packages/playground/vue/CssModules.vue b/packages/playground/vue/CssModules.vue
deleted file mode 100644
index f7897e2e57f652..00000000000000
--- a/packages/playground/vue/CssModules.vue
+++ /dev/null
@@ -1,23 +0,0 @@
-
- CSS Modules
-
- <style module> - this should be blue
-
{{ $style }}
-
-
- CSS - this should be orange
-
{{ mod }}
-
-
-
-
-
-
diff --git a/packages/playground/vue/CustomBlock.vue b/packages/playground/vue/CustomBlock.vue
deleted file mode 100644
index 0a7b3901693154..00000000000000
--- a/packages/playground/vue/CustomBlock.vue
+++ /dev/null
@@ -1,32 +0,0 @@
-
- Custom Blocks
- {{ t('hello') }}
-
-
-
-
-
-en:
- hello: 'hello,vite!'
-ja:
- hello: 'こんにちは、vite!'
-
diff --git a/packages/playground/vue/CustomBlockPlugin.ts b/packages/playground/vue/CustomBlockPlugin.ts
deleted file mode 100644
index 4f5def023902bc..00000000000000
--- a/packages/playground/vue/CustomBlockPlugin.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-import type { Plugin } from 'vite'
-
-export const vueI18nPlugin: Plugin = {
- name: 'vue-i18n',
- transform(code, id) {
- if (!/vue&type=i18n/.test(id)) {
- return
- }
- if (/\.ya?ml$/.test(id)) {
- code = JSON.stringify(require('js-yaml').load(code.trim()))
- }
- return {
- code: `export default Comp => {
- Comp.i18n = ${code}
- }`,
- map: { mappings: '' }
- }
- }
-}
diff --git a/packages/playground/vue/CustomElement.ce.vue b/packages/playground/vue/CustomElement.ce.vue
deleted file mode 100644
index 58d94650d1a74a..00000000000000
--- a/packages/playground/vue/CustomElement.ce.vue
+++ /dev/null
@@ -1,26 +0,0 @@
-
- Custom Element
-
- {{ label }}: {{ state.count }}
-
-
-
-
-
-
diff --git a/packages/playground/vue/Hmr.vue b/packages/playground/vue/Hmr.vue
deleted file mode 100644
index 5535467af3858f..00000000000000
--- a/packages/playground/vue/Hmr.vue
+++ /dev/null
@@ -1,20 +0,0 @@
-
- HMR
- Click the button then edit this message. The count should be preserved.
- count is {{ count }}
-
-
-
-
-
diff --git a/packages/playground/vue/Main.vue b/packages/playground/vue/Main.vue
deleted file mode 100644
index d10ae401f7aa8e..00000000000000
--- a/packages/playground/vue/Main.vue
+++ /dev/null
@@ -1,49 +0,0 @@
-
-
- Vue SFCs
- {{ time as string }}
-
-
-
-
-
-
-
-
-
-
- this should be red
-
-
-
-
-
-
-
-
-
-
diff --git a/packages/playground/vue/Node.vue b/packages/playground/vue/Node.vue
deleted file mode 100644
index 246442d29f522c..00000000000000
--- a/packages/playground/vue/Node.vue
+++ /dev/null
@@ -1,3 +0,0 @@
-
- this is node
-
diff --git a/packages/playground/vue/PreProcessors.vue b/packages/playground/vue/PreProcessors.vue
deleted file mode 100644
index ddb636678e8cdd..00000000000000
--- a/packages/playground/vue/PreProcessors.vue
+++ /dev/null
@@ -1,44 +0,0 @@
-
-h2.pre-processors Pre-Processors
-p.pug
- | This is rendered from <template lang="pug">
- | and styled with <style lang="sass">. It should be megenta.
-p.pug-less
- | This is rendered from <template lang="pug">
- | and styled with <style lang="less">. It should be green.
-p.pug-stylus
- | This is rendered from <template lang="pug">
- | and styled with <style lang="stylus">. It should be orange.
-SlotComponent
- template(v-slot:test-slot)
- div.pug-slot slot content
-
-
-
-
-
-
-
-
-
diff --git a/packages/playground/vue/ReactivityTransform.vue b/packages/playground/vue/ReactivityTransform.vue
deleted file mode 100644
index 0dc2b09343d641..00000000000000
--- a/packages/playground/vue/ReactivityTransform.vue
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
- Reactivity Transform
- Prop foo: {{ bar }}
- {{ a }}
-
diff --git a/packages/playground/vue/ScanDep.vue b/packages/playground/vue/ScanDep.vue
deleted file mode 100644
index 17b398beab1cd2..00000000000000
--- a/packages/playground/vue/ScanDep.vue
+++ /dev/null
@@ -1,8 +0,0 @@
-
- Scan Deps from <script setup lang=ts> blocks
- {{ typeof debounce === 'function' ? 'ok' : 'error' }}
-
-
-
diff --git a/packages/playground/vue/Slotted.vue b/packages/playground/vue/Slotted.vue
deleted file mode 100644
index fb25a9c5100215..00000000000000
--- a/packages/playground/vue/Slotted.vue
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
:slotted
-
-
-
-
-
diff --git a/packages/playground/vue/Syntax.vue b/packages/playground/vue/Syntax.vue
deleted file mode 100644
index de100226922c55..00000000000000
--- a/packages/playground/vue/Syntax.vue
+++ /dev/null
@@ -1,14 +0,0 @@
-
- Syntax Support
- {{ a?.b }}
-
-
-
diff --git a/packages/playground/vue/__tests__/vue.spec.ts b/packages/playground/vue/__tests__/vue.spec.ts
deleted file mode 100644
index 63680d6f021684..00000000000000
--- a/packages/playground/vue/__tests__/vue.spec.ts
+++ /dev/null
@@ -1,244 +0,0 @@
-import { editFile, getBg, getColor, isBuild, untilUpdated } from 'testUtils'
-
-test('should render', async () => {
- expect(await page.textContent('h1')).toMatch('Vue SFCs')
-})
-
-test('should update', async () => {
- expect(await page.textContent('.hmr-inc')).toMatch('count is 0')
- await page.click('.hmr-inc')
- expect(await page.textContent('.hmr-inc')).toMatch('count is 1')
-})
-
-test('template/script latest syntax support', async () => {
- expect(await page.textContent('.syntax')).toBe('baz')
-})
-
-test('should remove comments in prod', async () => {
- expect(await page.innerHTML('.comments')).toBe(isBuild ? `` : ``)
-})
-
-test(':slotted', async () => {
- expect(await getColor('.slotted')).toBe('red')
-})
-
-describe('dep scan', () => {
- test('scan deps from
-
diff --git a/packages/playground/vue/package.json b/packages/playground/vue/package.json
deleted file mode 100644
index f493e9028b6ec3..00000000000000
--- a/packages/playground/vue/package.json
+++ /dev/null
@@ -1,23 +0,0 @@
-{
- "name": "test-vue",
- "private": true,
- "version": "0.0.0",
- "scripts": {
- "dev": "vite",
- "build": "vite build",
- "debug": "node --inspect-brk ../../vite/bin/vite",
- "preview": "vite preview"
- },
- "dependencies": {
- "lodash-es": "^4.17.21",
- "vue": "^3.2.25"
- },
- "devDependencies": {
- "@vitejs/plugin-vue": "workspace:*",
- "js-yaml": "^4.1.0",
- "less": "^4.1.2",
- "pug": "^3.0.2",
- "sass": "^1.43.4",
- "stylus": "^0.55.0"
- }
-}
diff --git a/packages/playground/vue/public/favicon.ico b/packages/playground/vue/public/favicon.ico
deleted file mode 100644
index df36fcfb72584e..00000000000000
Binary files a/packages/playground/vue/public/favicon.ico and /dev/null differ
diff --git a/packages/playground/vue/setup-import-template/SetupImportTemplate.vue b/packages/playground/vue/setup-import-template/SetupImportTemplate.vue
deleted file mode 100644
index d7fb119e3cfdc0..00000000000000
--- a/packages/playground/vue/setup-import-template/SetupImportTemplate.vue
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
diff --git a/packages/playground/vue/setup-import-template/template.html b/packages/playground/vue/setup-import-template/template.html
deleted file mode 100644
index 414069f2e9e929..00000000000000
--- a/packages/playground/vue/setup-import-template/template.html
+++ /dev/null
@@ -1,2 +0,0 @@
-Setup Import Template
-{{ count }}
diff --git a/packages/playground/vue/src-import/SrcImport.vue b/packages/playground/vue/src-import/SrcImport.vue
deleted file mode 100644
index d70e1f48a84331..00000000000000
--- a/packages/playground/vue/src-import/SrcImport.vue
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
diff --git a/packages/playground/vue/src-import/script.ts b/packages/playground/vue/src-import/script.ts
deleted file mode 100644
index 54e6e35db41f46..00000000000000
--- a/packages/playground/vue/src-import/script.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-import { defineComponent } from 'vue'
-import SrcImportStyle from './srcImportStyle.vue'
-import SrcImportStyle2 from './srcImportStyle2.vue'
-
-export default defineComponent({
- components: {
- SrcImportStyle,
- SrcImportStyle2
- },
- setup() {
- return {
- msg: 'hello from script src!'
- }
- }
-})
diff --git a/packages/playground/vue/src-import/srcImportStyle.vue b/packages/playground/vue/src-import/srcImportStyle.vue
deleted file mode 100644
index de91769858fe93..00000000000000
--- a/packages/playground/vue/src-import/srcImportStyle.vue
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
- {{ msg }}
-
-
diff --git a/packages/playground/vue/src-import/srcImportStyle2.vue b/packages/playground/vue/src-import/srcImportStyle2.vue
deleted file mode 100644
index 1e0f327413103e..00000000000000
--- a/packages/playground/vue/src-import/srcImportStyle2.vue
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
- This should be tan
-
diff --git a/packages/playground/vue/src-import/style.css b/packages/playground/vue/src-import/style.css
deleted file mode 100644
index 49ab2d93176f4f..00000000000000
--- a/packages/playground/vue/src-import/style.css
+++ /dev/null
@@ -1,3 +0,0 @@
-.src-imports-style {
- color: tan;
-}
diff --git a/packages/playground/vue/src-import/style2.css b/packages/playground/vue/src-import/style2.css
deleted file mode 100644
index 8c93cb983cc09d..00000000000000
--- a/packages/playground/vue/src-import/style2.css
+++ /dev/null
@@ -1,3 +0,0 @@
-.src-imports-script {
- color: #0088ff;
-}
diff --git a/packages/playground/vue/src-import/template.html b/packages/playground/vue/src-import/template.html
deleted file mode 100644
index 6b55c545daac6a..00000000000000
--- a/packages/playground/vue/src-import/template.html
+++ /dev/null
@@ -1,5 +0,0 @@
-SFC Src Imports
-{{ msg }}
-This should be tan
-
-
diff --git a/packages/playground/vue/vite.config.ts b/packages/playground/vue/vite.config.ts
deleted file mode 100644
index f99a68ce8b6b10..00000000000000
--- a/packages/playground/vue/vite.config.ts
+++ /dev/null
@@ -1,37 +0,0 @@
-import { defineConfig, splitVendorChunkPlugin } from 'vite'
-import vuePlugin from '@vitejs/plugin-vue'
-import { vueI18nPlugin } from './CustomBlockPlugin'
-
-export default defineConfig({
- resolve: {
- alias: {
- '/@': __dirname
- }
- },
- plugins: [
- vuePlugin({
- reactivityTransform: true
- }),
- splitVendorChunkPlugin(),
- vueI18nPlugin
- ],
- build: {
- // to make tests faster
- minify: false,
- rollupOptions: {
- output: {
- // Test splitVendorChunkPlugin composition
- manualChunks(id) {
- if (id.includes('src-import')) {
- return 'src-import'
- }
- }
- }
- }
- },
- css: {
- modules: {
- localsConvention: 'camelCaseOnly'
- }
- }
-})
diff --git a/packages/playground/wasm/__tests__/wasm.spec.ts b/packages/playground/wasm/__tests__/wasm.spec.ts
deleted file mode 100644
index 112617212251fa..00000000000000
--- a/packages/playground/wasm/__tests__/wasm.spec.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-import { untilUpdated } from '../../testUtils'
-
-test('should work when inlined', async () => {
- await page.click('.inline-wasm .run')
- await untilUpdated(() => page.textContent('.inline-wasm .result'), '42')
-})
-
-test('should work when output', async () => {
- await page.click('.output-wasm .run')
- await untilUpdated(() => page.textContent('.output-wasm .result'), '24')
-})
-
-test('should work when wasm in worker', async () => {
- await untilUpdated(() => page.textContent('.worker-wasm .result'), '3')
-})
diff --git a/packages/playground/wasm/index.html b/packages/playground/wasm/index.html
deleted file mode 100644
index ecb0b66e913fbb..00000000000000
--- a/packages/playground/wasm/index.html
+++ /dev/null
@@ -1,54 +0,0 @@
-Web Assembly
-
-
-
When wasm is inline, result should be 42
- Click to run
-
-
-
-
-
When wasm is output, result should be 24
- Click to run
-
-
-
-
-
worker wasm
-
-
-
-
diff --git a/packages/playground/wasm/package.json b/packages/playground/wasm/package.json
deleted file mode 100644
index 9d903be37887b8..00000000000000
--- a/packages/playground/wasm/package.json
+++ /dev/null
@@ -1,11 +0,0 @@
-{
- "name": "test-wasm",
- "private": true,
- "version": "0.0.0",
- "scripts": {
- "dev": "vite",
- "build": "vite build",
- "debug": "node --inspect-brk ../../vite/bin/vite",
- "preview": "vite preview"
- }
-}
diff --git a/packages/playground/wasm/vite.config.ts b/packages/playground/wasm/vite.config.ts
deleted file mode 100644
index 43833d2f95d302..00000000000000
--- a/packages/playground/wasm/vite.config.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-import { defineConfig } from 'vite'
-export default defineConfig({
- build: {
- // make can no emit light.wasm
- // and emit add.wasm
- assetsInlineLimit: 80
- }
-})
diff --git a/packages/playground/wasm/worker.js b/packages/playground/wasm/worker.js
deleted file mode 100644
index a483865bd42bff..00000000000000
--- a/packages/playground/wasm/worker.js
+++ /dev/null
@@ -1,5 +0,0 @@
-import init from './add.wasm'
-init().then((exports) => {
- // eslint-disable-next-line no-undef
- self.postMessage({ result: exports.add(1, 2) })
-})
diff --git a/packages/playground/worker/__tests__/worker.spec.ts b/packages/playground/worker/__tests__/worker.spec.ts
deleted file mode 100644
index 6d93e810c0c510..00000000000000
--- a/packages/playground/worker/__tests__/worker.spec.ts
+++ /dev/null
@@ -1,91 +0,0 @@
-import fs from 'fs'
-import path from 'path'
-import { untilUpdated, isBuild, testDir } from '../../testUtils'
-import type { Page } from 'playwright-chromium'
-
-test('normal', async () => {
- await page.click('.ping')
- await untilUpdated(() => page.textContent('.pong'), 'pong')
- await untilUpdated(
- () => page.textContent('.mode'),
- isBuild ? 'production' : 'development'
- )
- await untilUpdated(
- () => page.textContent('.bundle-with-plugin'),
- 'worker bundle with plugin success!'
- )
-})
-
-test('TS output', async () => {
- await page.click('.ping-ts-output')
- await untilUpdated(() => page.textContent('.pong-ts-output'), 'pong')
-})
-
-test('inlined', async () => {
- await page.click('.ping-inline')
- await untilUpdated(() => page.textContent('.pong-inline'), 'pong')
-})
-
-const waitSharedWorkerTick = (
- (resolvedSharedWorkerCount: number) => async (page: Page) => {
- await untilUpdated(async () => {
- const count = await page.textContent('.tick-count')
- // ignore the initial 0
- return count === '1' ? 'page loaded' : ''
- }, 'page loaded')
- // test.concurrent sequential is not guaranteed
- // force page to wait to ensure two pages overlap in time
- resolvedSharedWorkerCount++
- if (resolvedSharedWorkerCount < 2) return
-
- await untilUpdated(() => {
- return resolvedSharedWorkerCount === 2 ? 'all pages loaded' : ''
- }, 'all pages loaded')
- }
-)(0)
-
-test.concurrent.each([[true], [false]])('shared worker', async (doTick) => {
- if (doTick) {
- await page.click('.tick-shared')
- }
- await waitSharedWorkerTick(page)
-})
-
-test('worker emitted', async () => {
- await untilUpdated(() => page.textContent('.nested-worker'), 'pong')
- await untilUpdated(
- () => page.textContent('.nested-worker-dynamic-import'),
- '"msg":"pong"'
- )
-})
-
-if (isBuild) {
- const assetsDir = path.resolve(testDir, 'dist/assets')
- // assert correct files
- test('inlined code generation', async () => {
- const files = fs.readdirSync(assetsDir)
- expect(files.length).toBe(11)
- const index = files.find((f) => f.includes('index'))
- const content = fs.readFileSync(path.resolve(assetsDir, index), 'utf-8')
- const worker = files.find((f) => f.includes('my-worker'))
- const workerContent = fs.readFileSync(
- path.resolve(assetsDir, worker),
- 'utf-8'
- )
-
- // worker should have all imports resolved and no exports
- expect(workerContent).not.toMatch(`import`)
- expect(workerContent).not.toMatch(`export`)
- // chunk
- expect(content).toMatch(`new Worker("/assets`)
- expect(content).toMatch(`new SharedWorker("/assets`)
- // inlined
- expect(content).toMatch(`(window.URL||window.webkitURL).createObjectURL`)
- expect(content).toMatch(`window.Blob`)
- })
-}
-
-test('classic worker is run', async () => {
- expect(await page.textContent('.classic-worker')).toMatch('A classic')
- expect(await page.textContent('.classic-shared-worker')).toMatch('A classic')
-})
diff --git a/packages/playground/worker/classic-worker.js b/packages/playground/worker/classic-worker.js
deleted file mode 100644
index bb6f9c3f49fc84..00000000000000
--- a/packages/playground/worker/classic-worker.js
+++ /dev/null
@@ -1,29 +0,0 @@
-// prettier-ignore
-function text(el, text) {
- document.querySelector(el).textContent = text
-}
-
-const classicWorker = new Worker(
- new URL('./newUrl/classic-worker.js', import.meta.url) /* , */ ,
- // test comment
-
-)
-
-classicWorker.addEventListener('message', ({ data }) => {
- text('.classic-worker', data)
-})
-classicWorker.postMessage('ping')
-
-const classicSharedWorker = new SharedWorker(
- new URL('./newUrl/classic-shared-worker.js', import.meta.url),
- {
- type: 'classic'
- }
-)
-classicSharedWorker.port.addEventListener('message', (ev) => {
- text(
- '.classic-shared-worker',
- ev.data
- )
-})
-classicSharedWorker.port.start()
diff --git a/packages/playground/worker/index.html b/packages/playground/worker/index.html
deleted file mode 100644
index b3525da299ff5a..00000000000000
--- a/packages/playground/worker/index.html
+++ /dev/null
@@ -1,132 +0,0 @@
-Expected values:
-Ping
-
- Response from worker:
-
-bundle-with-plugin:
-
-Ping Inline Worker
-Response from inline worker:
-
-Ping Possible Compiled TS Worker
-
- Response from worker imported from code that might be compiled TS:
-
-
-
-Tick Shared Worker
-
- Tick from shared worker, it syncs between pages:
- 0
-
-
-new Worker(new Url('path', import.meta.url), { type: 'module' })
-
-
-new SharedWorker(new Url('path', import.meta.url), { type: 'module' })
-
-
-nested worker
-
-
-new Worker(new Url('path', import.meta.url))
-
-
-new Worker(new Url('path', import.meta.url), { type: 'classic' })
-
-
-
diff --git a/packages/playground/worker/my-shared-worker.ts b/packages/playground/worker/my-shared-worker.ts
deleted file mode 100644
index cd5b24f265b955..00000000000000
--- a/packages/playground/worker/my-shared-worker.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-let count = 0
-const ports = new Set()
-
-onconnect = (event) => {
- const port = event.ports[0]
- ports.add(port)
- port.postMessage(count)
- port.onmessage = (message) => {
- if (message.data === 'tick') {
- count++
- ports.forEach((p) => {
- p.postMessage(count)
- })
- }
- }
-}
diff --git a/packages/playground/worker/my-worker.ts b/packages/playground/worker/my-worker.ts
deleted file mode 100644
index 550382be72c331..00000000000000
--- a/packages/playground/worker/my-worker.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-import { msg, mode } from './workerImport'
-import { bundleWithPlugin } from './test-plugin'
-
-self.onmessage = (e) => {
- if (e.data === 'ping') {
- self.postMessage({ msg, mode, bundleWithPlugin })
- }
-}
diff --git a/packages/playground/worker/newUrl/classic-shared-worker.js b/packages/playground/worker/newUrl/classic-shared-worker.js
deleted file mode 100644
index 462e49dfa8847f..00000000000000
--- a/packages/playground/worker/newUrl/classic-shared-worker.js
+++ /dev/null
@@ -1,6 +0,0 @@
-importScripts('/classic.js')
-
-self.onconnect = (event) => {
- const port = event.ports[0]
- port.postMessage(self.constant)
-}
diff --git a/packages/playground/worker/newUrl/classic-worker.js b/packages/playground/worker/newUrl/classic-worker.js
deleted file mode 100644
index 865810c76fbf85..00000000000000
--- a/packages/playground/worker/newUrl/classic-worker.js
+++ /dev/null
@@ -1,5 +0,0 @@
-importScripts('/classic.js')
-
-self.addEventListener('message', () => {
- self.postMessage(self.constant)
-})
diff --git a/packages/playground/worker/newUrl/url-shared-worker.js b/packages/playground/worker/newUrl/url-shared-worker.js
deleted file mode 100644
index f52de169243056..00000000000000
--- a/packages/playground/worker/newUrl/url-shared-worker.js
+++ /dev/null
@@ -1,6 +0,0 @@
-import constant from './module'
-
-self.onconnect = (event) => {
- const port = event.ports[0]
- port.postMessage(constant)
-}
diff --git a/packages/playground/worker/newUrl/url-worker.js b/packages/playground/worker/newUrl/url-worker.js
deleted file mode 100644
index afd91bfe613dc2..00000000000000
--- a/packages/playground/worker/newUrl/url-worker.js
+++ /dev/null
@@ -1,3 +0,0 @@
-import constant from './module'
-
-self.postMessage(constant)
diff --git a/packages/playground/worker/package.json b/packages/playground/worker/package.json
deleted file mode 100644
index 131df8c4cbf336..00000000000000
--- a/packages/playground/worker/package.json
+++ /dev/null
@@ -1,14 +0,0 @@
-{
- "name": "test-worker",
- "private": true,
- "version": "0.0.0",
- "scripts": {
- "dev": "vite",
- "build": "vite build",
- "debug": "node --inspect-brk ../../vite/bin/vite",
- "preview": "vite preview"
- },
- "devDependencies": {
- "@vitejs/plugin-vue-jsx": "workspace:*"
- }
-}
diff --git a/packages/playground/worker/possible-ts-output-worker.mjs b/packages/playground/worker/possible-ts-output-worker.mjs
deleted file mode 100644
index 2bcce3faa8a50e..00000000000000
--- a/packages/playground/worker/possible-ts-output-worker.mjs
+++ /dev/null
@@ -1,7 +0,0 @@
-import { msg, mode } from './workerImport'
-
-self.onmessage = (e) => {
- if (e.data === 'ping') {
- self.postMessage({ msg, mode })
- }
-}
diff --git a/packages/playground/worker/sub-worker.js b/packages/playground/worker/sub-worker.js
deleted file mode 100644
index ab64b3667099bb..00000000000000
--- a/packages/playground/worker/sub-worker.js
+++ /dev/null
@@ -1,13 +0,0 @@
-self.onmessage = (event) => {
- if (event.data === 'ping') {
- self.postMessage('pong')
- }
-}
-const data = import('./workerImport')
-data.then((data) => {
- const { mode, msg } = data
- self.postMessage({
- mode,
- msg
- })
-})
diff --git a/packages/playground/worker/test-plugin.tsx b/packages/playground/worker/test-plugin.tsx
deleted file mode 100644
index 15b6b94f460bc3..00000000000000
--- a/packages/playground/worker/test-plugin.tsx
+++ /dev/null
@@ -1 +0,0 @@
-export const bundleWithPlugin: string = 'worker bundle with plugin success!'
diff --git a/packages/playground/worker/vite.config.ts b/packages/playground/worker/vite.config.ts
deleted file mode 100644
index 6cef7d9cea0bed..00000000000000
--- a/packages/playground/worker/vite.config.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-import vueJsx from '@vitejs/plugin-vue-jsx'
-import { defineConfig } from 'vite'
-
-export default defineConfig({
- worker: {
- format: 'es',
- plugins: [vueJsx()]
- }
-})
diff --git a/packages/playground/worker/worker-nested-worker.js b/packages/playground/worker/worker-nested-worker.js
deleted file mode 100644
index 6d4d1e4969005f..00000000000000
--- a/packages/playground/worker/worker-nested-worker.js
+++ /dev/null
@@ -1,13 +0,0 @@
-import SubWorker from './sub-worker?worker'
-
-const subWorker = new SubWorker()
-
-self.onmessage = (event) => {
- if (event.data === 'ping') {
- subWorker.postMessage('ping')
- }
-}
-
-subWorker.onmessage = (event) => {
- self.postMessage(event.data)
-}
diff --git a/packages/plugin-legacy/CHANGELOG.md b/packages/plugin-legacy/CHANGELOG.md
index e92ca1e12357fe..a434a7d81225e3 100644
--- a/packages/plugin-legacy/CHANGELOG.md
+++ b/packages/plugin-legacy/CHANGELOG.md
@@ -1,291 +1,1004 @@
-## [1.7.1](https://github.com/vitejs/vite/compare/plugin-legacy@1.7.0...plugin-legacy@1.7.1) (2022-02-11)
+## [8.0.0-beta.2](https://github.com/vitejs/vite/compare/plugin-legacy@8.0.0-beta.1...plugin-legacy@8.0.0-beta.2) (2026-02-03)
+### Bug Fixes
+
+* **deps:** update all non-major dependencies ([#21488](https://github.com/vitejs/vite/issues/21488)) ([2b32ca2](https://github.com/vitejs/vite/commit/2b32ca24fe9d742901c2cb5c88e6b1fd734f8c73))
+* **deps:** update all non-major dependencies ([#21540](https://github.com/vitejs/vite/issues/21540)) ([9ebaeaa](https://github.com/vitejs/vite/commit/9ebaeaac094db996b1d12665052633c20ac8a9cf))
+* **legacy:** use `prebuilt-chunk` for polyfill chunks ([#21498](https://github.com/vitejs/vite/issues/21498)) ([7999843](https://github.com/vitejs/vite/commit/79998430eb7cd549a7fb803882447c4a15b11899))
+
+### Miscellaneous Chores
+
+* **deps:** update rolldown-related dependencies ([#21487](https://github.com/vitejs/vite/issues/21487)) ([5863e51](https://github.com/vitejs/vite/commit/5863e513fab6b481cfb42da86202f9db728c077d))
+
+### Code Refactoring
+
+* use `import.meta.dirname` everywhere ([#21509](https://github.com/vitejs/vite/issues/21509)) ([7becf5f](https://github.com/vitejs/vite/commit/7becf5f8fe9041cff60f495ef975faaba68f9eb2))
+## [8.0.0-beta.1](https://github.com/vitejs/vite/compare/plugin-legacy@8.0.0-beta.0...plugin-legacy@8.0.0-beta.1) (2026-01-22)
### Bug Fixes
-* require Vite 2.8.0 ([#6272](https://github.com/vitejs/vite/issues/6272)) ([#6869](https://github.com/vitejs/vite/issues/6869)) ([997b8f1](https://github.com/vitejs/vite/commit/997b8f11cb156cc374ae991875a09534b5489a93))
+* **deps:** update all non-major dependencies ([#21231](https://github.com/vitejs/vite/issues/21231)) ([859789c](https://github.com/vitejs/vite/commit/859789c856412dfa67969232ddda1df754febf40))
+* **deps:** update all non-major dependencies ([#21389](https://github.com/vitejs/vite/issues/21389)) ([30f48df](https://github.com/vitejs/vite/commit/30f48df33ec9e9bd0b8164461eede5574398370b))
+### Miscellaneous Chores
+* cleanup changelog ([#21202](https://github.com/vitejs/vite/issues/21202)) ([8c8c56e](https://github.com/vitejs/vite/commit/8c8c56e1eb465e6dcd0c1b40f187228edc0e2be4))
+* **deps:** update dependency tsdown to ^0.17.4 ([#21284](https://github.com/vitejs/vite/issues/21284)) ([43f061a](https://github.com/vitejs/vite/commit/43f061adc677d40ce226de4dd07ee9a1f5e4ca73))
+* **deps:** update dependency tsdown to ^0.18.4 ([#21344](https://github.com/vitejs/vite/issues/21344)) ([964c718](https://github.com/vitejs/vite/commit/964c718a382ff46ec1f906d7d6bc3f135a6dcd3f))
+* **deps:** update rolldown-related dependencies ([#21230](https://github.com/vitejs/vite/issues/21230)) ([9349446](https://github.com/vitejs/vite/commit/9349446e9344bd81ccfb37af482f479cd1b59bbc))
+* **deps:** update rolldown-related dependencies ([#21390](https://github.com/vitejs/vite/issues/21390)) ([be9dd4e](https://github.com/vitejs/vite/commit/be9dd4e08d899f9ed27f2bdcb81bf27d018377a6))
+* **legacy:** add metadata for vite-plugin-registry ([#21453](https://github.com/vitejs/vite/issues/21453)) ([2723c6c](https://github.com/vitejs/vite/commit/2723c6c849a0022e682b7fdccb7c5d109177e1c3))
-# [1.7.0](https://github.com/vitejs/vite/compare/plugin-legacy@1.6.4...plugin-legacy@1.7.0) (2022-02-09)
+## [8.0.0-beta.0](https://github.com/vitejs/vite/compare/plugin-legacy@7.2.1...plugin-legacy@8.0.0-beta.0) (2025-12-03)
+### Features
+
+* the epic `rolldown-vite` merge ([#21189](https://github.com/vitejs/vite/issues/21189)) ([4a7f8d4](https://github.com/vitejs/vite/commit/4a7f8d43e6b14b89fef278c3ea86f9e3f64b7fc2))
+
+## [7.2.1](https://github.com/vitejs/vite/compare/plugin-legacy@7.2.0...plugin-legacy@7.2.1) (2025-08-07)
+### Features
+* **legacy:** update plugin-legacy code for rolldown-vite ([6401a49](https://github.com/vitejs/vite/commit/6401a49d53fbff8a4a519bb2ad107b8a223250d6))
+## [7.2.0](https://github.com/vitejs/vite/compare/plugin-legacy@7.1.0...plugin-legacy@7.2.0) (2025-08-07)
### Bug Fixes
-* don't force terser on non-legacy (fix [#6266](https://github.com/vitejs/vite/issues/6266)) ([#6272](https://github.com/vitejs/vite/issues/6272)) ([1da104e](https://github.com/vitejs/vite/commit/1da104e8597e2965313e8cd582d032bca551e4ee))
-* **legacy:** fix conflict with the modern build on css emitting ([#6584](https://github.com/vitejs/vite/issues/6584)) ([f48255e](https://github.com/vitejs/vite/commit/f48255e6e0058e973b949fb4a2372974f0480e11)), closes [#3296](https://github.com/vitejs/vite/issues/3296) [#3317](https://github.com/vitejs/vite/issues/3317) [/github.com/vitejs/vite/commit/6bce1081991501f3779bff1a81e5dd1e63e5d38e#diff-2cfbd4f4d8c32727cd8e1a561cffbde0b384a3ce0789340440e144f9d64c10f6R262-R263](https://github.com//github.com/vitejs/vite/commit/6bce1081991501f3779bff1a81e5dd1e63e5d38e/issues/diff-2cfbd4f4d8c32727cd8e1a561cffbde0b384a3ce0789340440e144f9d64c10f6R262-R263)
+* **deps:** update all non-major dependencies ([#20537](https://github.com/vitejs/vite/issues/20537)) ([fc9a9d3](https://github.com/vitejs/vite/commit/fc9a9d3f1493caa3d614f64e0a61fd5684f0928b))
+* **legacy:** `modernTargets` should set `build.target` ([#20393](https://github.com/vitejs/vite/issues/20393)) ([76c5e40](https://github.com/vitejs/vite/commit/76c5e40864f42bb33ee7ea9184e32d5156fa1a4a))
+
+### Miscellaneous Chores
+
+* **deps:** update rolldown-related dependencies ([#20441](https://github.com/vitejs/vite/issues/20441)) ([f689d61](https://github.com/vitejs/vite/commit/f689d613429ae9452c74f8bc482d8cc2584ea6b8))
+* **deps:** update rolldown-related dependencies ([#20536](https://github.com/vitejs/vite/issues/20536)) ([8be2787](https://github.com/vitejs/vite/commit/8be278748a92b128c49a24619d8d537dd2b08ceb))
+
+## [7.1.0](https://github.com/vitejs/vite/compare/plugin-legacy@7.0.1...plugin-legacy@7.1.0) (2025-07-22)
+### Features
+
+* **legacy:** add rolldown-vite support ([#20417](https://github.com/vitejs/vite/issues/20417)) ([ab62ca4](https://github.com/vitejs/vite/commit/ab62ca4897aa969fb72508656da2eae7fb3906be))
+
+## [7.0.1](https://github.com/vitejs/vite/compare/plugin-legacy@7.0.0...plugin-legacy@7.0.1) (2025-07-17)
+### Bug Fixes
+
+* **deps:** update all non-major dependencies ([#20324](https://github.com/vitejs/vite/issues/20324)) ([3e81af3](https://github.com/vitejs/vite/commit/3e81af38a80c7617aba6bf3300d8b4267570f9cf))
+* **deps:** update all non-major dependencies ([#20366](https://github.com/vitejs/vite/issues/20366)) ([43ac73d](https://github.com/vitejs/vite/commit/43ac73da27b3907c701e95e6a7d28fde659729ec))
+* **deps:** update all non-major dependencies ([#20406](https://github.com/vitejs/vite/issues/20406)) ([1a1cc8a](https://github.com/vitejs/vite/commit/1a1cc8a435a21996255b3e5cc75ed4680de2a7f3))
+* **legacy:** don't lower CSS if legacy chunks are not generated ([#20392](https://github.com/vitejs/vite/issues/20392)) ([d2c81f7](https://github.com/vitejs/vite/commit/d2c81f7c13030c08becd8a768182074eedb87333))
+
+### Performance Improvements
+
+* **legacy:** skip lowering when detecting polyfills ([#20387](https://github.com/vitejs/vite/issues/20387)) ([7cc0338](https://github.com/vitejs/vite/commit/7cc0338de3a67597956af58e931e46e7913c063b))
+
+### Miscellaneous Chores
+* **deps:** update rolldown-related dependencies ([#20323](https://github.com/vitejs/vite/issues/20323)) ([30d2f1b](https://github.com/vitejs/vite/commit/30d2f1b38c72387ffdca3ee4746730959a020b59))
+* group commits by category in changelog ([#20310](https://github.com/vitejs/vite/issues/20310)) ([41e83f6](https://github.com/vitejs/vite/commit/41e83f62b1adb65f5af4c1ec006de1c845437edc))
+### Code Refactoring
-## [1.6.4](https://github.com/vitejs/vite/compare/plugin-legacy@1.6.3...plugin-legacy@1.6.4) (2021-12-07)
+* **legacy:** use Rollup type export from Vite ([#20335](https://github.com/vitejs/vite/issues/20335)) ([d62dc33](https://github.com/vitejs/vite/commit/d62dc3321db05d91e74facff51799496ce8601f3))
+* use `foo.endsWith("bar")` instead of `/bar$/.test(foo)` ([#20413](https://github.com/vitejs/vite/issues/20413)) ([862e192](https://github.com/vitejs/vite/commit/862e192d21f66039635a998724bdc6b94fd293a0))
+## [7.0.0](https://github.com/vitejs/vite/compare/plugin-legacy@7.0.0-beta.1...plugin-legacy@7.0.0) (2025-06-24)
+### Miscellaneous Chores
+* **deps:** update rolldown-related dependencies ([#20270](https://github.com/vitejs/vite/issues/20270)) ([f7377c3](https://github.com/vitejs/vite/commit/f7377c3eae6323bd3237ff5de5ae55c879fe7325))
+* **legacy:** update peer dep Vite to 7 ([8ff13cd](https://github.com/vitejs/vite/commit/8ff13cdba1c57284eb8f4586b52f814fcf5afcdf))
-## [1.6.3](https://github.com/vitejs/vite/compare/plugin-legacy@1.6.2...plugin-legacy@1.6.3) (2021-11-22)
+## [7.0.0-beta.1](https://github.com/vitejs/vite/compare/plugin-legacy@7.0.0-beta.0...plugin-legacy@7.0.0-beta.1) (2025-06-17)
+### ⚠ BREAKING CHANGES
+* **legacy:** remove `location.protocol!="file:"` condition for modern android webview (#20179)
### Bug Fixes
-* **build:** resolve `rollupOptions.input` paths ([#5601](https://github.com/vitejs/vite/issues/5601)) ([5b6b016](https://github.com/vitejs/vite/commit/5b6b01693720290e8998b2613f0dcb2d699ee84f))
+* **deps:** update all non-major dependencies ([#20141](https://github.com/vitejs/vite/issues/20141)) ([89ca65b](https://github.com/vitejs/vite/commit/89ca65ba1d849046dccdea52e9eca980f331be26))
+* **deps:** update all non-major dependencies ([#20181](https://github.com/vitejs/vite/issues/20181)) ([d91d4f7](https://github.com/vitejs/vite/commit/d91d4f7ad55edbcb4a51fc23376cbff89f776d30))
+* **legacy:** remove `location.protocol!="file:"` condition for modern android webview ([#20179](https://github.com/vitejs/vite/issues/20179)) ([a6d5997](https://github.com/vitejs/vite/commit/a6d599718ee109798e8f552e317f175513d157e7))
+
+### Miscellaneous Chores
+
+* **deps:** update rolldown-related dependencies ([#20140](https://github.com/vitejs/vite/issues/20140)) ([0387447](https://github.com/vitejs/vite/commit/03874471e3de14e7d2f474ecb354499e7f5eb418))
+* **deps:** update rolldown-related dependencies ([#20182](https://github.com/vitejs/vite/issues/20182)) ([6172f41](https://github.com/vitejs/vite/commit/6172f410b44cbae8d052997bb1819a6197a4d397))
+## [7.0.0-beta.0](https://github.com/vitejs/vite/compare/plugin-legacy@6.1.1...plugin-legacy@7.0.0-beta.0) (2025-06-02)
+### ⚠ BREAKING CHANGES
+
+* bump required node version to 20.19+, 22.12+ and remove cjs build (#20032)
+* remove node 18 support (#19972)
+
+### Bug Fixes
+* **deps:** update all non-major dependencies ([#19953](https://github.com/vitejs/vite/issues/19953)) ([ac8e1fb](https://github.com/vitejs/vite/commit/ac8e1fb289a06fc0671dab1f4ef68e508e34360e))
-## [1.6.2](https://github.com/vitejs/vite/compare/plugin-legacy@1.6.1...plugin-legacy@1.6.2) (2021-10-11)
+### Miscellaneous Chores
+
+* remove node 18 support ([#19972](https://github.com/vitejs/vite/issues/19972)) ([00b8a98](https://github.com/vitejs/vite/commit/00b8a98f36376804437e1342265453915ae613de))
+* use tsdown ([#20065](https://github.com/vitejs/vite/issues/20065)) ([d488efd](https://github.com/vitejs/vite/commit/d488efda95ff40f63684194d51858f84c3d05379))
+
+### Code Refactoring
+
+* bump required node version to 20.19+, 22.12+ and remove cjs build ([#20032](https://github.com/vitejs/vite/issues/20032)) ([2b80243](https://github.com/vitejs/vite/commit/2b80243fada75378e80475028fdcc78f4432bd6f))
+
+## [6.1.1](https://github.com/vitejs/vite/compare/plugin-legacy@6.1.0...plugin-legacy@6.1.1) (2025-04-28)
+### Bug Fixes
+* **legacy:** use unbuild 3.4 for now ([#19928](https://github.com/vitejs/vite/issues/19928)) ([96f73d1](https://github.com/vitejs/vite/commit/96f73d16c8501013be57aee1c8a2353a56460281))
+## [6.1.0](https://github.com/vitejs/vite/compare/plugin-legacy@6.0.2...plugin-legacy@6.1.0) (2025-04-16)
### Features
-* add `build.cssTarget` option ([#5132](https://github.com/vitejs/vite/issues/5132)) ([b17444f](https://github.com/vitejs/vite/commit/b17444fd97b02bc54410c8575e7d3cb25e4058c2)), closes [#4746](https://github.com/vitejs/vite/issues/4746) [#5070](https://github.com/vitejs/vite/issues/5070) [#4930](https://github.com/vitejs/vite/issues/4930)
+* **legacy:** add 'assumptions' option ([#19719](https://github.com/vitejs/vite/issues/19719)) ([d1d99c9](https://github.com/vitejs/vite/commit/d1d99c9220989ce903dea9cae6c3608f57f377ea))
+* **legacy:** add sourcemapBaseUrl support ([#19281](https://github.com/vitejs/vite/issues/19281)) ([a92c74b](https://github.com/vitejs/vite/commit/a92c74b088a253257c01596bd7c67e0f8fa39512))
+### Bug Fixes
+* **deps:** update all non-major dependencies ([#19555](https://github.com/vitejs/vite/issues/19555)) ([f612e0f](https://github.com/vitejs/vite/commit/f612e0fdf6810317b61fcca1ded125755f261d78))
+* **deps:** update all non-major dependencies ([#19613](https://github.com/vitejs/vite/issues/19613)) ([363d691](https://github.com/vitejs/vite/commit/363d691b4995d72f26a14eb59ed88a9483b1f931))
+* **deps:** update all non-major dependencies ([#19649](https://github.com/vitejs/vite/issues/19649)) ([f4e712f](https://github.com/vitejs/vite/commit/f4e712ff861f8a9504594a4a5e6d35a7547e5a7e))
-## [1.6.1](https://github.com/vitejs/vite/compare/plugin-legacy@1.6.0...plugin-legacy@1.6.1) (2021-10-05)
+### Code Refactoring
+* restore endsWith usage ([#19554](https://github.com/vitejs/vite/issues/19554)) ([6113a96](https://github.com/vitejs/vite/commit/6113a9670cc9b7d29fe0bffe033f7823e36ded00))
+## [6.0.2](https://github.com/vitejs/vite/compare/plugin-legacy@6.0.1...plugin-legacy@6.0.2) (2025-02-25)
### Bug Fixes
-* **plugin-legacy:** use terser as the default minifier ([#5168](https://github.com/vitejs/vite/issues/5168)) ([9ee7234](https://github.com/vitejs/vite/commit/9ee72343884a7d679767833f7a659bbca6b96595))
+* **deps:** update all non-major dependencies ([#19392](https://github.com/vitejs/vite/issues/19392)) ([60456a5](https://github.com/vitejs/vite/commit/60456a54fe90872dbd4bed332ecbd85bc88deb92))
+* **deps:** update all non-major dependencies ([#19440](https://github.com/vitejs/vite/issues/19440)) ([ccac73d](https://github.com/vitejs/vite/commit/ccac73d9d0e92c7232f09207d1d6b893e823ed8e))
+* **legacy:** warn if plugin-legacy is passed to `worker.plugins` ([#19079](https://github.com/vitejs/vite/issues/19079)) ([171f2fb](https://github.com/vitejs/vite/commit/171f2fbe0afe09eeb49f5f29f9ecd845c39a8401))
+### Miscellaneous Chores
+
+* fix typos ([#19398](https://github.com/vitejs/vite/issues/19398)) ([b44e3d4](https://github.com/vitejs/vite/commit/b44e3d43db65babe1c32e143964add02e080dc15))
+
+## [6.0.1](https://github.com/vitejs/vite/compare/plugin-legacy@6.0.0...plugin-legacy@6.0.1) (2025-02-05)
+### Bug Fixes
+* **deps:** update all non-major dependencies ([#18853](https://github.com/vitejs/vite/issues/18853)) ([5c02236](https://github.com/vitejs/vite/commit/5c0223636fa277d5daeb4d93c3f32d9f3cd69fc5))
+* **deps:** update all non-major dependencies ([#18967](https://github.com/vitejs/vite/issues/18967)) ([d88d000](https://github.com/vitejs/vite/commit/d88d0004a8e891ca6026d356695e0b319caa7fce))
+* **deps:** update all non-major dependencies ([#19098](https://github.com/vitejs/vite/issues/19098)) ([8639538](https://github.com/vitejs/vite/commit/8639538e6498d1109da583ad942c1472098b5919))
+* **deps:** update all non-major dependencies ([#19190](https://github.com/vitejs/vite/issues/19190)) ([f2c07db](https://github.com/vitejs/vite/commit/f2c07dbfc874b46f6e09bb04996d0514663e4544))
+* **deps:** update all non-major dependencies ([#19296](https://github.com/vitejs/vite/issues/19296)) ([2bea7ce](https://github.com/vitejs/vite/commit/2bea7cec4b7fddbd5f2fb6090a7eaf5ae7ca0f1b))
+* **legacy:** build respect `hashCharacters` config ([#19262](https://github.com/vitejs/vite/issues/19262)) ([3aa10b7](https://github.com/vitejs/vite/commit/3aa10b7d618b178aec0f027b1f5fcd3353d2b166))
+* **legacy:** import babel once ([#19152](https://github.com/vitejs/vite/issues/19152)) ([282496d](https://github.com/vitejs/vite/commit/282496daaca43494feceaa59809f6ceafd62dedd))
-# [1.6.0](https://github.com/vitejs/vite/compare/plugin-legacy@1.5.3...plugin-legacy@1.6.0) (2021-09-29)
+### Reverts
+
+* update moduleResolution value casing ([#18409](https://github.com/vitejs/vite/issues/18409)) ([#18774](https://github.com/vitejs/vite/issues/18774)) ([b0fc6e3](https://github.com/vitejs/vite/commit/b0fc6e3c2591a30360d3714263cf7cc0e2acbfdf))
+
+## [6.0.0](https://github.com/vitejs/vite/compare/plugin-legacy@5.4.3...plugin-legacy@6.0.0) (2024-11-26)
+### ⚠ BREAKING CHANGES
+
+* drop node 21 support in version ranges (#18729)
+
+### Features
+* drop node 21 support in version ranges ([#18729](https://github.com/vitejs/vite/issues/18729)) ([a384d8f](https://github.com/vitejs/vite/commit/a384d8fd39162190675abcfea31ba657383a3d03))
### Bug Fixes
-* **deps:** update all non-major dependencies ([#4545](https://github.com/vitejs/vite/issues/4545)) ([a44fd5d](https://github.com/vitejs/vite/commit/a44fd5d38679da0be2536103e83af730cda73a95))
-* esbuild minification and renderLegacyChunks false ([#5054](https://github.com/vitejs/vite/issues/5054)) ([ed384cf](https://github.com/vitejs/vite/commit/ed384cfeff9e3ccb0fdbb07ec91758308da66226))
-* normalize internal plugin names ([#4976](https://github.com/vitejs/vite/issues/4976)) ([37f0b2f](https://github.com/vitejs/vite/commit/37f0b2fff74109d381513ed052a32b43655ee11d))
-* **plugin-legacy:** fix type errors ([#4762](https://github.com/vitejs/vite/issues/4762)) ([5491143](https://github.com/vitejs/vite/commit/5491143be0b4214d2dab91076a85739d6d106481))
+* **deps:** update all non-major dependencies ([#18484](https://github.com/vitejs/vite/issues/18484)) ([2ec12df](https://github.com/vitejs/vite/commit/2ec12df98d07eb4c986737e86a4a9f8066724658))
+* **deps:** update all non-major dependencies ([#18691](https://github.com/vitejs/vite/issues/18691)) ([f005461](https://github.com/vitejs/vite/commit/f005461ecce89ada21cb0c021f7af460b5479736))
+
+### Miscellaneous Chores
+
+* **deps:** update all non-major dependencies ([#18562](https://github.com/vitejs/vite/issues/18562)) ([fb227ec](https://github.com/vitejs/vite/commit/fb227ec4402246b5a13e274c881d9de6dd8082dd))
+* **legacy:** bump terser peer dep to ^5.16 ([#18772](https://github.com/vitejs/vite/issues/18772)) ([3f6d5fe](https://github.com/vitejs/vite/commit/3f6d5fed8739f30cddb821a680576d93b3a60bba))
+* **legacy:** update peer dep Vite to 6 ([#18771](https://github.com/vitejs/vite/issues/18771)) ([63c62b3](https://github.com/vitejs/vite/commit/63c62b3059b589a51d1673bfdcefdb0b4e87c089))
+* **plugin-legacy:** add type module in package.json ([#18535](https://github.com/vitejs/vite/issues/18535)) ([28cefca](https://github.com/vitejs/vite/commit/28cefcaf2861b72901abe1f047d9ec6298b745f8))
+* upgrade to unbuild v3 rc ([#18502](https://github.com/vitejs/vite/issues/18502)) ([ddd5c5d](https://github.com/vitejs/vite/commit/ddd5c5d00ff7894462a608841560883f9c771f22))
+
+## [5.4.3](https://github.com/vitejs/vite/compare/plugin-legacy@5.4.2...plugin-legacy@5.4.3) (2024-10-25)
+### Bug Fixes
+
+* **deps:** update all non-major dependencies ([#18170](https://github.com/vitejs/vite/issues/18170)) ([c8aea5a](https://github.com/vitejs/vite/commit/c8aea5ae0af90dc6796ef3bdd612d1eb819f157b))
+* **deps:** update all non-major dependencies ([#18292](https://github.com/vitejs/vite/issues/18292)) ([5cac054](https://github.com/vitejs/vite/commit/5cac0544dca2764f0114aac38e9922a0c13d7ef4))
+* **deps:** update all non-major dependencies ([#18345](https://github.com/vitejs/vite/issues/18345)) ([5552583](https://github.com/vitejs/vite/commit/5552583a2272cd4208b30ad60e99d984e34645f0))
+* **legacy:** generate sourcemap for polyfill chunks ([#18250](https://github.com/vitejs/vite/issues/18250)) ([f311ff3](https://github.com/vitejs/vite/commit/f311ff3c2b19636457c3023095ef32ab9a96b84a))
+
+### Performance Improvements
+
+* use `crypto.hash` when available ([#18317](https://github.com/vitejs/vite/issues/18317)) ([2a14884](https://github.com/vitejs/vite/commit/2a148844cf2382a5377b75066351f00207843352))
+
+### Miscellaneous Chores
+
+* **deps:** update all non-major dependencies ([#17945](https://github.com/vitejs/vite/issues/17945)) ([cfb621e](https://github.com/vitejs/vite/commit/cfb621e7a5a3e24d710a9af156e6855e73caf891))
+* **deps:** update all non-major dependencies ([#18050](https://github.com/vitejs/vite/issues/18050)) ([7cac03f](https://github.com/vitejs/vite/commit/7cac03fa5197a72d2e2422bd0243a85a9a18abfc))
+* **deps:** update all non-major dependencies ([#18404](https://github.com/vitejs/vite/issues/18404)) ([802839d](https://github.com/vitejs/vite/commit/802839d48335a69eb15f71f2cd816d0b6e4d3556))
+* enable some eslint rules ([#18084](https://github.com/vitejs/vite/issues/18084)) ([e9a2746](https://github.com/vitejs/vite/commit/e9a2746ca77473b1814fd05db3d299c074135fe5))
+* remove stale TODOs ([#17866](https://github.com/vitejs/vite/issues/17866)) ([e012f29](https://github.com/vitejs/vite/commit/e012f296df583bd133d26399397bd4ae49de1497))
+* update license copyright ([#18278](https://github.com/vitejs/vite/issues/18278)) ([56eb869](https://github.com/vitejs/vite/commit/56eb869a67551a257d20cba00016ea59b1e1a2c4))
+
+## [5.4.2](https://github.com/vitejs/vite/compare/plugin-legacy@5.4.1...plugin-legacy@5.4.2) (2024-08-15)
+### Bug Fixes
+
+* **deps:** update all non-major dependencies ([#17430](https://github.com/vitejs/vite/issues/17430)) ([4453d35](https://github.com/vitejs/vite/commit/4453d3578b343d16a8a5298bf154f280088968d9))
+* **deps:** update all non-major dependencies ([#17494](https://github.com/vitejs/vite/issues/17494)) ([bf123f2](https://github.com/vitejs/vite/commit/bf123f2c6242424a3648cf9234281fd9ff44e3d5))
+* **deps:** update all non-major dependencies ([#17629](https://github.com/vitejs/vite/issues/17629)) ([93281b0](https://github.com/vitejs/vite/commit/93281b0e09ff8b00e21c24b80ed796db89cbc1ef))
+* **deps:** update all non-major dependencies ([#17780](https://github.com/vitejs/vite/issues/17780)) ([e408542](https://github.com/vitejs/vite/commit/e408542748edebd93dba07f21e3fd107725cadca))
+* handle encoded base paths ([#17577](https://github.com/vitejs/vite/issues/17577)) ([720447e](https://github.com/vitejs/vite/commit/720447ee725046323387f661341d44e2ad390f41))
+
+### Performance Improvements
+
+* improve regex performance ([#17789](https://github.com/vitejs/vite/issues/17789)) ([952bae3](https://github.com/vitejs/vite/commit/952bae3efcbd871fc3df5b1947060de7ebdafa36))
+
+### Documentation
+
+* rename cdnjs link ([#17565](https://github.com/vitejs/vite/issues/17565)) ([61357f6](https://github.com/vitejs/vite/commit/61357f67cdb8eca2c551150a1f0329e272f4da62))
+### Miscellaneous Chores
+* **deps:** update all non-major dependencies ([#17820](https://github.com/vitejs/vite/issues/17820)) ([bb2f8bb](https://github.com/vitejs/vite/commit/bb2f8bb55fdd64e4f16831ff98921c221a5e734a))
+* extend commit hash ([#17709](https://github.com/vitejs/vite/issues/17709)) ([4fc9b64](https://github.com/vitejs/vite/commit/4fc9b6424c27aca8004c368b69991a56264e4fdb))
+
+## [5.4.1](https://github.com/vitejs/vite/compare/plugin-legacy@5.4.0...plugin-legacy@5.4.1) (2024-05-30)
+### Bug Fixes
+
+* **deps:** update all non-major dependencies ([#17321](https://github.com/vitejs/vite/issues/17321)) ([4a89766](https://github.com/vitejs/vite/commit/4a89766d838527c144f14e842211100b16792018))
+* **plugin-legacy:** group discovered polyfills by output ([#17347](https://github.com/vitejs/vite/issues/17347)) ([c735cc7](https://github.com/vitejs/vite/commit/c735cc7895b34dd760f57145a00ddc1da7526b8c))
+* **plugin-legacy:** improve deterministic polyfills discovery ([#16566](https://github.com/vitejs/vite/issues/16566)) ([48edfcd](https://github.com/vitejs/vite/commit/48edfcd91386a28817cbe5a361beb87c7d17f490))
+
+### Documentation
+
+* **plugin-legacy:** update outdated warning about `modernPolyfills` ([#17335](https://github.com/vitejs/vite/issues/17335)) ([e6a70b7](https://github.com/vitejs/vite/commit/e6a70b7c2d8a23cd2c3a20fbd9c33f199fdc3944))
+
+### Miscellaneous Chores
+
+* **deps:** remove unused deps ([#17329](https://github.com/vitejs/vite/issues/17329)) ([5a45745](https://github.com/vitejs/vite/commit/5a457454bfee1892b0d58c4b1c401cfb15986097))
+* **deps:** update all non-major dependencies ([#16722](https://github.com/vitejs/vite/issues/16722)) ([b45922a](https://github.com/vitejs/vite/commit/b45922a91d4a73c27f78f26e369b7b1fd8d800e3))
+
+## [5.4.0](https://github.com/vitejs/vite/compare/plugin-legacy@5.3.2...plugin-legacy@5.4.0) (2024-05-08)
### Features
-* **plugin-legacy:** add externalSystemJS option ([#5024](https://github.com/vitejs/vite/issues/5024)) ([60b6f55](https://github.com/vitejs/vite/commit/60b6f5595a00cbf014a30d57721081eb79436a97))
+* **plugin-legacy:** support `additionalModernPolyfills` ([#16514](https://github.com/vitejs/vite/issues/16514)) ([2322657](https://github.com/vitejs/vite/commit/232265783670563e34cf96240bf0e383a3653e6c))
+
+### Bug Fixes
+* **deps:** update all non-major dependencies ([#16258](https://github.com/vitejs/vite/issues/16258)) ([7caef42](https://github.com/vitejs/vite/commit/7caef4216e16d9ac71e38598a9ecedce2281d42f))
+* **deps:** update all non-major dependencies ([#16376](https://github.com/vitejs/vite/issues/16376)) ([58a2938](https://github.com/vitejs/vite/commit/58a2938a9766981fdc2ed89bec8ff1c96cae0716))
+* **deps:** update all non-major dependencies ([#16488](https://github.com/vitejs/vite/issues/16488)) ([2d50be2](https://github.com/vitejs/vite/commit/2d50be2a5424e4f4c22774652ed313d2a232f8af))
+* **deps:** update all non-major dependencies ([#16549](https://github.com/vitejs/vite/issues/16549)) ([2d6a13b](https://github.com/vitejs/vite/commit/2d6a13b0aa1f3860482dac2ce260cfbb0713033f))
+* **legacy:** modern polyfill autodetection was not injecting enough polyfills ([#16367](https://github.com/vitejs/vite/issues/16367)) ([4af9f97](https://github.com/vitejs/vite/commit/4af9f97cade9fdb349e4928871bbf15c190f9e2b))
+### Documentation
-## [1.5.3](https://github.com/vitejs/vite/compare/plugin-legacy@1.5.2...plugin-legacy@1.5.3) (2021-09-07)
+* **legacy:** update `modernTargets` option default value description ([#16491](https://github.com/vitejs/vite/issues/16491)) ([7171837](https://github.com/vitejs/vite/commit/7171837abbf8634be2c2e9c32d5dc6a8cbf31e0d))
+### Miscellaneous Chores
+* **deps:** update all non-major dependencies ([#16131](https://github.com/vitejs/vite/issues/16131)) ([a862ecb](https://github.com/vitejs/vite/commit/a862ecb941a432b6e3bab62331012e4b53ddd4e8))
+
+## [5.3.2](https://github.com/vitejs/vite/compare/plugin-legacy@5.3.1...plugin-legacy@5.3.2) (2024-03-08)
### Bug Fixes
-* **plugin-legacy:** fix regression introduced in [#4536](https://github.com/vitejs/vite/issues/4536) ([#4861](https://github.com/vitejs/vite/issues/4861)) ([fdc3212](https://github.com/vitejs/vite/commit/fdc3212474ff951e7e67810eca6cfb3ef1ed9ea2))
-* **plugin-legacy:** skip in SSR build ([#4536](https://github.com/vitejs/vite/issues/4536)) ([1f068fc](https://github.com/vitejs/vite/commit/1f068fcec38fc07c34e75a19821064386e460907))
+* **plugin-legacy:** dynamic import browserslist-to-esbuild ([#16011](https://github.com/vitejs/vite/issues/16011)) ([42fd11c](https://github.com/vitejs/vite/commit/42fd11c1c6d37402bd15ba816fbf65dbed3abe55))
+* **plugin-legacy:** replace `esbuild-plugin-browserslist` with `browserslist-to-esbuild` ([#15988](https://github.com/vitejs/vite/issues/15988)) ([37af8a7](https://github.com/vitejs/vite/commit/37af8a7be417f1fb2cf9a0d5e9ad90b76ff211b4))
+* **plugin-legacy:** respect modernTargets option if renderLegacyChunks disabled ([#15789](https://github.com/vitejs/vite/issues/15789)) ([0813531](https://github.com/vitejs/vite/commit/081353179a4029d8aedaf3dfd78b95d95b757668))
+## [5.3.1](https://github.com/vitejs/vite/compare/plugin-legacy@5.3.0...plugin-legacy@5.3.1) (2024-02-21)
+### Bug Fixes
+* **deps:** update all non-major dependencies ([#15675](https://github.com/vitejs/vite/issues/15675)) ([4d9363a](https://github.com/vitejs/vite/commit/4d9363ad6bc460fe2da811cb48b036e53b8cfc75))
+* **deps:** update all non-major dependencies ([#15803](https://github.com/vitejs/vite/issues/15803)) ([e0a6ef2](https://github.com/vitejs/vite/commit/e0a6ef2b9e6f1df8c5e71efab6182b7cf662d18d))
+* **deps:** update all non-major dependencies ([#15959](https://github.com/vitejs/vite/issues/15959)) ([571a3fd](https://github.com/vitejs/vite/commit/571a3fde438d60540cfeba132e24646badf5ff2f))
-## [1.5.2](https://github.com/vitejs/vite/compare/plugin-legacy@1.5.1...plugin-legacy@1.5.2) (2021-09-01)
+## [5.3.0](https://github.com/vitejs/vite/compare/plugin-legacy@5.2.0...plugin-legacy@5.3.0) (2024-01-25)
+### Features
+* **legacy:** build file name optimization ([#15115](https://github.com/vitejs/vite/issues/15115)) ([39f435d](https://github.com/vitejs/vite/commit/39f435d8ce329870754f33509e9fdbb61883c9fc))
+* **legacy:** support any separator before hash in fileNames ([#15170](https://github.com/vitejs/vite/issues/15170)) ([ecab41a](https://github.com/vitejs/vite/commit/ecab41a7f8ee09c43e7ace6ef20d2f8693a5978a))
+* **plugin-legacy:** add `modernTargets` option ([#15506](https://github.com/vitejs/vite/issues/15506)) ([cf56507](https://github.com/vitejs/vite/commit/cf56507dbfd41c4af63de511a320971668d5204f))
### Bug Fixes
-* **plugin-legacy:** avoid executing blank dynamic import ([#4767](https://github.com/vitejs/vite/issues/4767)) ([de71408](https://github.com/vitejs/vite/commit/de7140853140029a3f48600b60e700464e7662b5)), closes [#4568](https://github.com/vitejs/vite/issues/4568)
+* **deps:** update all non-major dependencies ([#15233](https://github.com/vitejs/vite/issues/15233)) ([ad3adda](https://github.com/vitejs/vite/commit/ad3adda7215c33874a07cbd4d430fcffe4c85dce))
+* **deps:** update all non-major dependencies ([#15304](https://github.com/vitejs/vite/issues/15304)) ([bb07f60](https://github.com/vitejs/vite/commit/bb07f605cca698a81f1b4606ddefb34485069dd1))
+* **deps:** update all non-major dependencies ([#15375](https://github.com/vitejs/vite/issues/15375)) ([ab56227](https://github.com/vitejs/vite/commit/ab56227d89c92bfa781264e1474ed522892e3b8f))
+### Documentation
+* fix commit id collision ([#15105](https://github.com/vitejs/vite/issues/15105)) ([0654d1b](https://github.com/vitejs/vite/commit/0654d1b52448db4d7a9b69aee6aad9e015481452))
+* fix dead link ([#15700](https://github.com/vitejs/vite/issues/15700)) ([aa7916a](https://github.com/vitejs/vite/commit/aa7916a5c2d580cdd9968fc221826ddd17443bac))
-## [1.5.1](https://github.com/vitejs/vite/compare/plugin-legacy@1.5.0...plugin-legacy@1.5.1) (2021-08-03)
+### Miscellaneous Chores
+* **deps:** update all non-major dependencies ([#15145](https://github.com/vitejs/vite/issues/15145)) ([7ff2c0a](https://github.com/vitejs/vite/commit/7ff2c0afe8c6b6901385af829f2e7e80c1fe344c))
+## [5.2.0](https://github.com/vitejs/vite/compare/plugin-legacy@5.1.0...plugin-legacy@5.2.0) (2023-11-22)
### Bug Fixes
-* **deps:** update all non-major dependencies ([#4468](https://github.com/vitejs/vite/issues/4468)) ([cd54a22](https://github.com/vitejs/vite/commit/cd54a22b8d5048f5d25a9954697f49b6d65dcc1f))
-* **plugin-legacy:** bake-in Promise polyfill, fix [#4414](https://github.com/vitejs/vite/issues/4414) ([#4440](https://github.com/vitejs/vite/issues/4440)) ([024a2de](https://github.com/vitejs/vite/commit/024a2de63df60a4037f9f7b13a0bc6e2b0d41fb6))
+* **plugin-legacy:** syntax error in variable `detectModernBrowserCode` ([#15095](https://github.com/vitejs/vite/issues/15095)) ([1c605ff](https://github.com/vitejs/vite/commit/1c605ffe9b56dcf731f341a687b1d5b55bba48c6))
+
+### Tests
+
+* **legacy:** add a test to checks all inline snippets are valid JS ([#15098](https://github.com/vitejs/vite/issues/15098)) ([1b9ca66](https://github.com/vitejs/vite/commit/1b9ca66b6d777bd4a03165512de5d65d83a2f25b))
+
+## [5.1.0](https://github.com/vitejs/vite/compare/plugin-legacy@5.0.0...plugin-legacy@5.1.0) (2023-11-21)
+### Bug Fixes
+
+* **legacy:** preserve async generator function invocation ([#15021](https://github.com/vitejs/vite/issues/15021)) ([47551a6](https://github.com/vitejs/vite/commit/47551a6f32eace64a4f5b669f997892c5ab867af))
+
+### Documentation
+
+* **legacy:** clarify that csp hashes could change between minors ([#15057](https://github.com/vitejs/vite/issues/15057)) ([cd35330](https://github.com/vitejs/vite/commit/cd353306eefa9787c07b257c8c7f3f68e0949240))
+
+## [5.0.0](https://github.com/vitejs/vite/compare/plugin-legacy@5.0.0-beta.3...plugin-legacy@5.0.0) (2023-11-16)
+### ⚠ BREAKING CHANGES
+
+* **plugin-legacy:** bump vite peer dep (#15004)
+### Features
+* **plugin-legacy:** bump vite peer dep ([#15004](https://github.com/vitejs/vite/issues/15004)) ([3c92c7b](https://github.com/vitejs/vite/commit/3c92c7bca23616f156b70311b149cbc1af59d40b))
-# [1.5.0](https://github.com/vitejs/vite/compare/plugin-legacy@1.4.4...plugin-legacy@1.5.0) (2021-07-27)
+## [5.0.0-beta.3](https://github.com/vitejs/vite/compare/plugin-legacy@5.0.0-beta.2...plugin-legacy@5.0.0-beta.3) (2023-11-14)
+### Features
+* **legacy:** export `Options` ([#14933](https://github.com/vitejs/vite/issues/14933)) ([071bfc8](https://github.com/vitejs/vite/commit/071bfc8e18ebe3981bded8e7bab605bd473d72b9))
### Bug Fixes
-* **deps:** update all non-major dependencies ([#4387](https://github.com/vitejs/vite/issues/4387)) ([2f900ba](https://github.com/vitejs/vite/commit/2f900ba4d4ad8061e0046898e8d1de3129e7f784))
-* **plugin-legacy:** legacy fallback for dynamic import ([#3885](https://github.com/vitejs/vite/issues/3885)) ([fc6d8f1](https://github.com/vitejs/vite/commit/fc6d8f1d3efe836f17f3c45375dd3749128b8137))
+* **deps:** update all non-major dependencies ([#14635](https://github.com/vitejs/vite/issues/14635)) ([21017a9](https://github.com/vitejs/vite/commit/21017a9408643cbc7204215ecc5a3fdaf74dc81e))
+* **deps:** update all non-major dependencies ([#14729](https://github.com/vitejs/vite/issues/14729)) ([d5d96e7](https://github.com/vitejs/vite/commit/d5d96e712788bc762d9c135bc84628dbcfc7fb58))
+* **deps:** update all non-major dependencies ([#14883](https://github.com/vitejs/vite/issues/14883)) ([e5094e5](https://github.com/vitejs/vite/commit/e5094e5bf2aee3516d04ce35ba2fb27e70ea9858))
+* **deps:** update all non-major dependencies ([#14961](https://github.com/vitejs/vite/issues/14961)) ([0bb3995](https://github.com/vitejs/vite/commit/0bb3995a7d2245ef1cc7b2ed8a5242e33af16874))
+* **plugin-legacy:** add invoke to modern detector to avoid terser treeshaking ([#14968](https://github.com/vitejs/vite/issues/14968)) ([4033a32](https://github.com/vitejs/vite/commit/4033a320d6809c9a0c2552f0ef2bf686c63aa35a))
+
+### Miscellaneous Chores
+* **deps:** update dependency eslint-plugin-regexp to v2 ([#14730](https://github.com/vitejs/vite/issues/14730)) ([0a7c753](https://github.com/vitejs/vite/commit/0a7c75305a312161979eaf13d7b48d784bdb6b76))
+## [5.0.0-beta.2](https://github.com/vitejs/vite/compare/plugin-legacy@5.0.0-beta.1...plugin-legacy@5.0.0-beta.2) (2023-10-09)
+### ⚠ BREAKING CHANGES
-## [1.4.4](https://github.com/vitejs/vite/compare/plugin-legacy@1.4.3...plugin-legacy@1.4.4) (2021-07-12)
+* **legacy:** should rename `x.[hash].js` to `x-legacy.[hash].js` (#11599)
+### Bug Fixes
+
+* **deps:** update all non-major dependencies ([#14510](https://github.com/vitejs/vite/issues/14510)) ([eb204fd](https://github.com/vitejs/vite/commit/eb204fd3c5bffb6c6fb40f562f762e426fbaf183))
+* **legacy:** fix broken build when renderModernChunks=false & polyfills=false (fix [#14324](https://github.com/vitejs/vite/issues/14324)) ([#14346](https://github.com/vitejs/vite/issues/14346)) ([27e5b11](https://github.com/vitejs/vite/commit/27e5b1114ce653b5716cd175aed9e2775da2f97a))
+* **legacy:** should rename `x.[hash].js` to `x-legacy.[hash].js` ([#11599](https://github.com/vitejs/vite/issues/11599)) ([e7d7a6f](https://github.com/vitejs/vite/commit/e7d7a6f4ee095bca4ed4fddf387a9ff06fcea7bb))
+
+## [5.0.0-beta.1](https://github.com/vitejs/vite/compare/plugin-legacy@5.0.0-beta.0...plugin-legacy@5.0.0-beta.1) (2023-09-25)
+### ⚠ BREAKING CHANGES
+
+* **legacy:** remove `ignoreBrowserslistConfig` option (#14429)
+
+### Bug Fixes
+
+* **deps:** update all non-major dependencies ([#14460](https://github.com/vitejs/vite/issues/14460)) ([b77bff0](https://github.com/vitejs/vite/commit/b77bff0b93ba9449f63c8373ecf82289a39832a0))
+* **legacy:** add guard to modern polyfill chunk ([#13719](https://github.com/vitejs/vite/issues/13719)) ([945dc4d](https://github.com/vitejs/vite/commit/945dc4de52e64a1a8f6e2451fadf6aba7e460234))
+* **legacy:** modern polyfill autodetection was injecting more polyfills than needed ([#14428](https://github.com/vitejs/vite/issues/14428)) ([1c2e941](https://github.com/vitejs/vite/commit/1c2e941d03621a4b77d9dfca8841e336b716691c))
+* **legacy:** suppress babel warning during polyfill scan ([#14425](https://github.com/vitejs/vite/issues/14425)) ([aae3a83](https://github.com/vitejs/vite/commit/aae3a83b5fb49bbd9f174cfeac66f00483829da4))
+* **plugin-legacy:** ensure correct typing for node esm ([#13892](https://github.com/vitejs/vite/issues/13892)) ([d914a9d](https://github.com/vitejs/vite/commit/d914a9d79adfe0aed2ee5d69f6f6d1e80b613b98))
+
+### Code Refactoring
+
+* **legacy:** remove `ignoreBrowserslistConfig` option ([#14429](https://github.com/vitejs/vite/issues/14429)) ([941bb16](https://github.com/vitejs/vite/commit/941bb1610c9c9576e0b5738c9075b3eb2f16a357))
+
+## [5.0.0-beta.0](https://github.com/vitejs/vite/compare/plugin-legacy@4.1.1...plugin-legacy@5.0.0-beta.0) (2023-09-19)
+### ⚠ BREAKING CHANGES
+
+* bump minimum node version to 18 (#14030)
### Features
-* allow entryFileNames, chunkFileNames functions for legacy ([#4122](https://github.com/vitejs/vite/issues/4122)) ([df29bff](https://github.com/vitejs/vite/commit/df29bfff44ad7f2e822f92935d0ca9c99f15b67e))
+* bump minimum node version to 18 ([#14030](https://github.com/vitejs/vite/issues/14030)) ([2c1a45c](https://github.com/vitejs/vite/commit/2c1a45c86cab6ecf02abb6e50385f773d5ed568e))
+### Bug Fixes
+
+* **deps:** update all non-major dependencies ([#14092](https://github.com/vitejs/vite/issues/14092)) ([68638f7](https://github.com/vitejs/vite/commit/68638f7b0b04ddfdf35dc8686c6a022aadbb9453))
+
+### Performance Improvements
+
+* use magic-string hires boundary for sourcemaps ([#13971](https://github.com/vitejs/vite/issues/13971)) ([b9a8d65](https://github.com/vitejs/vite/commit/b9a8d65fd64d101ea596bc98a0aea0f95674a95a))
+
+### Documentation
+* **legacy:** correct `modernPolyfills` description ([#14233](https://github.com/vitejs/vite/issues/14233)) ([a57f388](https://github.com/vitejs/vite/commit/a57f388f53bdcbcacd7585724b43953c32e6806e))
+* **plugin-legacy:** fix typo ([#13936](https://github.com/vitejs/vite/issues/13936)) ([28ddd43](https://github.com/vitejs/vite/commit/28ddd43906825db9e1ffa030551e8f975d97f3a9))
-## [1.4.3](https://github.com/vitejs/vite/compare/plugin-legacy@1.4.2...plugin-legacy@1.4.3) (2021-06-27)
+### Miscellaneous Chores
+* **deps:** update all non-major dependencies ([#13938](https://github.com/vitejs/vite/issues/13938)) ([a1b519e](https://github.com/vitejs/vite/commit/a1b519e2c71593b6b4286c2f0bd8bfe2e0ad046d))
+* **eslint:** allow type annotations ([#13920](https://github.com/vitejs/vite/issues/13920)) ([d1264fd](https://github.com/vitejs/vite/commit/d1264fd34313a2da80af8dadbeab1c8e6013bb10))
+* upgrade babel and release-scripts ([#14330](https://github.com/vitejs/vite/issues/14330)) ([b361ffa](https://github.com/vitejs/vite/commit/b361ffa6724d9191fc6a581acfeab5bc3ebbd931))
+## [4.1.1](https://github.com/vitejs/vite/compare/plugin-legacy@4.1.0...plugin-legacy@4.1.1) (2023-07-20)
### Bug Fixes
-* don't force polyfillDynamicImport if renderLegacyChunks is false ([#3695](https://github.com/vitejs/vite/issues/3695)) ([#3774](https://github.com/vitejs/vite/issues/3774)) ([d2a51ca](https://github.com/vitejs/vite/commit/d2a51ca4eda2ca9f99d9a066836d76d2253cfc24))
-* **deps:** update all non-major dependencies ([#3878](https://github.com/vitejs/vite/issues/3878)) ([a66a805](https://github.com/vitejs/vite/commit/a66a8053e9520d20bcf95fce870570c5195bcc91))
-* **plugin-legacy:** chunk may not exist ([#3886](https://github.com/vitejs/vite/issues/3886)) ([dd5931d](https://github.com/vitejs/vite/commit/dd5931d9c1cf382849047332b2c3409755ceebf5))
+* **deps:** update all non-major dependencies ([#13758](https://github.com/vitejs/vite/issues/13758)) ([8ead116](https://github.com/vitejs/vite/commit/8ead11648514ae4975bf4328d6e15bd4dd42e45e))
+* **deps:** update all non-major dependencies ([#13872](https://github.com/vitejs/vite/issues/13872)) ([975a631](https://github.com/vitejs/vite/commit/975a631ec7c2373354aeeac6bc2977f24b54d13d))
+## [4.1.0](https://github.com/vitejs/vite/compare/plugin-legacy@4.0.5...plugin-legacy@4.1.0) (2023-07-06)
+### Features
+* **plugin-legacy:** add option to output only legacy builds ([#10139](https://github.com/vitejs/vite/issues/10139)) ([931b24f](https://github.com/vitejs/vite/commit/931b24f5eac4b9b5422a235782ca13baa9a99563))
-## [1.4.2](https://github.com/vitejs/vite/compare/plugin-legacy@1.4.1...plugin-legacy@1.4.2) (2021-06-22)
+### Bug Fixes
+* **deps:** update all non-major dependencies ([#13701](https://github.com/vitejs/vite/issues/13701)) ([02c6bc3](https://github.com/vitejs/vite/commit/02c6bc38645ce18f9e1c8a71421fb8aad7081688))
+## [4.0.5](https://github.com/vitejs/vite/compare/plugin-legacy@4.0.4...plugin-legacy@4.0.5) (2023-06-21)
### Bug Fixes
-* **deps:** update all non-major dependencies ([#3791](https://github.com/vitejs/vite/issues/3791)) ([74d409e](https://github.com/vitejs/vite/commit/74d409eafca8d74ec4a6ece621ea2895bc1f2a32))
-* **plugin-legacy:** wrap chunks in IIFE ([#3783](https://github.com/vitejs/vite/issues/3783)) ([9abdb81](https://github.com/vitejs/vite/commit/9abdb8137ef54dd095e7bc47ae6a1ccf490fd196))
+* **deps:** update all non-major dependencies ([#13059](https://github.com/vitejs/vite/issues/13059)) ([123ef4c](https://github.com/vitejs/vite/commit/123ef4c47c611ebd99d8b41c89c547422aea9c1d))
+* **deps:** update all non-major dependencies ([#13488](https://github.com/vitejs/vite/issues/13488)) ([bd09248](https://github.com/vitejs/vite/commit/bd09248e50ae50ec57b9a72efe0a27aa397ec2e1))
+### Documentation
+* **legacy:** add test case to ensure correct csp hashes in readme.md ([#13384](https://github.com/vitejs/vite/issues/13384)) ([bf0cd25](https://github.com/vitejs/vite/commit/bf0cd25adb3b8bb5d53433ba9323d0a95e9f756a))
-## [1.4.1](https://github.com/vitejs/vite/compare/plugin-legacy@1.4.0...plugin-legacy@1.4.1) (2021-06-01)
+### Miscellaneous Chores
+* add funding field ([#13585](https://github.com/vitejs/vite/issues/13585)) ([2501627](https://github.com/vitejs/vite/commit/250162775031a8798f67e8be71fd226a79c9831b))
+* **deps:** update all non-major dependencies ([#13553](https://github.com/vitejs/vite/issues/13553)) ([3ea0534](https://github.com/vitejs/vite/commit/3ea05342d41277baf11a73763f082e6e75c46a8f))
+## [4.0.4](https://github.com/vitejs/vite/compare/plugin-legacy@4.0.3...plugin-legacy@4.0.4) (2023-05-24)
### Bug Fixes
-* **plugin-legacy:** respect custom filenames formats, fix [#2356](https://github.com/vitejs/vite/issues/2356) ([#2641](https://github.com/vitejs/vite/issues/2641)) ([d852731](https://github.com/vitejs/vite/commit/d852731622a1c009d15a5172343fc166c4bb5cb7))
+* **legacy:** import `@babel/preset-env` ([#12961](https://github.com/vitejs/vite/issues/12961)) ([d53c650](https://github.com/vitejs/vite/commit/d53c650a69aeb43efd99b210ccc3a5606f2fc31b))
+### Miscellaneous Chores
+* **deps:** update all non-major dependencies ([#12805](https://github.com/vitejs/vite/issues/12805)) ([5731ac9](https://github.com/vitejs/vite/commit/5731ac9caaef629e892e20394f0cc73c565d9a87))
-# [1.4.0](https://github.com/vitejs/vite/compare/plugin-legacy@1.3.4...plugin-legacy@1.4.0) (2021-05-17)
+## [4.0.3](https://github.com/vitejs/vite/compare/plugin-legacy@4.0.2...plugin-legacy@4.0.3) (2023-04-25)
+### Features
+* **plugin-legacy:** support file protocol ([#8524](https://github.com/vitejs/vite/issues/8524)) ([7a87ff4](https://github.com/vitejs/vite/commit/7a87ff4b0950012ad0d85b05fe36b17e1ee2ee76))
### Bug Fixes
-* **plugin-legacy:** turn off babel loose mode ([#3406](https://github.com/vitejs/vite/issues/3406)) ([5348c02](https://github.com/vitejs/vite/commit/5348c02f58bde36c412dbfe36c3ad37772bf83e5))
-* restore dynamic-import-polyfill ([#3434](https://github.com/vitejs/vite/issues/3434)) ([4112c5d](https://github.com/vitejs/vite/commit/4112c5d103673b83c50d446096086617dfaac5a3))
+* **deps:** update all non-major dependencies ([#12389](https://github.com/vitejs/vite/issues/12389)) ([3e60b77](https://github.com/vitejs/vite/commit/3e60b778b0ed178a83f674031f5edb123e6c123c))
+
+### Code Refactoring
+
+* **eslint:** migrate to `eslint-plugin-n` ([#12895](https://github.com/vitejs/vite/issues/12895)) ([62ebe28](https://github.com/vitejs/vite/commit/62ebe28d4023c1f67578b1977edd3371f44f475a))
+
+## [4.0.2](https://github.com/vitejs/vite/compare/plugin-legacy@4.0.1...plugin-legacy@4.0.2) (2023-03-16)
+### Bug Fixes
+
+* **deps:** update all non-major dependencies ([#12036](https://github.com/vitejs/vite/issues/12036)) ([48150f2](https://github.com/vitejs/vite/commit/48150f2ea4d7ff8e3b67f15239ae05f5be317436))
+* **plugin-legacy:** no `build.target` override on SSR build ([#12171](https://github.com/vitejs/vite/issues/12171)) ([a1019f8](https://github.com/vitejs/vite/commit/a1019f80a5d5b6242d8fb0975994ce1ae6e78e94))
+### Documentation
+* **plugin-legacy:** outdated csp hash (fix [#12112](https://github.com/vitejs/vite/issues/12112)) ([#12118](https://github.com/vitejs/vite/issues/12118)) ([5f7f5dc](https://github.com/vitejs/vite/commit/5f7f5dcb0c006012631c1d5df61d79307d9097f4))
-## [1.3.4](https://github.com/vitejs/vite/compare/plugin-legacy@1.3.3...plugin-legacy@1.3.4) (2021-05-11)
+### Miscellaneous Chores
+* **deps:** update all non-major dependencies ([#12299](https://github.com/vitejs/vite/issues/12299)) ([b41336e](https://github.com/vitejs/vite/commit/b41336e450b093fb3e454806ec4245ebad7ba9c5))
+* **deps:** update rollup to 3.17.2 ([#12110](https://github.com/vitejs/vite/issues/12110)) ([e54ffbd](https://github.com/vitejs/vite/commit/e54ffbdcbd5d90d560a1bda7a140de046019fcf5))
+## [4.0.1](https://github.com/vitejs/vite/compare/plugin-legacy@4.0.0...plugin-legacy@4.0.1) (2023-02-02)
### Bug Fixes
-* **plugin-legacy:** move polyfills in plugin post, fixes [#2786](https://github.com/vitejs/vite/issues/2786) and [#2781](https://github.com/vitejs/vite/issues/2781) ([#3023](https://github.com/vitejs/vite/issues/3023)) ([43150e3](https://github.com/vitejs/vite/commit/43150e352d164744e2fda766927053439bdf7db9))
-* **plugin-legacy:** require Vite 2.0.0 final ([#3265](https://github.com/vitejs/vite/issues/3265)) ([e395dee](https://github.com/vitejs/vite/commit/e395deeb0f11ebb1bcebe69233adebaad10f77eb))
+* **legacy:** fix browserslist import, close https://github.com/vitejs/vite/issues/11898 ([#11899](https://github.com/vitejs/vite/issues/11899)) ([9241d08](https://github.com/vitejs/vite/commit/9241d0895b37c658a2dccfd961958c0c5238a49b))
+## [4.0.0](https://github.com/vitejs/vite/compare/plugin-legacy@3.0.1...plugin-legacy@4.0.0) (2023-02-02)
+### ⚠ BREAKING CHANGES
+* **legacy:** bump modern target to support async generator (#11896)
+* **plugin-legacy:** support browserslist and update default target (#11318)
-## [1.3.3](https://github.com/vitejs/vite/compare/plugin-legacy@1.3.2...plugin-legacy@1.3.3) (2021-05-03)
+### Features
+* **legacy:** bump modern target to support async generator ([#11896](https://github.com/vitejs/vite/issues/11896)) ([55b9711](https://github.com/vitejs/vite/commit/55b971139557f65f249f5385b580fa45946cb1d3))
### Bug Fixes
-* **plugin-legacy:** correct log level to error ([#3241](https://github.com/vitejs/vite/issues/3241)) ([474fe9a](https://github.com/vitejs/vite/commit/474fe9a3abbdf4845447eaab821d2ba51fda6207))
-* ignore babelrc in legacy plugin ([#2801](https://github.com/vitejs/vite/issues/2801)) ([d466ad0](https://github.com/vitejs/vite/commit/d466ad0a095859a895fd4cb85f425ad4c4583e4e))
+* **deps:** update all non-major dependencies ([#11846](https://github.com/vitejs/vite/issues/11846)) ([5d55083](https://github.com/vitejs/vite/commit/5d5508311f9856de69babd72dc4de0e7c21c7ae8))
+* **plugin-legacy:** legacy sourcemap not generate (fix [#11693](https://github.com/vitejs/vite/issues/11693)) ([#11841](https://github.com/vitejs/vite/issues/11841)) ([2ff5930](https://github.com/vitejs/vite/commit/2ff5930e02d80d6254037281b4c62b8e489d63ba))
+* **plugin-legacy:** support browserslist and update default target ([#11318](https://github.com/vitejs/vite/issues/11318)) ([d5b8f86](https://github.com/vitejs/vite/commit/d5b8f8615e880e854a3e1105e3193c24cc964f30))
+* typo ([#11283](https://github.com/vitejs/vite/issues/11283)) ([bf234a6](https://github.com/vitejs/vite/commit/bf234a63b46f0dc7a629abe0d69863ac15c381e1))
+
+### Miscellaneous Chores
+
+* **deps:** update all non-major dependencies ([#11419](https://github.com/vitejs/vite/issues/11419)) ([896475d](https://github.com/vitejs/vite/commit/896475dc6c7e5f1168e21d556201a61659552617))
+* **deps:** update all non-major dependencies ([#11787](https://github.com/vitejs/vite/issues/11787)) ([271394f](https://github.com/vitejs/vite/commit/271394fc7157a08b19f22d3751c8ec6e69f0bd5f))
+* enable `@typescript-eslint/ban-ts-comment` ([#11326](https://github.com/vitejs/vite/issues/11326)) ([e58a4f0](https://github.com/vitejs/vite/commit/e58a4f00e201e9c0d43ddda51ccac7b612d58650))
+* update packages' (vite, vite-legacy) keywords ([#11402](https://github.com/vitejs/vite/issues/11402)) ([a56bc34](https://github.com/vitejs/vite/commit/a56bc3434e9d4bc7f9d580ae630ccc633e7d436a))
+
+### Code Refactoring
+
+* **plugin-legacy:** optimize cspHashes array ([#11734](https://github.com/vitejs/vite/issues/11734)) ([b1a8e58](https://github.com/vitejs/vite/commit/b1a8e5856db91df264a7d1e40bf847dde5eb0981))
+
+## [3.0.1](https://github.com/vitejs/vite/compare/plugin-legacy@3.0.0...plugin-legacy@3.0.1) (2022-12-09)
+### Miscellaneous Chores
+
+* udpate vite and plugins to stable ([#11278](https://github.com/vitejs/vite/issues/11278)) ([026f41e](https://github.com/vitejs/vite/commit/026f41e87e6eb89491c88f62952d7a094f810811))
+## [3.0.0](https://github.com/vitejs/vite/compare/plugin-legacy@3.0.0-alpha.0...plugin-legacy@3.0.0) (2022-12-09)
+### Miscellaneous Chores
+* **deps:** update all non-major dependencies ([#11182](https://github.com/vitejs/vite/issues/11182)) ([8b83089](https://github.com/vitejs/vite/commit/8b830899ef0ce4ebe257ed18222543f60b775832))
+* enable prettier trailing commas ([#11167](https://github.com/vitejs/vite/issues/11167)) ([134ce68](https://github.com/vitejs/vite/commit/134ce6817984bad0f5fb043481502531fee9b1db))
-## [1.3.2](https://github.com/vitejs/vite/compare/plugin-legacy@1.3.1...plugin-legacy@1.3.2) (2021-03-27)
+## [3.0.0-alpha.0](https://github.com/vitejs/vite/compare/plugin-legacy@2.3.1...plugin-legacy@3.0.0-alpha.0) (2022-11-30)
+### Features
+* align default chunk and asset file names with rollup ([#10927](https://github.com/vitejs/vite/issues/10927)) ([cc2adb3](https://github.com/vitejs/vite/commit/cc2adb39254d6de139bc3dfad976430c03250b27))
+* rollup 3 ([#9870](https://github.com/vitejs/vite/issues/9870)) ([beb7166](https://github.com/vitejs/vite/commit/beb716695d5dd11fd9f3d7350c1c807dfa37a216))
### Bug Fixes
-* typo in plugin-legacy ([#2651](https://github.com/vitejs/vite/issues/2651)) ([9a2ce75](https://github.com/vitejs/vite/commit/9a2ce7580772cb783a9f8fda7e45e4a9adacbec2))
+* **deps:** update all non-major dependencies ([#10804](https://github.com/vitejs/vite/issues/10804)) ([f686afa](https://github.com/vitejs/vite/commit/f686afa6d3bc0f501b936dcbc2c4552c865fa3f9))
+* **deps:** update all non-major dependencies ([#11091](https://github.com/vitejs/vite/issues/11091)) ([073a4bf](https://github.com/vitejs/vite/commit/073a4bfe2642a4dda2183a9dfecac864524893e1))
+* support polyfill import paths containing an escaping char (e.g. '\') ([#10859](https://github.com/vitejs/vite/issues/10859)) ([7ac2535](https://github.com/vitejs/vite/commit/7ac2535cfc1eb276237a66f9776f9cda3db1148a))
+### Miscellaneous Chores
+* **deps:** update all non-major dependencies ([#10910](https://github.com/vitejs/vite/issues/10910)) ([f6ad607](https://github.com/vitejs/vite/commit/f6ad607d2430a44ea7dc71ecd3c44c1e8bf8446f))
+* **deps:** update all non-major dependencies ([#11006](https://github.com/vitejs/vite/issues/11006)) ([96f2e98](https://github.com/vitejs/vite/commit/96f2e98f6a196652962ccb5f2fa6195c050c463f))
-## [1.3.1](https://github.com/vitejs/vite/compare/plugin-legacy@1.3.0...plugin-legacy@1.3.1) (2021-02-15)
+## [2.3.1](https://github.com/vitejs/vite/compare/plugin-legacy@2.3.0...plugin-legacy@2.3.1) (2022-11-07)
+### Miscellaneous Chores
+* **deps:** update all non-major dependencies ([#10725](https://github.com/vitejs/vite/issues/10725)) ([22cfad8](https://github.com/vitejs/vite/commit/22cfad87c824e717b6c616129f3b579be2e979b2))
+## [2.3.0](https://github.com/vitejs/vite/compare/plugin-legacy@2.3.0-beta.0...plugin-legacy@2.3.0) (2022-10-26)
### Bug Fixes
-* **plugin-legacy:** prevent constant folding for import.meta.env.LEGACY ([bace724](https://github.com/vitejs/vite/commit/bace7244e776b3f4c9dd7e3ff1885df668cbcb87)), closes [#1999](https://github.com/vitejs/vite/issues/1999)
-* **plugin-legacy:** use correct string length in legacy env replacement ([#2015](https://github.com/vitejs/vite/issues/2015)) ([7f48086](https://github.com/vitejs/vite/commit/7f4808634f57ca8f4be3b455cc4fb8016acdc4fd))
+* **deps:** update all non-major dependencies ([#10610](https://github.com/vitejs/vite/issues/10610)) ([bb95467](https://github.com/vitejs/vite/commit/bb954672e3ee863e5cb37fa78167e5fc6df9ae4e))
+
+### Miscellaneous Chores
+
+* **deps:** update all non-major dependencies ([#10393](https://github.com/vitejs/vite/issues/10393)) ([f519423](https://github.com/vitejs/vite/commit/f519423170fafeee9d58aeb2052cb3bc224f25f8))
+* **deps:** update all non-major dependencies ([#10488](https://github.com/vitejs/vite/issues/10488)) ([15aa827](https://github.com/vitejs/vite/commit/15aa827283d6cbf9f55c02d6d8a3fe43dbd792e4))
+
+## [2.3.0-beta.0](https://github.com/vitejs/vite/compare/plugin-legacy@2.2.0...plugin-legacy@2.3.0-beta.0) (2022-10-05)
+### Bug Fixes
+
+* **deps:** update all non-major dependencies ([#10160](https://github.com/vitejs/vite/issues/10160)) ([6233c83](https://github.com/vitejs/vite/commit/6233c830201085d869fbbd2a7e622a59272e0f43))
+* **deps:** update all non-major dependencies ([#10246](https://github.com/vitejs/vite/issues/10246)) ([81d4d04](https://github.com/vitejs/vite/commit/81d4d04c37b805843ea83075d1c0819c31726c4e))
+* **deps:** update all non-major dependencies ([#10316](https://github.com/vitejs/vite/issues/10316)) ([a38b450](https://github.com/vitejs/vite/commit/a38b450441eea02a680b80ac0624126ba6abe3f7))
+* **legacy:** don't force set `build.target` when `renderLegacyChunks=false` (fixes [#10201](https://github.com/vitejs/vite/issues/10201)) ([#10220](https://github.com/vitejs/vite/issues/10220)) ([7f548e8](https://github.com/vitejs/vite/commit/7f548e874a2fb2b09f08fe123bb3ebc10aa2f54b))
+
+### Code Refactoring
+
+* **types:** bundle client types ([#9966](https://github.com/vitejs/vite/issues/9966)) ([da632bf](https://github.com/vitejs/vite/commit/da632bf36f561c0fc4031830721a7d4d86135efb))
+## [2.2.0](https://github.com/vitejs/vite/compare/plugin-legacy@2.1.0...plugin-legacy@2.2.0) (2022-09-19)
+### Bug Fixes
+
+* **deps:** update all non-major dependencies ([#10077](https://github.com/vitejs/vite/issues/10077)) ([caf00c8](https://github.com/vitejs/vite/commit/caf00c8c7a5c81a92182116ffa344b34ce4c3b5e))
+* **deps:** update all non-major dependencies ([#9985](https://github.com/vitejs/vite/issues/9985)) ([855f2f0](https://github.com/vitejs/vite/commit/855f2f077eb8dc41b395bccecb6a5b836eb526a9))
+* **plugin-legacy:** force set `build.target` ([#10072](https://github.com/vitejs/vite/issues/10072)) ([a13a7eb](https://github.com/vitejs/vite/commit/a13a7eb4165d38ce0ab6eefd4e4d38104ce63699))
+
+### Documentation
+
+* **plugin-legacy:** fix Vite default target ([#10158](https://github.com/vitejs/vite/issues/10158)) ([62ff788](https://github.com/vitejs/vite/commit/62ff7887870392f0cce2a40b3cc5d1b7c48a9a47))
+
+## [2.1.0](https://github.com/vitejs/vite/compare/plugin-legacy@2.1.0-beta.0...plugin-legacy@2.1.0) (2022-09-05)
+## [2.1.0-beta.0](https://github.com/vitejs/vite/compare/plugin-legacy@2.0.1...plugin-legacy@2.1.0-beta.0) (2022-08-29)
+### Bug Fixes
+
+* **deps:** update all non-major dependencies ([#9888](https://github.com/vitejs/vite/issues/9888)) ([e35a58b](https://github.com/vitejs/vite/commit/e35a58ba46f906feea8ab46886c3306257c61560))
+* **plugin-legacy:** prevent global process.env.NODE_ENV mutation ([#9741](https://github.com/vitejs/vite/issues/9741)) ([a8279af](https://github.com/vitejs/vite/commit/a8279af608657861b64af5980942cced0b04c8ac))
+
+### Miscellaneous Chores
+
+* **deps:** update all non-major dependencies ([#9675](https://github.com/vitejs/vite/issues/9675)) ([4e56e87](https://github.com/vitejs/vite/commit/4e56e87623501109198e019ebe809872528ab088))
+* **deps:** update all non-major dependencies ([#9778](https://github.com/vitejs/vite/issues/9778)) ([aceaefc](https://github.com/vitejs/vite/commit/aceaefc19eaa05c76b8a7adec035a0e4b33694c6))
+
+### Code Refactoring
+
+* **legacy:** build polyfill chunk ([#9639](https://github.com/vitejs/vite/issues/9639)) ([7ba0c9f](https://github.com/vitejs/vite/commit/7ba0c9f60e147a0039d2607a10c55e4feecd4bee))
+* **legacy:** remove code for Vite 2 ([#9640](https://github.com/vitejs/vite/issues/9640)) ([b1bbc5b](https://github.com/vitejs/vite/commit/b1bbc5bcc01bfc9b5923e9e58d744c594799a873))
+## [2.0.1](https://github.com/vitejs/vite/compare/plugin-legacy@2.0.0...plugin-legacy@2.0.1) (2022-08-11)
+### Bug Fixes
+
+* **deps:** update all non-major dependencies ([#9176](https://github.com/vitejs/vite/issues/9176)) ([31d3b70](https://github.com/vitejs/vite/commit/31d3b70672ea8759a8d7ff1993d64bb4f0e30fab))
+* **deps:** update all non-major dependencies ([#9575](https://github.com/vitejs/vite/issues/9575)) ([8071325](https://github.com/vitejs/vite/commit/80713256d0dd5716e42086fb617e96e9e92c3675))
+* **legacy:** skip esbuild transform for systemjs ([#9635](https://github.com/vitejs/vite/issues/9635)) ([ac16abd](https://github.com/vitejs/vite/commit/ac16abda0a3f96daa61507bda666ade5867ec909))
+* mention that Node.js 13/15 support is dropped (fixes [#9113](https://github.com/vitejs/vite/issues/9113)) ([#9116](https://github.com/vitejs/vite/issues/9116)) ([2826303](https://github.com/vitejs/vite/commit/2826303bd253e20df2746f84f6a7c06cb5cf3d6b))
+
+### Miscellaneous Chores
+
+* **deps:** update all non-major dependencies ([#9347](https://github.com/vitejs/vite/issues/9347)) ([2fcb027](https://github.com/vitejs/vite/commit/2fcb0272442664c395322acfc7899ab6a32bd86c))
+* **deps:** update all non-major dependencies ([#9478](https://github.com/vitejs/vite/issues/9478)) ([c530d16](https://github.com/vitejs/vite/commit/c530d168309557c7a254128364f07f7b4f017e14))
+* fix code typos ([#9033](https://github.com/vitejs/vite/issues/9033)) ([ed02861](https://github.com/vitejs/vite/commit/ed0286186b24748ec7bfa336f83c382363a22f1d))
+
+## [2.0.0](https://github.com/vitejs/vite/compare/plugin-legacy@2.0.0-beta.1...plugin-legacy@2.0.0) (2022-07-13)
+### Documentation
+
+* cleanup changes ([#8989](https://github.com/vitejs/vite/issues/8989)) ([07aef1b](https://github.com/vitejs/vite/commit/07aef1b4c02a64732b31b00591d2d9d9c8025aab))
-# [1.3.0](https://github.com/vitejs/vite/compare/plugin-legacy@1.2.3...plugin-legacy@1.3.0) (2021-02-11)
+### Miscellaneous Chores
+* 3.0 release notes and bump peer deps ([#9072](https://github.com/vitejs/vite/issues/9072)) ([427ba26](https://github.com/vitejs/vite/commit/427ba26fa720a11530d135b2ee39876fc9a778fb))
+* **deps:** update all non-major dependencies ([#9022](https://github.com/vitejs/vite/issues/9022)) ([6342140](https://github.com/vitejs/vite/commit/6342140e6ac7e033ca83d3494f94ea20ca2eaf07))
+## [2.0.0-beta.1](https://github.com/vitejs/vite/compare/plugin-legacy@2.0.0-beta.0...plugin-legacy@2.0.0-beta.1) (2022-07-06)
### Features
-* **plugin-legacy:** inject import.meta.env.LEGACY ([416f190](https://github.com/vitejs/vite/commit/416f190aa92f06a06f3ded403fb1e4cb8729256d))
+* experimental.renderBuiltUrl (revised build base options) ([#8762](https://github.com/vitejs/vite/issues/8762)) ([895a7d6](https://github.com/vitejs/vite/commit/895a7d66bc93beaf18ebcbee23b00fda9ca4c33c))
+### Bug Fixes
+* **deps:** update all non-major dependencies ([#8802](https://github.com/vitejs/vite/issues/8802)) ([a4a634d](https://github.com/vitejs/vite/commit/a4a634d6a08f8b54f052cfc2cc1b60c1bca6d48a))
-## [1.2.3](https://github.com/vitejs/vite/compare/plugin-legacy@1.2.2...plugin-legacy@1.2.3) (2021-02-01)
+### Miscellaneous Chores
+* use `tsx` directly instead of indirect `esno` ([#8773](https://github.com/vitejs/vite/issues/8773)) ([f018f13](https://github.com/vitejs/vite/commit/f018f135ffa5a2885c063d4509d39958c788120c))
+## [2.0.0-beta.0](https://github.com/vitejs/vite/compare/plugin-legacy@2.0.0-alpha.2...plugin-legacy@2.0.0-beta.0) (2022-06-21)
### Features
-* **plugin-legacy:** use compact output when transpiling legacy chunks ([045e519](https://github.com/vitejs/vite/commit/045e519d51fbce94bddb60793e9e99311acc5aa2)), closes [#1828](https://github.com/vitejs/vite/issues/1828)
+* bump minimum node version to 14.18.0 ([#8662](https://github.com/vitejs/vite/issues/8662)) ([8a05432](https://github.com/vitejs/vite/commit/8a05432e6dcc0e11d78c7b029e7340fa47fceb92))
+* experimental.buildAdvancedBaseOptions ([#8450](https://github.com/vitejs/vite/issues/8450)) ([8ef7333](https://github.com/vitejs/vite/commit/8ef733368fd6618a252e44616f7577f593fd4fbc))
+
+### Bug Fixes
+* **plugin-legacy:** prevent esbuild injecting arrow function ([#8660](https://github.com/vitejs/vite/issues/8660)) ([c0e74e5](https://github.com/vitejs/vite/commit/c0e74e5f687b8f2bb308acb51cd94c31aea2808b))
+### Miscellaneous Chores
-## [1.2.2](https://github.com/vitejs/vite/compare/plugin-legacy@1.2.1...plugin-legacy@1.2.2) (2021-01-25)
+* **deps:** update all non-major dependencies ([#8669](https://github.com/vitejs/vite/issues/8669)) ([628863d](https://github.com/vitejs/vite/commit/628863dc6120804cc1af8bda2ea98e802ded0e84))
+* use node prefix ([#8309](https://github.com/vitejs/vite/issues/8309)) ([60721ac](https://github.com/vitejs/vite/commit/60721ac53a1bf326d1cac097f23642faede234ff))
+## [2.0.0-alpha.2](https://github.com/vitejs/vite/compare/plugin-legacy@2.0.0-alpha.1...plugin-legacy@2.0.0-alpha.2) (2022-06-19)
+### ⚠ BREAKING CHANGES
+
+* make terser an optional dependency (#8049)
### Bug Fixes
-* **plugin-legacy:** throw error when using esbuild minify with legacy plugin ([8fb2511](https://github.com/vitejs/vite/commit/8fb2511a02c163d85f60dfab0bef104756768e35))
+* **build:** use crossorigin for nomodule ([#8322](https://github.com/vitejs/vite/issues/8322)) ([7f59989](https://github.com/vitejs/vite/commit/7f59989ec1fee7f8b71d297169589e010d3b84e3))
+* **deps:** update all non-major dependencies ([#8281](https://github.com/vitejs/vite/issues/8281)) ([c68db4d](https://github.com/vitejs/vite/commit/c68db4d7ad2c1baee41f280b34ae89a85ba0373d))
+* **deps:** update all non-major dependencies ([#8391](https://github.com/vitejs/vite/issues/8391)) ([842f995](https://github.com/vitejs/vite/commit/842f995ca69600c4c06c46d202fe713b80373418))
+* **plugin-legacy:** disable babel.compact when minify is disabled ([#8244](https://github.com/vitejs/vite/issues/8244)) ([742188c](https://github.com/vitejs/vite/commit/742188cc04526439060bdac7125237c20463d5a5))
+* **plugin-legacy:** don't include SystemJS in modern polyfills ([#6902](https://github.com/vitejs/vite/issues/6902)) ([eb47b1e](https://github.com/vitejs/vite/commit/eb47b1e2580cd6f8285dadba8f943e1b667ec390))
+* **plugin-legacy:** empty base makes import fail (fixes [#4212](https://github.com/vitejs/vite/issues/4212)) ([#8387](https://github.com/vitejs/vite/issues/8387)) ([1a16f12](https://github.com/vitejs/vite/commit/1a16f123e0781449c511af2d0112b8c4639972f1))
+* **plugin-legacy:** modern polyfill latest features (fixes [#8399](https://github.com/vitejs/vite/issues/8399)) ([#8408](https://github.com/vitejs/vite/issues/8408)) ([ed25817](https://github.com/vitejs/vite/commit/ed2581778baff3201f47866799f006a490a7e35b))
+* **plugin-legacy:** prevent failed to load module ([#8285](https://github.com/vitejs/vite/issues/8285)) ([d671811](https://github.com/vitejs/vite/commit/d67181195aec99ee6aa71bd8fdb69f1f09f57c9d))
+* **plugin-legacy:** respect `entryFileNames` for polyfill chunks ([#8247](https://github.com/vitejs/vite/issues/8247)) ([baa9632](https://github.com/vitejs/vite/commit/baa9632a2c2befafdfde0f131f84f247fa8b6478))
+
+### Miscellaneous Chores
+
+* **deps:** update all non-major dependencies ([#8474](https://github.com/vitejs/vite/issues/8474)) ([6d0ede7](https://github.com/vitejs/vite/commit/6d0ede7c60aaa4c010207a047bf30a2b87b5049f))
+* enable `@typescript-eslint/explicit-module-boundary-types` ([#8372](https://github.com/vitejs/vite/issues/8372)) ([104caf9](https://github.com/vitejs/vite/commit/104caf95ecd8cdf2d21ca7171931622b52fd74ff))
+* update major deps ([#8572](https://github.com/vitejs/vite/issues/8572)) ([0e20949](https://github.com/vitejs/vite/commit/0e20949dbf0ba38bdaefbf32a36764fe29858e20))
+* use `esno` to replace `ts-node` ([#8162](https://github.com/vitejs/vite/issues/8162)) ([c18a5f3](https://github.com/vitejs/vite/commit/c18a5f36410e418aaf8309102f1cacf7aef31b43))
+
+### Code Refactoring
+
+* make terser an optional dependency ([#8049](https://github.com/vitejs/vite/issues/8049)) ([164f528](https://github.com/vitejs/vite/commit/164f528838f3a146c82d68992d38316b9214f9b8))
+* **plugin-legacy:** improve default polyfill ([#8312](https://github.com/vitejs/vite/issues/8312)) ([4370d91](https://github.com/vitejs/vite/commit/4370d9123da20c586938753d9f606d84907334c9))
+## [2.0.0-alpha.1](https://github.com/vitejs/vite/compare/plugin-legacy@2.0.0-alpha.0...plugin-legacy@2.0.0-alpha.1) (2022-05-19)
+### ⚠ BREAKING CHANGES
+
+* bump targets (#8045)
+* relative base (#7644)
### Features
-* default vendor chunk splitting ([f6b58a0](https://github.com/vitejs/vite/commit/f6b58a0f535b1c26f9c1dfda74c28c685402c3c9))
-* support `base` option during dev, deprecate `build.base` ([#1556](https://github.com/vitejs/vite/issues/1556)) ([809d4bd](https://github.com/vitejs/vite/commit/809d4bd3bf62d3bc6b35f182178922d2ab2175f1))
+* relative base ([#7644](https://github.com/vitejs/vite/issues/7644)) ([09648c2](https://github.com/vitejs/vite/commit/09648c220a67852c38da0ba742501a15837e16c2))
+
+### Bug Fixes
+
+* **plugin-legacy:** fail to get the fileName ([#5250](https://github.com/vitejs/vite/issues/5250)) ([c7fc1d4](https://github.com/vitejs/vite/commit/c7fc1d4a532eae7b519bd70c6eba701e23b0635a))
+* rewrite CJS specific funcs/vars in plugins ([#8227](https://github.com/vitejs/vite/issues/8227)) ([9baa70b](https://github.com/vitejs/vite/commit/9baa70b788ec0b0fc419db30d627567242c6af7d))
+
+### Documentation
+
+* use latest core-js unpkg link ([#8190](https://github.com/vitejs/vite/issues/8190)) ([102b678](https://github.com/vitejs/vite/commit/102b678335ba74ac8f0ab94c8c49cba97e836e6d))
+
+### Build System
+
+* bump targets ([#8045](https://github.com/vitejs/vite/issues/8045)) ([66efd69](https://github.com/vitejs/vite/commit/66efd69a399fd73284cc7a3bffc904e154291a14))
+
+## [2.0.0-alpha.0](https://github.com/vitejs/vite/compare/plugin-legacy@1.8.2...plugin-legacy@2.0.0-alpha.0) (2022-05-13)
+### ⚠ BREAKING CHANGES
+
+* remove node v12 support (#7833)
+
+### Documentation
+
+* **plugin-legacy:** remove regenerator-runtime note ([#8007](https://github.com/vitejs/vite/issues/8007)) ([834efe9](https://github.com/vitejs/vite/commit/834efe94fe2c26fcdeabcc34a667dcc6a52326ee))
+
+### Miscellaneous Chores
+
+* bump minors and rebuild lock ([#8074](https://github.com/vitejs/vite/issues/8074)) ([aeb5b74](https://github.com/vitejs/vite/commit/aeb5b7436df5a4d7cf0ee1a9f6f110d00ef7aac1))
+* **deps:** use `esno` to replace `ts-node` ([#8152](https://github.com/vitejs/vite/issues/8152)) ([2363bd3](https://github.com/vitejs/vite/commit/2363bd3e5443aad43351ac16400b5a6ab7e0ef83))
+* revert vitejs/vite[#8152](https://github.com/vitejs/vite/issues/8152) ([#8161](https://github.com/vitejs/vite/issues/8161)) ([85b8b55](https://github.com/vitejs/vite/commit/85b8b55c0d39f53581047f622717d4a009c594f6))
+* update plugins peer deps ([d57c23c](https://github.com/vitejs/vite/commit/d57c23ca9b59491160017cea996fdbff4216263c))
+* use `unbuild` to bundle plugins ([#8139](https://github.com/vitejs/vite/issues/8139)) ([638b168](https://github.com/vitejs/vite/commit/638b1686288ad685243d34cd9f1db3814f4db1c0))
+
+### Build System
+* remove node v12 support ([#7833](https://github.com/vitejs/vite/issues/7833)) ([eeac2d2](https://github.com/vitejs/vite/commit/eeac2d2e217ddbca79d5b1dfde9bb5097e821b6a))
+## [1.8.2](https://github.com/vitejs/vite/compare/plugin-legacy@1.8.1...plugin-legacy@1.8.2) (2022-05-02)
+### Miscellaneous Chores
-## [1.2.1](https://github.com/vitejs/vite/compare/plugin-legacy@1.2.0...plugin-legacy@1.2.1) (2021-01-14)
+* **deps:** update all non-major dependencies ([#7780](https://github.com/vitejs/vite/issues/7780)) ([eba9d05](https://github.com/vitejs/vite/commit/eba9d05d7adbb5d4dd25f14b085b15eb3488dfe4))
+* **deps:** update all non-major dependencies ([#7847](https://github.com/vitejs/vite/issues/7847)) ([e29d1d9](https://github.com/vitejs/vite/commit/e29d1d92f7810c5160aac2f1e56f7b03bfa4c933))
+* **deps:** update all non-major dependencies ([#7949](https://github.com/vitejs/vite/issues/7949)) ([b877d30](https://github.com/vitejs/vite/commit/b877d30a05691bb6ea2da4e67b931a5a3d32809f))
+### Code Refactoring
+* **legacy:** remove unneeded dynamic import var init code ([#7759](https://github.com/vitejs/vite/issues/7759)) ([12a4e7d](https://github.com/vitejs/vite/commit/12a4e7d8bbf06d35d6fcc0135dcb76fd06a57c22))
+
+## [1.8.1](https://github.com/vitejs/vite/compare/plugin-legacy@1.8.0...plugin-legacy@1.8.1) (2022-04-13)
### Bug Fixes
-* **plugin-legacy:** respect config.build.assetsDir ([#1532](https://github.com/vitejs/vite/issues/1532)) ([3e7ad3f](https://github.com/vitejs/vite/commit/3e7ad3fa26a6149b44b2e522648cbda1009e4888)), closes [#1530](https://github.com/vitejs/vite/issues/1530)
+* **deps:** update all non-major dependencies ([#7668](https://github.com/vitejs/vite/issues/7668)) ([485263c](https://github.com/vitejs/vite/commit/485263cdca78e5b30fce77f1af9862b3ea3d76f1))
+
+### Documentation
+
+* **legacy:** note works in build only ([#7596](https://github.com/vitejs/vite/issues/7596)) ([f26b14a](https://github.com/vitejs/vite/commit/f26b14a0d8f4c909cb8cf3188684333b488c0714))
+
+## [1.8.0](https://github.com/vitejs/vite/compare/plugin-legacy@1.7.0...plugin-legacy@1.8.0) (2022-03-30)
+### Bug Fixes
+* **deps:** update all non-major dependencies ([#6782](https://github.com/vitejs/vite/issues/6782)) ([e38be3e](https://github.com/vitejs/vite/commit/e38be3e6ca7bf79319d5d7188e1d347b1d6091ef))
+* **deps:** update all non-major dependencies ([#7392](https://github.com/vitejs/vite/issues/7392)) ([b63fc3b](https://github.com/vitejs/vite/commit/b63fc3bbdaf59358b89a0844c264deea1b25c034))
+* **plugin-legacy:** always fallback legacy build when CSP ([#6535](https://github.com/vitejs/vite/issues/6535)) ([a118a1d](https://github.com/vitejs/vite/commit/a118a1d98c63028ddc8b2b3389b8cfa58d771e76))
+* **plugin-legacy:** polyfill latest features ([#7514](https://github.com/vitejs/vite/issues/7514)) ([cb388e2](https://github.com/vitejs/vite/commit/cb388e2dfd39fab751d0656a811c39f8440c48e2))
+* **plugin-legacy:** require Vite 2.8.0 ([#6272](https://github.com/vitejs/vite/issues/6272)) ([#6869](https://github.com/vitejs/vite/issues/6869)) ([997b8f1](https://github.com/vitejs/vite/commit/997b8f11cb156cc374ae991875a09534b5489a93))
+### Documentation
-# [1.2.0](https://github.com/vitejs/vite/compare/plugin-legacy@1.1.1...plugin-legacy@1.2.0) (2021-01-11)
+* **vite-legacy:** Note about using `regenerator-runtime` in Content Security Policy environment ([#7234](https://github.com/vitejs/vite/issues/7234)) ([0fd6422](https://github.com/vitejs/vite/commit/0fd64223304442bb483c55d818fcf808b7ffbaa8))
+### Miscellaneous Chores
+* **deps:** update all non-major dependencies ([#6905](https://github.com/vitejs/vite/issues/6905)) ([839665c](https://github.com/vitejs/vite/commit/839665c5985101c1765f0d68cf429ac96157d062))
+
+## [1.7.0](https://github.com/vitejs/vite/compare/plugin-legacy@1.6.4...plugin-legacy@1.7.0) (2022-02-09)
### Bug Fixes
-* **plugin-html:** typo in the Safari 10 nomodule snippet ([#1483](https://github.com/vitejs/vite/issues/1483)) ([e5576c3](https://github.com/vitejs/vite/commit/e5576c32c08214766c8bac5458dfcad8301d3a1a))
+* don't force terser on non-legacy (fix [#6266](https://github.com/vitejs/vite/issues/6266)) ([#6272](https://github.com/vitejs/vite/issues/6272)) ([1da104e](https://github.com/vitejs/vite/commit/1da104e8597e2965313e8cd582d032bca551e4ee))
+* **legacy:** fix conflict with the modern build on css emitting ([#6584](https://github.com/vitejs/vite/issues/6584)) ([f48255e](https://github.com/vitejs/vite/commit/f48255e6e0058e973b949fb4a2372974f0480e11)), closes [#3296](https://github.com/vitejs/vite/issues/3296) [#3317](https://github.com/vitejs/vite/issues/3317)
+
+### Miscellaneous Chores
+
+* convert scripts to TS ([#6160](https://github.com/vitejs/vite/issues/6160)) ([15b6f1b](https://github.com/vitejs/vite/commit/15b6f1ba82731c16b19e00ca3b28b1a898caa4d4))
+* **deps:** update all non-major dependencies ([#5879](https://github.com/vitejs/vite/issues/5879)) ([aab303f](https://github.com/vitejs/vite/commit/aab303f7bd333307c77363259f97a310762c4848))
+* **deps:** update all non-major dependencies ([#6185](https://github.com/vitejs/vite/issues/6185)) ([b45f4ad](https://github.com/vitejs/vite/commit/b45f4ad9f1336d1e88d271d7aca9498dde2e5013))
+* **deps:** update all non-major dependencies ([#6357](https://github.com/vitejs/vite/issues/6357)) ([a272c07](https://github.com/vitejs/vite/commit/a272c07e5c442f54e4439a4f3a9da0ebb10f73c9))
+* prefer type imports ([#5835](https://github.com/vitejs/vite/issues/5835)) ([7186857](https://github.com/vitejs/vite/commit/71868579058512b51991718655e089a78b99d39c))
+* properly parse process.env.DEBUG in plugin-legacy ([#6545](https://github.com/vitejs/vite/issues/6545)) ([155fd11](https://github.com/vitejs/vite/commit/155fd11b783eddd33f0cd7e411eea40a3585217a))
+## [1.6.4](https://github.com/vitejs/vite/compare/plugin-legacy@1.6.3...plugin-legacy@1.6.4) (2021-12-07)
+### Miscellaneous Chores
+* use cjs extension with scripts ([#5877](https://github.com/vitejs/vite/issues/5877)) ([775baba](https://github.com/vitejs/vite/commit/775babac40da546b01b8b8cbd7dff32b5cfcee6d))
+
+## [1.6.3](https://github.com/vitejs/vite/compare/plugin-legacy@1.6.2...plugin-legacy@1.6.3) (2021-11-22)
+### Bug Fixes
+
+* **build:** resolve `rollupOptions.input` paths ([#5601](https://github.com/vitejs/vite/issues/5601)) ([5b6b016](https://github.com/vitejs/vite/commit/5b6b01693720290e8998b2613f0dcb2d699ee84f))
+
+### Miscellaneous Chores
+
+* **deps:** update all non-major dependencies ([#5679](https://github.com/vitejs/vite/issues/5679)) ([09f4d57](https://github.com/vitejs/vite/commit/09f4d57e12de46ffc5fa151c218f31845ad4b471))
+* **deps:** update all non-major dependencies ([#5783](https://github.com/vitejs/vite/issues/5783)) ([eee9406](https://github.com/vitejs/vite/commit/eee940678b76966647b543e1f10fdb113da64b21))
+* **deps:** update non critical deps ([#5569](https://github.com/vitejs/vite/issues/5569)) ([09e2a5f](https://github.com/vitejs/vite/commit/09e2a5f16f36d84e95448a9ae819cec5faeb41f3))
+* **deps:** update plugins ([#5462](https://github.com/vitejs/vite/issues/5462)) ([50b5e2e](https://github.com/vitejs/vite/commit/50b5e2ea2605ba392fa622c9419b8cb5da69e0d2))
+
+## [1.6.2](https://github.com/vitejs/vite/compare/plugin-legacy@1.6.1...plugin-legacy@1.6.2) (2021-10-11)
### Features
-* **plugin-legacy:** support additionalLegacyPolyfills ([ca25896](https://github.com/vitejs/vite/commit/ca258962957c32df0196f30267c3d77b544aacbd)), closes [#1475](https://github.com/vitejs/vite/issues/1475)
+* add `build.cssTarget` option ([#5132](https://github.com/vitejs/vite/issues/5132)) ([b17444f](https://github.com/vitejs/vite/commit/b17444fd97b02bc54410c8575e7d3cb25e4058c2)), closes [#4746](https://github.com/vitejs/vite/issues/4746) [#5070](https://github.com/vitejs/vite/issues/5070) [#4930](https://github.com/vitejs/vite/issues/4930)
+## [1.6.1](https://github.com/vitejs/vite/compare/plugin-legacy@1.6.0...plugin-legacy@1.6.1) (2021-10-05)
+### Bug Fixes
+* **plugin-legacy:** use terser as the default minifier ([#5168](https://github.com/vitejs/vite/issues/5168)) ([9ee7234](https://github.com/vitejs/vite/commit/9ee72343884a7d679767833f7a659bbca6b96595))
-## [1.1.1](https://github.com/vitejs/vite/compare/plugin-legacy@1.1.0...plugin-legacy@1.1.1) (2021-01-09)
+## [1.6.0](https://github.com/vitejs/vite/compare/plugin-legacy@1.5.3...plugin-legacy@1.6.0) (2021-09-29)
+### Features
+* **plugin-legacy:** add externalSystemJS option ([#5024](https://github.com/vitejs/vite/issues/5024)) ([60b6f55](https://github.com/vitejs/vite/commit/60b6f5595a00cbf014a30d57721081eb79436a97))
### Bug Fixes
-* **plugin-legacy:** add `index.d.ts` at publish ([#1457](https://github.com/vitejs/vite/issues/1457)) ([dce2456](https://github.com/vitejs/vite/commit/dce245629651edab9719127deaf07ecfbcf20c5f))
+* **deps:** update all non-major dependencies ([#4545](https://github.com/vitejs/vite/issues/4545)) ([a44fd5d](https://github.com/vitejs/vite/commit/a44fd5d38679da0be2536103e83af730cda73a95))
+* esbuild minification and renderLegacyChunks false ([#5054](https://github.com/vitejs/vite/issues/5054)) ([ed384cf](https://github.com/vitejs/vite/commit/ed384cfeff9e3ccb0fdbb07ec91758308da66226))
+* normalize internal plugin names ([#4976](https://github.com/vitejs/vite/issues/4976)) ([37f0b2f](https://github.com/vitejs/vite/commit/37f0b2fff74109d381513ed052a32b43655ee11d))
+* **plugin-legacy:** fix type errors ([#4762](https://github.com/vitejs/vite/issues/4762)) ([5491143](https://github.com/vitejs/vite/commit/5491143be0b4214d2dab91076a85739d6d106481))
+
+### Miscellaneous Chores
+
+* **deps:** update all non-major dependencies ([#4992](https://github.com/vitejs/vite/issues/4992)) ([5274c2e](https://github.com/vitejs/vite/commit/5274c2e9fbfd7d80392b5d0c9daacbe2d7237649))
+* **deps:** update all non-major dependencies ([#5100](https://github.com/vitejs/vite/issues/5100)) ([b2ae627](https://github.com/vitejs/vite/commit/b2ae627c74ee8aeff82c80d3461ae3004d0d8369))
+* prettier format ([#5121](https://github.com/vitejs/vite/issues/5121)) ([16fc894](https://github.com/vitejs/vite/commit/16fc8942e2b2181d78359cdc37e85a17be031af4))
+
+## [1.5.3](https://github.com/vitejs/vite/compare/plugin-legacy@1.5.2...plugin-legacy@1.5.3) (2021-09-07)
+### Bug Fixes
+* **plugin-legacy:** fix regression introduced in [#4536](https://github.com/vitejs/vite/issues/4536) ([#4861](https://github.com/vitejs/vite/issues/4861)) ([fdc3212](https://github.com/vitejs/vite/commit/fdc3212474ff951e7e67810eca6cfb3ef1ed9ea2))
+* **plugin-legacy:** skip in SSR build ([#4536](https://github.com/vitejs/vite/issues/4536)) ([1f068fc](https://github.com/vitejs/vite/commit/1f068fcec38fc07c34e75a19821064386e460907))
+
+## [1.5.2](https://github.com/vitejs/vite/compare/plugin-legacy@1.5.1...plugin-legacy@1.5.2) (2021-09-01)
+### Bug Fixes
+
+* **plugin-legacy:** avoid executing blank dynamic import ([#4767](https://github.com/vitejs/vite/issues/4767)) ([de71408](https://github.com/vitejs/vite/commit/de7140853140029a3f48600b60e700464e7662b5)), closes [#4568](https://github.com/vitejs/vite/issues/4568)
+### Documentation
+
+* include algorithm in Content Security Policy hash ([#4690](https://github.com/vitejs/vite/issues/4690)) ([6815edd](https://github.com/vitejs/vite/commit/6815eddbb94f60bf0c08f91ee9404d93357eb602))
+
+## [1.5.1](https://github.com/vitejs/vite/compare/plugin-legacy@1.5.0...plugin-legacy@1.5.1) (2021-08-03)
+### Bug Fixes
+
+* **deps:** update all non-major dependencies ([#4468](https://github.com/vitejs/vite/issues/4468)) ([cd54a22](https://github.com/vitejs/vite/commit/cd54a22b8d5048f5d25a9954697f49b6d65dcc1f))
+* **plugin-legacy:** bake-in Promise polyfill, fix [#4414](https://github.com/vitejs/vite/issues/4414) ([#4440](https://github.com/vitejs/vite/issues/4440)) ([024a2de](https://github.com/vitejs/vite/commit/024a2de63df60a4037f9f7b13a0bc6e2b0d41fb6))
-# [1.1.0](https://github.com/vitejs/vite/compare/plugin-legacy@1.0.1...plugin-legacy@1.1.0) (2021-01-07)
+## [1.5.0](https://github.com/vitejs/vite/compare/plugin-legacy@1.4.4...plugin-legacy@1.5.0) (2021-07-27)
+### Bug Fixes
+* **deps:** update all non-major dependencies ([#4387](https://github.com/vitejs/vite/issues/4387)) ([2f900ba](https://github.com/vitejs/vite/commit/2f900ba4d4ad8061e0046898e8d1de3129e7f784))
+* **plugin-legacy:** legacy fallback for dynamic import ([#3885](https://github.com/vitejs/vite/issues/3885)) ([fc6d8f1](https://github.com/vitejs/vite/commit/fc6d8f1d3efe836f17f3c45375dd3749128b8137))
+## [1.4.4](https://github.com/vitejs/vite/compare/plugin-legacy@1.4.3...plugin-legacy@1.4.4) (2021-07-12)
### Features
-* use constant inline script + provide CSP hashes ([72107cd](https://github.com/vitejs/vite/commit/72107cdcdb7241e9fadd67528abb14f54b1c901d))
+* allow entryFileNames, chunkFileNames functions for legacy ([#4122](https://github.com/vitejs/vite/issues/4122)) ([df29bff](https://github.com/vitejs/vite/commit/df29bfff44ad7f2e822f92935d0ca9c99f15b67e))
+### Miscellaneous Chores
+* **deps:** update all non-major dependencies ([#4117](https://github.com/vitejs/vite/issues/4117)) ([e30ce56](https://github.com/vitejs/vite/commit/e30ce56861a154389a47e679c46a93680aae1325))
+* improve prettier config ([#4154](https://github.com/vitejs/vite/issues/4154)) ([98d95e3](https://github.com/vitejs/vite/commit/98d95e3e11bbc43e410b213b682e315b9344d2d7))
-## [1.0.1](https://github.com/vitejs/vite/compare/plugin-legacy@1.0.0...plugin-legacy@1.0.1) (2021-01-07)
+## [1.4.3](https://github.com/vitejs/vite/compare/plugin-legacy@1.4.2...plugin-legacy@1.4.3) (2021-06-27)
+### Bug Fixes
+* **deps:** update all non-major dependencies ([#3878](https://github.com/vitejs/vite/issues/3878)) ([a66a805](https://github.com/vitejs/vite/commit/a66a8053e9520d20bcf95fce870570c5195bcc91))
+* don't force polyfillDynamicImport if renderLegacyChunks is false ([#3695](https://github.com/vitejs/vite/issues/3695)) ([#3774](https://github.com/vitejs/vite/issues/3774)) ([d2a51ca](https://github.com/vitejs/vite/commit/d2a51ca4eda2ca9f99d9a066836d76d2253cfc24))
+* **plugin-legacy:** chunk may not exist ([#3886](https://github.com/vitejs/vite/issues/3886)) ([dd5931d](https://github.com/vitejs/vite/commit/dd5931d9c1cf382849047332b2c3409755ceebf5))
+## [1.4.2](https://github.com/vitejs/vite/compare/plugin-legacy@1.4.1...plugin-legacy@1.4.2) (2021-06-22)
### Bug Fixes
-* **plugin-legacy:** avoid esbuild transform on legacy chunks ([7734105](https://github.com/vitejs/vite/commit/7734105093c6dabf64da6bfc11486aa9ac62efea))
-* add promise polyfill if not used in bundle ([b72db4e](https://github.com/vitejs/vite/commit/b72db4e3ec5ca8974ea2f1913d5611f73c0978b5))
+* **deps:** update all non-major dependencies ([#3791](https://github.com/vitejs/vite/issues/3791)) ([74d409e](https://github.com/vitejs/vite/commit/74d409eafca8d74ec4a6ece621ea2895bc1f2a32))
+* **plugin-legacy:** wrap chunks in IIFE ([#3783](https://github.com/vitejs/vite/issues/3783)) ([9abdb81](https://github.com/vitejs/vite/commit/9abdb8137ef54dd095e7bc47ae6a1ccf490fd196))
+## [1.4.1](https://github.com/vitejs/vite/compare/plugin-legacy@1.4.0...plugin-legacy@1.4.1) (2021-06-01)
+### Bug Fixes
-### Performance Improvements
+* **plugin-legacy:** respect custom filenames formats, fix [#2356](https://github.com/vitejs/vite/issues/2356) ([#2641](https://github.com/vitejs/vite/issues/2641)) ([d852731](https://github.com/vitejs/vite/commit/d852731622a1c009d15a5172343fc166c4bb5cb7))
-* use @babel/standalone + lazy load ([b2f98fb](https://github.com/vitejs/vite/commit/b2f98fba0215ef171af2abc62e3aefc35f00d168))
+## [1.4.0](https://github.com/vitejs/vite/compare/plugin-legacy@1.3.4...plugin-legacy@1.4.0) (2021-05-17)
+### Bug Fixes
+* **plugin-legacy:** turn off babel loose mode ([#3406](https://github.com/vitejs/vite/issues/3406)) ([5348c02](https://github.com/vitejs/vite/commit/5348c02f58bde36c412dbfe36c3ad37772bf83e5))
+* restore dynamic-import-polyfill ([#3434](https://github.com/vitejs/vite/issues/3434)) ([4112c5d](https://github.com/vitejs/vite/commit/4112c5d103673b83c50d446096086617dfaac5a3))
+### Documentation
-# 1.0.0 (2021-01-07)
+* **plugin-legacy:** add note about IE11, close [#3362](https://github.com/vitejs/vite/issues/3362) ([#3389](https://github.com/vitejs/vite/issues/3389)) ([b0b62f9](https://github.com/vitejs/vite/commit/b0b62f96d7d4b04d3bfea683feff84c8b31f1eca))
+## [1.3.4](https://github.com/vitejs/vite/compare/plugin-legacy@1.3.3...plugin-legacy@1.3.4) (2021-05-11)
+### Bug Fixes
+
+* **plugin-legacy:** move polyfills in plugin post, fixes [#2786](https://github.com/vitejs/vite/issues/2786) and [#2781](https://github.com/vitejs/vite/issues/2781) ([#3023](https://github.com/vitejs/vite/issues/3023)) ([43150e3](https://github.com/vitejs/vite/commit/43150e352d164744e2fda766927053439bdf7db9))
+* **plugin-legacy:** require Vite 2.0.0 final ([#3265](https://github.com/vitejs/vite/issues/3265)) ([e395dee](https://github.com/vitejs/vite/commit/e395deeb0f11ebb1bcebe69233adebaad10f77eb))
+
+## [1.3.3](https://github.com/vitejs/vite/compare/plugin-legacy@1.3.2...plugin-legacy@1.3.3) (2021-05-03)
+### Bug Fixes
+
+* ignore babelrc in legacy plugin ([#2801](https://github.com/vitejs/vite/issues/2801)) ([d466ad0](https://github.com/vitejs/vite/commit/d466ad0a095859a895fd4cb85f425ad4c4583e4e))
+* **plugin-legacy:** correct log level to error ([#3241](https://github.com/vitejs/vite/issues/3241)) ([474fe9a](https://github.com/vitejs/vite/commit/474fe9a3abbdf4845447eaab821d2ba51fda6207))
+
+### Miscellaneous Chores
+
+* Add `repository.directory` to `packages/**/package.json` ([#2687](https://github.com/vitejs/vite/issues/2687)) ([0ecff94](https://github.com/vitejs/vite/commit/0ecff9417ee0ccfea9132fb9df39eb4398c11eaf))
+
+### Tests
+
+* fix timeout hiding runtime build error ([#3185](https://github.com/vitejs/vite/issues/3185)) ([978d991](https://github.com/vitejs/vite/commit/978d991293f5b8e1a4197ac4e3aee4fa2e838a88))
+
+## [1.3.2](https://github.com/vitejs/vite/compare/plugin-legacy@1.3.1...plugin-legacy@1.3.2) (2021-03-27)
+### Bug Fixes
+
+* typo in plugin-legacy ([#2651](https://github.com/vitejs/vite/issues/2651)) ([9a2ce75](https://github.com/vitejs/vite/commit/9a2ce7580772cb783a9f8fda7e45e4a9adacbec2))
+
+### Miscellaneous Chores
+
+* **plugin-legacy:** upgrade @babel/standalone to 7.13.12 ([#2649](https://github.com/vitejs/vite/issues/2649)) ([4b89f5b](https://github.com/vitejs/vite/commit/4b89f5b97e715cff9078a528d06ef1255dfff293))
+* **plugin-legacy:** upgrade @babel/standalone to 7.13.6 ([#2198](https://github.com/vitejs/vite/issues/2198)) ([609f8aa](https://github.com/vitejs/vite/commit/609f8aa726d81a4094b60d3e0374c78238837a07))
+
+## [1.3.1](https://github.com/vitejs/vite/compare/plugin-legacy@1.3.0...plugin-legacy@1.3.1) (2021-02-15)
+### Bug Fixes
+
+* **plugin-legacy:** prevent constant folding for import.meta.env.LEGACY ([bace724](https://github.com/vitejs/vite/commit/bace7244e776b3f4c9dd7e3ff1885df668cbcb87)), closes [#1999](https://github.com/vitejs/vite/issues/1999)
+* **plugin-legacy:** use correct string length in legacy env replacement ([#2015](https://github.com/vitejs/vite/issues/2015)) ([7f48086](https://github.com/vitejs/vite/commit/7f4808634f57ca8f4be3b455cc4fb8016acdc4fd))
+
+### Miscellaneous Chores
+
+* **plugin-legacy:** typo ([#2004](https://github.com/vitejs/vite/issues/2004)) [skip ci] ([5225253](https://github.com/vitejs/vite/commit/5225253bc9730b2db1a8b62642cd1496053fce6e))
+
+## [1.3.0](https://github.com/vitejs/vite/compare/plugin-legacy@1.2.3...plugin-legacy@1.3.0) (2021-02-11)
+### Features
+
+* **plugin-legacy:** inject import.meta.env.LEGACY ([416f190](https://github.com/vitejs/vite/commit/416f190aa92f06a06f3ded403fb1e4cb8729256d))
+
+### Code Refactoring
+
+* remove unused ast, adjust logic ([859fed6](https://github.com/vitejs/vite/commit/859fed6780e2412e64e27aa841a7de0aa2986728))
+
+## [1.2.3](https://github.com/vitejs/vite/compare/plugin-legacy@1.2.2...plugin-legacy@1.2.3) (2021-02-01)
+### Features
+
+* **plugin-legacy:** use compact output when transpiling legacy chunks ([045e519](https://github.com/vitejs/vite/commit/045e519d51fbce94bddb60793e9e99311acc5aa2)), closes [#1828](https://github.com/vitejs/vite/issues/1828)
+
+### Code Refactoring
+
+* **plugin-legacy:** improve polyfill import removal strategy ([e40e6b2](https://github.com/vitejs/vite/commit/e40e6b29e62acf8300de5cca16e376bfeb27bc5e))
+
+## [1.2.2](https://github.com/vitejs/vite/compare/plugin-legacy@1.2.1...plugin-legacy@1.2.2) (2021-01-25)
+### Features
+
+* default vendor chunk splitting ([f6b58a0](https://github.com/vitejs/vite/commit/f6b58a0f535b1c26f9c1dfda74c28c685402c3c9))
+* support `base` option during dev, deprecate `build.base` ([#1556](https://github.com/vitejs/vite/issues/1556)) ([809d4bd](https://github.com/vitejs/vite/commit/809d4bd3bf62d3bc6b35f182178922d2ab2175f1))
+
+### Bug Fixes
+
+* **plugin-legacy:** throw error when using esbuild minify with legacy plugin ([8fb2511](https://github.com/vitejs/vite/commit/8fb2511a02c163d85f60dfab0bef104756768e35))
+
+## [1.2.1](https://github.com/vitejs/vite/compare/plugin-legacy@1.2.0...plugin-legacy@1.2.1) (2021-01-14)
+### Bug Fixes
+
+* **plugin-legacy:** respect config.build.assetsDir ([#1532](https://github.com/vitejs/vite/issues/1532)) ([3e7ad3f](https://github.com/vitejs/vite/commit/3e7ad3fa26a6149b44b2e522648cbda1009e4888)), closes [#1530](https://github.com/vitejs/vite/issues/1530)
+
+## [1.2.0](https://github.com/vitejs/vite/compare/plugin-legacy@1.1.1...plugin-legacy@1.2.0) (2021-01-11)
+### Features
+
+* **plugin-legacy:** support additionalLegacyPolyfills ([ca25896](https://github.com/vitejs/vite/commit/ca258962957c32df0196f30267c3d77b544aacbd)), closes [#1475](https://github.com/vitejs/vite/issues/1475)
+
+### Bug Fixes
+
+* **plugin-html:** typo in the Safari 10 nomodule snippet ([#1483](https://github.com/vitejs/vite/issues/1483)) ([e5576c3](https://github.com/vitejs/vite/commit/e5576c32c08214766c8bac5458dfcad8301d3a1a))
+
+## [1.1.1](https://github.com/vitejs/vite/compare/plugin-legacy@1.1.0...plugin-legacy@1.1.1) (2021-01-09)
+### Bug Fixes
+
+* **plugin-legacy:** add `index.d.ts` at publish ([#1457](https://github.com/vitejs/vite/issues/1457)) ([dce2456](https://github.com/vitejs/vite/commit/dce245629651edab9719127deaf07ecfbcf20c5f))
+
+### Documentation
+
+* **plugin-legacy:** fix typos ([#1422](https://github.com/vitejs/vite/issues/1422)) [skip ci] ([16cf3d0](https://github.com/vitejs/vite/commit/16cf3d0cc758591c2a44abbf10b7f2fd21d5ef99))
+* Typo in plugin-legacy README ([#1455](https://github.com/vitejs/vite/issues/1455)) [skip ci] ([4647e07](https://github.com/vitejs/vite/commit/4647e072cb89d6eac648a66314f26cb6b65c68b4))
+
+### Miscellaneous Chores
+* add version badge for plugins [skip ci] ([62925eb](https://github.com/vitejs/vite/commit/62925eb4da2ce2053e3d28068db5423e2e66ae3d))
+
+## [1.1.0](https://github.com/vitejs/vite/compare/plugin-legacy@1.0.1...plugin-legacy@1.1.0) (2021-01-07)
+### Features
+
+* use constant inline script + provide CSP hashes ([72107cd](https://github.com/vitejs/vite/commit/72107cdcdb7241e9fadd67528abb14f54b1c901d))
+
+## [1.0.1](https://github.com/vitejs/vite/compare/plugin-legacy@1.0.0...plugin-legacy@1.0.1) (2021-01-07)
### Features
* **plugin-legacy:** @vitejs/plugin-legacy ([8c34870](https://github.com/vitejs/vite/commit/8c34870040f8c2f4be7d00245a3683f9e64d894e))
+### Bug Fixes
+
+* add promise polyfill if not used in bundle ([b72db4e](https://github.com/vitejs/vite/commit/b72db4e3ec5ca8974ea2f1913d5611f73c0978b5))
+* **plugin-legacy:** avoid esbuild transform on legacy chunks ([7734105](https://github.com/vitejs/vite/commit/7734105093c6dabf64da6bfc11486aa9ac62efea))
+
+### Performance Improvements
+
+* use @babel/standalone + lazy load ([b2f98fb](https://github.com/vitejs/vite/commit/b2f98fba0215ef171af2abc62e3aefc35f00d168))
+
+### Documentation
+* **plugin-legacy:** fix typo ([#1411](https://github.com/vitejs/vite/issues/1411)) ([3321111](https://github.com/vitejs/vite/commit/3321111c77f33ccb9bc06bfa84f6d3fc27902a6e))
+### Miscellaneous Chores
+
+* add plugin-legacy version requirement ([3b7a07a](https://github.com/vitejs/vite/commit/3b7a07ac5c7422b01577a26b2ca5b8e1e7001fa3))
+* changelog for plugin-legacy [skip ci] ([52ac81a](https://github.com/vitejs/vite/commit/52ac81a14298bf41e11f3bc0d2ae870d67b5ae9d))
+
+# 1.0.0 (2021-01-07)
+
+
+### Features
+
+* **plugin-legacy:** @vitejs/plugin-legacy ([8c34870](https://github.com/vitejs/vite/commit/8c34870040f8c2f4be7d00245a3683f9e64d894e))
diff --git a/packages/plugin-legacy/LICENSE b/packages/plugin-legacy/LICENSE
new file mode 100644
index 00000000000000..b7e97ecb6aa4dd
--- /dev/null
+++ b/packages/plugin-legacy/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2019-present, VoidZero Inc. and Vite contributors
+
+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
+SOFTWARE.
diff --git a/packages/plugin-legacy/README.md b/packages/plugin-legacy/README.md
index b5bf572e53fa2c..23128df2875d50 100644
--- a/packages/plugin-legacy/README.md
+++ b/packages/plugin-legacy/README.md
@@ -1,8 +1,6 @@
# @vitejs/plugin-legacy [](https://npmjs.com/package/@vitejs/plugin-legacy)
-**Note: this plugin requires `vite@^2.0.0`**.
-
-Vite's default browser support baseline is [Native ESM](https://caniuse.com/es6-module). This plugin provides support for legacy browsers that do not support native ESM.
+Vite's default browser support baseline is [Native ESM](https://caniuse.com/es6-module), [native ESM dynamic import](https://caniuse.com/es6-module-dynamic-import), and [`import.meta`](https://caniuse.com/mdn-javascript_operators_import_meta). This plugin provides support for legacy browsers that do not support those features when building for production.
By default, this plugin will:
@@ -10,7 +8,7 @@ By default, this plugin will:
- Generate a polyfill chunk including SystemJS runtime, and any necessary polyfills determined by specified browser targets and **actual usage** in the bundle.
-- Inject `
diff --git a/packages/vite/src/node/__tests__/packages/child/index.js b/packages/vite/src/node/__tests__/packages/child/index.js
new file mode 100644
index 00000000000000..186b120756be19
--- /dev/null
+++ b/packages/vite/src/node/__tests__/packages/child/index.js
@@ -0,0 +1 @@
+export default true
diff --git a/packages/vite/src/node/__tests__/packages/child/package.json b/packages/vite/src/node/__tests__/packages/child/package.json
new file mode 100644
index 00000000000000..77e2aa64615b63
--- /dev/null
+++ b/packages/vite/src/node/__tests__/packages/child/package.json
@@ -0,0 +1,5 @@
+{
+ "name": "@vitejs/child",
+ "type": "module",
+ "main": "./index.js"
+}
diff --git a/packages/vite/src/node/__tests__/packages/module/package.json b/packages/vite/src/node/__tests__/packages/module/package.json
new file mode 100644
index 00000000000000..67756e1d2c410e
--- /dev/null
+++ b/packages/vite/src/node/__tests__/packages/module/package.json
@@ -0,0 +1,4 @@
+{
+ "name": "mylib",
+ "type": "module"
+}
diff --git a/packages/vite/src/node/__tests__/packages/package.json b/packages/vite/src/node/__tests__/packages/package.json
new file mode 100644
index 00000000000000..bd6442dcacf7c9
--- /dev/null
+++ b/packages/vite/src/node/__tests__/packages/package.json
@@ -0,0 +1,3 @@
+{
+ "name": "named-testing-package"
+}
diff --git a/packages/vite/src/node/__tests__/packages/parent/index.ts b/packages/vite/src/node/__tests__/packages/parent/index.ts
new file mode 100644
index 00000000000000..747305283cadb2
--- /dev/null
+++ b/packages/vite/src/node/__tests__/packages/parent/index.ts
@@ -0,0 +1,6 @@
+// @ts-expect-error not typed
+import child from '@vitejs/child'
+
+export default {
+ child,
+}
diff --git a/packages/vite/src/node/__tests__/packages/parent/package.json b/packages/vite/src/node/__tests__/packages/parent/package.json
new file mode 100644
index 00000000000000..d966448a0560a8
--- /dev/null
+++ b/packages/vite/src/node/__tests__/packages/parent/package.json
@@ -0,0 +1,8 @@
+{
+ "name": "@vitejs/parent",
+ "type": "module",
+ "main": "./index.ts",
+ "dependencies": {
+ "@vitejs/child": "link:../child"
+ }
+}
diff --git a/packages/vite/src/node/__tests__/plugins/__snapshots__/license.spec.ts.snap b/packages/vite/src/node/__tests__/plugins/__snapshots__/license.spec.ts.snap
new file mode 100644
index 00000000000000..d93b6c9515daa3
--- /dev/null
+++ b/packages/vite/src/node/__tests__/plugins/__snapshots__/license.spec.ts.snap
@@ -0,0 +1,47 @@
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
+
+exports[`json 1`] = `
+"[
+ {
+ "name": "@vitejs/test-dep-licence-cc0",
+ "version": "0.0.0",
+ "identifier": "CC0-1.0",
+ "text": "CC0 1.0 Universal\\n\\n..."
+ },
+ {
+ "name": "@vitejs/test-dep-license-mit",
+ "version": "0.0.0",
+ "identifier": "MIT",
+ "text": "MIT License\\n\\nCopyright (c) ..."
+ },
+ {
+ "name": "@vitejs/test-dep-nested-license-isc",
+ "version": "0.0.0",
+ "identifier": "ISC",
+ "text": "Copyright (c) ..."
+ }
+]"
+`;
+
+exports[`markdown 1`] = `
+"# Licenses
+
+The app bundles dependencies which contain the following licenses:
+
+## @vitejs/test-dep-licence-cc0 - 0.0.0 (CC0-1.0)
+
+CC0 1.0 Universal
+
+...
+
+## @vitejs/test-dep-license-mit - 0.0.0 (MIT)
+
+MIT License
+
+Copyright (c) ...
+
+## @vitejs/test-dep-nested-license-isc - 0.0.0 (ISC)
+
+Copyright (c) ...
+"
+`;
diff --git a/packages/vite/src/node/__tests__/plugins/assetImportMetaUrl.spec.ts b/packages/vite/src/node/__tests__/plugins/assetImportMetaUrl.spec.ts
new file mode 100644
index 00000000000000..38355b38fe6b31
--- /dev/null
+++ b/packages/vite/src/node/__tests__/plugins/assetImportMetaUrl.spec.ts
@@ -0,0 +1,65 @@
+import { describe, expect, test } from 'vitest'
+import { parseAst } from 'rollup/parseAst'
+import { assetImportMetaUrlPlugin } from '../../plugins/assetImportMetaUrl'
+import { resolveConfig } from '../../config'
+import { PartialEnvironment } from '../../baseEnvironment'
+
+async function createAssetImportMetaurlPluginTransform() {
+ const config = await resolveConfig({ configFile: false }, 'serve')
+ const instance = assetImportMetaUrlPlugin(config)
+ const environment = new PartialEnvironment('client', config)
+
+ return async (code: string) => {
+ // @ts-expect-error transform.handler should exist
+ const result = await instance.transform.handler.call(
+ { environment, parse: parseAst },
+ code,
+ 'foo.ts',
+ )
+ return result?.code || result
+ }
+}
+
+describe('assetImportMetaUrlPlugin', async () => {
+ const transform = await createAssetImportMetaurlPluginTransform()
+
+ test('variable between /', async () => {
+ expect(
+ await transform('new URL(`./foo/${dir}/index.js`, import.meta.url)'),
+ ).toMatchInlineSnapshot(
+ `"new URL((import.meta.glob("./foo/*/index.js", {"eager":true,"import":"default","query":"?url"}))[\`./foo/\${dir}/index.js\`], import.meta.url)"`,
+ )
+ })
+
+ test('variable before non-/', async () => {
+ expect(
+ await transform('new URL(`./foo/${dir}.js`, import.meta.url)'),
+ ).toMatchInlineSnapshot(
+ `"new URL((import.meta.glob("./foo/*.js", {"eager":true,"import":"default","query":"?url"}))[\`./foo/\${dir}.js\`], import.meta.url)"`,
+ )
+ })
+
+ test('two variables', async () => {
+ expect(
+ await transform('new URL(`./foo/${dir}${file}.js`, import.meta.url)'),
+ ).toMatchInlineSnapshot(
+ `"new URL((import.meta.glob("./foo/*.js", {"eager":true,"import":"default","query":"?url"}))[\`./foo/\${dir}\${file}.js\`], import.meta.url)"`,
+ )
+ })
+
+ test('two variables between /', async () => {
+ expect(
+ await transform(
+ 'new URL(`./foo/${dir}${dir2}/index.js`, import.meta.url)',
+ ),
+ ).toMatchInlineSnapshot(
+ `"new URL((import.meta.glob("./foo/*/index.js", {"eager":true,"import":"default","query":"?url"}))[\`./foo/\${dir}\${dir2}/index.js\`], import.meta.url)"`,
+ )
+ })
+
+ test('ignore starting with a variable', async () => {
+ expect(
+ await transform('new URL(`${file}.js`, import.meta.url)'),
+ ).toMatchInlineSnapshot(`"new URL(\`\${file}.js\`, import.meta.url)"`)
+ })
+})
diff --git a/packages/vite/src/node/__tests__/plugins/css.spec.ts b/packages/vite/src/node/__tests__/plugins/css.spec.ts
index 539ec2f1af1810..2deda4e3711072 100644
--- a/packages/vite/src/node/__tests__/plugins/css.spec.ts
+++ b/packages/vite/src/node/__tests__/plugins/css.spec.ts
@@ -1,18 +1,30 @@
-import { cssUrlRE, cssPlugin } from '../../plugins/css'
+import path from 'node:path'
+import { describe, expect, test } from 'vitest'
import { resolveConfig } from '../../config'
-import fs from 'fs'
-import path from 'path'
+import type { InlineConfig } from '../../config'
+import {
+ convertTargets,
+ cssPlugin,
+ cssUrlRE,
+ getEmptyChunkReplacer,
+ hoistAtRules,
+ preprocessCSS,
+ resolveLibCssFilename,
+} from '../../plugins/css'
+import { PartialEnvironment } from '../../baseEnvironment'
+
+const dirname = import.meta.dirname
describe('search css url function', () => {
test('some spaces before it', () => {
expect(
- cssUrlRE.test("list-style-image: url('../images/bullet.jpg');")
+ cssUrlRE.test("list-style-image: url('../images/bullet.jpg');"),
).toBe(true)
})
test('no space after colon', () => {
expect(cssUrlRE.test("list-style-image:url('../images/bullet.jpg');")).toBe(
- true
+ true,
)
})
@@ -24,93 +36,425 @@ describe('search css url function', () => {
expect(
cssUrlRE.test(`@function svg-url($string) {
@return "";
- }`)
+ }`),
).toBe(false)
})
test('after parenthesis', () => {
expect(
cssUrlRE.test(
- 'mask-image: image(url(mask.png), skyblue, linear-gradient(rgba(0, 0, 0, 1.0), transparent));'
- )
+ 'mask-image: image(url(mask.png), skyblue, linear-gradient(rgba(0, 0, 0, 1.0), transparent));',
+ ),
).toBe(true)
})
test('after comma', () => {
expect(
cssUrlRE.test(
- 'mask-image: image(skyblue,url(mask.png), linear-gradient(rgba(0, 0, 0, 1.0), transparent));'
- )
+ 'mask-image: image(skyblue,url(mask.png), linear-gradient(rgba(0, 0, 0, 1.0), transparent));',
+ ),
).toBe(true)
})
+
+ test('should capture the full url with escaped parentheses', () => {
+ const css = 'background-image: url(public/awkward-name\\)2.png);'
+ const match = cssUrlRE.exec(css)
+ expect(match?.[1].trim()).toBe('public/awkward-name\\)2.png')
+ })
})
-describe('css path resolutions', () => {
- const mockedProjectPath = path.join(process.cwd(), '/foo/bar/project')
- const mockedBarCssRelativePath = '/css/bar.module.css'
- const mockedFooCssRelativePath = '/css/foo.module.css'
+describe('css modules', () => {
+ test('css module compose/from path resolutions', async () => {
+ const { transform } = await createCssPluginTransform({
+ configFile: false,
+ resolve: {
+ alias: [
+ {
+ find: '@',
+ replacement: path.join(
+ import.meta.dirname,
+ './fixtures/css-module-compose',
+ ),
+ },
+ ],
+ },
+ })
- test('cssmodule compose/from path resolutions', async () => {
- const config = await resolveConfig(
- {
- resolve: {
- alias: [
- {
- find: '@',
- replacement: mockedProjectPath
- }
- ]
- }
+ const result = await transform(
+ `\
+.foo {
+position: fixed;
+composes: bar from '@/css/bar.module.css';
+}`,
+ '/css/foo.module.css',
+ )
+
+ expect(result.code).toMatchInlineSnapshot(
+ `
+ "._bar_1b4ow_1 {
+ display: block;
+ background: #f0f;
+ }
+ ._foo_86148_1 {
+ position: fixed;
+ }"
+ `,
+ )
+ })
+
+ test('custom generateScopedName', async () => {
+ const { transform } = await createCssPluginTransform({
+ configFile: false,
+ css: {
+ modules: {
+ generateScopedName: 'custom__[hash:base64:5]',
+ },
},
- 'serve'
+ })
+ const css = `\
+.foo {
+ color: red;
+}`
+ const result1 = await transform(css, '/foo.module.css') // server
+ const result2 = await transform(css, '/foo.module.css?direct') // client
+ expect(result1.code).toBe(result2.code)
+ })
+
+ test('custom generateScopedName with lightningcss', async () => {
+ const { transform } = await createCssPluginTransform({
+ configFile: false,
+ css: {
+ modules: {
+ generateScopedName: 'custom__[hash:base64:5]',
+ },
+ transformer: 'lightningcss',
+ },
+ })
+ const css = `\
+.foo {
+ color: red;
+}`
+ const result1 = await transform(css, '/foo.module.css') // server
+ const result2 = await transform(css, '/foo.module.css?direct') // client
+ expect(result1.code).toBe(result2.code)
+ })
+})
+
+describe('hoist @ rules', () => {
+ test('hoist @import', async () => {
+ const css = `.foo{color:red;}@import "bla";`
+ const result = await hoistAtRules(css)
+ expect(result).toBe(`@import "bla";.foo{color:red;}`)
+ })
+
+ test('hoist @import url with semicolon', async () => {
+ const css = `.foo{color:red;}@import url("bla;bla");`
+ const result = await hoistAtRules(css)
+ expect(result).toBe(`@import url("bla;bla");.foo{color:red;}`)
+ })
+
+ test('hoist @import url data with semicolon', async () => {
+ const css = `.foo{color:red;}@import url(data:image/png;base64,iRxVB0);`
+ const result = await hoistAtRules(css)
+ expect(result).toBe(
+ `@import url(data:image/png;base64,iRxVB0);.foo{color:red;}`,
+ )
+ })
+
+ test('hoist @import with semicolon in quotes', async () => {
+ const css = `.foo{color:red;}@import "bla;bar";`
+ const result = await hoistAtRules(css)
+ expect(result).toBe(`@import "bla;bar";.foo{color:red;}`)
+ })
+
+ test('hoist @charset', async () => {
+ const css = `.foo{color:red;}@charset "utf-8";`
+ const result = await hoistAtRules(css)
+ expect(result).toBe(`@charset "utf-8";.foo{color:red;}`)
+ })
+
+ test('hoist one @charset only', async () => {
+ const css = `.foo{color:red;}@charset "utf-8";@charset "utf-8";`
+ const result = await hoistAtRules(css)
+ expect(result).toBe(`@charset "utf-8";.foo{color:red;}`)
+ })
+
+ test('hoist @import and @charset', async () => {
+ const css = `.foo{color:red;}@import "bla";@charset "utf-8";.bar{color:green;}@import "baz";`
+ const result = await hoistAtRules(css)
+ expect(result).toBe(
+ `@charset "utf-8";@import "bla";@import "baz";.foo{color:red;}.bar{color:green;}`,
)
+ })
+
+ test('dont hoist @import in comments', async () => {
+ const css = `.foo{color:red;}/* @import "bla"; */@import "bar";`
+ const result = await hoistAtRules(css)
+ expect(result).toBe(`@import "bar";.foo{color:red;}/* @import "bla"; */`)
+ })
+
+ test('dont hoist @charset in comments', async () => {
+ const css = `.foo{color:red;}/* @charset "utf-8"; */@charset "utf-8";`
+ const result = await hoistAtRules(css)
+ expect(result).toBe(
+ `@charset "utf-8";.foo{color:red;}/* @charset "utf-8"; */`,
+ )
+ })
- const { transform, buildStart } = cssPlugin(config)
+ test('dont hoist @import and @charset in comments', async () => {
+ const css = `
+.foo{color:red;}
+/*
+ @import "bla";
+*/
+@charset "utf-8";
+/*
+ @charset "utf-8";
+ @import "bar";
+*/
+@import "baz";`
+ const result = await hoistAtRules(css)
+ expect(result).toMatchInlineSnapshot(`
+ "@charset "utf-8";@import "baz";
+ .foo{color:red;}
+ /*
+ @import "bla";
+ */
- await buildStart.call({})
+ /*
+ @charset "utf-8";
+ @import "bar";
+ */
+ "
+ `)
+ })
+})
+
+async function createCssPluginTransform(inlineConfig: InlineConfig = {}) {
+ const config = await resolveConfig(inlineConfig, 'serve')
+ const environment = new PartialEnvironment('client', config)
- const mockFs = jest
- .spyOn(fs, 'readFile')
- // @ts-ignore jest.spyOn not recognize overrided `fs.readFile` definition.
- .mockImplementationOnce((p, encoding, callback) => {
- expect(p).toBe(path.join(mockedProjectPath, mockedBarCssRelativePath))
- expect(encoding).toBe('utf-8')
- callback(
- null,
- Buffer.from(`
-.bar {
- display: block;
- background: #f0f;
+ const { transform, buildStart } = cssPlugin(config)
+
+ // @ts-expect-error buildStart is function
+ await buildStart.call({})
+
+ return {
+ async transform(code: string, id: string) {
+ // @ts-expect-error transform.handler is function
+ return await transform.handler.call(
+ {
+ addWatchFile() {
+ return
+ },
+ environment,
+ },
+ code,
+ id,
+ )
+ },
+ }
}
- `)
- )
- })
- const { code } = await transform.call(
+describe('convertTargets', () => {
+ test('basic cases', () => {
+ expect(convertTargets('es2018')).toStrictEqual({
+ chrome: 4128768,
+ edge: 5177344,
+ firefox: 3801088,
+ safari: 786432,
+ ios_saf: 786432,
+ opera: 3276800,
+ })
+ expect(convertTargets(['safari13.1', 'ios13', 'node14'])).toStrictEqual({
+ ios_saf: 851968,
+ safari: 852224,
+ })
+ })
+})
+
+describe('getEmptyChunkReplacer', () => {
+ test('replaces import call', () => {
+ const code = `\
+import "some-module";
+import "pure_css_chunk.js";
+import "other-module";`
+
+ const replacer = getEmptyChunkReplacer(['pure_css_chunk.js'], 'es')
+ const replaced = replacer(code)
+ expect(replaced.length).toBe(code.length)
+ expect(replaced).toMatchInlineSnapshot(`
+ "import "some-module";
+ /* empty css */
+ import "other-module";"
+ `)
+ })
+
+ test('replaces import call without new lines', () => {
+ const code = `import "some-module";import "pure_css_chunk.js";import "other-module";`
+
+ const replacer = getEmptyChunkReplacer(['pure_css_chunk.js'], 'es')
+ const replaced = replacer(code)
+ expect(replaced.length).toBe(code.length)
+ expect(replaced).toMatchInlineSnapshot(
+ `"import "some-module";/* empty css */import "other-module";"`,
+ )
+ })
+
+ test('replaces require call', () => {
+ const code = `\
+require("some-module");
+require("pure_css_chunk.js");
+require("other-module");`
+
+ const replacer = getEmptyChunkReplacer(['pure_css_chunk.js'], 'cjs')
+ const replaced = replacer(code)
+ expect(replaced.length).toBe(code.length)
+ expect(replaced).toMatchInlineSnapshot(`
+ "require("some-module");
+ ;/* empty css */
+ require("other-module");"
+ `)
+ })
+
+ test('replaces require call in minified code without new lines', () => {
+ const code = `require("some-module");require("pure_css_chunk.js");require("other-module");`
+
+ const replacer = getEmptyChunkReplacer(['pure_css_chunk.js'], 'cjs')
+ const replaced = replacer(code)
+ expect(replaced.length).toBe(code.length)
+ expect(replaced).toMatchInlineSnapshot(
+ `"require("some-module");;/* empty css */require("other-module");"`,
+ )
+ })
+
+ test('replaces require call in minified code that uses comma operator', () => {
+ const code =
+ 'require("some-module"),require("pure_css_chunk.js"),require("other-module");'
+
+ const replacer = getEmptyChunkReplacer(['pure_css_chunk.js'], 'cjs')
+ const newCode = replacer(code)
+ expect(newCode.length).toBe(code.length)
+ expect(newCode).toMatchInlineSnapshot(
+ `"require("some-module"),/* empty css */require("other-module");"`,
+ )
+ // So there should be no pure css chunk anymore
+ expect(newCode).not.toContain('pure_css_chunk.js')
+ })
+
+ test('replaces require call in minified code that uses comma operator 2', () => {
+ const code = 'require("pure_css_chunk.js"),console.log();'
+ const replacer = getEmptyChunkReplacer(['pure_css_chunk.js'], 'cjs')
+ const newCode = replacer(code)
+ expect(newCode.length).toBe(code.length)
+ expect(newCode).toMatchInlineSnapshot(
+ `"/* empty css */console.log();"`,
+ )
+ expect(newCode).not.toContain('pure_css_chunk.js')
+ })
+
+ test('replaces require call in minified code that uses comma operator followed by assignment', () => {
+ const code =
+ 'require("some-module"),require("pure_css_chunk.js");const v=require("other-module");'
+
+ const replacer = getEmptyChunkReplacer(['pure_css_chunk.js'], 'cjs')
+ const newCode = replacer(code)
+ expect(newCode.length).toBe(code.length)
+ expect(newCode).toMatchInlineSnapshot(
+ `"require("some-module");/* empty css */const v=require("other-module");"`,
+ )
+ expect(newCode).not.toContain('pure_css_chunk.js')
+ })
+})
+
+describe('preprocessCSS', () => {
+ test('works', async () => {
+ const resolvedConfig = await resolveConfig({ configFile: false }, 'serve')
+ const result = await preprocessCSS(
+ `\
+.foo {
+ color:red;
+ background: url(./foo.png);
+}`,
+ 'foo.css',
+ resolvedConfig,
+ )
+ expect(result.code).toMatchInlineSnapshot(`
+ ".foo {
+ color:red;
+ background: url(./foo.png);
+ }"
+ `)
+ })
+
+ test('works with lightningcss', async () => {
+ const resolvedConfig = await resolveConfig(
{
- addWatchFile() {
- return
- }
+ configFile: false,
+ css: { transformer: 'lightningcss' },
},
- `
+ 'serve',
+ )
+ const result = await preprocessCSS(
+ `\
.foo {
- position: fixed;
- composes: bar from '@${mockedBarCssRelativePath}';
-}
- `,
- path.join(mockedProjectPath, mockedFooCssRelativePath)
+ color: red;
+ background: url(./foo.png);
+}`,
+ 'foo.css',
+ resolvedConfig,
)
-
- expect(code).toBe(`
-._bar_soicv_2 {
- display: block;
- background: #f0f;
-}
-._foo_sctn3_2 {
- position: fixed;
-}
+ expect(result.code).toMatchInlineSnapshot(`
+ ".foo {
+ color: red;
+ background: url("./foo.png");
+ }
+ "
`)
+ })
+})
+
+describe('resolveLibCssFilename', () => {
+ test('use name from package.json', () => {
+ const filename = resolveLibCssFilename(
+ {
+ entry: 'mylib.js',
+ },
+ path.resolve(dirname, '../packages/name'),
+ )
+ expect(filename).toBe('mylib.css')
+ })
+
+ test('set cssFileName', () => {
+ const filename = resolveLibCssFilename(
+ {
+ entry: 'mylib.js',
+ cssFileName: 'style',
+ },
+ path.resolve(dirname, '../packages/noname'),
+ )
+ expect(filename).toBe('style.css')
+ })
+
+ test('use fileName if set', () => {
+ const filename = resolveLibCssFilename(
+ {
+ entry: 'mylib.js',
+ fileName: 'custom-name',
+ },
+ path.resolve(dirname, '../packages/name'),
+ )
+ expect(filename).toBe('custom-name.css')
+ })
- mockFs.mockReset()
+ test('use fileName if set and has array entry', () => {
+ const filename = resolveLibCssFilename(
+ {
+ entry: ['mylib.js', 'mylib2.js'],
+ fileName: 'custom-name',
+ },
+ path.resolve(dirname, '../packages/name'),
+ )
+ expect(filename).toBe('custom-name.css')
})
})
diff --git a/packages/vite/src/node/__tests__/plugins/define.spec.ts b/packages/vite/src/node/__tests__/plugins/define.spec.ts
index 9a65f8f3a51cea..a8f309859c5158 100644
--- a/packages/vite/src/node/__tests__/plugins/define.spec.ts
+++ b/packages/vite/src/node/__tests__/plugins/define.spec.ts
@@ -1,39 +1,297 @@
+import { describe, expect, test } from 'vitest'
+import { rolldown } from 'rolldown'
import { definePlugin } from '../../plugins/define'
import { resolveConfig } from '../../config'
+import { PartialEnvironment } from '../../baseEnvironment'
async function createDefinePluginTransform(
define: Record = {},
build = true,
- ssr = false
+ ssr = false,
) {
- const config = await resolveConfig({ define }, build ? 'build' : 'serve')
+ const config = await resolveConfig(
+ { configFile: false, define },
+ build ? 'build' : 'serve',
+ )
const instance = definePlugin(config)
+ const environment = new PartialEnvironment(ssr ? 'ssr' : 'client', config)
+
return async (code: string) => {
- const result = await instance.transform.call({}, code, 'foo.ts', { ssr })
- return result?.code || result
+ if (process.env._VITE_TEST_JS_PLUGIN) {
+ // @ts-expect-error transform.handler should exist
+ const result = await instance.transform.handler.call(
+ { environment },
+ code,
+ 'foo.ts',
+ )
+ return result?.code || result
+ } else {
+ const bundler = await rolldown({
+ input: 'entry.js',
+ plugins: [
+ {
+ name: 'test',
+ resolveId(id) {
+ if (id === 'entry.js') {
+ return '\0' + id
+ }
+ },
+ load(id) {
+ if (id === '\0entry.js') {
+ return code
+ }
+ },
+ },
+ {
+ name: 'native:define',
+ options: (definePlugin(config).options! as any).bind({
+ environment,
+ }),
+ },
+ ],
+ experimental: {
+ attachDebugInfo: 'none',
+ },
+ })
+ return (await bundler.generate()).output[0].code
+ }
}
}
-describe('definePlugin', () => {
+describe.skipIf(!process.env._VITE_TEST_JS_PLUGIN)('definePlugin', () => {
test('replaces custom define', async () => {
const transform = await createDefinePluginTransform({
- __APP_VERSION__: JSON.stringify('1.0')
+ __APP_VERSION__: JSON.stringify('1.0'),
})
- expect(await transform('const version = __APP_VERSION__ ;')).toBe(
- 'const version = "1.0" ;'
+ expect(await transform('export const version = __APP_VERSION__ ;')).toBe(
+ 'export const version = "1.0";\n',
)
- expect(await transform('const version = __APP_VERSION__;')).toBe(
- 'const version = "1.0";'
+ expect(await transform('export const version = __APP_VERSION__;')).toBe(
+ 'export const version = "1.0";\n',
)
})
+ test('should not replace if not defined', async () => {
+ const transform = await createDefinePluginTransform({
+ __APP_VERSION__: JSON.stringify('1.0'),
+ })
+ expect(await transform('export const version = "1.0";')).toBe(undefined)
+ expect(
+ await transform('export const version = import.meta.SOMETHING'),
+ ).toBe(undefined)
+ })
+
test('replaces import.meta.env.SSR with false', async () => {
const transform = await createDefinePluginTransform()
- expect(await transform('const isSSR = import.meta.env.SSR ;')).toBe(
- 'const isSSR = false ;'
+ 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 = void 0;\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',
)
- expect(await transform('const isSSR = import.meta.env.SSR;')).toBe(
- 'const isSSR = false;'
+ })
+
+ test('replace import.meta.env when it is a invalid json', async () => {
+ const transform = await createDefinePluginTransform({
+ 'import.meta.env.LEGACY': '__VITE_IS_LEGACY__',
+ })
+
+ expect(
+ await transform(
+ 'export const isLegacy = import.meta.env.LEGACY;\nimport.meta.env.UNDEFINED && console.log(import.meta.env.UNDEFINED);',
+ ),
+ ).toMatchInlineSnapshot(`
+ "export const isLegacy = __VITE_IS_LEGACY__;
+ undefined && console.log(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', () => {
+ test('replaces custom define', async () => {
+ const transform = await createDefinePluginTransform({
+ __APP_VERSION__: JSON.stringify('1.0'),
+ })
+ expect(await transform('export const version = __APP_VERSION__;')).toBe(
+ 'const version = "1.0";\n\nexport { version };',
+ )
+ expect(await transform('export const version = __APP_VERSION__ ;')).toBe(
+ 'const version = "1.0";\n\nexport { version };',
+ )
+ })
+
+ test('should not replace if not defined', async () => {
+ const transform = await createDefinePluginTransform({
+ __APP_VERSION__: JSON.stringify('1.0'),
+ })
+ expect(await transform('export const version = "1.0";')).toBe(
+ 'const version = "1.0";\n\nexport { version };',
+ )
+ expect(
+ await transform('export const version = import.meta.SOMETHING'),
+ ).toBe('const version = import.meta.SOMETHING;\n\nexport { version };')
+ })
+
+ test('replaces import.meta.env.SSR with false', async () => {
+ const transform = await createDefinePluginTransform()
+ expect(await transform('export const isSSR = import.meta.env.SSR;')).toBe(
+ 'const isSSR = false;\n\nexport { isSSR };',
+ )
+ })
+
+ 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(
+ 'const hot = void 0;\n\nexport { hot };',
+ )
+ // 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(
+ 'const hot = import.meta.hot;\n\nexport { hot };',
+ )
+ })
+
+ test('replace import.meta.env.UNKNOWN with undefined', async () => {
+ const transform = await createDefinePluginTransform()
+ expect(await transform('export const foo = import.meta.env.UNKNOWN;')).toBe(
+ 'const foo = void 0;\n\nexport { foo };',
+ )
+ })
+
+ 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 foo = .*\["UNKNOWN"\];\n\nexport \{ foo \};/s)
+ })
+
+ 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(
+ 'const foo = import.meta.env.UNKNOWN;\n\nexport { foo };',
+ )
+ })
+
+ test('replace import.meta.env when it is a invalid json', async () => {
+ const transform = await createDefinePluginTransform({
+ 'import.meta.env.LEGACY': '__VITE_IS_LEGACY__',
+ })
+
+ expect(
+ await transform(
+ 'export const isLegacy = import.meta.env.LEGACY;\nimport.meta.env.UNDEFINED && console.log(import.meta.env.UNDEFINED);',
+ ),
+ ).toMatchInlineSnapshot(
+ `"const isLegacy = __VITE_IS_LEGACY__;\n\nexport { isLegacy };"`,
+ )
+ })
+
+ test('replace bare import.meta.env', async () => {
+ const transform = await createDefinePluginTransform()
+ expect(await transform('export const env = import.meta.env;')).toMatch(
+ /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/__tests__/plugins/dynamicImportVar/__snapshots__/parse.spec.ts.snap b/packages/vite/src/node/__tests__/plugins/dynamicImportVar/__snapshots__/parse.spec.ts.snap
new file mode 100644
index 00000000000000..3d6a6910422d7c
--- /dev/null
+++ b/packages/vite/src/node/__tests__/plugins/dynamicImportVar/__snapshots__/parse.spec.ts.snap
@@ -0,0 +1,23 @@
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
+
+exports[`parse positives > ? in url 1`] = `"__variableDynamicImportRuntimeHelper((import.meta.glob("./mo\\\\?ds/*.js", {"query":"?url","import":"*"})), \`./mo?ds/\${base ?? foo}.js\`)"`;
+
+exports[`parse positives > ? in variables 1`] = `"__variableDynamicImportRuntimeHelper((import.meta.glob("./mods/*.js", {"query":"?raw","import":"*"})), \`./mods/\${base ?? foo}.js\`)"`;
+
+exports[`parse positives > ? in worker 1`] = `"__variableDynamicImportRuntimeHelper((import.meta.glob("./mo\\\\?ds/*.js", {"query":"?worker","import":"*"})), \`./mo?ds/\${base ?? foo}.js\`)"`;
+
+exports[`parse positives > alias path 1`] = `"__variableDynamicImportRuntimeHelper((import.meta.glob("./mods/*.js")), \`./mods/\${base}.js\`)"`;
+
+exports[`parse positives > alias path with multi ../ 1`] = `"__variableDynamicImportRuntimeHelper((import.meta.glob("../../*.js")), \`../../\${base}.js\`)"`;
+
+exports[`parse positives > basic 1`] = `"__variableDynamicImportRuntimeHelper((import.meta.glob("./mods/*.js")), \`./mods/\${base}.js\`)"`;
+
+exports[`parse positives > with ../ and itself 1`] = `"__variableDynamicImportRuntimeHelper((import.meta.glob("../dynamicImportVar/*.js")), \`./\${name}.js\`)"`;
+
+exports[`parse positives > with multi ../ and itself 1`] = `"__variableDynamicImportRuntimeHelper((import.meta.glob("../../plugins/dynamicImportVar/*.js")), \`./\${name}.js\`)"`;
+
+exports[`parse positives > with query 1`] = `"__variableDynamicImportRuntimeHelper((import.meta.glob("./mods/*.js", {"query":"?foo=bar"})), \`./mods/\${base}.js\`)"`;
+
+exports[`parse positives > with query raw 1`] = `"__variableDynamicImportRuntimeHelper((import.meta.glob("./mods/*.js", {"query":"?raw","import":"*"})), \`./mods/\${base}.js\`)"`;
+
+exports[`parse positives > with query url 1`] = `"__variableDynamicImportRuntimeHelper((import.meta.glob("./mods/*.js", {"query":"?url","import":"*"})), \`./mods/\${base}.js\`)"`;
diff --git a/packages/vite/src/node/__tests__/plugins/dynamicImportVar/mods/hello.js b/packages/vite/src/node/__tests__/plugins/dynamicImportVar/mods/hello.js
new file mode 100644
index 00000000000000..67900ef0999962
--- /dev/null
+++ b/packages/vite/src/node/__tests__/plugins/dynamicImportVar/mods/hello.js
@@ -0,0 +1,3 @@
+export function hello() {
+ return 'hello'
+}
diff --git a/packages/vite/src/node/__tests__/plugins/dynamicImportVar/mods/hi.js b/packages/vite/src/node/__tests__/plugins/dynamicImportVar/mods/hi.js
new file mode 100644
index 00000000000000..45d3506803b2b6
--- /dev/null
+++ b/packages/vite/src/node/__tests__/plugins/dynamicImportVar/mods/hi.js
@@ -0,0 +1,3 @@
+export function hi() {
+ return 'hi'
+}
diff --git a/packages/vite/src/node/__tests__/plugins/dynamicImportVar/parse.spec.ts b/packages/vite/src/node/__tests__/plugins/dynamicImportVar/parse.spec.ts
new file mode 100644
index 00000000000000..d9aeb9c24bea17
--- /dev/null
+++ b/packages/vite/src/node/__tests__/plugins/dynamicImportVar/parse.spec.ts
@@ -0,0 +1,71 @@
+import { resolve } from 'node:path'
+import { describe, expect, it } from 'vitest'
+import { transformDynamicImport } from '../../../plugins/dynamicImportVars'
+import { normalizePath } from '../../../utils'
+import { isWindows } from '../../../../shared/utils'
+
+const dirname = import.meta.dirname
+
+async function run(input: string) {
+ const { glob, rawPattern } =
+ (await transformDynamicImport(
+ input,
+ normalizePath(resolve(dirname, 'index.js')),
+ (id) =>
+ id
+ .replace('@', resolve(dirname, './mods/'))
+ .replace('#', resolve(dirname, '../../')),
+ dirname,
+ )) || {}
+ return `__variableDynamicImportRuntimeHelper(${glob}, \`${rawPattern}\`)`
+}
+
+describe('parse positives', () => {
+ it('basic', async () => {
+ expect(await run('`./mods/${base}.js`')).toMatchSnapshot()
+ })
+
+ it('alias path', async () => {
+ expect(await run('`@/${base}.js`')).toMatchSnapshot()
+ })
+
+ it('alias path with multi ../', async () => {
+ expect(await run('`#/${base}.js`')).toMatchSnapshot()
+ })
+
+ it('with query', async () => {
+ expect(await run('`./mods/${base}.js?foo=bar`')).toMatchSnapshot()
+ })
+
+ it('with query raw', async () => {
+ expect(await run('`./mods/${base}.js?raw`')).toMatchSnapshot()
+ })
+
+ it('with query url', async () => {
+ expect(await run('`./mods/${base}.js?url`')).toMatchSnapshot()
+ })
+
+ it('? in variables', async () => {
+ expect(await run('`./mods/${base ?? foo}.js?raw`')).toMatchSnapshot()
+ })
+
+ // ? is not escaped on windows (? cannot be used as a filename on windows)
+ it.skipIf(isWindows)('? in url', async () => {
+ expect(await run('`./mo?ds/${base ?? foo}.js?url`')).toMatchSnapshot()
+ })
+
+ // ? is not escaped on windows (? cannot be used as a filename on windows)
+ it.skipIf(isWindows)('? in worker', async () => {
+ expect(await run('`./mo?ds/${base ?? foo}.js?worker`')).toMatchSnapshot()
+ })
+
+ it('with ../ and itself', async () => {
+ expect(await run('`../dynamicImportVar/${name}.js`')).toMatchSnapshot()
+ })
+
+ it('with multi ../ and itself', async () => {
+ expect(
+ await run('`../../plugins/dynamicImportVar/${name}.js`'),
+ ).toMatchSnapshot()
+ })
+})
diff --git a/packages/vite/src/node/__tests__/plugins/esbuild.spec.ts b/packages/vite/src/node/__tests__/plugins/esbuild.spec.ts
new file mode 100644
index 00000000000000..79cd8b4f3da6a0
--- /dev/null
+++ b/packages/vite/src/node/__tests__/plugins/esbuild.spec.ts
@@ -0,0 +1,436 @@
+import path from 'node:path'
+import { describe, expect, test } from 'vitest'
+import type { ResolvedConfig, UserConfig } from '../../config'
+import {
+ injectEsbuildHelpers,
+ resolveEsbuildTranspileOptions,
+ transformWithEsbuild,
+} from '../../plugins/esbuild'
+import { normalizePath } from '../../utils'
+
+describe('resolveEsbuildTranspileOptions', () => {
+ test('resolve default', () => {
+ const options = resolveEsbuildTranspileOptions(
+ defineResolvedConfig({
+ build: {
+ target: 'es2020',
+ minify: 'esbuild',
+ },
+ esbuild: {
+ keepNames: true,
+ },
+ }),
+ 'es',
+ )
+ expect(options).toEqual({
+ loader: 'js',
+ target: 'es2020',
+ format: 'esm',
+ keepNames: true,
+ minify: true,
+ treeShaking: true,
+ supported: {
+ 'dynamic-import': true,
+ 'import-meta': true,
+ },
+ })
+ })
+
+ test('resolve esnext no minify', () => {
+ const options = resolveEsbuildTranspileOptions(
+ defineResolvedConfig({
+ build: {
+ target: 'esnext',
+ minify: false,
+ },
+ esbuild: {
+ keepNames: true,
+ },
+ }),
+ 'es',
+ )
+ expect(options).toEqual(null)
+ })
+
+ test('resolve specific minify options', () => {
+ const options = resolveEsbuildTranspileOptions(
+ defineResolvedConfig({
+ build: {
+ minify: 'esbuild',
+ },
+ esbuild: {
+ keepNames: true,
+ minifyIdentifiers: false,
+ },
+ }),
+ 'es',
+ )
+ expect(options).toEqual({
+ loader: 'js',
+ target: undefined,
+ format: 'esm',
+ keepNames: true,
+ minify: false,
+ minifyIdentifiers: false,
+ minifySyntax: true,
+ minifyWhitespace: true,
+ treeShaking: true,
+ supported: {
+ 'dynamic-import': true,
+ 'import-meta': true,
+ },
+ })
+ })
+
+ test('resolve no minify', () => {
+ const options = resolveEsbuildTranspileOptions(
+ defineResolvedConfig({
+ build: {
+ target: 'es2020',
+ minify: false,
+ },
+ esbuild: {
+ keepNames: true,
+ },
+ }),
+ 'es',
+ )
+ expect(options).toEqual({
+ loader: 'js',
+ target: 'es2020',
+ format: 'esm',
+ keepNames: true,
+ minify: false,
+ minifyIdentifiers: false,
+ minifySyntax: false,
+ minifyWhitespace: false,
+ treeShaking: false,
+ supported: {
+ 'dynamic-import': true,
+ 'import-meta': true,
+ },
+ })
+ })
+
+ test('resolve es lib', () => {
+ const options = resolveEsbuildTranspileOptions(
+ defineResolvedConfig({
+ build: {
+ minify: 'esbuild',
+ lib: {
+ entry: './somewhere.js',
+ },
+ },
+ esbuild: {
+ keepNames: true,
+ },
+ }),
+ 'es',
+ )
+ expect(options).toEqual({
+ loader: 'js',
+ target: undefined,
+ format: 'esm',
+ keepNames: true,
+ minify: false,
+ minifyIdentifiers: true,
+ minifySyntax: true,
+ minifyWhitespace: false,
+ treeShaking: true,
+ supported: {
+ 'dynamic-import': true,
+ 'import-meta': true,
+ },
+ })
+ })
+
+ test('resolve cjs lib', () => {
+ const options = resolveEsbuildTranspileOptions(
+ defineResolvedConfig({
+ build: {
+ minify: 'esbuild',
+ lib: {
+ entry: './somewhere.js',
+ },
+ },
+ esbuild: {
+ keepNames: true,
+ },
+ }),
+ 'cjs',
+ )
+ expect(options).toEqual({
+ loader: 'js',
+ target: undefined,
+ format: 'cjs',
+ keepNames: true,
+ minify: true,
+ treeShaking: true,
+ supported: {
+ 'dynamic-import': true,
+ 'import-meta': true,
+ },
+ })
+ })
+
+ test('resolve es lib with specific minify options', () => {
+ const options = resolveEsbuildTranspileOptions(
+ defineResolvedConfig({
+ build: {
+ minify: 'esbuild',
+ lib: {
+ entry: './somewhere.js',
+ },
+ },
+ esbuild: {
+ keepNames: true,
+ minifyIdentifiers: true,
+ minifyWhitespace: true,
+ },
+ }),
+ 'es',
+ )
+ expect(options).toEqual({
+ loader: 'js',
+ target: undefined,
+ format: 'esm',
+ keepNames: true,
+ minify: false,
+ minifyIdentifiers: true,
+ minifySyntax: true,
+ minifyWhitespace: false,
+ treeShaking: true,
+ supported: {
+ 'dynamic-import': true,
+ 'import-meta': true,
+ },
+ })
+ })
+
+ test('resolve cjs lib with specific minify options', () => {
+ const options = resolveEsbuildTranspileOptions(
+ defineResolvedConfig({
+ build: {
+ minify: 'esbuild',
+ lib: {
+ entry: './somewhere.js',
+ },
+ },
+ esbuild: {
+ keepNames: true,
+ minifyIdentifiers: true,
+ minifySyntax: false,
+ treeShaking: true,
+ },
+ }),
+ 'cjs',
+ )
+ expect(options).toEqual({
+ loader: 'js',
+ target: undefined,
+ format: 'cjs',
+ keepNames: true,
+ minify: false,
+ minifyIdentifiers: true,
+ minifySyntax: false,
+ minifyWhitespace: true,
+ treeShaking: true,
+ supported: {
+ 'dynamic-import': true,
+ 'import-meta': true,
+ },
+ })
+ })
+})
+
+describe('transformWithEsbuild', () => {
+ test('not throw on inline sourcemap', async () => {
+ const result = await transformWithEsbuild(`const foo = 'bar'`, '', {
+ sourcemap: 'inline',
+ })
+ expect(result?.code).toBeTruthy()
+ expect(result?.map).toBeTruthy()
+ })
+
+ test('correctly overrides TS configuration and applies automatic transform', async () => {
+ const jsxImportSource = 'bar'
+ const result = await transformWithEsbuild(
+ 'const foo = () => <>>',
+ 'baz.jsx',
+ {
+ tsconfigRaw: {
+ compilerOptions: {
+ jsx: 'preserve',
+ },
+ },
+ jsx: 'automatic',
+ jsxImportSource,
+ },
+ )
+ expect(result?.code).toContain(`${jsxImportSource}/jsx-runtime`)
+ expect(result?.code).toContain('/* @__PURE__ */')
+ })
+
+ test('correctly overrides TS configuration and preserves code', async () => {
+ const foo = 'const foo = () => <>>'
+ const result = await transformWithEsbuild(foo, 'baz.jsx', {
+ tsconfigRaw: {
+ compilerOptions: {
+ jsx: 'react-jsx',
+ },
+ },
+ jsx: 'preserve',
+ })
+ expect(result?.code).toContain(foo)
+ })
+
+ test('correctly overrides TS configuration and transforms code', async () => {
+ const jsxFactory = 'h',
+ jsxFragment = 'bar'
+ const result = await transformWithEsbuild(
+ 'const foo = () => <>>',
+ 'baz.jsx',
+ {
+ tsconfigRaw: {
+ compilerOptions: {
+ jsxFactory: 'g',
+ jsxFragmentFactory: 'foo',
+ jsxImportSource: 'baz',
+ },
+ },
+ jsx: 'transform',
+ jsxFactory,
+ jsxFragment,
+ },
+ )
+ expect(result?.code).toContain(
+ `/* @__PURE__ */ ${jsxFactory}(${jsxFragment}, null)`,
+ )
+ })
+
+ describe('useDefineForClassFields', async () => {
+ const transformClassCode = async (
+ target: string,
+ tsconfigCompilerOptions: {
+ target?: string
+ useDefineForClassFields?: boolean
+ },
+ ) => {
+ const result = await transformWithEsbuild(
+ `
+ class foo {
+ bar = 'bar'
+ }
+ `,
+ normalizePath(path.resolve(import.meta.dirname, 'bar.ts')),
+ {
+ target,
+ tsconfigRaw: { compilerOptions: tsconfigCompilerOptions },
+ },
+ )
+ return result?.code
+ }
+
+ const [
+ defineForClassFieldsTrueTransformedCode,
+ defineForClassFieldsTrueLowerTransformedCode,
+ defineForClassFieldsFalseTransformedCode,
+ ] = await Promise.all([
+ transformClassCode('esnext', {
+ useDefineForClassFields: true,
+ }),
+ transformClassCode('es2021', {
+ useDefineForClassFields: true,
+ }),
+ transformClassCode('esnext', {
+ useDefineForClassFields: false,
+ }),
+ ])
+
+ test('target: esnext and tsconfig.target: esnext => true', async () => {
+ const actual = await transformClassCode('esnext', {
+ target: 'esnext',
+ })
+ expect(actual).toBe(defineForClassFieldsTrueTransformedCode)
+ })
+
+ test('target: es2021 and tsconfig.target: esnext => true', async () => {
+ const actual = await transformClassCode('es2021', {
+ target: 'esnext',
+ })
+ expect(actual).toBe(defineForClassFieldsTrueLowerTransformedCode)
+ })
+
+ test('target: es2021 and tsconfig.target: es2021 => false', async () => {
+ const actual = await transformClassCode('es2021', {
+ target: 'es2021',
+ })
+ expect(actual).toBe(defineForClassFieldsFalseTransformedCode)
+ })
+
+ test('target: esnext and tsconfig.target: es2021 => false', async () => {
+ const actual = await transformClassCode('esnext', {
+ target: 'es2021',
+ })
+ expect(actual).toBe(defineForClassFieldsFalseTransformedCode)
+ })
+
+ test('target: es2022 and tsconfig.target: es2022 => true', async () => {
+ const actual = await transformClassCode('es2022', {
+ target: 'es2022',
+ })
+ expect(actual).toBe(defineForClassFieldsTrueTransformedCode)
+ })
+
+ test('target: es2022 and tsconfig.target: undefined => false', async () => {
+ const actual = await transformClassCode('es2022', {
+ target: undefined,
+ })
+ expect(actual).toBe(defineForClassFieldsFalseTransformedCode)
+ })
+ })
+})
+
+describe('injectEsbuildHelpers', () => {
+ test('injects helpers in IIFE format', () => {
+ const esbuildCode =
+ 'var $=function(){};var MyLib=(function(){"use strict";return 42;})()'
+ const result = injectEsbuildHelpers(esbuildCode, 'iife')
+ expect(result).toBe(
+ 'var MyLib=(function(){"use strict";var $=function(){};return 42;})()',
+ )
+ })
+
+ test('injects helpers in IIFE format (pre esbuild 0.25.9)', () => {
+ const esbuildCode =
+ 'var $=function(){};var MyLib=function(){"use strict";return 42;}()'
+ const result = injectEsbuildHelpers(esbuildCode, 'iife')
+ expect(result).toBe(
+ 'var MyLib=function(){"use strict";var $=function(){};return 42;}()',
+ )
+ })
+
+ test('injects helpers in UMD format', () => {
+ const esbuildCode =
+ 'var $=function(){};(function(global){"use strict";return 42;})'
+ const result = injectEsbuildHelpers(esbuildCode, 'umd')
+ expect(result).toBe(
+ '(function(global){"use strict";var $=function(){};return 42;})',
+ )
+ })
+
+ test('handles helpers with special characters', () => {
+ const esbuildCode =
+ 'var $$=function(){};var MyLib=(function(){"use strict";return 42;})()'
+ const result = injectEsbuildHelpers(esbuildCode, 'iife')
+ expect(result).toContain('"use strict";var $$=function(){};')
+ })
+})
+
+/**
+ * Helper for `resolveEsbuildTranspileOptions` to created resolved config with types.
+ * Note: The function only uses `build.target`, `build.minify` and `esbuild` options.
+ */
+function defineResolvedConfig(config: UserConfig): ResolvedConfig {
+ return config as any
+}
diff --git a/packages/vite/src/node/__tests__/plugins/fixtures/css-module-compose/css/bar.module.css b/packages/vite/src/node/__tests__/plugins/fixtures/css-module-compose/css/bar.module.css
new file mode 100644
index 00000000000000..fa163ddd5179cc
--- /dev/null
+++ b/packages/vite/src/node/__tests__/plugins/fixtures/css-module-compose/css/bar.module.css
@@ -0,0 +1,4 @@
+.bar {
+ display: block;
+ background: #f0f;
+}
diff --git a/packages/vite/src/node/__tests__/plugins/fixtures/license/dep-licence-cc0/index.js b/packages/vite/src/node/__tests__/plugins/fixtures/license/dep-licence-cc0/index.js
new file mode 100644
index 00000000000000..b33537e2d6da91
--- /dev/null
+++ b/packages/vite/src/node/__tests__/plugins/fixtures/license/dep-licence-cc0/index.js
@@ -0,0 +1,4 @@
+// Avoid to be inlined completely: https://github.com/rolldown/rolldown/issues/8100
+console.log()
+
+export default 'ok'
diff --git a/packages/vite/src/node/__tests__/plugins/fixtures/license/dep-licence-cc0/licence b/packages/vite/src/node/__tests__/plugins/fixtures/license/dep-licence-cc0/licence
new file mode 100644
index 00000000000000..7837407e70efb5
--- /dev/null
+++ b/packages/vite/src/node/__tests__/plugins/fixtures/license/dep-licence-cc0/licence
@@ -0,0 +1,3 @@
+CC0 1.0 Universal
+
+...
diff --git a/packages/vite/src/node/__tests__/plugins/fixtures/license/dep-licence-cc0/package.json b/packages/vite/src/node/__tests__/plugins/fixtures/license/dep-licence-cc0/package.json
new file mode 100644
index 00000000000000..9e4c8b22a55c6f
--- /dev/null
+++ b/packages/vite/src/node/__tests__/plugins/fixtures/license/dep-licence-cc0/package.json
@@ -0,0 +1,7 @@
+{
+ "name": "@vitejs/test-dep-licence-cc0",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "license": "CC0-1.0"
+}
diff --git a/packages/vite/src/node/__tests__/plugins/fixtures/license/dep-license-mit/index.js b/packages/vite/src/node/__tests__/plugins/fixtures/license/dep-license-mit/index.js
new file mode 100644
index 00000000000000..42bd75ecce8bd7
--- /dev/null
+++ b/packages/vite/src/node/__tests__/plugins/fixtures/license/dep-license-mit/index.js
@@ -0,0 +1,3 @@
+import nestedDep from '@vitejs/test-dep-nested-license-isc'
+
+export default 'ok' + nestedDep
diff --git a/packages/vite/src/node/__tests__/plugins/fixtures/license/dep-license-mit/license b/packages/vite/src/node/__tests__/plugins/fixtures/license/dep-license-mit/license
new file mode 100644
index 00000000000000..1732da241e5252
--- /dev/null
+++ b/packages/vite/src/node/__tests__/plugins/fixtures/license/dep-license-mit/license
@@ -0,0 +1,3 @@
+MIT License
+
+Copyright (c) ...
diff --git a/packages/vite/src/node/__tests__/plugins/fixtures/license/dep-license-mit/package.json b/packages/vite/src/node/__tests__/plugins/fixtures/license/dep-license-mit/package.json
new file mode 100644
index 00000000000000..4b4a49eb85ca76
--- /dev/null
+++ b/packages/vite/src/node/__tests__/plugins/fixtures/license/dep-license-mit/package.json
@@ -0,0 +1,10 @@
+{
+ "name": "@vitejs/test-dep-license-mit",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "license": "MIT",
+ "dependencies": {
+ "@vitejs/test-dep-nested-license-isc": "file:../dep-nested-license-isc"
+ }
+}
diff --git a/packages/vite/src/node/__tests__/plugins/fixtures/license/dep-nested-license-isc/LICENSE b/packages/vite/src/node/__tests__/plugins/fixtures/license/dep-nested-license-isc/LICENSE
new file mode 100644
index 00000000000000..40ce705e530b07
--- /dev/null
+++ b/packages/vite/src/node/__tests__/plugins/fixtures/license/dep-nested-license-isc/LICENSE
@@ -0,0 +1 @@
+Copyright (c) ...
diff --git a/packages/vite/src/node/__tests__/plugins/fixtures/license/dep-nested-license-isc/index.js b/packages/vite/src/node/__tests__/plugins/fixtures/license/dep-nested-license-isc/index.js
new file mode 100644
index 00000000000000..b33537e2d6da91
--- /dev/null
+++ b/packages/vite/src/node/__tests__/plugins/fixtures/license/dep-nested-license-isc/index.js
@@ -0,0 +1,4 @@
+// Avoid to be inlined completely: https://github.com/rolldown/rolldown/issues/8100
+console.log()
+
+export default 'ok'
diff --git a/packages/vite/src/node/__tests__/plugins/fixtures/license/dep-nested-license-isc/package.json b/packages/vite/src/node/__tests__/plugins/fixtures/license/dep-nested-license-isc/package.json
new file mode 100644
index 00000000000000..70b3745d3dc0ef
--- /dev/null
+++ b/packages/vite/src/node/__tests__/plugins/fixtures/license/dep-nested-license-isc/package.json
@@ -0,0 +1,7 @@
+{
+ "name": "@vitejs/test-dep-nested-license-isc",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "license": "ISC"
+}
diff --git a/packages/vite/src/node/__tests__/plugins/fixtures/license/index.html b/packages/vite/src/node/__tests__/plugins/fixtures/license/index.html
new file mode 100644
index 00000000000000..b0825ecb300d5b
--- /dev/null
+++ b/packages/vite/src/node/__tests__/plugins/fixtures/license/index.html
@@ -0,0 +1,5 @@
+
diff --git a/packages/vite/src/node/__tests__/plugins/fixtures/license/package.json b/packages/vite/src/node/__tests__/plugins/fixtures/license/package.json
new file mode 100644
index 00000000000000..4e06638e94f0cc
--- /dev/null
+++ b/packages/vite/src/node/__tests__/plugins/fixtures/license/package.json
@@ -0,0 +1,10 @@
+{
+ "name": "@vitejs/test-license",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "dependencies": {
+ "@vitejs/test-dep-license-mit": "file:./dep-license-mit",
+ "@vitejs/test-dep-licence-cc0": "file:./dep-licence-cc0"
+ }
+}
diff --git a/packages/vite/src/node/__tests__/plugins/fixtures/oxc-tsconfigs/decorator-metadata/tsconfig.json b/packages/vite/src/node/__tests__/plugins/fixtures/oxc-tsconfigs/decorator-metadata/tsconfig.json
new file mode 100644
index 00000000000000..6dacb8cc2c548f
--- /dev/null
+++ b/packages/vite/src/node/__tests__/plugins/fixtures/oxc-tsconfigs/decorator-metadata/tsconfig.json
@@ -0,0 +1,6 @@
+{
+ "compilerOptions": {
+ "experimentalDecorators": true,
+ "emitDecoratorMetadata": true
+ }
+}
diff --git a/packages/playground/dynamic-import/mxd.json b/packages/vite/src/node/__tests__/plugins/fixtures/oxc-tsconfigs/empty/tsconfig.json
similarity index 100%
rename from packages/playground/dynamic-import/mxd.json
rename to packages/vite/src/node/__tests__/plugins/fixtures/oxc-tsconfigs/empty/tsconfig.json
diff --git a/packages/vite/src/node/__tests__/plugins/fixtures/oxc-tsconfigs/jsx-complex-options/tsconfig.json b/packages/vite/src/node/__tests__/plugins/fixtures/oxc-tsconfigs/jsx-complex-options/tsconfig.json
new file mode 100644
index 00000000000000..a224293f4e48ac
--- /dev/null
+++ b/packages/vite/src/node/__tests__/plugins/fixtures/oxc-tsconfigs/jsx-complex-options/tsconfig.json
@@ -0,0 +1,5 @@
+{
+ "compilerOptions": {
+ "jsx": "react-jsx"
+ }
+}
diff --git a/packages/vite/src/node/__tests__/plugins/fixtures/oxc-tsconfigs/jsx-preserve/tsconfig.json b/packages/vite/src/node/__tests__/plugins/fixtures/oxc-tsconfigs/jsx-preserve/tsconfig.json
new file mode 100644
index 00000000000000..186ad251537010
--- /dev/null
+++ b/packages/vite/src/node/__tests__/plugins/fixtures/oxc-tsconfigs/jsx-preserve/tsconfig.json
@@ -0,0 +1,5 @@
+{
+ "compilerOptions": {
+ "jsx": "preserve"
+ }
+}
diff --git a/packages/vite/src/node/__tests__/plugins/fixtures/oxc-tsconfigs/jsx-react-jsx/tsconfig.json b/packages/vite/src/node/__tests__/plugins/fixtures/oxc-tsconfigs/jsx-react-jsx/tsconfig.json
new file mode 100644
index 00000000000000..a6377dd1adcf1b
--- /dev/null
+++ b/packages/vite/src/node/__tests__/plugins/fixtures/oxc-tsconfigs/jsx-react-jsx/tsconfig.json
@@ -0,0 +1,7 @@
+{
+ "compilerOptions": {
+ "jsxFactory": "g",
+ "jsxFragmentFactory": "foo",
+ "jsxImportSource": "baz"
+ }
+}
diff --git a/packages/vite/src/node/__tests__/plugins/fixtures/oxc-tsconfigs/target-es2021/tsconfig.json b/packages/vite/src/node/__tests__/plugins/fixtures/oxc-tsconfigs/target-es2021/tsconfig.json
new file mode 100644
index 00000000000000..ad0827577c94d1
--- /dev/null
+++ b/packages/vite/src/node/__tests__/plugins/fixtures/oxc-tsconfigs/target-es2021/tsconfig.json
@@ -0,0 +1,5 @@
+{
+ "compilerOptions": {
+ "target": "es2021"
+ }
+}
diff --git a/packages/vite/src/node/__tests__/plugins/fixtures/oxc-tsconfigs/target-es2022/tsconfig.json b/packages/vite/src/node/__tests__/plugins/fixtures/oxc-tsconfigs/target-es2022/tsconfig.json
new file mode 100644
index 00000000000000..f75c15e1d689bb
--- /dev/null
+++ b/packages/vite/src/node/__tests__/plugins/fixtures/oxc-tsconfigs/target-es2022/tsconfig.json
@@ -0,0 +1,5 @@
+{
+ "compilerOptions": {
+ "target": "es2022"
+ }
+}
diff --git a/packages/vite/src/node/__tests__/plugins/fixtures/oxc-tsconfigs/target-esnext/tsconfig.json b/packages/vite/src/node/__tests__/plugins/fixtures/oxc-tsconfigs/target-esnext/tsconfig.json
new file mode 100644
index 00000000000000..07b9f80cb05196
--- /dev/null
+++ b/packages/vite/src/node/__tests__/plugins/fixtures/oxc-tsconfigs/target-esnext/tsconfig.json
@@ -0,0 +1,5 @@
+{
+ "compilerOptions": {
+ "target": "esnext"
+ }
+}
diff --git a/packages/vite/src/node/__tests__/plugins/fixtures/oxc-tsconfigs/use-define-false/tsconfig.json b/packages/vite/src/node/__tests__/plugins/fixtures/oxc-tsconfigs/use-define-false/tsconfig.json
new file mode 100644
index 00000000000000..28eb978d59d0fe
--- /dev/null
+++ b/packages/vite/src/node/__tests__/plugins/fixtures/oxc-tsconfigs/use-define-false/tsconfig.json
@@ -0,0 +1,5 @@
+{
+ "compilerOptions": {
+ "useDefineForClassFields": false
+ }
+}
diff --git a/packages/vite/src/node/__tests__/plugins/fixtures/oxc-tsconfigs/use-define-true/tsconfig.json b/packages/vite/src/node/__tests__/plugins/fixtures/oxc-tsconfigs/use-define-true/tsconfig.json
new file mode 100644
index 00000000000000..de4dca88946c03
--- /dev/null
+++ b/packages/vite/src/node/__tests__/plugins/fixtures/oxc-tsconfigs/use-define-true/tsconfig.json
@@ -0,0 +1,5 @@
+{
+ "compilerOptions": {
+ "useDefineForClassFields": true
+ }
+}
diff --git a/packages/vite/src/node/__tests__/plugins/hooks.spec.ts b/packages/vite/src/node/__tests__/plugins/hooks.spec.ts
new file mode 100644
index 00000000000000..0ceb0497d3bc10
--- /dev/null
+++ b/packages/vite/src/node/__tests__/plugins/hooks.spec.ts
@@ -0,0 +1,348 @@
+import path from 'node:path'
+import { describe, expect, onTestFinished, test } from 'vitest'
+import { build } from '../../build'
+import type { Plugin } from '../../plugin'
+import { resolveConfig } from '../../config'
+import { createServer } from '../../server'
+import { preview } from '../../preview'
+import { promiseWithResolvers } from '../../../shared/utils'
+
+const resolveConfigWithPlugin = (
+ plugin: Plugin,
+ command: 'serve' | 'build' = 'serve',
+) => {
+ return resolveConfig(
+ { configFile: false, plugins: [plugin], logLevel: 'error' },
+ command,
+ )
+}
+
+const ENTRY_ID = 'entry.js'
+const RESOLVED_ENTRY_ID = `\0${ENTRY_ID}`
+const resolveEntryPlugin: Plugin = {
+ name: 'resolve-entry.js',
+ resolveId(id) {
+ if (id === ENTRY_ID) {
+ return RESOLVED_ENTRY_ID
+ }
+ },
+ load(id) {
+ if (id === RESOLVED_ENTRY_ID) {
+ return 'export default {}'
+ }
+ },
+}
+
+const createServerWithPlugin = async (plugin: Plugin) => {
+ const server = await createServer({
+ configFile: false,
+ root: import.meta.dirname,
+ plugins: [plugin, resolveEntryPlugin],
+ logLevel: 'error',
+ server: {
+ middlewareMode: true,
+ ws: false,
+ },
+ })
+ onTestFinished(() => server.close())
+ return server
+}
+
+const createPreviewServerWithPlugin = async (plugin: Plugin) => {
+ const server = await preview({
+ configFile: false,
+ root: import.meta.dirname,
+ plugins: [
+ {
+ name: 'mock-preview',
+ configurePreviewServer({ httpServer }) {
+ // NOTE: make httpServer.listen no-op to avoid starting a server
+ httpServer.listen = () => {
+ const lastListener = httpServer.listeners('listening').at(-1)!
+ lastListener.call(httpServer)
+ return httpServer as any
+ }
+ },
+ },
+ plugin,
+ ],
+ logLevel: 'error',
+ })
+ onTestFinished(() => server.close())
+ return server
+}
+
+const buildWithPlugin = async (plugin: Plugin) => {
+ await build({
+ root: path.resolve(import.meta.dirname, '../packages/build-project'),
+ logLevel: 'error',
+ build: {
+ write: false,
+ },
+ plugins: [plugin, resolveEntryPlugin],
+ })
+}
+
+describe('supports plugin context', () => {
+ test('config hook', async () => {
+ expect.assertions(4)
+
+ await resolveConfigWithPlugin({
+ name: 'test',
+ config() {
+ expect(this).toMatchObject({
+ debug: expect.any(Function),
+ info: expect.any(Function),
+ warn: expect.any(Function),
+ error: expect.any(Function),
+ meta: expect.any(Object),
+ })
+ expect(this.meta.rollupVersion).toBeTypeOf('string')
+ expect(this.meta.viteVersion).toBeTypeOf('string')
+ // @ts-expect-error watchMode should not exist in types
+ expect(this.meta.watchMode).toBeUndefined()
+ },
+ })
+ })
+
+ test('configEnvironment hook', async () => {
+ expect.assertions(4)
+
+ await resolveConfigWithPlugin({
+ name: 'test',
+ configEnvironment(name) {
+ if (name !== 'client') return
+
+ expect(this).toMatchObject({
+ debug: expect.any(Function),
+ info: expect.any(Function),
+ warn: expect.any(Function),
+ error: expect.any(Function),
+ meta: expect.any(Object),
+ })
+ expect(this.meta.rollupVersion).toBeTypeOf('string')
+ expect(this.meta.viteVersion).toBeTypeOf('string')
+ // @ts-expect-error watchMode should not exist in types
+ expect(this.meta.watchMode).toBeUndefined()
+ },
+ })
+ })
+
+ test('configResolved hook', async () => {
+ expect.assertions(4)
+
+ await resolveConfigWithPlugin({
+ name: 'test',
+ configResolved() {
+ expect(this).toMatchObject({
+ debug: expect.any(Function),
+ info: expect.any(Function),
+ warn: expect.any(Function),
+ error: expect.any(Function),
+ meta: expect.any(Object),
+ })
+ expect(this.meta.rollupVersion).toBeTypeOf('string')
+ expect(this.meta.viteVersion).toBeTypeOf('string')
+ expect(this.meta.watchMode).toBe(true)
+ },
+ })
+ })
+
+ test('configureServer hook', async () => {
+ expect.assertions(4)
+
+ await createServerWithPlugin({
+ name: 'test',
+ configureServer() {
+ expect(this).toMatchObject({
+ debug: expect.any(Function),
+ info: expect.any(Function),
+ warn: expect.any(Function),
+ error: expect.any(Function),
+ meta: expect.any(Object),
+ })
+ expect(this.meta.rollupVersion).toBeTypeOf('string')
+ expect(this.meta.viteVersion).toBeTypeOf('string')
+ expect(this.meta.watchMode).toBe(true)
+ },
+ })
+ })
+
+ test('configurePreviewServer hook', async () => {
+ expect.assertions(4)
+
+ await createPreviewServerWithPlugin({
+ name: 'test',
+ configurePreviewServer() {
+ expect(this).toMatchObject({
+ debug: expect.any(Function),
+ info: expect.any(Function),
+ warn: expect.any(Function),
+ error: expect.any(Function),
+ meta: expect.any(Object),
+ })
+ expect(this.meta.rollupVersion).toBeTypeOf('string')
+ expect(this.meta.viteVersion).toBeTypeOf('string')
+ expect(this.meta.watchMode).toBe(false)
+ },
+ })
+ })
+
+ test('transformIndexHtml hook in dev', async () => {
+ expect.assertions(4)
+
+ const server = await createServerWithPlugin({
+ name: 'test',
+ transformIndexHtml() {
+ expect(this).toMatchObject({
+ debug: expect.any(Function),
+ info: expect.any(Function),
+ warn: expect.any(Function),
+ error: expect.any(Function),
+ meta: expect.any(Object),
+ })
+ expect(this.meta.rollupVersion).toBeTypeOf('string')
+ expect(this.meta.viteVersion).toBeTypeOf('string')
+ expect(this.meta.watchMode).toBe(true)
+ },
+ })
+ await server.transformIndexHtml('/index.html', '')
+ })
+
+ test('transformIndexHtml hook in build', async () => {
+ expect.assertions(4)
+
+ await buildWithPlugin({
+ name: 'test',
+ transformIndexHtml() {
+ expect(this).toMatchObject({
+ debug: expect.any(Function),
+ info: expect.any(Function),
+ warn: expect.any(Function),
+ error: expect.any(Function),
+ meta: expect.any(Object),
+ })
+ expect(this.meta.rollupVersion).toBeTypeOf('string')
+ expect(this.meta.viteVersion).toBeTypeOf('string')
+ expect(this.meta.watchMode).toBe(false)
+ },
+ })
+ })
+
+ test('handleHotUpdate hook', async () => {
+ expect.assertions(4)
+
+ const { promise, resolve } = promiseWithResolvers()
+ const server = await createServerWithPlugin({
+ name: 'test',
+ handleHotUpdate() {
+ expect(this).toMatchObject({
+ debug: expect.any(Function),
+ info: expect.any(Function),
+ warn: expect.any(Function),
+ error: expect.any(Function),
+ meta: expect.any(Object),
+ })
+ expect(this.meta.rollupVersion).toBeTypeOf('string')
+ expect(this.meta.viteVersion).toBeTypeOf('string')
+ expect(this.meta.watchMode).toBe(true)
+ resolve()
+ },
+ })
+ server.watcher.emit(
+ 'change',
+ path.resolve(import.meta.dirname, 'index.html'),
+ )
+
+ await promise
+ })
+
+ test('hotUpdate hook', async () => {
+ expect.assertions(4)
+
+ const { promise, resolve } = promiseWithResolvers()
+ const server = await createServerWithPlugin({
+ name: 'test',
+ hotUpdate() {
+ if (this.environment.name !== 'client') return
+
+ expect(this).toMatchObject({
+ debug: expect.any(Function),
+ info: expect.any(Function),
+ warn: expect.any(Function),
+ error: expect.any(Function),
+ meta: expect.any(Object),
+ environment: expect.any(Object),
+ })
+ expect(this.meta.rollupVersion).toBeTypeOf('string')
+ expect(this.meta.viteVersion).toBeTypeOf('string')
+ expect(this.meta.watchMode).toBe(true)
+ resolve()
+ },
+ })
+ server.watcher.emit(
+ 'change',
+ path.resolve(import.meta.dirname, 'index.html'),
+ )
+
+ await promise
+ })
+
+ test('transform hook in dev', async () => {
+ expect.assertions(4)
+
+ const server = await createServerWithPlugin({
+ name: 'test',
+ transform(_code, id) {
+ if (id !== RESOLVED_ENTRY_ID) return
+ expect(this).toMatchObject({
+ debug: expect.any(Function),
+ info: expect.any(Function),
+ warn: expect.any(Function),
+ error: expect.any(Function),
+ meta: expect.any(Object),
+ })
+ expect(this.meta.rollupVersion).toBeTypeOf('string')
+ expect(this.meta.viteVersion).toBeTypeOf('string')
+ expect(this.meta.watchMode).toBe(true)
+ },
+ })
+ await server.transformRequest(ENTRY_ID)
+ await server.close()
+ })
+
+ test('transform hook in build', async () => {
+ expect.assertions(4)
+
+ await buildWithPlugin({
+ name: 'test',
+ transform(_code, id) {
+ if (id !== RESOLVED_ENTRY_ID) return
+ expect(this).toMatchObject({
+ debug: expect.any(Function),
+ info: expect.any(Function),
+ warn: expect.any(Function),
+ error: expect.any(Function),
+ meta: expect.any(Object),
+ })
+ expect(this.meta.rollupVersion).toBeTypeOf('string')
+ expect(this.meta.viteVersion).toBeTypeOf('string')
+ expect(this.meta.watchMode).toBe(false)
+ },
+ })
+ })
+
+ test('this.fs is supported in dev', async () => {
+ expect.hasAssertions()
+
+ const server = await createServerWithPlugin({
+ name: 'test',
+ resolveId(id) {
+ if (id !== ENTRY_ID) return
+ expect(this.fs.readFile).toBeTypeOf('function')
+ },
+ })
+ await server.transformRequest(ENTRY_ID)
+ await server.close()
+ })
+})
diff --git a/packages/vite/src/node/__tests__/plugins/import.spec.ts b/packages/vite/src/node/__tests__/plugins/import.spec.ts
index f0341e81b50f3c..ac5b94c9be9624 100644
--- a/packages/vite/src/node/__tests__/plugins/import.spec.ts
+++ b/packages/vite/src/node/__tests__/plugins/import.spec.ts
@@ -1,127 +1,184 @@
+import { beforeEach, describe, expect, test, vi } from 'vitest'
import { transformCjsImport } from '../../plugins/importAnalysis'
-describe('transformCjsImport', () => {
- const url = './node_modules/.vite/react.js'
- const rawUrl = 'react'
+describe('runTransform', () => {
+ const config: any = {
+ command: 'serve',
+ logger: {
+ warn: vi.fn(),
+ },
+ }
+
+ function runTransformCjsImport(importExp: string, isNodeMode: boolean) {
+ const result = transformCjsImport(
+ importExp,
+ './node_modules/.vite/deps/react.js',
+ 'react',
+ 0,
+ 'modA',
+ isNodeMode,
+ config,
+ )
+ if (result !== undefined) {
+ expect(result.split('\n').length, 'result line count').toBe(
+ importExp.split('\n').length,
+ )
+ }
+ return result?.replaceAll(';', ';\n')
+ }
+
+ beforeEach(() => {
+ config.logger.warn.mockClear()
+ })
test('import specifier', () => {
expect(
- transformCjsImport(
- 'import { useState, Component } from "react"',
- url,
- rawUrl,
- 0
- )
- ).toBe(
- 'import __vite__cjsImport0_react from "./node_modules/.vite/react.js"; ' +
- 'const useState = __vite__cjsImport0_react["useState"]; ' +
- 'const Component = __vite__cjsImport0_react["Component"]'
- )
+ runTransformCjsImport(
+ 'import { useState, Component, "👋" as fake } from "react"',
+ false,
+ ),
+ ).toMatchInlineSnapshot(`
+ "import __vite__cjsImport0_react from "./node_modules/.vite/deps/react.js";
+ const useState = __vite__cjsImport0_react["useState"];
+ const Component = __vite__cjsImport0_react["Component"];
+ const fake = __vite__cjsImport0_react["👋"]"
+ `)
+ expect(
+ runTransformCjsImport(
+ 'import { useState, Component, "👋" as fake } from "react"',
+ true,
+ ),
+ ).toMatchInlineSnapshot(`
+ "import __vite__cjsImport0_react from "./node_modules/.vite/deps/react.js";
+ const useState = __vite__cjsImport0_react["useState"];
+ const Component = __vite__cjsImport0_react["Component"];
+ const fake = __vite__cjsImport0_react["👋"]"
+ `)
})
test('import default specifier', () => {
- expect(
- transformCjsImport('import React from "react"', url, rawUrl, 0)
- ).toBe(
- 'import __vite__cjsImport0_react from "./node_modules/.vite/react.js"; ' +
- 'const React = __vite__cjsImport0_react.__esModule ? __vite__cjsImport0_react.default : __vite__cjsImport0_react'
- )
+ expect(runTransformCjsImport('import React from "react"', false))
+ .toMatchInlineSnapshot(`
+ "import __vite__cjsImport0_react from "./node_modules/.vite/deps/react.js";
+ const React = !__vite__cjsImport0_react.__esModule ? __vite__cjsImport0_react : __vite__cjsImport0_react.default"
+ `)
+ expect(runTransformCjsImport('import React from "react"', true))
+ .toMatchInlineSnapshot(`
+ "import __vite__cjsImport0_react from "./node_modules/.vite/deps/react.js";
+ const React = __vite__cjsImport0_react"
+ `)
expect(
- transformCjsImport(
- 'import { default as React } from "react"',
- url,
- rawUrl,
- 0
- )
- ).toBe(
- 'import __vite__cjsImport0_react from "./node_modules/.vite/react.js"; ' +
- 'const React = __vite__cjsImport0_react.__esModule ? __vite__cjsImport0_react.default : __vite__cjsImport0_react'
- )
+ runTransformCjsImport('import { default as React } from "react"', false),
+ ).toMatchInlineSnapshot(`
+ "import __vite__cjsImport0_react from "./node_modules/.vite/deps/react.js";
+ const React = !__vite__cjsImport0_react.__esModule ? __vite__cjsImport0_react : __vite__cjsImport0_react.default"
+ `)
})
test('import all specifier', () => {
- expect(
- transformCjsImport('import * as react from "react"', url, rawUrl, 0)
- ).toBe(
- 'import __vite__cjsImport0_react from "./node_modules/.vite/react.js"; ' +
- 'const react = __vite__cjsImport0_react'
- )
+ expect(runTransformCjsImport('import * as react from "react"', false))
+ .toMatchInlineSnapshot(`
+ "import __vite__cjsImport0_react from "./node_modules/.vite/deps/react.js";
+ const react = ((m, n) => n || !m?.__esModule ? { ...typeof m === "object" && !Array.isArray(m) || typeof m === "function" ? m : {}, default: m} : m)(__vite__cjsImport0_react, 0)"
+ `)
+ expect(runTransformCjsImport('import * as react from "react"', true))
+ .toMatchInlineSnapshot(`
+ "import __vite__cjsImport0_react from "./node_modules/.vite/deps/react.js";
+ const react = ((m, n) => n || !m?.__esModule ? { ...typeof m === "object" && !Array.isArray(m) || typeof m === "function" ? m : {}, default: m} : m)(__vite__cjsImport0_react, 1)"
+ `)
})
test('export all specifier', () => {
- expect(transformCjsImport('export * from "react"', url, rawUrl, 0)).toBe(
- undefined
+ expect(
+ runTransformCjsImport('export * from "react"', false),
+ ).toMatchInlineSnapshot(`undefined`)
+ expect(
+ runTransformCjsImport('export * from "react"', true),
+ ).toMatchInlineSnapshot(`undefined`)
+
+ expect(config.logger.warn).toBeCalledWith(
+ expect.stringContaining(`export * from "react"\` in modA`),
)
expect(
- transformCjsImport('export * as react from "react"', url, rawUrl, 0)
- ).toBe(undefined)
+ runTransformCjsImport('export * as react from "react"', false),
+ ).toMatchInlineSnapshot(`undefined`)
+
+ expect(config.logger.warn).toBeCalledTimes(2)
})
test('export name specifier', () => {
expect(
- transformCjsImport(
- 'export { useState, Component } from "react"',
- url,
- rawUrl,
- 0
- )
- ).toBe(
- 'import __vite__cjsImport0_react from "./node_modules/.vite/react.js"; ' +
- 'const useState = __vite__cjsImport0_react["useState"]; ' +
- 'const Component = __vite__cjsImport0_react["Component"]; ' +
- 'export { useState, Component }'
- )
+ runTransformCjsImport(
+ 'export { useState, Component, "👋" } from "react"',
+ false,
+ ),
+ ).toMatchInlineSnapshot(`
+ "import __vite__cjsImport0_react from "./node_modules/.vite/deps/react.js";
+ const __vite__cjsExportI_useState = __vite__cjsImport0_react["useState"];
+ const __vite__cjsExportI_Component = __vite__cjsImport0_react["Component"];
+ const __vite__cjsExportL_1d0452e3 = __vite__cjsImport0_react["👋"];
+ export { __vite__cjsExportI_useState as useState, __vite__cjsExportI_Component as Component, __vite__cjsExportL_1d0452e3 as "👋" }"
+ `)
+ expect(
+ runTransformCjsImport(
+ 'export { useState, Component, "👋" } from "react"',
+ true,
+ ),
+ ).toMatchInlineSnapshot(`
+ "import __vite__cjsImport0_react from "./node_modules/.vite/deps/react.js";
+ const __vite__cjsExportI_useState = __vite__cjsImport0_react["useState"];
+ const __vite__cjsExportI_Component = __vite__cjsImport0_react["Component"];
+ const __vite__cjsExportL_1d0452e3 = __vite__cjsImport0_react["👋"];
+ export { __vite__cjsExportI_useState as useState, __vite__cjsExportI_Component as Component, __vite__cjsExportL_1d0452e3 as "👋" }"
+ `)
expect(
- transformCjsImport(
- 'export { useState as useStateAlias, Component as ComponentAlias } from "react"',
- url,
- rawUrl,
- 0
- )
- ).toBe(
- 'import __vite__cjsImport0_react from "./node_modules/.vite/react.js"; ' +
- 'const useStateAlias = __vite__cjsImport0_react["useState"]; ' +
- 'const ComponentAlias = __vite__cjsImport0_react["Component"]; ' +
- 'export { useStateAlias, ComponentAlias }'
- )
+ runTransformCjsImport(
+ 'export { useState as useStateAlias, Component as ComponentAlias, "👋" as "👍" } from "react"',
+ false,
+ ),
+ ).toMatchInlineSnapshot(`
+ "import __vite__cjsImport0_react from "./node_modules/.vite/deps/react.js";
+ const __vite__cjsExportI_useStateAlias = __vite__cjsImport0_react["useState"];
+ const __vite__cjsExportI_ComponentAlias = __vite__cjsImport0_react["Component"];
+ const __vite__cjsExportL_5d57d39e = __vite__cjsImport0_react["👋"];
+ export { __vite__cjsExportI_useStateAlias as useStateAlias, __vite__cjsExportI_ComponentAlias as ComponentAlias, __vite__cjsExportL_5d57d39e as "👍" }"
+ `)
})
test('export default specifier', () => {
- expect(
- transformCjsImport('export { default } from "react"', url, rawUrl, 0)
- ).toBe(
- 'import __vite__cjsImport0_react from "./node_modules/.vite/react.js"; ' +
- 'const __vite__cjsExportDefault_0 = __vite__cjsImport0_react.__esModule ? __vite__cjsImport0_react.default : __vite__cjsImport0_react; ' +
- 'export default __vite__cjsExportDefault_0'
- )
+ expect(runTransformCjsImport('export { default } from "react"', false))
+ .toMatchInlineSnapshot(`
+ "import __vite__cjsImport0_react from "./node_modules/.vite/deps/react.js";
+ const __vite__cjsExportDefault_0 = !__vite__cjsImport0_react.__esModule ? __vite__cjsImport0_react : __vite__cjsImport0_react.default;
+ export default __vite__cjsExportDefault_0"
+ `)
+ expect(runTransformCjsImport('export { default } from "react"', true))
+ .toMatchInlineSnapshot(`
+ "import __vite__cjsImport0_react from "./node_modules/.vite/deps/react.js";
+ const __vite__cjsExportDefault_0 = __vite__cjsImport0_react;
+ export default __vite__cjsExportDefault_0"
+ `)
expect(
- transformCjsImport(
- 'export { default as React} from "react"',
- url,
- rawUrl,
- 0
- )
- ).toBe(
- 'import __vite__cjsImport0_react from "./node_modules/.vite/react.js"; ' +
- 'const React = __vite__cjsImport0_react.__esModule ? __vite__cjsImport0_react.default : __vite__cjsImport0_react; ' +
- 'export { React }'
- )
+ runTransformCjsImport('export { default as React} from "react"', false),
+ ).toMatchInlineSnapshot(`
+ "import __vite__cjsImport0_react from "./node_modules/.vite/deps/react.js";
+ const __vite__cjsExportI_React = !__vite__cjsImport0_react.__esModule ? __vite__cjsImport0_react : __vite__cjsImport0_react.default;
+ export { __vite__cjsExportI_React as React }"
+ `)
expect(
- transformCjsImport(
+ runTransformCjsImport(
'export { Component as default } from "react"',
- url,
- rawUrl,
- 0
- )
- ).toBe(
- 'import __vite__cjsImport0_react from "./node_modules/.vite/react.js"; ' +
- 'const __vite__cjsExportDefault_0 = __vite__cjsImport0_react["Component"]; ' +
- 'export default __vite__cjsExportDefault_0'
- )
+ false,
+ ),
+ ).toMatchInlineSnapshot(`
+ "import __vite__cjsImport0_react from "./node_modules/.vite/deps/react.js";
+ const __vite__cjsExportDefault_0 = __vite__cjsImport0_react["Component"];
+ export default __vite__cjsExportDefault_0"
+ `)
})
})
diff --git a/packages/vite/src/node/__tests__/plugins/importGlob/__snapshots__/fixture.spec.ts.snap b/packages/vite/src/node/__tests__/plugins/importGlob/__snapshots__/fixture.spec.ts.snap
new file mode 100644
index 00000000000000..a28e45055d25a0
--- /dev/null
+++ b/packages/vite/src/node/__tests__/plugins/importGlob/__snapshots__/fixture.spec.ts.snap
@@ -0,0 +1,215 @@
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
+
+exports[`fixture > transform 1`] = `
+"import * as __vite_glob_3_0 from "./modules/a.ts";import * as __vite_glob_3_1 from "./modules/b.ts";import * as __vite_glob_3_2 from "./modules/index.ts";import * as __vite_glob_5_0 from "./modules/a.ts";import * as __vite_glob_5_1 from "./modules/b.ts";import * as __vite_glob_5_2 from "./modules/index.ts";import { name as __vite_glob_9_0 } from "./modules/a.ts";import { name as __vite_glob_9_1 } from "./modules/b.ts";import { name as __vite_glob_9_2 } from "./modules/index.ts";import { name as __vite_glob_11_0 } from "./modules/a.ts";import { name as __vite_glob_11_1 } from "./modules/b.ts";import { name as __vite_glob_11_2 } from "./modules/index.ts";import { default as __vite_glob_15_0 } from "./modules/a.ts?raw";import { default as __vite_glob_15_1 } from "./modules/b.ts?raw";import * as __vite_glob_28_0 from "./.foo/test.ts";import "../../../../../../types/importMeta";
+export const basic = /* #__PURE__ */ Object.assign({"./modules/a.ts": () => import("./modules/a.ts"),"./modules/b.ts": () => import("./modules/b.ts"),"./modules/index.ts": () => import("./modules/index.ts")});
+export const basicWithObjectKeys = Object.keys({"./modules/a.ts": 0,"./modules/b.ts": 0,"./modules/index.ts": 0});
+export const basicWithObjectValues = Object.values([() => import("./modules/a.ts"),() => import("./modules/b.ts"),() => import("./modules/index.ts")]);
+export const basicEager = /* #__PURE__ */ Object.assign({"./modules/a.ts": __vite_glob_3_0,"./modules/b.ts": __vite_glob_3_1,"./modules/index.ts": __vite_glob_3_2
+
+});
+export const basicEagerWithObjectKeys = Object.keys(
+ {"./modules/a.ts": 0,"./modules/b.ts": 0,"./modules/index.ts": 0
+
+}
+);
+export const basicEagerWithObjectValues = Object.values(
+ [__vite_glob_5_0,__vite_glob_5_1,__vite_glob_5_2
+
+]
+);
+export const ignore = /* #__PURE__ */ Object.assign({"./modules/a.ts": () => import("./modules/a.ts"),"./modules/b.ts": () => import("./modules/b.ts")});
+export const ignoreWithObjectKeys = Object.keys(
+ {"./modules/a.ts": 0,"./modules/b.ts": 0}
+);
+export const ignoreWithObjectValues = Object.values(
+ [() => import("./modules/a.ts"),() => import("./modules/b.ts")]
+);
+export const namedEager = /* #__PURE__ */ Object.assign({"./modules/a.ts": __vite_glob_9_0,"./modules/b.ts": __vite_glob_9_1,"./modules/index.ts": __vite_glob_9_2
+
+
+});
+export const namedEagerWithObjectKeys = Object.keys(
+ {"./modules/a.ts": 0,"./modules/b.ts": 0,"./modules/index.ts": 0
+
+
+}
+);
+export const namedEagerWithObjectValues = Object.values(
+ [__vite_glob_11_0,__vite_glob_11_1,__vite_glob_11_2
+
+
+]
+);
+export const namedDefault = /* #__PURE__ */ Object.assign({"./modules/a.ts": () => import("./modules/a.ts").then(m => m["default"]),"./modules/b.ts": () => import("./modules/b.ts").then(m => m["default"]),"./modules/index.ts": () => import("./modules/index.ts").then(m => m["default"])
+
+});
+export const namedDefaultWithObjectKeys = Object.keys(
+ {"./modules/a.ts": 0,"./modules/b.ts": 0,"./modules/index.ts": 0
+
+}
+);
+export const namedDefaultWithObjectValues = Object.values(
+ [() => import("./modules/a.ts").then(m => m["default"]),() => import("./modules/b.ts").then(m => m["default"]),() => import("./modules/index.ts").then(m => m["default"])
+
+]
+);
+export const eagerAs = /* #__PURE__ */ Object.assign({"./modules/a.ts": __vite_glob_15_0,"./modules/b.ts": __vite_glob_15_1
+
+
+});
+export const rawImportModule = /* #__PURE__ */ Object.assign({"./modules/a.ts": () => import("./modules/a.ts?raw"),"./modules/b.ts": () => import("./modules/b.ts?raw")
+
+
+});
+export const excludeSelf = /* #__PURE__ */ Object.assign({"./sibling.ts": () => import("./sibling.ts")
+
+
+
+
+
+});
+export const excludeSelfRaw = /* #__PURE__ */ Object.assign({"./sibling.ts": () => import("./sibling.ts?raw")});
+export const customQueryString = /* #__PURE__ */ Object.assign({"./sibling.ts": () => import("./sibling.ts?custom")});
+export const customQueryObject = /* #__PURE__ */ Object.assign({"./sibling.ts": () => import("./sibling.ts?foo=bar&raw=true")
+
+
+
+
+});
+export const parent = /* #__PURE__ */ Object.assign({
+
+
+});
+export const rootMixedRelative = /* #__PURE__ */ Object.assign({"/fixture-b/a.ts": () => import("../fixture-b/a.ts?url").then(m => m["default"]),"/fixture-b/b.ts": () => import("../fixture-b/b.ts?url").then(m => m["default"]),"/fixture-b/index.ts": () => import("../fixture-b/index.ts?url").then(m => m["default"]),"/fixture.spec.ts": () => import("../fixture.spec.ts?url").then(m => m["default"]),"/parse.spec.ts": () => import("../parse.spec.ts?url").then(m => m["default"]),"/utils.spec.ts": () => import("../utils.spec.ts?url").then(m => m["default"])
+
+
+});
+export const cleverCwd1 = /* #__PURE__ */ Object.assign({"./node_modules/framework/pages/hello.page.js": () => import("./node_modules/framework/pages/hello.page.js")
+
+});
+export const cleverCwd2 = /* #__PURE__ */ Object.assign({"./modules/a.ts": () => import("./modules/a.ts"),"./modules/b.ts": () => import("./modules/b.ts"),"../fixture-b/a.ts": () => import("../fixture-b/a.ts"),"../fixture-b/b.ts": () => import("../fixture-b/b.ts")
+
+
+
+});
+export const customBase = /* #__PURE__ */ Object.assign({"./modules/a.ts": () => import("./modules/a.ts"),"./modules/b.ts": () => import("./modules/b.ts"),"./modules/index.ts": () => import("./modules/index.ts"),"./sibling.ts": () => import("./sibling.ts")});
+export const customRootBase = /* #__PURE__ */ Object.assign({"./a.ts": () => import("/fixture-b/a.ts"),"./b.ts": () => import("/fixture-b/b.ts"),"./index.ts": () => import("/fixture-b/index.ts")
+
+});
+export const customBaseParent = /* #__PURE__ */ Object.assign({"../fixture-b/a.ts": () => import("/fixture-b/a.ts"),"../fixture-b/b.ts": () => import("/fixture-b/b.ts"),"../fixture-b/index.ts": () => import("/fixture-b/index.ts")
+
+});
+export const dotFolder = /* #__PURE__ */ Object.assign({"./.foo/test.ts": __vite_glob_28_0});
+"
+`;
+
+exports[`fixture > transform with restoreQueryExtension 1`] = `
+"import * as __vite_glob_3_0 from "./modules/a.ts";import * as __vite_glob_3_1 from "./modules/b.ts";import * as __vite_glob_3_2 from "./modules/index.ts";import * as __vite_glob_5_0 from "./modules/a.ts";import * as __vite_glob_5_1 from "./modules/b.ts";import * as __vite_glob_5_2 from "./modules/index.ts";import { name as __vite_glob_9_0 } from "./modules/a.ts";import { name as __vite_glob_9_1 } from "./modules/b.ts";import { name as __vite_glob_9_2 } from "./modules/index.ts";import { name as __vite_glob_11_0 } from "./modules/a.ts";import { name as __vite_glob_11_1 } from "./modules/b.ts";import { name as __vite_glob_11_2 } from "./modules/index.ts";import { default as __vite_glob_15_0 } from "./modules/a.ts?raw";import { default as __vite_glob_15_1 } from "./modules/b.ts?raw";import * as __vite_glob_28_0 from "./.foo/test.ts";import "../../../../../../types/importMeta";
+export const basic = /* #__PURE__ */ Object.assign({"./modules/a.ts": () => import("./modules/a.ts"),"./modules/b.ts": () => import("./modules/b.ts"),"./modules/index.ts": () => import("./modules/index.ts")});
+export const basicWithObjectKeys = Object.keys({"./modules/a.ts": 0,"./modules/b.ts": 0,"./modules/index.ts": 0});
+export const basicWithObjectValues = Object.values([() => import("./modules/a.ts"),() => import("./modules/b.ts"),() => import("./modules/index.ts")]);
+export const basicEager = /* #__PURE__ */ Object.assign({"./modules/a.ts": __vite_glob_3_0,"./modules/b.ts": __vite_glob_3_1,"./modules/index.ts": __vite_glob_3_2
+
+});
+export const basicEagerWithObjectKeys = Object.keys(
+ {"./modules/a.ts": 0,"./modules/b.ts": 0,"./modules/index.ts": 0
+
+}
+);
+export const basicEagerWithObjectValues = Object.values(
+ [__vite_glob_5_0,__vite_glob_5_1,__vite_glob_5_2
+
+]
+);
+export const ignore = /* #__PURE__ */ Object.assign({"./modules/a.ts": () => import("./modules/a.ts"),"./modules/b.ts": () => import("./modules/b.ts")});
+export const ignoreWithObjectKeys = Object.keys(
+ {"./modules/a.ts": 0,"./modules/b.ts": 0}
+);
+export const ignoreWithObjectValues = Object.values(
+ [() => import("./modules/a.ts"),() => import("./modules/b.ts")]
+);
+export const namedEager = /* #__PURE__ */ Object.assign({"./modules/a.ts": __vite_glob_9_0,"./modules/b.ts": __vite_glob_9_1,"./modules/index.ts": __vite_glob_9_2
+
+
+});
+export const namedEagerWithObjectKeys = Object.keys(
+ {"./modules/a.ts": 0,"./modules/b.ts": 0,"./modules/index.ts": 0
+
+
+}
+);
+export const namedEagerWithObjectValues = Object.values(
+ [__vite_glob_11_0,__vite_glob_11_1,__vite_glob_11_2
+
+
+]
+);
+export const namedDefault = /* #__PURE__ */ Object.assign({"./modules/a.ts": () => import("./modules/a.ts").then(m => m["default"]),"./modules/b.ts": () => import("./modules/b.ts").then(m => m["default"]),"./modules/index.ts": () => import("./modules/index.ts").then(m => m["default"])
+
+});
+export const namedDefaultWithObjectKeys = Object.keys(
+ {"./modules/a.ts": 0,"./modules/b.ts": 0,"./modules/index.ts": 0
+
+}
+);
+export const namedDefaultWithObjectValues = Object.values(
+ [() => import("./modules/a.ts").then(m => m["default"]),() => import("./modules/b.ts").then(m => m["default"]),() => import("./modules/index.ts").then(m => m["default"])
+
+]
+);
+export const eagerAs = /* #__PURE__ */ Object.assign({"./modules/a.ts": __vite_glob_15_0,"./modules/b.ts": __vite_glob_15_1
+
+
+});
+export const rawImportModule = /* #__PURE__ */ Object.assign({"./modules/a.ts": () => import("./modules/a.ts?raw"),"./modules/b.ts": () => import("./modules/b.ts?raw")
+
+
+});
+export const excludeSelf = /* #__PURE__ */ Object.assign({"./sibling.ts": () => import("./sibling.ts")
+
+
+
+
+
+});
+export const excludeSelfRaw = /* #__PURE__ */ Object.assign({"./sibling.ts": () => import("./sibling.ts?raw")});
+export const customQueryString = /* #__PURE__ */ Object.assign({"./sibling.ts": () => import("./sibling.ts?custom&lang.ts")});
+export const customQueryObject = /* #__PURE__ */ Object.assign({"./sibling.ts": () => import("./sibling.ts?foo=bar&raw=true&lang.ts")
+
+
+
+
+});
+export const parent = /* #__PURE__ */ Object.assign({
+
+
+});
+export const rootMixedRelative = /* #__PURE__ */ Object.assign({"/fixture-b/a.ts": () => import("../fixture-b/a.ts?url&lang.ts").then(m => m["default"]),"/fixture-b/b.ts": () => import("../fixture-b/b.ts?url&lang.ts").then(m => m["default"]),"/fixture-b/index.ts": () => import("../fixture-b/index.ts?url&lang.ts").then(m => m["default"]),"/fixture.spec.ts": () => import("../fixture.spec.ts?url&lang.ts").then(m => m["default"]),"/parse.spec.ts": () => import("../parse.spec.ts?url&lang.ts").then(m => m["default"]),"/utils.spec.ts": () => import("../utils.spec.ts?url&lang.ts").then(m => m["default"])
+
+
+});
+export const cleverCwd1 = /* #__PURE__ */ Object.assign({"./node_modules/framework/pages/hello.page.js": () => import("./node_modules/framework/pages/hello.page.js")
+
+});
+export const cleverCwd2 = /* #__PURE__ */ Object.assign({"./modules/a.ts": () => import("./modules/a.ts"),"./modules/b.ts": () => import("./modules/b.ts"),"../fixture-b/a.ts": () => import("../fixture-b/a.ts"),"../fixture-b/b.ts": () => import("../fixture-b/b.ts")
+
+
+
+});
+export const customBase = /* #__PURE__ */ Object.assign({"./modules/a.ts": () => import("./modules/a.ts"),"./modules/b.ts": () => import("./modules/b.ts"),"./modules/index.ts": () => import("./modules/index.ts"),"./sibling.ts": () => import("./sibling.ts")});
+export const customRootBase = /* #__PURE__ */ Object.assign({"./a.ts": () => import("/fixture-b/a.ts"),"./b.ts": () => import("/fixture-b/b.ts"),"./index.ts": () => import("/fixture-b/index.ts")
+
+});
+export const customBaseParent = /* #__PURE__ */ Object.assign({"../fixture-b/a.ts": () => import("/fixture-b/a.ts"),"../fixture-b/b.ts": () => import("/fixture-b/b.ts"),"../fixture-b/index.ts": () => import("/fixture-b/index.ts")
+
+});
+export const dotFolder = /* #__PURE__ */ Object.assign({"./.foo/test.ts": __vite_glob_28_0});
+"
+`;
+
+exports[`fixture > virtual modules 1`] = `
+"/* #__PURE__ */ Object.assign({"/modules/a.ts": () => import("/modules/a.ts"),"/modules/b.ts": () => import("/modules/b.ts"),"/modules/index.ts": () => import("/modules/index.ts")})
+/* #__PURE__ */ Object.assign({"/../fixture-b/a.ts": () => import("/../fixture-b/a.ts"),"/../fixture-b/b.ts": () => import("/../fixture-b/b.ts"),"/../fixture-b/index.ts": () => import("/../fixture-b/index.ts")})
+/* #__PURE__ */ Object.assign({"./a.ts": () => import("/modules/a.ts"),"./b.ts": () => import("/modules/b.ts"),"./index.ts": () => import("/modules/index.ts")})"
+`;
diff --git a/packages/vite/src/node/__tests__/plugins/importGlob/fixture-a/.foo/test.ts b/packages/vite/src/node/__tests__/plugins/importGlob/fixture-a/.foo/test.ts
new file mode 100644
index 00000000000000..63239baf13bdde
--- /dev/null
+++ b/packages/vite/src/node/__tests__/plugins/importGlob/fixture-a/.foo/test.ts
@@ -0,0 +1 @@
+export const msg = 'dot-folder-test'
diff --git a/packages/vite/src/node/__tests__/plugins/importGlob/fixture-a/.gitignore b/packages/vite/src/node/__tests__/plugins/importGlob/fixture-a/.gitignore
new file mode 100644
index 00000000000000..2b9b8877da603f
--- /dev/null
+++ b/packages/vite/src/node/__tests__/plugins/importGlob/fixture-a/.gitignore
@@ -0,0 +1 @@
+!/node_modules/
diff --git a/packages/vite/src/node/__tests__/plugins/importGlob/fixture-a/index.ts b/packages/vite/src/node/__tests__/plugins/importGlob/fixture-a/index.ts
new file mode 100644
index 00000000000000..6b3fdbaeefb23b
--- /dev/null
+++ b/packages/vite/src/node/__tests__/plugins/importGlob/fixture-a/index.ts
@@ -0,0 +1,126 @@
+// NOTE: `#types/importMeta` does not work as `src/node/__tests__/package.json` shadows the root
+// `package.json` and does not declare the subpath import.
+import '../../../../../../types/importMeta'
+
+export interface ModuleType {
+ name: string
+}
+
+export const basic = import.meta.glob('./modules/*.ts')
+// prettier-ignore
+export const basicWithObjectKeys = Object.keys(import.meta.glob('./modules/*.ts'))
+// prettier-ignore
+export const basicWithObjectValues = Object.values(import.meta.glob('./modules/*.ts'))
+
+export const basicEager = import.meta.glob('./modules/*.ts', {
+ eager: true,
+})
+export const basicEagerWithObjectKeys = Object.keys(
+ import.meta.glob('./modules/*.ts', {
+ eager: true,
+ }),
+)
+export const basicEagerWithObjectValues = Object.values(
+ import.meta.glob('./modules/*.ts', {
+ eager: true,
+ }),
+)
+
+export const ignore = import.meta.glob(['./modules/*.ts', '!**/index.ts'])
+export const ignoreWithObjectKeys = Object.keys(
+ import.meta.glob(['./modules/*.ts', '!**/index.ts']),
+)
+export const ignoreWithObjectValues = Object.values(
+ import.meta.glob(['./modules/*.ts', '!**/index.ts']),
+)
+
+export const namedEager = import.meta.glob('./modules/*.ts', {
+ eager: true,
+ import: 'name',
+})
+export const namedEagerWithObjectKeys = Object.keys(
+ import.meta.glob('./modules/*.ts', {
+ eager: true,
+ import: 'name',
+ }),
+)
+export const namedEagerWithObjectValues = Object.values(
+ import.meta.glob('./modules/*.ts', {
+ eager: true,
+ import: 'name',
+ }),
+)
+
+export const namedDefault = import.meta.glob('./modules/*.ts', {
+ import: 'default',
+})
+export const namedDefaultWithObjectKeys = Object.keys(
+ import.meta.glob('./modules/*.ts', {
+ import: 'default',
+ }),
+)
+export const namedDefaultWithObjectValues = Object.values(
+ import.meta.glob('./modules/*.ts', {
+ import: 'default',
+ }),
+)
+
+export const eagerAs = import.meta.glob(
+ ['./modules/*.ts', '!**/index.ts'],
+ { eager: true, query: '?raw', import: 'default' },
+)
+
+export const rawImportModule = import.meta.glob(
+ ['./modules/*.ts', '!**/index.ts'],
+ { query: '?raw', import: '*' },
+)
+
+export const excludeSelf = import.meta.glob(
+ './*.ts',
+ // for test: annotation contain ")"
+ /*
+ * for test: annotation contain ")"
+ * */
+)
+export const excludeSelfRaw = import.meta.glob('./*.ts', { query: '?raw' })
+
+export const customQueryString = import.meta.glob('./*.ts', { query: 'custom' })
+
+export const customQueryObject = import.meta.glob('./*.ts', {
+ query: {
+ foo: 'bar',
+ raw: true,
+ },
+})
+
+export const parent = import.meta.glob('../../playground/src/*.ts', {
+ query: '?url',
+ import: 'default',
+})
+
+export const rootMixedRelative = import.meta.glob(
+ ['/*.ts', '../fixture-b/*.ts'],
+ { query: '?url', import: 'default' },
+)
+
+export const cleverCwd1 = import.meta.glob(
+ './node_modules/framework/**/*.page.js',
+)
+
+export const cleverCwd2 = import.meta.glob([
+ './modules/*.ts',
+ '../fixture-b/*.ts',
+ '!**/index.ts',
+])
+
+export const customBase = import.meta.glob('./**/*.ts', { base: './' })
+
+export const customRootBase = import.meta.glob('./**/*.ts', {
+ base: '/fixture-b',
+})
+
+export const customBaseParent = import.meta.glob('/fixture-b/**/*.ts', {
+ base: '/fixture-a',
+})
+
+export const dotFolder = import.meta.glob('./.foo/*.ts', { eager: true })
diff --git a/packages/vite/src/node/__tests__/plugins/importGlob/fixture-a/modules/a.ts b/packages/vite/src/node/__tests__/plugins/importGlob/fixture-a/modules/a.ts
new file mode 100644
index 00000000000000..facd48a0875e65
--- /dev/null
+++ b/packages/vite/src/node/__tests__/plugins/importGlob/fixture-a/modules/a.ts
@@ -0,0 +1 @@
+export const name = 'a'
diff --git a/packages/vite/src/node/__tests__/plugins/importGlob/fixture-a/modules/b.ts b/packages/vite/src/node/__tests__/plugins/importGlob/fixture-a/modules/b.ts
new file mode 100644
index 00000000000000..0b1eb38d9087a2
--- /dev/null
+++ b/packages/vite/src/node/__tests__/plugins/importGlob/fixture-a/modules/b.ts
@@ -0,0 +1 @@
+export const name = 'b'
diff --git a/packages/vite/src/node/__tests__/plugins/importGlob/fixture-a/modules/index.ts b/packages/vite/src/node/__tests__/plugins/importGlob/fixture-a/modules/index.ts
new file mode 100644
index 00000000000000..25b59ae7d30714
--- /dev/null
+++ b/packages/vite/src/node/__tests__/plugins/importGlob/fixture-a/modules/index.ts
@@ -0,0 +1,6 @@
+export { name as a } from './a'
+export { name as b } from './b'
+
+export const name = 'index'
+
+export default 'indexDefault'
diff --git a/packages/vite/src/node/__tests__/plugins/importGlob/fixture-a/node_modules/framework/pages/hello.page.js b/packages/vite/src/node/__tests__/plugins/importGlob/fixture-a/node_modules/framework/pages/hello.page.js
new file mode 100644
index 00000000000000..cbe518a8e79477
--- /dev/null
+++ b/packages/vite/src/node/__tests__/plugins/importGlob/fixture-a/node_modules/framework/pages/hello.page.js
@@ -0,0 +1,4 @@
+// A fake Page file. (This technique of globbing into `node_modules/`
+// is used by vite-plugin-ssr frameworks and Hydrogen.)
+
+export const a = 1
diff --git a/packages/vite/src/node/__tests__/plugins/importGlob/fixture-a/sibling.ts b/packages/vite/src/node/__tests__/plugins/importGlob/fixture-a/sibling.ts
new file mode 100644
index 00000000000000..b286816bf5d63a
--- /dev/null
+++ b/packages/vite/src/node/__tests__/plugins/importGlob/fixture-a/sibling.ts
@@ -0,0 +1 @@
+export const name = 'I am your sibling!'
diff --git a/packages/vite/src/node/__tests__/plugins/importGlob/fixture-b/a.ts b/packages/vite/src/node/__tests__/plugins/importGlob/fixture-b/a.ts
new file mode 100644
index 00000000000000..facd48a0875e65
--- /dev/null
+++ b/packages/vite/src/node/__tests__/plugins/importGlob/fixture-b/a.ts
@@ -0,0 +1 @@
+export const name = 'a'
diff --git a/packages/vite/src/node/__tests__/plugins/importGlob/fixture-b/b.ts b/packages/vite/src/node/__tests__/plugins/importGlob/fixture-b/b.ts
new file mode 100644
index 00000000000000..0b1eb38d9087a2
--- /dev/null
+++ b/packages/vite/src/node/__tests__/plugins/importGlob/fixture-b/b.ts
@@ -0,0 +1 @@
+export const name = 'b'
diff --git a/packages/vite/src/node/__tests__/plugins/importGlob/fixture-b/index.ts b/packages/vite/src/node/__tests__/plugins/importGlob/fixture-b/index.ts
new file mode 100644
index 00000000000000..39bdbfd1a8befb
--- /dev/null
+++ b/packages/vite/src/node/__tests__/plugins/importGlob/fixture-b/index.ts
@@ -0,0 +1,2 @@
+export { name as a } from './a'
+export { name as b } from './b'
diff --git a/packages/vite/src/node/__tests__/plugins/importGlob/fixture.spec.ts b/packages/vite/src/node/__tests__/plugins/importGlob/fixture.spec.ts
new file mode 100644
index 00000000000000..74736f7dc640ac
--- /dev/null
+++ b/packages/vite/src/node/__tests__/plugins/importGlob/fixture.spec.ts
@@ -0,0 +1,80 @@
+import { resolve } from 'node:path'
+import { promises as fs } from 'node:fs'
+import { describe, expect, it } from 'vitest'
+import { transformGlobImport } from '../../../plugins/importMetaGlob'
+import { transformWithEsbuild } from '../../../plugins/esbuild'
+
+describe('fixture', async () => {
+ const resolveId = (id: string) => id
+ const root = import.meta.dirname
+
+ it('transform', async () => {
+ const id = resolve(import.meta.dirname, './fixture-a/index.ts')
+ const code = (
+ await transformWithEsbuild(await fs.readFile(id, 'utf-8'), id)
+ ).code
+
+ expect(
+ (await transformGlobImport(code, id, root, resolveId))?.s.toString(),
+ ).toMatchSnapshot()
+ })
+
+ it('preserve line count', async () => {
+ const getTransformedLineCount = async (code: string) =>
+ (await transformGlobImport(code, 'virtual:module', root, resolveId))?.s
+ .toString()
+ .split('\n').length
+
+ expect(await getTransformedLineCount("import.meta.glob('./*.js')")).toBe(1)
+ expect(
+ await getTransformedLineCount(
+ `
+ import.meta.glob(
+ './*.js'
+ )
+ `.trim(),
+ ),
+ ).toBe(3)
+ })
+
+ it('virtual modules', async () => {
+ const root = resolve(import.meta.dirname, './fixture-a')
+ const code = [
+ "import.meta.glob('/modules/*.ts')",
+ "import.meta.glob(['/../fixture-b/*.ts'])",
+ "import.meta.glob(['./*.ts'], { base: '/modules' })",
+ ].join('\n')
+ expect(
+ (
+ await transformGlobImport(code, 'virtual:module', root, resolveId)
+ )?.s.toString(),
+ ).toMatchSnapshot()
+
+ try {
+ await transformGlobImport(
+ "import.meta.glob('./modules/*.ts')",
+ 'virtual:module',
+ root,
+ resolveId,
+ )
+ expect('no error').toBe('should throw an error')
+ } catch (err) {
+ expect(err).toMatchInlineSnapshot(
+ "[Error: In virtual modules, all globs must start with '/']",
+ )
+ }
+ })
+
+ it('transform with restoreQueryExtension', async () => {
+ const id = resolve(import.meta.dirname, './fixture-a/index.ts')
+ const code = (
+ await transformWithEsbuild(await fs.readFile(id, 'utf-8'), id)
+ ).code
+
+ expect(
+ (
+ await transformGlobImport(code, id, root, resolveId, true)
+ )?.s.toString(),
+ ).toMatchSnapshot()
+ })
+})
diff --git a/packages/vite/src/node/__tests__/plugins/importGlob/parse.spec.ts b/packages/vite/src/node/__tests__/plugins/importGlob/parse.spec.ts
new file mode 100644
index 00000000000000..3be198b1282977
--- /dev/null
+++ b/packages/vite/src/node/__tests__/plugins/importGlob/parse.spec.ts
@@ -0,0 +1,417 @@
+import { describe, expect, it } from 'vitest'
+import { parseImportGlob } from '../../../plugins/importMetaGlob'
+
+async function run(input: string) {
+ const items = await parseImportGlob(
+ input,
+ process.cwd(),
+ process.cwd(),
+ (id) => id,
+ )
+ return items.map((i) => ({
+ globs: i.globs,
+ options: i.options,
+ start: i.start,
+ }))
+}
+
+async function runError(input: string) {
+ try {
+ await run(input)
+ } catch (e) {
+ return e
+ }
+}
+
+describe('parse positives', async () => {
+ it('basic', async () => {
+ expect(
+ await run(`
+ import.meta.glob('./modules/*.ts')
+ `),
+ ).toMatchInlineSnapshot(`
+ [
+ {
+ "globs": [
+ "./modules/*.ts",
+ ],
+ "options": {},
+ "start": 5,
+ },
+ ]
+ `)
+ })
+
+ it('array', async () => {
+ expect(
+ await run(`
+ import.meta.glob(['./modules/*.ts', './dir/*.{js,ts}'])
+ `),
+ ).toMatchInlineSnapshot(`
+ [
+ {
+ "globs": [
+ "./modules/*.ts",
+ "./dir/*.{js,ts}",
+ ],
+ "options": {},
+ "start": 5,
+ },
+ ]
+ `)
+ })
+
+ it('options with multilines', async () => {
+ expect(
+ await run(`
+ import.meta.glob([
+ './modules/*.ts',
+ "!./dir/*.{js,ts}"
+ ], {
+ eager: true,
+ import: 'named'
+ })
+ `),
+ ).toMatchInlineSnapshot(`
+ [
+ {
+ "globs": [
+ "./modules/*.ts",
+ "!./dir/*.{js,ts}",
+ ],
+ "options": {
+ "eager": true,
+ "import": "named",
+ },
+ "start": 5,
+ },
+ ]
+ `)
+ })
+
+ it('options with multilines', async () => {
+ expect(
+ await run(`
+ const modules = import.meta.glob(
+ '/dir/**'
+ // for test: annotation contain ")"
+ /*
+ * for test: annotation contain ")"
+ * */
+ )
+ `),
+ ).toMatchInlineSnapshot(`
+ [
+ {
+ "globs": [
+ "/dir/**",
+ ],
+ "options": {},
+ "start": 21,
+ },
+ ]
+ `)
+ })
+
+ it('options query', async () => {
+ expect(
+ await run(`
+ const modules = import.meta.glob(
+ '/dir/**',
+ {
+ query: {
+ foo: 'bar',
+ raw: true,
+ }
+ }
+ )
+ `),
+ ).toMatchInlineSnapshot(`
+ [
+ {
+ "globs": [
+ "/dir/**",
+ ],
+ "options": {
+ "query": "?foo=bar&raw=true",
+ },
+ "start": 21,
+ },
+ ]
+ `)
+ })
+
+ it('options with base', async () => {
+ expect(
+ await run(`
+ import.meta.glob('./**/dir/*.md', {
+ base: './path/to/base'
+ })
+ `),
+ ).toMatchInlineSnapshot(`
+ [
+ {
+ "globs": [
+ "./**/dir/*.md",
+ ],
+ "options": {
+ "base": "./path/to/base",
+ },
+ "start": 5,
+ },
+ ]
+ `)
+ })
+
+ it('object properties - 1', async () => {
+ expect(
+ await run(`
+ export const pageFiles = {
+ '.page': import.meta.glob('/**/*.page.*([a-zA-Z0-9])')
+};`),
+ ).toMatchInlineSnapshot(`
+ [
+ {
+ "globs": [
+ "/**/*.page.*([a-zA-Z0-9])",
+ ],
+ "options": {},
+ "start": 47,
+ },
+ ]
+`)
+ })
+
+ it('object properties - 2', async () => {
+ expect(
+ await run(`
+ export const pageFiles = {
+ '.page': import.meta.glob('/**/*.page.*([a-zA-Z0-9])'),
+};`),
+ ).toMatchInlineSnapshot(`
+ [
+ {
+ "globs": [
+ "/**/*.page.*([a-zA-Z0-9])",
+ ],
+ "options": {},
+ "start": 47,
+ },
+ ]
+`)
+ })
+
+ it('object properties - 3', async () => {
+ expect(
+ await run(`
+ export const pageFiles = {
+ '.page.client': import.meta.glob('/**/*.page.client.*([a-zA-Z0-9])'),
+ '.page.server': import.meta.glob('/**/*.page.server.*([a-zA-Z0-9])'),
+};`),
+ ).toMatchInlineSnapshot(`
+ [
+ {
+ "globs": [
+ "/**/*.page.client.*([a-zA-Z0-9])",
+ ],
+ "options": {},
+ "start": 54,
+ },
+ {
+ "globs": [
+ "/**/*.page.server.*([a-zA-Z0-9])",
+ ],
+ "options": {},
+ "start": 130,
+ },
+ ]
+`)
+ })
+
+ it('array item', async () => {
+ expect(
+ await run(`
+ export const pageFiles = [
+ import.meta.glob('/**/*.page.client.*([a-zA-Z0-9])'),
+ import.meta.glob('/**/*.page.server.*([a-zA-Z0-9])'),
+ ]`),
+ ).toMatchInlineSnapshot(`
+ [
+ {
+ "globs": [
+ "/**/*.page.client.*([a-zA-Z0-9])",
+ ],
+ "options": {},
+ "start": 38,
+ },
+ {
+ "globs": [
+ "/**/*.page.server.*([a-zA-Z0-9])",
+ ],
+ "options": {},
+ "start": 98,
+ },
+ ]
+ `)
+ })
+})
+
+describe('parse negatives', async () => {
+ it('syntax error', async () => {
+ expect(await runError('import.meta.glob(')).toMatchInlineSnapshot(
+ '[Error: Invalid glob import syntax: Close parenthesis not found]',
+ )
+ })
+
+ it('empty', async () => {
+ expect(await runError('import.meta.glob()')).toMatchInlineSnapshot(
+ '[Error: Invalid glob import syntax: Expected 1-2 arguments, but got 0]',
+ )
+ })
+
+ it('3 args', async () => {
+ expect(
+ await runError('import.meta.glob("", {}, {})'),
+ ).toMatchInlineSnapshot(
+ '[Error: Invalid glob import syntax: Expected 1-2 arguments, but got 3]',
+ )
+ })
+
+ it('in string', async () => {
+ expect(await runError('"import.meta.glob()"')).toBeUndefined()
+ })
+
+ it('variable', async () => {
+ expect(await runError('import.meta.glob(hey)')).toMatchInlineSnapshot(
+ '[Error: Invalid glob import syntax: Could only use literals]',
+ )
+ })
+
+ it('template', async () => {
+ expect(
+ await runError('import.meta.glob(`hi ${hey}`)'),
+ ).toMatchInlineSnapshot(
+ '[Error: Invalid glob import syntax: Expected glob to be a string, but got dynamic template literal]',
+ )
+ })
+
+ it('template with unicode', async () => {
+ expect(await run('import.meta.glob(`/\u0068\u0065\u006c\u006c\u006f`)'))
+ .toMatchInlineSnapshot(`
+ [
+ {
+ "globs": [
+ "/hello",
+ ],
+ "options": {},
+ "start": 0,
+ },
+ ]
+ `)
+ })
+
+ it('template without expressions', async () => {
+ expect(await run('import.meta.glob(`/**/*.page.client.*([a-zA-Z0-9])`)'))
+ .toMatchInlineSnapshot(`
+ [
+ {
+ "globs": [
+ "/**/*.page.client.*([a-zA-Z0-9])",
+ ],
+ "options": {},
+ "start": 0,
+ },
+ ]
+ `)
+ })
+
+ it('be string', async () => {
+ expect(await runError('import.meta.glob(1)')).toMatchInlineSnapshot(
+ '[Error: Invalid glob import syntax: Expected glob to be a string, but got "number"]',
+ )
+ })
+
+ it('be array variable', async () => {
+ expect(await runError('import.meta.glob([hey])')).toMatchInlineSnapshot(
+ '[Error: Invalid glob import syntax: Could only use literals]',
+ )
+ expect(
+ await runError('import.meta.glob(["1", hey])'),
+ ).toMatchInlineSnapshot(
+ '[Error: Invalid glob import syntax: Could only use literals]',
+ )
+ })
+
+ it('options', async () => {
+ expect(
+ await runError('import.meta.glob("hey", hey)'),
+ ).toMatchInlineSnapshot(
+ '[Error: Invalid glob import syntax: Expected the second argument to be an object literal, but got "Identifier"]',
+ )
+ expect(await runError('import.meta.glob("hey", [])')).toMatchInlineSnapshot(
+ '[Error: Invalid glob import syntax: Expected the second argument to be an object literal, but got "ArrayExpression"]',
+ )
+ })
+
+ it('options props', async () => {
+ expect(
+ await runError('import.meta.glob("hey", { hey: 1 })'),
+ ).toMatchInlineSnapshot('[Error: Unknown glob option "hey"]')
+ expect(
+ await runError('import.meta.glob("hey", { import: hey })'),
+ ).toMatchInlineSnapshot(
+ '[Error: Vite is unable to parse the glob options as the value is not static]',
+ )
+ expect(
+ await runError('import.meta.glob("hey", { eager: 123 })'),
+ ).toMatchInlineSnapshot(
+ '[Error: Expected glob option "eager" to be of type boolean, but got number]',
+ )
+ })
+
+ it('options query', async () => {
+ expect(
+ await runError('import.meta.glob("./*.js", { as: "raw", query: "hi" })'),
+ ).toMatchInlineSnapshot(
+ '[Error: Options "as" and "query" cannot be used together]',
+ )
+ expect(
+ await runError('import.meta.glob("./*.js", { query: 123 })'),
+ ).toMatchInlineSnapshot(
+ '[Error: Expected glob option "query" to be of type object or string, but got number]',
+ )
+ expect(
+ await runError('import.meta.glob("./*.js", { query: { foo: {} } })'),
+ ).toMatchInlineSnapshot(
+ '[Error: Expected glob option "query.foo" to be of type string, number, or boolean, but got object]',
+ )
+ expect(
+ await runError('import.meta.glob("./*.js", { query: { foo: hey } })'),
+ ).toMatchInlineSnapshot(
+ '[Error: Vite is unable to parse the glob options as the value is not static]',
+ )
+ expect(
+ await runError(
+ 'import.meta.glob("./*.js", { query: { foo: 123, ...a } })',
+ ),
+ ).toMatchInlineSnapshot(
+ '[Error: Vite is unable to parse the glob options as the value is not static]',
+ )
+ })
+
+ it('options base', async () => {
+ expect(
+ await runError('import.meta.glob("./*.js", { base: 1 })'),
+ ).toMatchInlineSnapshot(
+ '[Error: Expected glob option "base" to be of type string, but got number]',
+ )
+ expect(
+ await runError('import.meta.glob("./*.js", { base: "foo" })'),
+ ).toMatchInlineSnapshot(
+ "[Error: Option \"base\" must start with '/', './' or '../', but got \"foo\"]",
+ )
+ expect(
+ await runError('import.meta.glob("./*.js", { base: "!/foo" })'),
+ ).toMatchInlineSnapshot('[Error: Option "base" cannot start with "!"]')
+ })
+})
diff --git a/packages/vite/src/node/__tests__/plugins/importGlob/utils.spec.ts b/packages/vite/src/node/__tests__/plugins/importGlob/utils.spec.ts
new file mode 100644
index 00000000000000..bd91c6165f798e
--- /dev/null
+++ b/packages/vite/src/node/__tests__/plugins/importGlob/utils.spec.ts
@@ -0,0 +1,31 @@
+import { describe, expect, it } from 'vitest'
+import { getCommonBase } from '../../../plugins/importMetaGlob'
+
+describe('getCommonBase()', async () => {
+ it('basic', () => {
+ expect(getCommonBase(['/a/b/*.js', '/a/c/*.js'])).toBe('/a')
+ })
+ it('common base', () => {
+ expect(getCommonBase(['/a/b/**/*.vue', '/a/b/**/*.jsx'])).toBe('/a/b')
+ })
+ it('static file', () => {
+ expect(
+ getCommonBase(['/a/b/**/*.vue', '/a/b/**/*.jsx', '/a/b/foo.js']),
+ ).toBe('/a/b')
+ expect(getCommonBase(['/a/b/**/*.vue', '/a/b/**/*.jsx', '/a/foo.js'])).toBe(
+ '/a',
+ )
+ })
+ it('correct `scan()`', () => {
+ expect(getCommonBase(['/a/*.vue'])).toBe('/a')
+ expect(getCommonBase(['/a/some.vue'])).toBe('/a')
+ expect(getCommonBase(['/a/b/**/c/foo.vue', '/a/b/c/**/*.jsx'])).toBe('/a/b')
+ })
+ it('single', () => {
+ expect(getCommonBase(['/a/b/c/*.vue'])).toBe('/a/b/c')
+ expect(getCommonBase(['/a/b/c/foo.vue'])).toBe('/a/b/c')
+ })
+ it('no common base', () => {
+ expect(getCommonBase(['/a/b/*.js', '/c/a/b/*.js'])).toBe('/')
+ })
+})
diff --git a/packages/vite/src/node/__tests__/plugins/index.spec.ts b/packages/vite/src/node/__tests__/plugins/index.spec.ts
new file mode 100644
index 00000000000000..e507a173defd1a
--- /dev/null
+++ b/packages/vite/src/node/__tests__/plugins/index.spec.ts
@@ -0,0 +1,169 @@
+import { RUNTIME_MODULE_ID } from 'rolldown'
+import { exactRegex } from 'rolldown/filter'
+import { afterAll, describe, expect, test, vi } from 'vitest'
+import { type InlineConfig, type Plugin, build, createServer } from '../..'
+
+const getConfigWithPlugin = (
+ plugins: Plugin[],
+ input?: string[],
+): InlineConfig => {
+ return {
+ configFile: false,
+ server: { middlewareMode: true, ws: false },
+ optimizeDeps: { noDiscovery: true, include: [] },
+ build: { rollupOptions: { input }, write: false },
+ plugins,
+ logLevel: 'silent',
+ }
+}
+
+describe('hook filter with plugin container', async () => {
+ const resolveId = vi.fn()
+ const load = vi.fn()
+ const transformWithId = vi.fn()
+ const transformWithCode = vi.fn()
+ const any = expect.toSatisfy(() => true) // anything including undefined and null
+ const config = getConfigWithPlugin([
+ {
+ name: 'test',
+ resolveId: {
+ filter: { id: /\.js$/ },
+ handler: resolveId,
+ },
+ load: {
+ filter: { id: '**/*.js' },
+ handler: load,
+ },
+ transform: {
+ filter: { id: '**/*.js' },
+ handler: transformWithId,
+ },
+ },
+ {
+ name: 'test2',
+ transform: {
+ filter: { code: 'import.meta' },
+ handler: transformWithCode,
+ },
+ },
+ ])
+ const server = await createServer(config)
+ afterAll(async () => {
+ await server.close()
+ })
+ const pluginContainer = server.environments.ssr.pluginContainer
+
+ test('resolveId', async () => {
+ await pluginContainer.resolveId('foo.js')
+ await pluginContainer.resolveId('foo.ts')
+ expect(resolveId).toHaveBeenCalledTimes(1)
+ expect(resolveId).toHaveBeenCalledWith('foo.js', any, any)
+ })
+
+ test('load', async () => {
+ await pluginContainer.load('foo.js')
+ await pluginContainer.load('foo.ts')
+ expect(load).toHaveBeenCalledTimes(1)
+ expect(load).toHaveBeenCalledWith('foo.js', any)
+ })
+
+ test('transform', async () => {
+ await server.environments.ssr.moduleGraph.ensureEntryFromUrl('foo.js')
+ await server.environments.ssr.moduleGraph.ensureEntryFromUrl('foo.ts')
+
+ await pluginContainer.transform('import_meta', 'foo.js')
+ await pluginContainer.transform('import.meta', 'foo.ts')
+ expect(transformWithId).toHaveBeenCalledTimes(1)
+ expect(transformWithId).toHaveBeenCalledWith(
+ expect.stringContaining('import_meta'),
+ 'foo.js',
+ any,
+ )
+ expect(transformWithCode).toHaveBeenCalledTimes(1)
+ expect(transformWithCode).toHaveBeenCalledWith(
+ expect.stringContaining('import.meta'),
+ 'foo.ts',
+ any,
+ )
+ })
+})
+
+describe('hook filter with build', async () => {
+ const resolveId = vi.fn()
+ const load = vi.fn()
+ const transformWithId = vi.fn()
+ const transformWithCode = vi.fn()
+ const any = expect.anything()
+ const config = getConfigWithPlugin(
+ [
+ {
+ name: 'test',
+ resolveId: {
+ filter: { id: /\.js$/ },
+ handler: resolveId,
+ },
+ load: {
+ filter: { id: '**/*.js' },
+ handler: load,
+ },
+ transform: {
+ filter: {
+ id: {
+ include: '**/*.js',
+ exclude: exactRegex(RUNTIME_MODULE_ID),
+ },
+ },
+ handler: transformWithId,
+ },
+ },
+ {
+ name: 'test2',
+ transform: {
+ filter: { code: 'import.meta' },
+ handler: transformWithCode,
+ },
+ },
+ {
+ name: 'resolver',
+ resolveId(id) {
+ return id
+ },
+ load(id) {
+ if (id === 'foo.js') {
+ return 'import "foo.ts"\n' + 'import_meta'
+ }
+ if (id === 'foo.ts') {
+ return 'import.meta'
+ }
+ },
+ },
+ ],
+ ['foo.js', 'foo.ts'],
+ )
+ await build(config)
+
+ test('resolveId', async () => {
+ expect(resolveId).toHaveBeenCalledTimes(1)
+ expect(resolveId).toHaveBeenCalledWith('foo.js', undefined, any)
+ })
+
+ test('load', async () => {
+ expect(load).toHaveBeenCalledTimes(1)
+ expect(load).toHaveBeenCalledWith('foo.js', any)
+ })
+
+ test('transform', async () => {
+ expect(transformWithId).toHaveBeenCalledTimes(1)
+ expect(transformWithId).toHaveBeenCalledWith(
+ expect.stringContaining('import_meta'),
+ 'foo.js',
+ any,
+ )
+ expect(transformWithCode).toHaveBeenCalledTimes(1)
+ expect(transformWithCode).toHaveBeenCalledWith(
+ expect.stringContaining('import.meta'),
+ 'foo.ts',
+ any,
+ )
+ })
+})
diff --git a/packages/vite/src/node/__tests__/plugins/license.spec.ts b/packages/vite/src/node/__tests__/plugins/license.spec.ts
new file mode 100644
index 00000000000000..67876e7b900bbe
--- /dev/null
+++ b/packages/vite/src/node/__tests__/plugins/license.spec.ts
@@ -0,0 +1,38 @@
+import { fileURLToPath } from 'node:url'
+import type { OutputAsset, RollupOutput } from 'rollup'
+import { expect, test } from 'vitest'
+import { build } from '../../build'
+
+test('markdown', async () => {
+ const result = (await build({
+ root: fileURLToPath(new URL('./fixtures/license', import.meta.url)),
+ logLevel: 'silent',
+ build: {
+ write: false,
+ license: true,
+ },
+ })) as RollupOutput
+ const licenseAsset = result.output.find(
+ (asset) => asset.fileName === '.vite/license.md',
+ ) as OutputAsset | undefined
+ expect(licenseAsset).toBeDefined()
+ expect(licenseAsset?.source).toMatchSnapshot()
+})
+
+test('json', async () => {
+ const result = (await build({
+ root: fileURLToPath(new URL('./fixtures/license', import.meta.url)),
+ logLevel: 'silent',
+ build: {
+ write: false,
+ license: {
+ fileName: '.vite/license.json',
+ },
+ },
+ })) as RollupOutput
+ const licenseAsset = result.output.find(
+ (asset) => asset.fileName === '.vite/license.json',
+ ) as OutputAsset | undefined
+ expect(licenseAsset).toBeDefined()
+ expect(licenseAsset?.source).toMatchSnapshot()
+})
diff --git a/packages/vite/src/node/__tests__/plugins/modulePreloadPolyfill/__snapshots__/modulePreloadPolyfill.spec.ts.snap b/packages/vite/src/node/__tests__/plugins/modulePreloadPolyfill/__snapshots__/modulePreloadPolyfill.spec.ts.snap
new file mode 100644
index 00000000000000..33a76ebf34f4ac
--- /dev/null
+++ b/packages/vite/src/node/__tests__/plugins/modulePreloadPolyfill/__snapshots__/modulePreloadPolyfill.spec.ts.snap
@@ -0,0 +1,36 @@
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
+
+exports[`load > doesn't load modulepreload polyfill when format is cjs 1`] = `""`;
+
+exports[`load > loads modulepreload polyfill 1`] = `
+"(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) => {
+ 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) {
+ const fetchOpts = {};
+ 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) {
+ if (link.ep) return;
+ link.ep = true;
+ const fetchOpts = getFetchOpts(link);
+ fetch(link.href, fetchOpts);
+ }
+})();
+"
+`;
diff --git a/packages/vite/src/node/__tests__/plugins/modulePreloadPolyfill/modulePreloadPolyfill.spec.ts b/packages/vite/src/node/__tests__/plugins/modulePreloadPolyfill/modulePreloadPolyfill.spec.ts
new file mode 100644
index 00000000000000..6eb0c1598b113d
--- /dev/null
+++ b/packages/vite/src/node/__tests__/plugins/modulePreloadPolyfill/modulePreloadPolyfill.spec.ts
@@ -0,0 +1,53 @@
+import { describe, it } from 'vitest'
+import type { ModuleFormat, RolldownOutput } from 'rolldown'
+import { build } from '../../../build'
+import { modulePreloadPolyfillId } from '../../../plugins/modulePreloadPolyfill'
+
+const buildProject = ({ format = 'es' as ModuleFormat } = {}) =>
+ build({
+ logLevel: 'silent',
+ build: {
+ write: false,
+ rollupOptions: {
+ input: 'main.js',
+ output: {
+ format,
+ },
+ treeshake: {
+ moduleSideEffects: false,
+ },
+ },
+ minify: false,
+ },
+ plugins: [
+ {
+ name: 'test',
+ resolveId(id) {
+ if (id === 'main.js') {
+ return `\0${id}`
+ }
+ },
+ load(id) {
+ if (id === '\0main.js') {
+ return `import '${modulePreloadPolyfillId}'`
+ }
+ },
+ },
+ ],
+ }) as Promise
+
+describe('load', () => {
+ it('loads modulepreload polyfill', async ({ expect }) => {
+ const { output } = await buildProject()
+ expect(output).toHaveLength(1)
+ expect(output[0].code).toMatchSnapshot()
+ })
+
+ it("doesn't load modulepreload polyfill when format is cjs", async ({
+ expect,
+ }) => {
+ const { output } = await buildProject({ format: 'cjs' })
+ expect(output).toHaveLength(1)
+ expect(output[0].code).toMatchSnapshot()
+ })
+})
diff --git a/packages/vite/src/node/__tests__/plugins/oxc.spec.ts b/packages/vite/src/node/__tests__/plugins/oxc.spec.ts
new file mode 100644
index 00000000000000..24b9735a141bd7
--- /dev/null
+++ b/packages/vite/src/node/__tests__/plugins/oxc.spec.ts
@@ -0,0 +1,426 @@
+import path from 'node:path'
+import { describe, expect, test } from 'vitest'
+import type { InternalModuleFormat } from 'rolldown'
+import { resolveConfig } from '../../config'
+import { buildOxcPlugin, transformWithOxc } from '../../plugins/oxc'
+import { PartialEnvironment } from '../../baseEnvironment'
+
+async function createBuildOxcPluginRenderChunk(target: string) {
+ const config = await resolveConfig(
+ { build: { target }, configFile: false },
+ 'build',
+ )
+ const instance = buildOxcPlugin()
+ const environment = new PartialEnvironment('client', config)
+
+ return async (code: string, format: InternalModuleFormat) => {
+ // @ts-expect-error renderChunk should exist
+ const result = await instance.renderChunk.call(
+ { environment },
+ code,
+ {
+ fileName: 'foo.ts',
+ },
+ { format },
+ )
+ return result?.code || result
+ }
+}
+
+describe('transformWithOxc', () => {
+ test('correctly overrides TS configuration and applies automatic transform', async () => {
+ const jsxImportSource = 'bar'
+ const result = await transformWithOxc(
+ 'const foo = () => <>>',
+ path.resolve(
+ import.meta.dirname,
+ './fixtures/oxc-tsconfigs/jsx-preserve/baz.jsx',
+ ),
+ {
+ jsx: {
+ runtime: 'automatic',
+ importSource: jsxImportSource,
+ },
+ },
+ )
+ expect(result?.code).toContain(`${jsxImportSource}/jsx-runtime`)
+ expect(result?.code).toContain('/* @__PURE__ */')
+ })
+
+ test('correctly overrides TS configuration and preserves code', async () => {
+ const foo = 'const foo = () => <>>'
+ const result = await transformWithOxc(
+ foo,
+ path.resolve(
+ import.meta.dirname,
+ './fixtures/oxc-tsconfigs/jsx-react-jsx/baz.jsx',
+ ),
+ {
+ jsx: 'preserve',
+ },
+ )
+ expect(result?.code).toContain(foo)
+ })
+
+ test('correctly overrides TS configuration and transforms code', async () => {
+ const jsxFactory = 'h',
+ jsxFragment = 'bar'
+ const result = await transformWithOxc(
+ 'const foo = () => <>>',
+ path.resolve(
+ import.meta.dirname,
+ './fixtures/oxc-tsconfigs/jsx-complex-options/baz.jsx',
+ ),
+ {
+ jsx: {
+ runtime: 'classic',
+ pragma: jsxFactory,
+ pragmaFrag: jsxFragment,
+ },
+ },
+ )
+ expect(result?.code).toContain(
+ `/* @__PURE__ */ ${jsxFactory}(${jsxFragment}, null)`,
+ )
+ })
+
+ describe('useDefineForClassFields', async () => {
+ const transformClassCode = async (target: string, tsconfigDir: string) => {
+ const result = await transformWithOxc(
+ `
+ class foo {
+ bar = 'bar'
+ }
+ `,
+ path.resolve(import.meta.dirname, tsconfigDir, './bar.ts'),
+ { target },
+ )
+ return result?.code
+ }
+
+ const [
+ defineForClassFieldsTrueTransformedCode,
+ defineForClassFieldsTrueLowerTransformedCode,
+ defineForClassFieldsFalseTransformedCode,
+ ] = await Promise.all([
+ transformClassCode('esnext', './fixtures/oxc-tsconfigs/use-define-true'),
+ transformClassCode('es2021', './fixtures/oxc-tsconfigs/use-define-true'),
+ transformClassCode('esnext', './fixtures/oxc-tsconfigs/use-define-false'),
+ ])
+
+ test('target: esnext and tsconfig.target: esnext => true', async () => {
+ const actual = await transformClassCode(
+ 'esnext',
+ './fixtures/oxc-tsconfigs/target-esnext',
+ )
+ expect(actual).toBe(defineForClassFieldsTrueTransformedCode)
+ })
+
+ test('target: es2021 and tsconfig.target: esnext => true', async () => {
+ const actual = await transformClassCode(
+ 'es2021',
+ './fixtures/oxc-tsconfigs/target-esnext',
+ )
+ expect(actual).toBe(defineForClassFieldsTrueLowerTransformedCode)
+ })
+
+ test('target: es2021 and tsconfig.target: es2021 => false', async () => {
+ const actual = await transformClassCode(
+ 'es2021',
+ './fixtures/oxc-tsconfigs/target-es2021',
+ )
+ expect(actual).toBe(defineForClassFieldsFalseTransformedCode)
+ })
+
+ test('target: esnext and tsconfig.target: es2021 => false', async () => {
+ const actual = await transformClassCode(
+ 'esnext',
+ './fixtures/oxc-tsconfigs/target-es2021',
+ )
+ expect(actual).toBe(defineForClassFieldsFalseTransformedCode)
+ })
+
+ test('target: es2022 and tsconfig.target: es2022 => true', async () => {
+ const actual = await transformClassCode(
+ 'es2022',
+ './fixtures/oxc-tsconfigs/target-es2022',
+ )
+ expect(actual).toBe(defineForClassFieldsTrueTransformedCode)
+ })
+
+ test('target: es2022 and tsconfig.target: undefined => false', async () => {
+ const actual = await transformClassCode(
+ 'es2022',
+ './fixtures/oxc-tsconfigs/empty',
+ )
+ expect(actual).toBe(defineForClassFieldsFalseTransformedCode)
+ })
+ })
+
+ test('supports emitDecoratorMetadata: true', async () => {
+ const result = await transformWithOxc(
+ `
+ function LogMethod(target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor) {
+ console.log(target, propertyKey, descriptor);
+ }
+
+ class Demo {
+ @LogMethod
+ public foo(bar: number) {}
+ }
+
+ const demo = new Demo();
+ `,
+ path.resolve(
+ import.meta.dirname,
+ './fixtures/oxc-tsconfigs/decorator-metadata/bar.ts',
+ ),
+ )
+ expect(result?.code).toContain('_decorateMetadata("design:type"')
+ })
+})
+
+describe('renderChunk', () => {
+ test('should inject helper for iife without exports from esm', async () => {
+ const renderChunk = await createBuildOxcPluginRenderChunk('es2015')
+ const result = await renderChunk(
+ `(function() {
+
+"use strict";
+
+//#region src/index.js
+(async () => {
+ await new Promise((resolve) => setTimeout(resolve, 1e3));
+ console.log("foo");
+})();
+
+//#endregion
+})();`,
+ 'iife',
+ )
+ expect(result).toMatchInlineSnapshot(`
+ "(function() {
+ "use strict";var babelHelpers_asyncToGenerator;!(() => {function e(e,t,n,r,i,a,o){try{var s=e[a](o),c=s.value}catch(e){n(e);return}s.done?t(c):Promise.resolve(c).then(r,i)}function t(t){return function(){var n=this,r=arguments;return new Promise(function(i,a){var o=t.apply(n,r);function s(t){e(o,i,a,s,c,\`next\`,t)}function c(t){e(o,i,a,s,c,\`throw\`,t)}s(void 0)})}}babelHelpers_asyncToGenerator=t;})();
+
+ //#region src/index.js
+ babelHelpers_asyncToGenerator(function* () {
+ yield new Promise((resolve) => setTimeout(resolve, 1e3));
+ console.log("foo");
+ })();
+ //#endregion
+ })();
+ "
+ `)
+ })
+
+ test('should inject helper for iife without exports from cjs', async () => {
+ const renderChunk = await createBuildOxcPluginRenderChunk('es2015')
+ const result = await renderChunk(
+ `(function() {
+
+
+//#region src/index.js
+(async () => {
+ await new Promise((resolve) => setTimeout(resolve, 1e3));
+ console.log("foo");
+})();
+
+//#endregion
+})();`,
+ 'iife',
+ )
+ expect(result).toMatchInlineSnapshot(`
+ "(function() {var babelHelpers_asyncToGenerator;!(() => {function e(e,t,n,r,i,a,o){try{var s=e[a](o),c=s.value}catch(e){n(e);return}s.done?t(c):Promise.resolve(c).then(r,i)}function t(t){return function(){var n=this,r=arguments;return new Promise(function(i,a){var o=t.apply(n,r);function s(t){e(o,i,a,s,c,\`next\`,t)}function c(t){e(o,i,a,s,c,\`throw\`,t)}s(void 0)})}}babelHelpers_asyncToGenerator=t;})();
+
+ //#region src/index.js
+ babelHelpers_asyncToGenerator(function* () {
+ yield new Promise((resolve) => setTimeout(resolve, 1e3));
+ console.log("foo");
+ })();
+ //#endregion
+ })();
+ "
+ `)
+ })
+
+ test('should inject helper for iife with exports', async () => {
+ const renderChunk = await createBuildOxcPluginRenderChunk('es2015')
+ const result = await renderChunk(
+ `var lib = (function(exports) {
+
+
+//#region entry.js
+(async () => {
+ await new Promise((resolve) => setTimeout(resolve, 1e3));
+ console.log("foo");
+})();
+const foo = "foo";
+
+//#endregion
+exports.foo = foo;
+return exports;
+})({});`,
+ 'iife',
+ )
+ expect(result).toMatchInlineSnapshot(`
+ "var lib = (function(exports) {var babelHelpers_asyncToGenerator;!(() => {function e(e,t,n,r,i,a,o){try{var s=e[a](o),c=s.value}catch(e){n(e);return}s.done?t(c):Promise.resolve(c).then(r,i)}function t(t){return function(){var n=this,r=arguments;return new Promise(function(i,a){var o=t.apply(n,r);function s(t){e(o,i,a,s,c,\`next\`,t)}function c(t){e(o,i,a,s,c,\`throw\`,t)}s(void 0)})}}babelHelpers_asyncToGenerator=t;})();
+
+ //#region entry.js
+ babelHelpers_asyncToGenerator(function* () {
+ yield new Promise((resolve) => setTimeout(resolve, 1e3));
+ console.log("foo");
+ })();
+ const foo = "foo";
+ //#endregion
+ exports.foo = foo;
+ return exports;
+ })({});
+ "
+ `)
+ })
+
+ test('should inject helper for umd without exports', async () => {
+ const renderChunk = await createBuildOxcPluginRenderChunk('es2015')
+ const result = await renderChunk(
+ `(function(factory) {
+
+ typeof define === 'function' && define.amd ? define([], factory) :
+ factory();
+})(function() {
+
+//#region entry.js
+(async () => {
+ await new Promise((resolve) => setTimeout(resolve, 1e3));
+ console.log("foo");
+})();
+
+//#endregion
+});`,
+ 'umd',
+ )
+ expect(result).toMatchInlineSnapshot(`
+ "(function(factory) {
+ typeof define === "function" && define.amd ? define([], factory) : factory();
+ })(function() {var babelHelpers_asyncToGenerator;!(() => {function e(e,t,n,r,i,a,o){try{var s=e[a](o),c=s.value}catch(e){n(e);return}s.done?t(c):Promise.resolve(c).then(r,i)}function t(t){return function(){var n=this,r=arguments;return new Promise(function(i,a){var o=t.apply(n,r);function s(t){e(o,i,a,s,c,\`next\`,t)}function c(t){e(o,i,a,s,c,\`throw\`,t)}s(void 0)})}}babelHelpers_asyncToGenerator=t;})();
+
+ //#region entry.js
+ babelHelpers_asyncToGenerator(function* () {
+ yield new Promise((resolve) => setTimeout(resolve, 1e3));
+ console.log("foo");
+ })();
+ //#endregion
+ });
+ "
+ `)
+ })
+
+ test('should inject helper for umd with exports', async () => {
+ const renderChunk = await createBuildOxcPluginRenderChunk('es2015')
+ const result = await renderChunk(
+ `(function(global, factory) {
+ typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
+ typeof define === 'function' && define.amd ? define(['exports'], factory) :
+ (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory((global.lib = {})));
+})(this, function(exports) {
+
+//#region entry.js
+(async () => {
+ await new Promise((resolve) => setTimeout(resolve, 1e3));
+ console.log("foo");
+})();
+const foo = "foo";
+
+//#endregion
+exports.foo = foo;
+});`,
+ 'umd',
+ )
+ expect(result).toMatchInlineSnapshot(`
+ "(function(global, factory) {
+ typeof exports === "object" && typeof module !== "undefined" ? factory(exports) : typeof define === "function" && define.amd ? define(["exports"], factory) : (global = typeof globalThis !== "undefined" ? globalThis : global || self, factory(global.lib = {}));
+ })(this, function(exports) {var babelHelpers_asyncToGenerator;!(() => {function e(e,t,n,r,i,a,o){try{var s=e[a](o),c=s.value}catch(e){n(e);return}s.done?t(c):Promise.resolve(c).then(r,i)}function t(t){return function(){var n=this,r=arguments;return new Promise(function(i,a){var o=t.apply(n,r);function s(t){e(o,i,a,s,c,\`next\`,t)}function c(t){e(o,i,a,s,c,\`throw\`,t)}s(void 0)})}}babelHelpers_asyncToGenerator=t;})();
+
+ //#region entry.js
+ babelHelpers_asyncToGenerator(function* () {
+ yield new Promise((resolve) => setTimeout(resolve, 1e3));
+ console.log("foo");
+ })();
+ const foo = "foo";
+ //#endregion
+ exports.foo = foo;
+ });
+ "
+ `)
+ })
+
+ test('should inject helper for umd with only default export', async () => {
+ const renderChunk = await createBuildOxcPluginRenderChunk('es2015')
+ const result = await renderChunk(
+ `(function(global, factory) {
+ typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
+ typeof define === 'function' && define.amd ? define([], factory) :
+ (global = typeof globalThis !== 'undefined' ? globalThis : global || self, (global.lib = factory()));
+})(this, function() {
+
+//#region entry.js
+(async () => {
+ await new Promise((resolve) => setTimeout(resolve, 1e3));
+ console.log("foo");
+})();
+var index_default = "foo";
+
+//#endregion
+return index_default;
+});`,
+ 'umd',
+ )
+ expect(result).toMatchInlineSnapshot(`
+ "(function(global, factory) {
+ typeof exports === "object" && typeof module !== "undefined" ? module.exports = factory() : typeof define === "function" && define.amd ? define([], factory) : (global = typeof globalThis !== "undefined" ? globalThis : global || self, global.lib = factory());
+ })(this, function() {var babelHelpers_asyncToGenerator;!(() => {function e(e,t,n,r,i,a,o){try{var s=e[a](o),c=s.value}catch(e){n(e);return}s.done?t(c):Promise.resolve(c).then(r,i)}function t(t){return function(){var n=this,r=arguments;return new Promise(function(i,a){var o=t.apply(n,r);function s(t){e(o,i,a,s,c,\`next\`,t)}function c(t){e(o,i,a,s,c,\`throw\`,t)}s(void 0)})}}babelHelpers_asyncToGenerator=t;})();
+
+ //#region entry.js
+ babelHelpers_asyncToGenerator(function* () {
+ yield new Promise((resolve) => setTimeout(resolve, 1e3));
+ console.log("foo");
+ })();
+ var index_default = "foo";
+ //#endregion
+ return index_default;
+ });
+ "
+ `)
+ })
+
+ test('should inject multiple helpers', async () => {
+ const renderChunk = await createBuildOxcPluginRenderChunk('es2015')
+ const result = await renderChunk(
+ `(function() {
+
+"use strict";
+
+//#region src/index.js
+(async () => {
+ await new Promise((resolve) => setTimeout(resolve, 1e3));
+ console.log("foo", { ..."foo" });
+})();
+
+//#endregion
+})();`,
+ 'iife',
+ )
+ expect(result).toMatchInlineSnapshot(`
+ "(function() {
+ "use strict";var babelHelpers_asyncToGenerator, babelHelpers_objectSpread2;!(() => {function e(e,t,n,r,i,a,o){try{var s=e[a](o),c=s.value}catch(e){n(e);return}s.done?t(c):Promise.resolve(c).then(r,i)}function t(t){return function(){var n=this,r=arguments;return new Promise(function(i,a){var o=t.apply(n,r);function s(t){e(o,i,a,s,c,\`next\`,t)}function c(t){e(o,i,a,s,c,\`throw\`,t)}s(void 0)})}}function n(e){"@babel/helpers - typeof";return n=typeof Symbol==\`function\`&&typeof Symbol.iterator==\`symbol\`?function(e){return typeof e}:function(e){return e&&typeof Symbol==\`function\`&&e.constructor===Symbol&&e!==Symbol.prototype?\`symbol\`:typeof e},n(e)}function r(e,t){if(n(e)!=\`object\`||!e)return e;var r=e[Symbol.toPrimitive];if(r!==void 0){var i=r.call(e,t||\`default\`);if(n(i)!=\`object\`)return i;throw TypeError(\`@@toPrimitive must return a primitive value.\`)}return(t===\`string\`?String:Number)(e)}function i(e){var t=r(e,\`string\`);return n(t)==\`symbol\`?t:t+\`\`}function a(e,t,n){return(t=i(t))in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function o(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function s(e){for(var t=1;t setTimeout(resolve, 1e3));
+ console.log("foo", babelHelpers_objectSpread2({}, "foo"));
+ })();
+ //#endregion
+ })();
+ "
+ `)
+ })
+})
diff --git a/packages/vite/src/node/__tests__/plugins/pluginFilter.spec.ts b/packages/vite/src/node/__tests__/plugins/pluginFilter.spec.ts
new file mode 100644
index 00000000000000..4cb30a1f3d4ddc
--- /dev/null
+++ b/packages/vite/src/node/__tests__/plugins/pluginFilter.spec.ts
@@ -0,0 +1,343 @@
+import util from 'node:util'
+import path from 'node:path'
+import { describe, expect, test } from 'vitest'
+import type { ModuleTypeFilter } from 'rolldown'
+import {
+ type StringFilter,
+ createCodeFilter,
+ createFilterForTransform,
+ createIdFilter,
+} from '../../plugins/pluginFilter'
+
+describe('createIdFilter', () => {
+ const filters = [
+ { inputFilter: undefined, cases: undefined },
+ {
+ inputFilter: 'foo.js',
+ cases: [
+ { id: 'foo.js', expected: true },
+ { id: 'foo.ts', expected: false },
+ { id: '\0foo.js', expected: false },
+ { id: '\0' + path.resolve('foo.js'), expected: false },
+ ],
+ },
+ {
+ inputFilter: ['foo.js'],
+ cases: [
+ { id: 'foo.js', expected: true },
+ { id: 'foo.ts', expected: false },
+ ],
+ },
+ {
+ inputFilter: { include: 'foo.js' },
+ cases: [
+ { id: 'foo.js', expected: true },
+ { id: 'foo.ts', expected: false },
+ ],
+ },
+ {
+ inputFilter: { include: '*.js' },
+ cases: [
+ { id: 'foo.js', expected: true },
+ { id: 'foo.ts', expected: false },
+ ],
+ },
+ {
+ inputFilter: { include: /\.js$/ },
+ cases: [
+ { id: 'foo.js', expected: true },
+ { id: 'foo.ts', expected: false },
+ ],
+ },
+ {
+ inputFilter: { include: /\/foo\.js$/ },
+ cases: [
+ { id: 'a/foo.js', expected: true },
+ ...(process.platform === 'win32'
+ ? [{ id: 'a\\foo.js', expected: true }]
+ : []),
+ { id: 'a_foo.js', expected: false },
+ ],
+ },
+ {
+ inputFilter: { include: [/\.js$/] },
+ cases: [
+ { id: 'foo.js', expected: true },
+ { id: 'foo.ts', expected: false },
+ ],
+ },
+ {
+ inputFilter: { exclude: 'foo.js' },
+ cases: [
+ { id: 'foo.js', expected: false },
+ { id: 'foo.ts', expected: true },
+ ],
+ },
+ {
+ inputFilter: { exclude: '*.js' },
+ cases: [
+ { id: 'foo.js', expected: false },
+ { id: 'foo.ts', expected: true },
+ ],
+ },
+ {
+ inputFilter: { exclude: /\.js$/ },
+ cases: [
+ { id: 'foo.js', expected: false },
+ { id: 'foo.ts', expected: true },
+ ],
+ },
+ {
+ inputFilter: { exclude: [/\.js$/] },
+ cases: [
+ { id: 'foo.js', expected: false },
+ { id: 'foo.ts', expected: true },
+ ],
+ },
+ {
+ inputFilter: { include: 'foo.js', exclude: 'bar.js' },
+ cases: [
+ { id: 'foo.js', expected: true },
+ { id: 'bar.js', expected: false },
+ { id: 'baz.js', expected: false },
+ ],
+ },
+ {
+ inputFilter: { include: '*.js', exclude: 'foo.*' },
+ cases: [
+ { id: 'foo.js', expected: false }, // exclude has higher priority
+ { id: 'bar.js', expected: true },
+ { id: 'foo.ts', expected: false },
+ ],
+ },
+ {
+ inputFilter: '/virtual/foo',
+ cases: [{ id: '/virtual/foo', expected: true }],
+ },
+ ]
+
+ for (const filter of filters) {
+ test(`${util.inspect(filter.inputFilter)}`, () => {
+ const idFilter = createIdFilter(filter.inputFilter, '')
+ if (!filter.cases) {
+ expect(idFilter).toBeUndefined()
+ return
+ }
+ expect(idFilter).not.toBeUndefined()
+
+ for (const testCase of filter.cases) {
+ const { id, expected } = testCase
+ expect(idFilter!(id), id).toBe(expected)
+ }
+ })
+ }
+})
+
+describe('createCodeFilter', () => {
+ const filters = [
+ { inputFilter: undefined, cases: undefined },
+ {
+ inputFilter: 'import.meta',
+ cases: [
+ { code: 'import.meta', expected: true },
+ { code: 'import_meta', expected: false },
+ ],
+ },
+ {
+ inputFilter: ['import.meta'],
+ cases: [
+ { code: 'import.meta', expected: true },
+ { code: 'import_meta', expected: false },
+ ],
+ },
+ {
+ inputFilter: { include: 'import.meta' },
+ cases: [
+ { code: 'import.meta', expected: true },
+ { code: 'import_meta', expected: false },
+ ],
+ },
+ {
+ inputFilter: { include: /import\.\w+/ },
+ cases: [
+ { code: 'import.meta', expected: true },
+ { code: 'import_meta', expected: false },
+ ],
+ },
+ {
+ inputFilter: { include: [/import\.\w+/] },
+ cases: [
+ { code: 'import.meta', expected: true },
+ { code: 'import_meta', expected: false },
+ ],
+ },
+ {
+ inputFilter: { exclude: 'import.meta' },
+ cases: [
+ { code: 'import.meta', expected: false },
+ { code: 'import_meta', expected: true },
+ ],
+ },
+ {
+ inputFilter: { exclude: /import\.\w+/ },
+ cases: [
+ { code: 'import.meta', expected: false },
+ { code: 'import_meta', expected: true },
+ ],
+ },
+ {
+ inputFilter: { exclude: [/import\.\w+/] },
+ cases: [
+ { code: 'import.meta', expected: false },
+ { code: 'import_meta', expected: true },
+ ],
+ },
+ {
+ inputFilter: { include: 'import.meta', exclude: 'import_meta' },
+ cases: [
+ { code: 'import.meta', expected: true },
+ { code: 'import_meta', expected: false },
+ { code: 'importmeta', expected: false },
+ ],
+ },
+ {
+ inputFilter: { include: /import\.\w+/, exclude: /\w+\.meta/ },
+ cases: [
+ { code: 'import.meta', expected: false }, // exclude has higher priority
+ { code: 'import.foo', expected: true },
+ { code: 'foo.meta', expected: false },
+ ],
+ },
+ ]
+
+ for (const filter of filters) {
+ test(`${util.inspect(filter.inputFilter)}`, () => {
+ const codeFilter = createCodeFilter(filter.inputFilter)
+ if (!filter.cases) {
+ expect(codeFilter).toBeUndefined()
+ return
+ }
+ expect(codeFilter).not.toBeUndefined()
+
+ for (const testCase of filter.cases) {
+ const { code, expected } = testCase
+ expect(codeFilter!(code), code).toBe(expected)
+ }
+ })
+ }
+})
+
+describe('createFilterForTransform', () => {
+ type Filters = {
+ inputFilter: [
+ idFilter: StringFilter | undefined,
+ codeFilter: StringFilter | undefined,
+ moduleTypeFilter?: ModuleTypeFilter | undefined,
+ ]
+ cases:
+ | {
+ id: string
+ code: string
+ moduleType?: string
+ expected: boolean
+ }[]
+ | undefined
+ }[]
+ const filters: Filters = [
+ { inputFilter: [undefined, undefined], cases: undefined },
+ {
+ inputFilter: ['*.js', undefined],
+ cases: [
+ { id: 'foo.js', code: 'foo', expected: true },
+ { id: 'foo.ts', code: 'foo', expected: false },
+ ],
+ },
+ {
+ inputFilter: [undefined, 'import.meta'],
+ cases: [
+ { id: 'foo.js', code: 'import.meta', expected: true },
+ { id: 'foo.js', code: 'import_meta', expected: false },
+ ],
+ },
+ {
+ inputFilter: [{ exclude: '*.js' }, 'import.meta'],
+ cases: [
+ { id: 'foo.js', code: 'import.meta', expected: false },
+ { id: 'foo.js', code: 'import_meta', expected: false },
+ { id: 'foo.ts', code: 'import.meta', expected: true },
+ { id: 'foo.ts', code: 'import_meta', expected: false },
+ ],
+ },
+ {
+ inputFilter: [{ include: 'foo.ts', exclude: '*.js' }, 'import.meta'],
+ cases: [
+ { id: 'foo.js', code: 'import.meta', expected: false },
+ { id: 'foo.js', code: 'import_meta', expected: false },
+ { id: 'foo.ts', code: 'import.meta', expected: true },
+ { id: 'foo.ts', code: 'import_meta', expected: false },
+ ],
+ },
+ {
+ inputFilter: [
+ { include: 'a*', exclude: '*b' },
+ { include: 'a', exclude: 'b' },
+ ],
+ cases: [
+ { id: 'ab', code: '', expected: false },
+ { id: 'a', code: 'b', expected: false },
+ { id: 'a', code: '', expected: false },
+ { id: 'c', code: 'a', expected: false },
+ { id: 'a', code: 'a', expected: true },
+ ],
+ },
+ {
+ inputFilter: [{ include: 'a*', exclude: '*b' }, { exclude: 'b' }],
+ cases: [
+ { id: 'ab', code: '', expected: false },
+ { id: 'a', code: 'b', expected: false },
+ { id: 'a', code: '', expected: true },
+ { id: 'c', code: 'a', expected: false },
+ { id: 'a', code: 'a', expected: true },
+ ],
+ },
+ {
+ inputFilter: [undefined, undefined, ['js']],
+ cases: [
+ { id: 'foo.js', code: 'foo', moduleType: 'js', expected: true },
+ { id: 'foo.ts', code: 'foo', moduleType: 'ts', expected: false },
+ ],
+ },
+ {
+ inputFilter: [undefined, undefined, { include: ['js'] }],
+ cases: [
+ { id: 'foo.js', code: 'foo', moduleType: 'js', expected: true },
+ { id: 'foo.ts', code: 'foo', moduleType: 'ts', expected: false },
+ ],
+ },
+ ]
+
+ for (const filter of filters) {
+ test(`${util.inspect(filter.inputFilter)}`, () => {
+ const [idFilter, codeFilter, moduleTypeFilter] = filter.inputFilter
+ const filterForTransform = createFilterForTransform(
+ idFilter,
+ codeFilter,
+ moduleTypeFilter,
+ '',
+ )
+ if (!filter.cases) {
+ expect(filterForTransform).toBeUndefined()
+ return
+ }
+ expect(filterForTransform).not.toBeUndefined()
+
+ for (const testCase of filter.cases) {
+ const { id, code, moduleType, expected } = testCase
+ expect(
+ filterForTransform!(id, code, moduleType ?? 'js'),
+ util.inspect({ id, code, moduleType }),
+ ).toBe(expected)
+ }
+ })
+ }
+})
diff --git a/packages/vite/src/node/__tests__/plugins/terser.spec.ts b/packages/vite/src/node/__tests__/plugins/terser.spec.ts
new file mode 100644
index 00000000000000..cd3e8e86ec0a03
--- /dev/null
+++ b/packages/vite/src/node/__tests__/plugins/terser.spec.ts
@@ -0,0 +1,74 @@
+import { resolve } from 'node:path'
+import { describe, expect, test } from 'vitest'
+import { build } from 'vite'
+import type { RollupOutput } from 'rollup'
+import type { TerserOptions } from '../../plugins/terser'
+
+describe('terser', () => {
+ const run = async (terserOptions: TerserOptions) => {
+ const result = (await build({
+ root: resolve(import.meta.dirname, '../packages/build-project'),
+ logLevel: 'silent',
+ build: {
+ write: false,
+ minify: 'terser',
+ terserOptions,
+ },
+ plugins: [
+ {
+ name: 'test',
+ resolveId(id) {
+ if (id === 'entry.js') {
+ return '\0' + id
+ }
+ },
+ load(id) {
+ if (id === '\0entry.js') {
+ return `
+ const foo = 1;
+ console.log(foo);
+ const bar = { hello: 1, ["world"]: 2 };
+ console.log(bar.hello + bar["world"]);
+ `
+ }
+ },
+ },
+ ],
+ })) as RollupOutput
+ return result.output[0].code
+ }
+
+ test('basic', async () => {
+ await run({})
+ })
+
+ test('nth', async () => {
+ const resultCode = await run({
+ mangle: {
+ nth_identifier: {
+ get: (n) => {
+ return 'prefix_' + n.toString()
+ },
+ },
+ },
+ })
+ expect(resultCode).toContain('prefix_')
+ })
+
+ test('nameCache', async () => {
+ const nameCache = {}
+
+ await run({
+ compress: false,
+ mangle: {
+ properties: {
+ keep_quoted: true,
+ },
+ },
+ nameCache,
+ })
+
+ expect(nameCache).toHaveProperty('props.props.$hello')
+ expect(nameCache).not.toHaveProperty('props.props.$world')
+ })
+})
diff --git a/packages/vite/src/node/__tests__/plugins/workerImportMetaUrl.spec.ts b/packages/vite/src/node/__tests__/plugins/workerImportMetaUrl.spec.ts
new file mode 100644
index 00000000000000..348e3e39be0147
--- /dev/null
+++ b/packages/vite/src/node/__tests__/plugins/workerImportMetaUrl.spec.ts
@@ -0,0 +1,256 @@
+import { describe, expect, test } from 'vitest'
+import { parseAst } from 'rollup/parseAst'
+import { workerImportMetaUrlPlugin } from '../../plugins/workerImportMetaUrl'
+import { resolveConfig } from '../../config'
+import { PartialEnvironment } from '../../baseEnvironment'
+
+async function createWorkerImportMetaUrlPluginTransform() {
+ const config = await resolveConfig({ configFile: false }, 'serve')
+ const instance = workerImportMetaUrlPlugin(config)
+ const environment = new PartialEnvironment('client', config)
+
+ return async (code: string) => {
+ // @ts-expect-error transform.handler should exist
+ const result = await instance.transform.handler.call(
+ { environment, parse: parseAst },
+ code,
+ 'foo.ts',
+ )
+ return result?.code || result
+ }
+}
+
+describe('workerImportMetaUrlPlugin', async () => {
+ const transform = await createWorkerImportMetaUrlPluginTransform()
+
+ test('without worker options', async () => {
+ expect(
+ await transform('new Worker(new URL("./worker.js", import.meta.url))'),
+ ).toMatchInlineSnapshot(
+ `"new Worker(new URL(/* @vite-ignore */ "/worker.js?worker_file&type=classic", '' + import.meta.url))"`,
+ )
+ })
+
+ test('with shared worker', async () => {
+ expect(
+ await transform(
+ 'new SharedWorker(new URL("./worker.js", import.meta.url))',
+ ),
+ ).toMatchInlineSnapshot(
+ `"new SharedWorker(new URL(/* @vite-ignore */ "/worker.js?worker_file&type=classic", '' + import.meta.url))"`,
+ )
+ })
+
+ test('with static worker options and identifier properties', async () => {
+ expect(
+ await transform(
+ 'new Worker(new URL("./worker.js", import.meta.url), { type: "module", name: "worker1" })',
+ ),
+ ).toMatchInlineSnapshot(
+ `"new Worker(new URL(/* @vite-ignore */ "/worker.js?worker_file&type=module", '' + import.meta.url), { type: "module", name: "worker1" })"`,
+ )
+ })
+
+ test('with static worker options and literal properties', async () => {
+ expect(
+ await transform(
+ 'new Worker(new URL("./worker.js", import.meta.url), { "type": "module", "name": "worker1" })',
+ ),
+ ).toMatchInlineSnapshot(
+ `"new Worker(new URL(/* @vite-ignore */ "/worker.js?worker_file&type=module", '' + import.meta.url), { "type": "module", "name": "worker1" })"`,
+ )
+ })
+
+ test('with dynamic name field in worker options', async () => {
+ expect(
+ await transform(
+ 'const id = 1; new Worker(new URL("./worker.js", import.meta.url), { name: "worker" + id })',
+ ),
+ ).toMatchInlineSnapshot(
+ `"const id = 1; new Worker(new URL(/* @vite-ignore */ "/worker.js?worker_file&type=classic", '' + import.meta.url), { name: "worker" + id })"`,
+ )
+ })
+
+ test('with interpolated dynamic name field in worker options', async () => {
+ expect(
+ await transform(
+ 'const id = 1; new Worker(new URL("./worker.js", import.meta.url), { name: `worker-${id}` })',
+ ),
+ ).toMatchInlineSnapshot(
+ `"const id = 1; new Worker(new URL(/* @vite-ignore */ "/worker.js?worker_file&type=classic", '' + import.meta.url), { name: \`worker-\${id}\` })"`,
+ )
+ })
+
+ test('with dynamic name field and static type in worker options', async () => {
+ expect(
+ await transform(
+ 'const id = 1; new Worker(new URL("./worker.js", import.meta.url), { name: "worker" + id, type: "module" })',
+ ),
+ ).toMatchInlineSnapshot(
+ `"const id = 1; new Worker(new URL(/* @vite-ignore */ "/worker.js?worker_file&type=module", '' + import.meta.url), { name: "worker" + id, type: "module" })"`,
+ )
+ })
+
+ test('with interpolated dynamic name field and static type in worker options', async () => {
+ expect(
+ await transform(
+ 'const id = 1; new Worker(new URL("./worker.js", import.meta.url), { name: `worker-${id}`, type: "module" })',
+ ),
+ ).toMatchInlineSnapshot(
+ `"const id = 1; new Worker(new URL(/* @vite-ignore */ "/worker.js?worker_file&type=module", '' + import.meta.url), { name: \`worker-\${id}\`, type: "module" })"`,
+ )
+ })
+
+ test('with parenthesis inside of worker options', async () => {
+ expect(
+ await transform(
+ 'const worker = new Worker(new URL("./worker.js", import.meta.url), { name: genName(), type: "module"})',
+ ),
+ ).toMatchInlineSnapshot(
+ `"const worker = new Worker(new URL(/* @vite-ignore */ "/worker.js?worker_file&type=module", '' + import.meta.url), { name: genName(), type: "module"})"`,
+ )
+ })
+
+ test('with multi-line code and worker options', async () => {
+ expect(
+ await transform(`
+const worker = new Worker(new URL("./worker.js", import.meta.url), {
+ name: genName(),
+ type: "module",
+ },
+)
+
+worker.addEventListener('message', (ev) => text('.simple-worker-url', JSON.stringify(ev.data)))
+`),
+ ).toMatchInlineSnapshot(`
+ "
+ const worker = new Worker(new URL(/* @vite-ignore */ "/worker.js?worker_file&type=module", '' + import.meta.url), {
+ name: genName(),
+ type: "module",
+ },
+ )
+
+ worker.addEventListener('message', (ev) => text('.simple-worker-url', JSON.stringify(ev.data)))
+ "
+ `)
+ })
+
+ test('trailing comma', async () => {
+ expect(
+ await transform(`
+new Worker(
+ new URL('./worker.js', import.meta.url),
+ {
+ type: 'module'
+ }, // },
+)
+`),
+ ).toMatchInlineSnapshot(`
+ "
+ new Worker(
+ new URL(/* @vite-ignore */ "/worker.js?worker_file&type=module", '' + import.meta.url),
+ {
+ type: 'module'
+ }, // },
+ )
+ "
+ `)
+ })
+
+ test('throws an error when non-static worker options are provided', async () => {
+ await expect(
+ transform(
+ 'new Worker(new URL("./worker.js", import.meta.url), myWorkerOptions)',
+ ),
+ ).rejects.toThrow(
+ 'Vite is unable to parse the worker options as the value is not static. To ignore this error, please use /* @vite-ignore */ in the worker options.',
+ )
+ })
+
+ test('throws an error when worker options are not an object', async () => {
+ await expect(
+ transform(
+ 'new Worker(new URL("./worker.js", import.meta.url), "notAnObject")',
+ ),
+ ).rejects.toThrow('Expected worker options to be an object, got string')
+ })
+
+ test('throws an error when non-literal type field in worker options', async () => {
+ await expect(
+ transform(
+ 'const type = "module"; new Worker(new URL("./worker.js", import.meta.url), { type })',
+ ),
+ ).rejects.toThrow(
+ 'Expected worker options type property to be a literal value.',
+ )
+ })
+
+ test('throws an error when spread operator used without the type field', async () => {
+ await expect(
+ transform(
+ 'const options = { name: "worker1" }; new Worker(new URL("./worker.js", import.meta.url), { ...options })',
+ ),
+ ).rejects.toThrow(
+ 'Expected object spread to be used before the definition of the type property. Vite needs a static value for the type property to correctly infer it.',
+ )
+ })
+
+ test('throws an error when spread operator used after definition of type field', async () => {
+ await expect(
+ transform(
+ 'const options = { name: "worker1" }; new Worker(new URL("./worker.js", import.meta.url), { type: "module", ...options })',
+ ),
+ ).rejects.toThrow(
+ 'Expected object spread to be used before the definition of the type property. Vite needs a static value for the type property to correctly infer it.',
+ )
+ })
+
+ test('find closing parenthesis correctly', async () => {
+ expect(
+ await transform(
+ `(() => { new Worker(new URL('./worker', import.meta.url)); repro({ test: "foo", }); })();`,
+ ),
+ ).toMatchInlineSnapshot(
+ `"(() => { new Worker(new URL(/* @vite-ignore */ "/worker?worker_file&type=classic", '' + import.meta.url)); repro({ test: "foo", }); })();"`,
+ )
+ expect(
+ await transform(
+ `repro(new Worker(new URL('./worker', import.meta.url)), { type: "module" })`,
+ ),
+ ).toMatchInlineSnapshot(
+ `"repro(new Worker(new URL(/* @vite-ignore */ "/worker?worker_file&type=classic", '' + import.meta.url)), { type: "module" })"`,
+ )
+ })
+
+ test('with multi-line new URL and trailing comma', async () => {
+ expect(
+ await transform(`new Worker(
+ new URL(
+ "./worker.js",
+ import.meta.url,
+ )
+)`),
+ ).toMatchInlineSnapshot(`
+ "new Worker(
+ new URL(/* @vite-ignore */ "/worker.js?worker_file&type=classic", '' + import.meta.url)
+ )"
+ `)
+ })
+
+ test('with multi-line new URL, trailing comma, and worker options', async () => {
+ expect(
+ await transform(`const worker = new Worker(
+ new URL(
+ "./worker.js",
+ import.meta.url,
+ ),
+ { type: "module" },
+)`),
+ ).toMatchInlineSnapshot(`
+ "const worker = new Worker(
+ new URL(/* @vite-ignore */ "/worker.js?worker_file&type=module", '' + import.meta.url),
+ { type: "module" },
+ )"
+ `)
+ })
+})
diff --git a/packages/vite/src/node/__tests__/resolve.spec.ts b/packages/vite/src/node/__tests__/resolve.spec.ts
new file mode 100644
index 00000000000000..4c5465b00833ef
--- /dev/null
+++ b/packages/vite/src/node/__tests__/resolve.spec.ts
@@ -0,0 +1,313 @@
+import { join } from 'node:path'
+import { describe, expect, onTestFinished, test, vi } from 'vitest'
+import { createServer } from '../server'
+import { createServerModuleRunner } from '../ssr/runtime/serverModuleRunner'
+import type { EnvironmentOptions, InlineConfig } from '../config'
+import { build } from '../build'
+
+describe('import and resolveId', () => {
+ async function createTestServer() {
+ const server = await createServer({
+ configFile: false,
+ root: import.meta.dirname,
+ logLevel: 'error',
+ server: {
+ middlewareMode: true,
+ ws: false,
+ },
+ })
+ onTestFinished(() => server.close())
+ const runner = createServerModuleRunner(server.environments.ssr, {
+ hmr: {
+ logger: false,
+ },
+ sourcemapInterceptor: false,
+ })
+ return { server, runner }
+ }
+
+ test('import first', async () => {
+ const { server, runner } = await createTestServer()
+ const mod = await runner.import(
+ '/fixtures/test-dep-conditions-app/entry-with-module',
+ )
+ const resolved = await server.environments.ssr.pluginContainer.resolveId(
+ '@vitejs/test-dep-conditions/with-module',
+ )
+ expect([mod.default, resolved?.id]).toEqual([
+ 'dir/index.default.js',
+ expect.stringContaining('dir/index.module.js'),
+ ])
+ })
+
+ test('resolveId first', async () => {
+ const { server, runner } = await createTestServer()
+ const resolved = await server.environments.ssr.pluginContainer.resolveId(
+ '@vitejs/test-dep-conditions/with-module',
+ )
+ const mod = await runner.import(
+ '/fixtures/test-dep-conditions-app/entry-with-module',
+ )
+ expect([mod.default, resolved?.id]).toEqual([
+ 'dir/index.default.js',
+ expect.stringContaining('dir/index.module.js'),
+ ])
+ })
+})
+
+describe('file url', () => {
+ const fileUrl = new URL('./fixtures/file-url/entry.js', import.meta.url)
+
+ function getConfig(): InlineConfig {
+ return {
+ configFile: false,
+ root: join(import.meta.dirname, 'fixtures/file-url'),
+ logLevel: 'error',
+ server: {
+ middlewareMode: true,
+ },
+ plugins: [
+ {
+ name: 'virtual-file-url',
+ resolveId(source) {
+ if (source.startsWith('virtual:test-dep/')) {
+ return '\0' + source
+ }
+ },
+ load(id) {
+ if (id === '\0virtual:test-dep/static') {
+ return `
+ import * as dep from ${JSON.stringify(fileUrl.href)};
+ export default dep;
+ `
+ }
+ if (id === '\0virtual:test-dep/static-postfix') {
+ return `
+ import * as dep from ${JSON.stringify(fileUrl.href + '?query=test')};
+ export default dep;
+ `
+ }
+ if (id === '\0virtual:test-dep/non-static') {
+ return `
+ const dep = await import(/* @vite-ignore */ String(${JSON.stringify(fileUrl.href)}));
+ export default dep;
+ `
+ }
+ if (id === '\0virtual:test-dep/non-static-postfix') {
+ return `
+ const dep = await import(/* @vite-ignore */ String(${JSON.stringify(fileUrl.href + '?query=test')}));
+ export default dep;
+ `
+ }
+ },
+ },
+ ],
+ }
+ }
+
+ test('dev', async () => {
+ const server = await createServer(getConfig())
+ onTestFinished(() => server.close())
+
+ const runner = createServerModuleRunner(server.environments.ssr, {
+ hmr: {
+ logger: false,
+ },
+ sourcemapInterceptor: false,
+ })
+
+ const mod = await runner.import('/entry.js')
+ expect(mod.default).toEqual('ok')
+
+ const mod2 = await runner.import(fileUrl.href)
+ expect(mod2).toBe(mod)
+
+ const mod3 = await runner.import('virtual:test-dep/static')
+ expect(mod3.default).toBe(mod)
+
+ const mod4 = await runner.import('virtual:test-dep/non-static')
+ expect(mod4.default).toBe(mod)
+
+ const mod5 = await runner.import(fileUrl.href + '?query=test')
+ expect(mod5).toEqual(mod)
+ expect(mod5).not.toBe(mod)
+
+ const mod6 = await runner.import('virtual:test-dep/static-postfix')
+ expect(mod6.default).toEqual(mod)
+ expect(mod6.default).not.toBe(mod)
+ expect(mod6.default).toBe(mod5)
+
+ const mod7 = await runner.import('virtual:test-dep/non-static-postfix')
+ expect(mod7.default).toEqual(mod)
+ expect(mod7.default).not.toBe(mod)
+ expect(mod7.default).toBe(mod5)
+ })
+
+ describe('environment builtins', () => {
+ function getConfig(
+ targetEnv: 'client' | 'ssr' | string,
+ builtins: NonNullable['builtins'],
+ ): InlineConfig {
+ return {
+ configFile: false,
+ root: join(import.meta.dirname, 'fixtures/file-url'),
+ logLevel: 'error',
+ server: {
+ middlewareMode: true,
+ },
+ environments: {
+ [targetEnv]: {
+ resolve: {
+ builtins,
+ },
+ },
+ },
+ }
+ }
+
+ async function run({
+ builtins,
+ targetEnv = 'custom',
+ testEnv = 'custom',
+ idToResolve,
+ }: {
+ builtins?: NonNullable['builtins']
+ targetEnv?: 'client' | 'ssr' | string
+ testEnv?: 'client' | 'ssr' | string
+ idToResolve: string
+ }) {
+ const server = await createServer(getConfig(targetEnv, builtins))
+ vi.spyOn(server.config.logger, 'warn').mockImplementationOnce(
+ (message) => {
+ throw new Error(message)
+ },
+ )
+ onTestFinished(() => server.close())
+
+ return server.environments[testEnv]?.pluginContainer.resolveId(
+ idToResolve,
+ )
+ }
+
+ test('declared builtin string', async () => {
+ const resolved = await run({
+ builtins: ['my-env:custom-builtin'],
+ idToResolve: 'my-env:custom-builtin',
+ })
+ expect(resolved?.external).toBe(true)
+ })
+
+ test('declared builtin regexp', async () => {
+ const resolved = await run({
+ builtins: [/^my-env:\w/],
+ idToResolve: 'my-env:custom-builtin',
+ })
+ expect(resolved?.external).toBe(true)
+ })
+
+ test('non declared builtin', async () => {
+ const resolved = await run({
+ builtins: [
+ /* empty */
+ ],
+ idToResolve: 'my-env:custom-builtin',
+ })
+ expect(resolved).toBeNull()
+ })
+
+ test('non declared node builtin', async () => {
+ await expect(
+ run({
+ builtins: [
+ /* empty */
+ ],
+ idToResolve: 'node:fs',
+ }),
+ ).rejects.toThrowError(
+ /warning: Automatically externalized node built-in module "node:fs"/,
+ )
+ })
+
+ test('default to node-like builtins', async () => {
+ const resolved = await run({
+ idToResolve: 'node:fs',
+ })
+ expect(resolved?.external).toBe(true)
+ })
+
+ test('default to node-like builtins for ssr environment', async () => {
+ const resolved = await run({
+ idToResolve: 'node:fs',
+ testEnv: 'ssr',
+ })
+ expect(resolved?.external).toBe(true)
+ })
+
+ test('no default to node-like builtins for client environment', async () => {
+ const resolved = await run({
+ idToResolve: 'node:fs',
+ testEnv: 'client',
+ })
+ expect(resolved?.id).toEqual('__vite-browser-external:node:fs')
+ })
+
+ test('no builtins overriding for client environment', async () => {
+ const resolved = await run({
+ idToResolve: 'node:fs',
+ testEnv: 'client',
+ targetEnv: 'client',
+ })
+ expect(resolved?.id).toEqual('__vite-browser-external:node:fs')
+ })
+
+ test('declared node builtin', async () => {
+ const resolved = await run({
+ builtins: [/^node:/],
+ idToResolve: 'node:fs',
+ })
+ expect(resolved?.external).toBe(true)
+ })
+
+ test('declared builtin string in different environment', async () => {
+ const resolved = await run({
+ builtins: ['my-env:custom-builtin'],
+ idToResolve: 'my-env:custom-builtin',
+ targetEnv: 'custom',
+ testEnv: 'ssr',
+ })
+ expect(resolved).toBe(null)
+ })
+ })
+
+ test('build', async () => {
+ await build({
+ ...getConfig(),
+ build: {
+ ssr: true,
+ outDir: 'dist/basic',
+ rollupOptions: {
+ input: { index: fileUrl.href },
+ },
+ },
+ })
+ const mod1 = await import(
+ join(import.meta.dirname, 'fixtures/file-url/dist/basic/index.js')
+ )
+ expect(mod1.default).toBe('ok')
+
+ await build({
+ ...getConfig(),
+ build: {
+ ssr: true,
+ outDir: 'dist/virtual',
+ rollupOptions: {
+ input: { index: 'virtual:test-dep/static' },
+ },
+ },
+ })
+ const mod2 = await import(
+ join(import.meta.dirname, 'fixtures/file-url/dist/virtual/index.js')
+ )
+ expect(mod2.default.default).toBe('ok')
+ })
+})
diff --git a/packages/vite/src/node/__tests__/runnerImport.spec.ts b/packages/vite/src/node/__tests__/runnerImport.spec.ts
new file mode 100644
index 00000000000000..4b646d8edf3acf
--- /dev/null
+++ b/packages/vite/src/node/__tests__/runnerImport.spec.ts
@@ -0,0 +1,78 @@
+import { resolve } from 'node:path'
+import { describe, expect, test } from 'vitest'
+import { loadConfigFromFile } from 'vite'
+import { runnerImport } from '../ssr/runnerImport'
+import { slash } from '../../shared/utils'
+
+// eslint-disable-next-line n/no-unsupported-features/node-builtins
+const isTypeStrippingSupported = !!process.features.typescript
+
+describe('importing files using inlined environment', () => {
+ const fixture = (name: string) =>
+ resolve(import.meta.dirname, './fixtures/runner-import', name)
+
+ test('importing a basic file works', async () => {
+ const { module } = await runnerImport<
+ typeof import('./fixtures/runner-import/basic')
+ >(fixture('basic'))
+ expect(module.test).toEqual({
+ field: true,
+ })
+ })
+
+ test("cannot import cjs, 'runnerImport' doesn't support CJS syntax at all", async () => {
+ await expect(() =>
+ runnerImport(
+ fixture('cjs.js'),
+ ),
+ ).rejects.toThrow('module is not defined')
+ })
+
+ test('can import vite config', async () => {
+ const { module, dependencies } = await runnerImport<
+ typeof import('./fixtures/runner-import/vite.config')
+ >(fixture('vite.config'))
+ expect(module.default).toEqual({
+ root: './test',
+ plugins: [
+ {
+ name: 'test',
+ },
+ ],
+ })
+ expect(dependencies).toEqual([slash(fixture('plugin.ts'))])
+ })
+
+ test('can import vite config that imports a TS external module', async () => {
+ const { module, dependencies } = await runnerImport<
+ typeof import('./fixtures/runner-import/vite.config.outside-pkg-import.mjs')
+ >(fixture('vite.config.outside-pkg-import.mts'))
+
+ expect(module.default.__injected).toBe(true)
+ expect(dependencies).toEqual([
+ slash(resolve(import.meta.dirname, './packages/parent/index.ts')),
+ ])
+
+ // confirm that it fails with a bundle approach
+ if (!isTypeStrippingSupported) {
+ await expect(async () => {
+ const root = resolve(import.meta.dirname, './fixtures/runner-import')
+ await loadConfigFromFile(
+ { mode: 'production', command: 'serve' },
+ resolve(root, './vite.config.outside-pkg-import.mts'),
+ root,
+ 'silent',
+ )
+ }).rejects.toThrow('Unknown file extension ".ts"')
+ }
+ })
+
+ test('dynamic import', async () => {
+ const { module } = await runnerImport(fixture('dynamic-import.ts'))
+ await expect(() => module.default()).rejects.toMatchInlineSnapshot(
+ `[Error: Vite module runner has been closed.]`,
+ )
+ // const dep = await module.default();
+ // expect(dep.default).toMatchInlineSnapshot(`"ok"`)
+ })
+})
diff --git a/packages/vite/src/node/__tests__/scan.spec.ts b/packages/vite/src/node/__tests__/scan.spec.ts
index db11bcc45b284c..d09b68d3ead76a 100644
--- a/packages/vite/src/node/__tests__/scan.spec.ts
+++ b/packages/vite/src/node/__tests__/scan.spec.ts
@@ -1,5 +1,8 @@
-import { scriptRE, commentRE, importsRE } from '../optimizer/scan'
+import path from 'node:path'
+import { describe, expect, test } from 'vitest'
+import { commentRE, importsRE, scriptRE } from '../optimizer/scan'
import { multilineCommentsRE, singlelineCommentsRE } from '../utils'
+import { createServer, createServerModuleRunner } from '..'
describe('optimizer-scan:script-test', () => {
const scriptContent = `import { defineComponent } from 'vue'
@@ -13,15 +16,15 @@ describe('optimizer-scan:script-test', () => {
test('component return value test', () => {
scriptRE.lastIndex = 0
const [, tsOpenTag, tsContent] = scriptRE.exec(
- ``
- )
+ ``,
+ )!
expect(tsOpenTag).toEqual('`
- )
+ ``,
+ )!
expect(openTag).toEqual(' -->
- `.replace(commentRE, '')
+ `.replace(commentRE, ''),
)
expect(ret).toEqual(null)
})
@@ -44,25 +47,29 @@ describe('optimizer-scan:script-test', () => {
scriptRE.lastIndex = 0
ret = scriptRE.exec(
- ` `
+ ` `,
)
expect(ret).toBe(null)
scriptRE.lastIndex = 0
ret = scriptRE.exec(
- ` content `
+ ` content `,
)
expect(ret).toBe(null)
})
test('ordinary script tag test', () => {
scriptRE.lastIndex = 0
- const [, tag, content] = scriptRE.exec(``)
+ const [, tag, content] = scriptRE.exec(
+ ``,
+ )!
expect(tag).toEqual('`)
+ const [, tag1, content1] = scriptRE.exec(
+ ``,
+ )!
expect(tag1).toEqual('
- const filePath = id.replace(normalizePath(config.root), '')
- addToHTMLProxyCache(
- config,
- filePath,
- inlineModuleIndex,
- contents
+ if (node.nodeName === 'script') {
+ const { src, srcSourceCodeLocation, isModule, isAsync, isIgnored } =
+ getScriptInfo(node)
+
+ if (isIgnored) {
+ removeViteIgnoreAttr(s, node.sourceCodeLocation!)
+ } else {
+ const url = src && src.value
+ const isPublicFile = !!(url && checkPublicFile(url, config))
+ if (isPublicFile) {
+ // referencing public dir url, prefix with base
+ overwriteAttrValue(
+ s,
+ srcSourceCodeLocation!,
+ partialEncodeURIPath(toOutputPublicFilePath(url)),
)
- 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(
- `
+ 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 (!isExcludedUrl(url)) {
- s.overwrite(start, end, await urlToBuiltUrl(url, id, config, this))
- } else if (checkPublicFile(url, config)) {
- s.overwrite(start, end, config.base + url.slice(1))
+ 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)
}
}
- processedHtml.set(id, s.toString())
+ processedHtml(this).set(id, s.toString())
// inject module preload polyfill only when configured and needed
+ const { modulePreload } = this.environment.config.build
if (
- config.build.polyfillModulePreload &&
+ modulePreload !== false &&
+ modulePreload.polyfill &&
(someScriptsAreAsync || someScriptsAreDefer)
) {
js = `import "${modulePreloadPolyfillId}";\n${js}`
}
- return js
- }
+ await Promise.all(setModuleSideEffectPromises)
+
+ // 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) {
- const analyzedChunk: Map = new Map()
+ const analyzedImportedCssFiles = new Map()
+ const inlineEntryChunk = new Set()
const getImportedChunks = (
chunk: OutputChunk,
- seen: Set = new Set()
- ): OutputChunk[] => {
- const chunks: OutputChunk[] = []
+ seen: Set = new Set(),
+ ): (OutputChunk | string)[] => {
+ const chunks: (OutputChunk | string)[] = []
chunk.imports.forEach((file) => {
const importee = bundle[file]
- if (importee?.type === 'chunk' && !seen.has(file)) {
- seen.add(file)
+ if (importee) {
+ if (importee.type === 'chunk' && !seen.has(file)) {
+ seen.add(file)
- // post-order traversal
- chunks.push(...getImportedChunks(importee, seen))
- chunks.push(importee)
+ // post-order traversal
+ chunks.push(...getImportedChunks(importee, seen))
+ chunks.push(importee)
+ }
+ } else {
+ // external imports
+ chunks.push(file)
}
})
return chunks
}
const toScriptTag = (
- chunk: OutputChunk,
- isAsync: boolean
+ chunkOrUrl: OutputChunk | string,
+ toOutputPath: (filename: string) => string,
+ isAsync: boolean,
): HtmlTagDescriptor => ({
tag: 'script',
attrs: {
...(isAsync ? { async: true } : {}),
type: 'module',
+ // crossorigin must be set not only for serving assets in a different origin
+ // but also to make it possible to preload the script using ` `.
+ // `
diff --git a/packages/vite/src/node/server/middlewares/hostCheck.ts b/packages/vite/src/node/server/middlewares/hostCheck.ts
new file mode 100644
index 00000000000000..af366de279b61a
--- /dev/null
+++ b/packages/vite/src/node/server/middlewares/hostCheck.ts
@@ -0,0 +1,62 @@
+import { hostValidationMiddleware as originalHostValidationMiddleware } from 'host-validation-middleware'
+import type { Connect } from '#dep-types/connect'
+import type { ResolvedPreviewOptions, ResolvedServerOptions } from '../..'
+
+export function getAdditionalAllowedHosts(
+ resolvedServerOptions: Pick,
+ resolvedPreviewOptions: Pick,
+): string[] {
+ const list = []
+
+ // allow host option by default as that indicates that the user is
+ // expecting Vite to respond on that host
+ if (
+ typeof resolvedServerOptions.host === 'string' &&
+ resolvedServerOptions.host
+ ) {
+ list.push(resolvedServerOptions.host)
+ }
+ if (
+ typeof resolvedServerOptions.hmr === 'object' &&
+ resolvedServerOptions.hmr.host
+ ) {
+ list.push(resolvedServerOptions.hmr.host)
+ }
+ if (
+ typeof resolvedPreviewOptions.host === 'string' &&
+ resolvedPreviewOptions.host
+ ) {
+ list.push(resolvedPreviewOptions.host)
+ }
+
+ // allow server origin by default as that indicates that the user is
+ // expecting Vite to respond on that host
+ if (resolvedServerOptions.origin) {
+ // some frameworks may pass the origin as a placeholder, so it's not
+ // possible to parse as URL, so use a try-catch here as a best effort
+ try {
+ const serverOriginUrl = new URL(resolvedServerOptions.origin)
+ list.push(serverOriginUrl.hostname)
+ } catch {}
+ }
+
+ return list
+}
+
+export function hostValidationMiddleware(
+ allowedHosts: string[],
+ isPreview: boolean,
+): Connect.NextHandleFunction {
+ return originalHostValidationMiddleware({
+ // Freeze the array to allow caching
+ allowedHosts: Object.freeze([...allowedHosts]),
+ generateErrorMessage(hostname) {
+ const hostnameWithQuotes = JSON.stringify(hostname)
+ const optionName = `${isPreview ? 'preview' : 'server'}.allowedHosts`
+ return (
+ `Blocked request. This host (${hostnameWithQuotes}) is not allowed.\n` +
+ `To allow this host, add ${hostnameWithQuotes} to \`${optionName}\` in vite.config.js.`
+ )
+ },
+ })
+}
diff --git a/packages/vite/src/node/server/middlewares/htmlFallback.ts b/packages/vite/src/node/server/middlewares/htmlFallback.ts
new file mode 100644
index 00000000000000..24d08f9537171c
--- /dev/null
+++ b/packages/vite/src/node/server/middlewares/htmlFallback.ts
@@ -0,0 +1,91 @@
+import path from 'node:path'
+import fs from 'node:fs'
+import type { Connect } from '#dep-types/connect'
+import { createDebugger, joinUrlSegments } from '../../utils'
+import { cleanUrl } from '../../../shared/utils'
+import type { DevEnvironment } from '../environment'
+import { FullBundleDevEnvironment } from '../environments/fullBundleEnvironment'
+
+const debug = createDebugger('vite:html-fallback')
+
+export function htmlFallbackMiddleware(
+ root: string,
+ spaFallback: boolean,
+ clientEnvironment?: DevEnvironment,
+): Connect.NextHandleFunction {
+ const memoryFiles =
+ clientEnvironment instanceof FullBundleDevEnvironment
+ ? clientEnvironment.memoryFiles
+ : undefined
+
+ function checkFileExists(relativePath: string) {
+ return (
+ memoryFiles?.has(
+ relativePath.slice(1), // remove first /
+ ) ?? fs.existsSync(path.join(root, relativePath))
+ )
+ }
+
+ // Keep the named function. The name is visible in debug logs via `DEBUG=connect:dispatcher ...`
+ return function viteHtmlFallbackMiddleware(req, _res, next) {
+ if (
+ // Only accept GET or HEAD
+ (req.method !== 'GET' && req.method !== 'HEAD') ||
+ // Exclude default favicon requests
+ req.url === '/favicon.ico' ||
+ // Require Accept: text/html or */*
+ !(
+ req.headers.accept === undefined || // equivalent to `Accept: */*`
+ req.headers.accept === '' || // equivalent to `Accept: */*`
+ req.headers.accept.includes('text/html') ||
+ req.headers.accept.includes('*/*')
+ )
+ ) {
+ return next()
+ }
+
+ const url = cleanUrl(req.url!)
+ let pathname
+ try {
+ pathname = decodeURIComponent(url)
+ } catch {
+ // ignore malformed URI
+ return next()
+ }
+
+ // .html files are not handled by serveStaticMiddleware
+ // so we need to check if the file exists
+ if (pathname.endsWith('.html')) {
+ if (checkFileExists(pathname)) {
+ debug?.(`Rewriting ${req.method} ${req.url} to ${url}`)
+ req.url = url
+ return next()
+ }
+ }
+ // trailing slash should check for fallback index.html
+ else if (pathname.endsWith('/')) {
+ if (checkFileExists(joinUrlSegments(pathname, 'index.html'))) {
+ const newUrl = url + 'index.html'
+ debug?.(`Rewriting ${req.method} ${req.url} to ${newUrl}`)
+ req.url = newUrl
+ return next()
+ }
+ }
+ // non-trailing slash should check for fallback .html
+ else {
+ if (checkFileExists(pathname + '.html')) {
+ const newUrl = url + '.html'
+ debug?.(`Rewriting ${req.method} ${req.url} to ${newUrl}`)
+ req.url = newUrl
+ return next()
+ }
+ }
+
+ if (spaFallback) {
+ debug?.(`Rewriting ${req.method} ${req.url} to /index.html`)
+ req.url = '/index.html'
+ }
+
+ next()
+ }
+}
diff --git a/packages/vite/src/node/server/middlewares/indexHtml.ts b/packages/vite/src/node/server/middlewares/indexHtml.ts
index 3c76d94c526930..0e9c48518cac45 100644
--- a/packages/vite/src/node/server/middlewares/indexHtml.ts
+++ b/packages/vite/src/node/server/middlewares/indexHtml.ts
@@ -1,35 +1,107 @@
-import fs from 'fs'
-import path from 'path'
+import fs from 'node:fs'
+import fsp from 'node:fs/promises'
+import path from 'node:path'
import MagicString from 'magic-string'
-import type { AttributeNode, ElementNode } from '@vue/compiler-dom'
-import { NodeTypes } from '@vue/compiler-dom'
-import type { Connect } from 'types/connect'
+import type { SourceMapInput } from 'rolldown'
+import type { DefaultTreeAdapterMap, Token } from 'parse5'
+import type { Connect } from '#dep-types/connect'
import type { IndexHtmlTransformHook } from '../../plugins/html'
import {
addToHTMLProxyCache,
applyHtmlTransforms,
- assetAttrsConfig,
+ extractImportExpressionFromClassicScript,
+ findNeedTransformStyleAttribute,
getScriptInfo,
+ htmlEnvHook,
+ htmlProxyResult,
+ injectCspNonceMetaTagHook,
+ injectNonceAttributeTagHook,
+ nodeIsElement,
+ overwriteAttrValue,
+ postImportMapHook,
+ preImportMapHook,
+ removeViteIgnoreAttr,
resolveHtmlTransforms,
- traverseHtml
+ traverseHtml,
} from '../../plugins/html'
-import type { ResolvedConfig, ViteDevServer } from '../..'
+import type { PreviewServer, ResolvedConfig, ViteDevServer } from '../..'
import { send } from '../send'
import { CLIENT_PUBLIC_PATH, FS_PREFIX } from '../../constants'
-import { cleanUrl, fsPathFromId, normalizePath, injectQuery } from '../../utils'
-import type { ModuleGraph } from '../moduleGraph'
+import {
+ ensureWatchedFile,
+ fsPathFromId,
+ getHash,
+ injectQuery,
+ isCSSRequest,
+ isDevServer,
+ isJSRequest,
+ isParentDirectory,
+ joinUrlSegments,
+ normalizePath,
+ processSrcSetSync,
+ stripBase,
+} from '../../utils'
+import { checkPublicFile } from '../../publicDir'
+import { getCodeWithSourcemap, injectSourcesContent } from '../sourcemap'
+import { cleanUrl, unwrapId, wrapId } from '../../../shared/utils'
+import { getNodeAssetAttributes } from '../../assetSource'
+import {
+ BasicMinimalPluginContext,
+ basePluginContextMeta,
+} from '../pluginContainer'
+import { FullBundleDevEnvironment } from '../environments/fullBundleEnvironment'
+import { getHmrImplementation } from '../../plugins/clientInjections'
+import { checkLoadingAccess, respondWithAccessDenied } from './static'
-export function createDevHtmlTransformFn(
- server: ViteDevServer
-): (url: string, html: string, originalUrl: string) => Promise {
- const [preHooks, postHooks] = resolveHtmlTransforms(server.config.plugins)
+interface AssetNode {
+ start: number
+ end: number
+ code: string
+}
+
+interface InlineStyleAttribute {
+ index: number
+ location: Token.Location
+ code: string
+}
- return (url: string, html: string, originalUrl: string): Promise => {
- return applyHtmlTransforms(html, [...preHooks, devHtmlHook, ...postHooks], {
+export function createDevHtmlTransformFn(
+ config: ResolvedConfig,
+): (
+ server: ViteDevServer,
+ url: string,
+ html: string,
+ originalUrl?: string,
+) => Promise {
+ const [preHooks, normalHooks, postHooks] = resolveHtmlTransforms(
+ config.plugins,
+ )
+ const transformHooks = [
+ preImportMapHook(config),
+ injectCspNonceMetaTagHook(config),
+ ...preHooks,
+ htmlEnvHook(config),
+ devHtmlHook,
+ ...normalHooks,
+ ...postHooks,
+ injectNonceAttributeTagHook(config),
+ postImportMapHook(),
+ ]
+ const pluginContext = new BasicMinimalPluginContext(
+ { ...basePluginContextMeta, watchMode: true },
+ config.logger,
+ )
+ return (
+ server: ViteDevServer,
+ url: string,
+ html: string,
+ originalUrl?: string,
+ ): Promise => {
+ return applyHtmlTransforms(html, transformHooks, pluginContext, {
path: url,
filename: getHtmlFilename(url, server),
server,
- originalUrl
+ originalUrl,
})
}
}
@@ -38,130 +110,318 @@ function getHtmlFilename(url: string, server: ViteDevServer) {
if (url.startsWith(FS_PREFIX)) {
return decodeURIComponent(fsPathFromId(url))
} else {
- return decodeURIComponent(path.join(server.config.root, url.slice(1)))
+ return decodeURIComponent(
+ normalizePath(path.join(server.config.root, url.slice(1))),
+ )
}
}
-const startsWithSingleSlashRE = /^\/(?!\/)/
+function shouldPreTransform(url: string, config: ResolvedConfig) {
+ return (
+ !checkPublicFile(url, config) && (isJSRequest(url) || isCSSRequest(url))
+ )
+}
+
+const wordCharRE = /\w/
+
+function isBareRelative(url: string) {
+ return wordCharRE.test(url[0]) && !url.includes(':')
+}
+
const processNodeUrl = (
- node: AttributeNode,
- s: MagicString,
+ url: string,
+ useSrcSetReplacer: boolean,
config: ResolvedConfig,
htmlPath: string,
originalUrl?: string,
- moduleGraph?: ModuleGraph
-) => {
- let url = node.value?.content || ''
+ server?: ViteDevServer,
+ isClassicScriptLink?: boolean,
+): string => {
+ // prefix with base (dev only, base is never relative)
+ const replacer = (url: string) => {
+ if (
+ (url[0] === '/' && url[1] !== '/') ||
+ // #3230 if some request url (localhost:3000/a/b) return to fallback html, the relative assets
+ // path will add `/a/` prefix, it will caused 404.
+ //
+ // skip if url contains `:` as it implies a url protocol or Windows path that we don't want to replace.
+ //
+ // rewrite `./index.js` -> `localhost:5173/a/index.js`.
+ // rewrite `../index.js` -> `localhost:5173/index.js`.
+ // rewrite `relative/index.js` -> `localhost:5173/a/relative/index.js`.
+ ((url[0] === '.' || isBareRelative(url)) &&
+ originalUrl &&
+ originalUrl !== '/' &&
+ htmlPath === '/index.html')
+ ) {
+ url = path.posix.join(config.base, url)
+ }
+
+ let preTransformUrl: string | undefined
- if (moduleGraph) {
- const mod = moduleGraph.urlToModuleMap.get(url)
- if (mod && mod.lastHMRTimestamp > 0) {
- url = injectQuery(url, `t=${mod.lastHMRTimestamp}`)
+ if (!isClassicScriptLink && shouldPreTransform(url, config)) {
+ if (url[0] === '/' && url[1] !== '/') {
+ preTransformUrl = url
+ } else if (url[0] === '.' || isBareRelative(url)) {
+ preTransformUrl = path.posix.join(
+ config.base,
+ path.posix.dirname(htmlPath),
+ url,
+ )
+ }
}
+
+ if (server) {
+ const mod = server.environments.client.moduleGraph.urlToModuleMap.get(
+ preTransformUrl || url,
+ )
+ if (mod && mod.lastHMRTimestamp > 0) {
+ url = injectQuery(url, `t=${mod.lastHMRTimestamp}`)
+ }
+ }
+
+ if (server && preTransformUrl) {
+ try {
+ preTransformUrl = decodeURI(preTransformUrl)
+ } catch {
+ // Malformed uri. Skip pre-transform.
+ return url
+ }
+ preTransformRequest(server, preTransformUrl, config.decodedBase)
+ }
+
+ return url
}
- if (startsWithSingleSlashRE.test(url)) {
- // prefix with base
- s.overwrite(
- node.value!.loc.start.offset,
- node.value!.loc.end.offset,
- `"${config.base + url.slice(1)}"`
- )
- } else if (
- url.startsWith('.') &&
- originalUrl &&
- originalUrl !== '/' &&
- htmlPath === '/index.html'
- ) {
- // #3230 if some request url (localhost:3000/a/b) return to fallback html, the relative assets
- // path will add `/a/` prefix, it will caused 404.
- // rewrite before `./index.js` -> `localhost:3000/a/index.js`.
- // rewrite after `../index.js` -> `localhost:3000/index.js`.
- s.overwrite(
- node.value!.loc.start.offset,
- node.value!.loc.end.offset,
- `"${path.posix.join(
- path.posix.relative(originalUrl, '/'),
- url.slice(1)
- )}"`
- )
- }
+
+ const processedUrl = useSrcSetReplacer
+ ? processSrcSetSync(url, ({ url }) => replacer(url))
+ : replacer(url)
+ return processedUrl
}
const devHtmlHook: IndexHtmlTransformHook = async (
html,
- { path: htmlPath, server, originalUrl }
+ { path: htmlPath, filename, server, originalUrl },
) => {
- const { config, moduleGraph } = server!
+ const { config, watcher } = server!
const base = config.base || '/'
+ const decodedBase = config.decodedBase || '/'
+
+ let proxyModulePath: string
+ let proxyModuleUrl: string
+
+ const trailingSlash = htmlPath.endsWith('/')
+ if (!trailingSlash && fs.existsSync(filename)) {
+ proxyModulePath = htmlPath
+ proxyModuleUrl = proxyModulePath
+ } else {
+ // There are users of vite.transformIndexHtml calling it with url '/'
+ // for SSR integrations #7993, filename is root for this case
+ // A user may also use a valid name for a virtual html file
+ // Mark the path as virtual in both cases so sourcemaps aren't processed
+ // and ids are properly handled
+ const validPath = `${htmlPath}${trailingSlash ? 'index.html' : ''}`
+ proxyModulePath = `\0${validPath}`
+ proxyModuleUrl = wrapId(proxyModulePath)
+ }
+ proxyModuleUrl = joinUrlSegments(decodedBase, proxyModuleUrl)
const s = new MagicString(html)
let inlineModuleIndex = -1
- const filePath = cleanUrl(htmlPath)
+ // The key to the proxyHtml cache is decoded, as it will be compared
+ // against decoded URLs by the HTML plugins.
+ const proxyCacheUrl = decodeURI(
+ cleanUrl(proxyModulePath).replace(normalizePath(config.root), ''),
+ )
+ const styleUrl: AssetNode[] = []
+ const inlineStyles: InlineStyleAttribute[] = []
+ const inlineModulePaths: string[] = []
- const addInlineModule = (node: ElementNode, ext: 'js' | 'css') => {
+ const addInlineModule = (
+ node: DefaultTreeAdapterMap['element'],
+ ext: 'js',
+ ) => {
inlineModuleIndex++
- const url = filePath.replace(normalizePath(config.root), '')
+ const contentNode = node.childNodes[0] as DefaultTreeAdapterMap['textNode']
- const contents = node.children
- .map((child: any) => child.content || '')
- .join('')
+ const code = contentNode.value
- // add HTML Proxy to Map
- addToHTMLProxyCache(config, url, inlineModuleIndex, contents)
+ let map: SourceMapInput | undefined
+ if (proxyModulePath[0] !== '\0') {
+ map = new MagicString(html)
+ .snip(
+ contentNode.sourceCodeLocation!.startOffset,
+ contentNode.sourceCodeLocation!.endOffset,
+ )
+ .generateMap({ hires: 'boundary' })
+ map.sources = [filename]
+ map.file = filename
+ }
- // inline js module. convert to src="proxy"
- const modulePath = `${
- config.base + htmlPath.slice(1)
- }?html-proxy&index=${inlineModuleIndex}.${ext}`
+ // add HTML Proxy to Map
+ addToHTMLProxyCache(config, proxyCacheUrl, inlineModuleIndex, { code, map })
- // invalidate the module so the newly cached contents will be served
- const module = server?.moduleGraph.getModuleById(modulePath)
- if (module) {
- server?.moduleGraph.invalidateModule(module)
- }
+ // inline js module. convert to src="proxy" (dev only, base is never relative)
+ const modulePath = `${proxyModuleUrl}?html-proxy&index=${inlineModuleIndex}.${ext}`
+ inlineModulePaths.push(modulePath)
- s.overwrite(
- node.loc.start.offset,
- node.loc.end.offset,
- ``
+ s.update(
+ node.sourceCodeLocation!.startOffset,
+ node.sourceCodeLocation!.endOffset,
+ ``,
)
+ preTransformRequest(server!, modulePath, decodedBase)
}
- await traverseHtml(html, htmlPath, (node) => {
- if (node.type !== NodeTypes.ELEMENT) {
+ await traverseHtml(html, filename, config.logger.warn, (node) => {
+ if (!nodeIsElement(node)) {
return
}
// script tags
- if (node.tag === 'script') {
- const { src, isModule } = getScriptInfo(node)
+ if (node.nodeName === 'script') {
+ const { src, srcSourceCodeLocation, isModule, isIgnored } =
+ getScriptInfo(node)
- if (src) {
- processNodeUrl(src, s, config, htmlPath, originalUrl, moduleGraph)
- } else if (isModule) {
+ if (isIgnored) {
+ removeViteIgnoreAttr(s, node.sourceCodeLocation!)
+ } else if (src) {
+ const processedUrl = processNodeUrl(
+ src.value,
+ /* useSrcSetReplacer */ false,
+ config,
+ htmlPath,
+ originalUrl,
+ server,
+ !isModule,
+ )
+ if (processedUrl !== src.value) {
+ overwriteAttrValue(s, srcSourceCodeLocation!, processedUrl)
+ }
+ } else if (isModule && node.childNodes.length) {
addInlineModule(node, 'js')
+ } else if (node.childNodes.length) {
+ const scriptNode = node.childNodes[
+ node.childNodes.length - 1
+ ] as DefaultTreeAdapterMap['textNode']
+ for (const {
+ url,
+ start,
+ end,
+ } of extractImportExpressionFromClassicScript(scriptNode)) {
+ const processedUrl = processNodeUrl(
+ url,
+ false,
+ config,
+ htmlPath,
+ originalUrl,
+ )
+ if (processedUrl !== url) {
+ s.update(start, end, processedUrl)
+ }
+ }
}
}
- if (node.tag === 'style' && node.children.length) {
- addInlineModule(node, 'css')
+ const inlineStyle = findNeedTransformStyleAttribute(node)
+ if (inlineStyle) {
+ inlineModuleIndex++
+ inlineStyles.push({
+ index: inlineModuleIndex,
+ location: inlineStyle.location!,
+ code: inlineStyle.attr.value,
+ })
+ }
+
+ if (node.nodeName === 'style' && node.childNodes.length) {
+ const children = node.childNodes[0] as DefaultTreeAdapterMap['textNode']
+ styleUrl.push({
+ start: children.sourceCodeLocation!.startOffset,
+ end: children.sourceCodeLocation!.endOffset,
+ code: children.value,
+ })
}
// elements with [href/src] attrs
- const assetAttrs = assetAttrsConfig[node.tag]
- if (assetAttrs) {
- for (const p of node.props) {
- if (
- p.type === NodeTypes.ATTRIBUTE &&
- p.value &&
- assetAttrs.includes(p.name)
- ) {
- processNodeUrl(p, s, config, htmlPath, originalUrl)
+ const assetAttributes = getNodeAssetAttributes(node)
+ for (const attr of assetAttributes) {
+ if (attr.type === 'remove') {
+ s.remove(attr.location.startOffset, attr.location.endOffset)
+ } else {
+ const processedUrl = processNodeUrl(
+ attr.value,
+ attr.type === 'srcset',
+ config,
+ htmlPath,
+ originalUrl,
+ )
+ if (processedUrl !== attr.value) {
+ overwriteAttrValue(s, attr.location, processedUrl)
}
}
}
})
+ // invalidate the module so the newly cached contents will be served
+ const clientModuleGraph = server?.environments.client.moduleGraph
+ if (clientModuleGraph) {
+ await Promise.all(
+ inlineModulePaths.map(async (url) => {
+ const module = await clientModuleGraph.getModuleByUrl(url)
+ if (module) {
+ clientModuleGraph.invalidateModule(module)
+ }
+ }),
+ )
+ }
+
+ await Promise.all([
+ ...styleUrl.map(async ({ start, end, code }, index) => {
+ const url = `${proxyModulePath}?html-proxy&direct&index=${index}.css`
+
+ // ensure module in graph after successful load
+ const mod =
+ await server!.environments.client.moduleGraph.ensureEntryFromUrl(
+ url,
+ false,
+ )
+ ensureWatchedFile(watcher, mod.file, config.root)
+
+ const result =
+ await server!.environments.client.pluginContainer.transform(
+ code,
+ mod.id!,
+ )
+ let content = ''
+ if (result.map && 'version' in result.map) {
+ if (result.map.mappings) {
+ await injectSourcesContent(result.map, proxyModulePath, config.logger)
+ }
+ content = getCodeWithSourcemap('css', result.code, result.map)
+ } else {
+ content = result.code
+ }
+ s.overwrite(start, end, content)
+ }),
+ ...inlineStyles.map(async ({ index, location, code }) => {
+ // will transform with css plugin and cache result with css-post plugin
+ const url = `${proxyModulePath}?html-proxy&inline-css&style-attr&index=${index}.css`
+
+ const mod =
+ await server!.environments.client.moduleGraph.ensureEntryFromUrl(
+ url,
+ false,
+ )
+ ensureWatchedFile(watcher, mod.file, config.root)
+
+ await server?.environments.client.pluginContainer.transform(code, mod.id!)
+
+ const hash = getHash(cleanUrl(mod.id!))
+ const result = htmlProxyResult.get(`${hash}_${index}`)
+ overwriteAttrValue(s, location, result ?? '')
+ }),
+ ])
+
html = s.toString()
return {
@@ -171,17 +431,24 @@ const devHtmlHook: IndexHtmlTransformHook = async (
tag: 'script',
attrs: {
type: 'module',
- src: path.posix.join(base, CLIENT_PUBLIC_PATH)
+ src: path.posix.join(base, CLIENT_PUBLIC_PATH),
},
- injectTo: 'head-prepend'
- }
- ]
+ injectTo: 'head-prepend',
+ },
+ ],
}
}
export function indexHtmlMiddleware(
- server: ViteDevServer
+ root: string,
+ server: ViteDevServer | PreviewServer,
): Connect.NextHandleFunction {
+ const isDev = isDevServer(server)
+ const fullBundleEnv =
+ isDev && server.environments.client instanceof FullBundleDevEnvironment
+ ? server.environments.client
+ : undefined
+
// Keep the named function. The name is visible in debug logs via `DEBUG=connect:dispatcher ...`
return async function viteIndexHtmlMiddleware(req, res, next) {
if (res.writableEnded) {
@@ -189,16 +456,82 @@ export function indexHtmlMiddleware(
}
const url = req.url && cleanUrl(req.url)
- // spa-fallback always redirects to /index.html
+ // htmlFallbackMiddleware appends '.html' to URLs
if (url?.endsWith('.html') && req.headers['sec-fetch-dest'] !== 'script') {
- const filename = getHtmlFilename(url, server)
- if (fs.existsSync(filename)) {
+ if (fullBundleEnv) {
+ const pathname = decodeURIComponent(url)
+ const filePath = pathname.slice(1) // remove first /
+
+ let file = fullBundleEnv.memoryFiles.get(filePath)
+ if (!file && fullBundleEnv.memoryFiles.size !== 0) {
+ return next()
+ }
+ const secFetchDest = req.headers['sec-fetch-dest']
+ if (
+ [
+ 'document',
+ 'iframe',
+ 'frame',
+ 'fencedframe',
+ '',
+ undefined,
+ ].includes(secFetchDest) &&
+ ((await fullBundleEnv.triggerBundleRegenerationIfStale()) ||
+ file === undefined)
+ ) {
+ file = { source: await generateFallbackHtml(server as ViteDevServer) }
+ }
+ if (!file) {
+ return next()
+ }
+
+ const html =
+ typeof file.source === 'string'
+ ? file.source
+ : Buffer.from(file.source)
+ const headers = isDev
+ ? server.config.server.headers
+ : server.config.preview.headers
+ return send(req, res, html, 'html', { headers, etag: file.etag })
+ }
+
+ let filePath: string
+ if (isDev && url.startsWith(FS_PREFIX)) {
+ filePath = decodeURIComponent(fsPathFromId(url))
+ } else {
+ filePath = normalizePath(
+ path.resolve(path.join(root, decodeURIComponent(url))),
+ )
+ }
+
+ if (isDev) {
+ const servingAccessResult = checkLoadingAccess(server.config, filePath)
+ if (servingAccessResult === 'denied') {
+ return respondWithAccessDenied(filePath, server, res)
+ }
+ if (servingAccessResult === 'fallback') {
+ return next()
+ }
+ servingAccessResult satisfies 'allowed'
+ } else {
+ // `server.fs` options does not apply to the preview server.
+ // But we should disallow serving files outside the output directory.
+ if (!isParentDirectory(root, filePath)) {
+ return next()
+ }
+ }
+
+ if (fs.existsSync(filePath)) {
+ const headers = isDev
+ ? server.config.server.headers
+ : server.config.preview.headers
+
try {
- let html = fs.readFileSync(filename, 'utf-8')
- html = await server.transformIndexHtml(url, html, req.originalUrl)
- return send(req, res, html, 'html', {
- headers: server.config.server.headers
- })
+ let html = await fsp.readFile(filePath, 'utf-8')
+ if (isDev) {
+ html = await server.transformIndexHtml(url, html, req.originalUrl)
+ }
+ return send(req, res, html, 'html', { headers })
} catch (e) {
return next(e)
}
@@ -207,3 +540,80 @@ export function indexHtmlMiddleware(
next()
}
}
+
+// NOTE: We usually don't prefix `url` and `base` with `decoded`, but in this file particularly
+// we're dealing with mixed encoded/decoded paths often, so we make this explicit for now.
+function preTransformRequest(
+ server: ViteDevServer,
+ decodedUrl: string,
+ decodedBase: string,
+) {
+ if (!server.config.server.preTransformRequests) return
+
+ // transform all url as non-ssr as html includes client-side assets only
+ decodedUrl = unwrapId(stripBase(decodedUrl, decodedBase))
+ server.warmupRequest(decodedUrl)
+}
+
+async function generateFallbackHtml(server: ViteDevServer) {
+ const hmrRuntime = await getHmrImplementation(server.config)
+ return /* html */ `
+
+
+
+ ', '<\\/script>')}
+
+
+
+
+
+
Bundling in progress
+
The page will automatically reload when ready.
+
+
+
+
+`
+}
diff --git a/packages/vite/src/node/server/middlewares/memoryFiles.ts b/packages/vite/src/node/server/middlewares/memoryFiles.ts
new file mode 100644
index 00000000000000..152989eb031cb7
--- /dev/null
+++ b/packages/vite/src/node/server/middlewares/memoryFiles.ts
@@ -0,0 +1,52 @@
+import * as mrmime from 'mrmime'
+import type { Connect } from '#dep-types/connect'
+import { cleanUrl } from '../../../shared/utils'
+import type { ViteDevServer } from '..'
+import { FullBundleDevEnvironment } from '../environments/fullBundleEnvironment'
+
+export function memoryFilesMiddleware(
+ server: ViteDevServer,
+): Connect.NextHandleFunction {
+ const memoryFiles =
+ server.environments.client instanceof FullBundleDevEnvironment
+ ? server.environments.client.memoryFiles
+ : undefined
+ if (!memoryFiles) {
+ throw new Error('memoryFilesMiddleware can only be used for fullBundleMode')
+ }
+ const headers = server.config.server.headers
+
+ return function viteMemoryFilesMiddleware(req, res, next) {
+ const cleanedUrl = cleanUrl(req.url!)
+ if (cleanedUrl.endsWith('.html')) {
+ return next()
+ }
+
+ const pathname = decodeURIComponent(cleanedUrl)
+ const filePath = pathname.slice(1) // remove first /
+
+ const file = memoryFiles.get(filePath)
+ if (file) {
+ if (file.etag) {
+ if (req.headers['if-none-match'] === file.etag) {
+ res.statusCode = 304
+ res.end()
+ return
+ }
+ res.setHeader('Etag', file.etag)
+ }
+
+ const mime = mrmime.lookup(filePath)
+ if (mime) {
+ res.setHeader('Content-Type', mime)
+ }
+
+ for (const name in headers) {
+ res.setHeader(name, headers[name]!)
+ }
+
+ return res.end(file.source)
+ }
+ next()
+ }
+}
diff --git a/packages/vite/src/node/server/middlewares/notFound.ts b/packages/vite/src/node/server/middlewares/notFound.ts
new file mode 100644
index 00000000000000..38bf93b4a6df17
--- /dev/null
+++ b/packages/vite/src/node/server/middlewares/notFound.ts
@@ -0,0 +1,9 @@
+import type { Connect } from '#dep-types/connect'
+
+export function notFoundMiddleware(): Connect.NextHandleFunction {
+ // Keep the named function. The name is visible in debug logs via `DEBUG=connect:dispatcher ...`
+ return function vite404Middleware(_, res) {
+ res.statusCode = 404
+ res.end()
+ }
+}
diff --git a/packages/vite/src/node/server/middlewares/proxy.ts b/packages/vite/src/node/server/middlewares/proxy.ts
index aa1100f13d5229..7c6d2b26fbd4b0 100644
--- a/packages/vite/src/node/server/middlewares/proxy.ts
+++ b/packages/vite/src/node/server/middlewares/proxy.ts
@@ -1,15 +1,14 @@
-import type * as http from 'http'
-import { createDebugger, isObject } from '../../utils'
-import httpProxy from 'http-proxy'
-import { HMR_HEADER } from '../ws'
-import type { Connect } from 'types/connect'
-import type { HttpProxy } from 'types/http-proxy'
+import type * as http from 'node:http'
+import * as httpProxy from 'http-proxy-3'
import colors from 'picocolors'
-import type { ResolvedConfig } from '../..'
+import type { Connect } from '#dep-types/connect'
+import { createDebugger } from '../../utils'
+import type { CommonServerOptions, ResolvedConfig } from '../..'
+import type { HttpServer } from '..'
const debug = createDebugger('vite:proxy')
-export interface ProxyOptions extends HttpProxy.ServerOptions {
+export interface ProxyOptions extends httpProxy.ServerOptions {
/**
* rewrite path
*/
@@ -17,61 +16,168 @@ export interface ProxyOptions extends HttpProxy.ServerOptions {
/**
* configure the proxy server (e.g. listen to events)
*/
- configure?: (proxy: HttpProxy.Server, options: ProxyOptions) => void
+ configure?: (proxy: httpProxy.ProxyServer, options: ProxyOptions) => void
/**
* webpack-dev-server style bypass function
*/
bypass?: (
req: http.IncomingMessage,
- res: http.ServerResponse,
- options: ProxyOptions
- ) => void | null | undefined | false | string
+ /** undefined for WebSocket upgrade requests */
+ res: http.ServerResponse | undefined,
+ options: ProxyOptions,
+ ) =>
+ | void
+ | null
+ | undefined
+ | false
+ | string
+ | Promise
+ /**
+ * rewrite the Origin header of a WebSocket request to match the target
+ *
+ * **Exercise caution as rewriting the Origin can leave the proxying open to [CSRF attacks](https://owasp.org/www-community/attacks/csrf).**
+ */
+ rewriteWsOrigin?: boolean | undefined
+}
+
+const rewriteOriginHeader = (
+ proxyReq: http.ClientRequest,
+ options: ProxyOptions,
+ config: ResolvedConfig,
+) => {
+ // Browsers may send Origin headers even with same-origin
+ // requests. It is common for WebSocket servers to check the Origin
+ // header, so if rewriteWsOrigin is true we change the Origin to match
+ // the target URL.
+ if (options.rewriteWsOrigin) {
+ const { target } = options
+
+ if (proxyReq.headersSent) {
+ config.logger.warn(
+ colors.yellow(
+ `Unable to rewrite Origin header as headers are already sent.`,
+ ),
+ )
+ return
+ }
+
+ if (proxyReq.getHeader('origin') && target) {
+ const changedOrigin =
+ typeof target === 'object'
+ ? `${target.protocol ?? 'http:'}//${target.host}`
+ : target
+
+ proxyReq.setHeader('origin', changedOrigin)
+ }
+ }
}
export function proxyMiddleware(
- httpServer: http.Server | null,
- config: ResolvedConfig
+ httpServer: HttpServer | null,
+ options: NonNullable,
+ config: ResolvedConfig,
): Connect.NextHandleFunction {
- const options = config.server.proxy!
-
// lazy require only when proxy is used
- const proxies: Record = {}
+ const proxies: Record = {}
Object.keys(options).forEach((context) => {
let opts = options[context]
+ if (!opts) {
+ return
+ }
if (typeof opts === 'string') {
- opts = { target: opts, changeOrigin: true } as ProxyOptions
+ opts = { target: opts, changeOrigin: true }
}
- const proxy = httpProxy.createProxyServer(opts) as HttpProxy.Server
-
- proxy.on('error', (err) => {
- config.logger.error(`${colors.red(`http proxy error:`)}\n${err.stack}`, {
- timestamp: true,
- error: err
- })
- })
+ const proxy = httpProxy.createProxyServer(opts)
if (opts.configure) {
opts.configure(proxy, opts)
}
+
+ proxy.on('error', (err, _req, res) => {
+ // When it is ws proxy, res is net.Socket
+ if ('req' in res) {
+ config.logger.error(
+ `${colors.red(`http proxy error: ${res.req.url}`)}\n${err.stack}`,
+ {
+ timestamp: true,
+ error: err,
+ },
+ )
+ if (!res.headersSent && !res.writableEnded) {
+ res
+ .writeHead(500, {
+ 'Content-Type': 'text/plain',
+ })
+ .end()
+ }
+ } else {
+ config.logger.error(`${colors.red(`ws proxy error:`)}\n${err.stack}`, {
+ timestamp: true,
+ error: err,
+ })
+ res.end()
+ }
+ })
+
+ proxy.on('proxyReqWs', (proxyReq, _req, socket, options) => {
+ rewriteOriginHeader(proxyReq, options, config)
+
+ socket.on('error', (err) => {
+ config.logger.error(
+ `${colors.red(`ws proxy socket error:`)}\n${err.stack}`,
+ {
+ timestamp: true,
+ error: err,
+ },
+ )
+ })
+ })
+
// clone before saving because http-proxy mutates the options
proxies[context] = [proxy, { ...opts }]
})
if (httpServer) {
- httpServer.on('upgrade', (req, socket, head) => {
+ httpServer.on('upgrade', async (req, socket, head) => {
const url = req.url!
for (const context in proxies) {
if (doesProxyContextMatchUrl(context, url)) {
const [proxy, opts] = proxies[context]
if (
- (opts.ws || opts.target?.toString().startsWith('ws:')) &&
- req.headers['sec-websocket-protocol'] !== HMR_HEADER
+ opts.ws ||
+ opts.target?.toString().startsWith('ws:') ||
+ opts.target?.toString().startsWith('wss:')
) {
+ if (opts.bypass) {
+ try {
+ const bypassResult = await opts.bypass(req, undefined, opts)
+ if (typeof bypassResult === 'string') {
+ debug?.(`bypass: ${req.url} -> ${bypassResult}`)
+ req.url = bypassResult
+ return
+ }
+ if (bypassResult === false) {
+ debug?.(`bypass: ${req.url} -> 404`)
+ socket.end('HTTP/1.1 404 Not Found\r\n\r\n', '')
+ return
+ }
+ } catch (err) {
+ config.logger.error(
+ `${colors.red(`ws proxy bypass error:`)}\n${err.stack}`,
+ {
+ timestamp: true,
+ error: err,
+ },
+ )
+ return
+ }
+ }
+
if (opts.rewrite) {
req.url = opts.rewrite(url)
}
- debug(`${req.url} -> ws ${opts.target}`)
+ debug?.(`${req.url} -> ws ${opts.target}`)
proxy.ws(req, socket, head)
return
}
@@ -81,30 +187,36 @@ export function proxyMiddleware(
}
// Keep the named function. The name is visible in debug logs via `DEBUG=connect:dispatcher ...`
- return function viteProxyMiddleware(req, res, next) {
+ return async function viteProxyMiddleware(req, res, next) {
const url = req.url!
for (const context in proxies) {
if (doesProxyContextMatchUrl(context, url)) {
const [proxy, opts] = proxies[context]
- const options: HttpProxy.ServerOptions = {}
+ const options: httpProxy.ServerOptions = {}
if (opts.bypass) {
- const bypassResult = opts.bypass(req, res, opts)
- if (typeof bypassResult === 'string') {
- req.url = bypassResult
- debug(`bypass: ${req.url} -> ${bypassResult}`)
- return next()
- } else if (isObject(bypassResult)) {
- Object.assign(options, bypassResult)
- debug(`bypass: ${req.url} use modified options: %O`, options)
- return next()
- } else if (bypassResult === false) {
- debug(`bypass: ${req.url} -> 404`)
- return res.end(404)
+ try {
+ const bypassResult = await opts.bypass(req, res, opts)
+ if (typeof bypassResult === 'string') {
+ debug?.(`bypass: ${req.url} -> ${bypassResult}`)
+ req.url = bypassResult
+ if (res.writableEnded) {
+ return
+ }
+ return next()
+ }
+ if (bypassResult === false) {
+ debug?.(`bypass: ${req.url} -> 404`)
+ res.statusCode = 404
+ return res.end()
+ }
+ } catch (e) {
+ debug?.(`bypass: ${req.url} -> ${e}`)
+ return next(e)
}
}
- debug(`${req.url} -> ${opts.target || opts.forward}`)
+ debug?.(`${req.url} -> ${opts.target || opts.forward}`)
if (opts.rewrite) {
req.url = opts.rewrite(req.url!)
}
@@ -118,7 +230,7 @@ export function proxyMiddleware(
function doesProxyContextMatchUrl(context: string, url: string): boolean {
return (
- (context.startsWith('^') && new RegExp(context).test(url)) ||
+ (context[0] === '^' && new RegExp(context).test(url)) ||
url.startsWith(context)
)
}
diff --git a/packages/vite/src/node/server/middlewares/rejectInvalidRequest.ts b/packages/vite/src/node/server/middlewares/rejectInvalidRequest.ts
new file mode 100644
index 00000000000000..fba9dbfc1d5adf
--- /dev/null
+++ b/packages/vite/src/node/server/middlewares/rejectInvalidRequest.ts
@@ -0,0 +1,23 @@
+import type { Connect } from '#dep-types/connect'
+
+/**
+ * disallows request that contains `#` in the URL
+ */
+export function rejectInvalidRequestMiddleware(): Connect.NextHandleFunction {
+ // Keep the named function. The name is visible in debug logs via `DEBUG=connect:dispatcher ...`
+ return function viteRejectInvalidRequestMiddleware(req, res, next) {
+ // HTTP spec does not allow `#` in the request-target
+ // (HTTP 1.1: https://datatracker.ietf.org/doc/html/rfc9112#section-3.2)
+ // (HTTP 2: https://datatracker.ietf.org/doc/html/rfc9113#section-8.3.1-2.4.1)
+ // But Node.js allows those requests.
+ // Our middlewares don't expect `#` to be included in `req.url`, especially the `server.fs.deny` checks.
+ if (req.url?.includes('#')) {
+ // HTTP 1.1 spec recommends sending 400 Bad Request
+ // (https://datatracker.ietf.org/doc/html/rfc9112#section-3.2-4)
+ res.writeHead(400)
+ res.end()
+ return
+ }
+ return next()
+ }
+}
diff --git a/packages/vite/src/node/server/middlewares/rejectNoCorsRequest.ts b/packages/vite/src/node/server/middlewares/rejectNoCorsRequest.ts
new file mode 100644
index 00000000000000..ebc025aca37cc4
--- /dev/null
+++ b/packages/vite/src/node/server/middlewares/rejectNoCorsRequest.ts
@@ -0,0 +1,36 @@
+import type { Connect } from '#dep-types/connect'
+
+/**
+ * A middleware that rejects no-cors mode requests that are not same-origin.
+ *
+ * We should avoid untrusted sites to load the script to avoid attacks like GHSA-4v9v-hfq4-rm2v.
+ * This is because:
+ * - the path of HMR patch files / entry point files can be predictable
+ * - the HMR patch files may not include ESM syntax
+ * (if they include ESM syntax, loading as a classic script would fail)
+ * - the HMR runtime in the browser has the list of all loaded modules
+ *
+ * https://github.com/webpack/webpack-dev-server/security/advisories/GHSA-4v9v-hfq4-rm2v
+ * https://green.sapphi.red/blog/local-server-security-best-practices#_2-using-xssi-and-modifying-the-prototype
+ * https://green.sapphi.red/blog/local-server-security-best-practices#properly-check-the-request-origin
+ */
+export function rejectNoCorsRequestMiddleware(): Connect.NextHandleFunction {
+ // Keep the named function. The name is visible in debug logs via `DEBUG=connect:dispatcher ...`
+ return function viteRejectNoCorsRequestMiddleware(req, res, next) {
+ // While we can set Cross-Origin-Resource-Policy header instead of rejecting requests,
+ // we choose to reject the request to be safer in case the request handler has any side-effects.
+ if (
+ req.headers['sec-fetch-mode'] === 'no-cors' &&
+ req.headers['sec-fetch-site'] !== 'same-origin' &&
+ // we only need to block classic script requests
+ req.headers['sec-fetch-dest'] === 'script'
+ ) {
+ res.statusCode = 403
+ res.end(
+ 'Cross-origin requests for classic scripts must be made with CORS mode enabled. Make sure to set the "crossorigin" attribute on your
+
+
+
diff --git a/playground/alias/package.json b/playground/alias/package.json
new file mode 100644
index 00000000000000..828b8cb73695aa
--- /dev/null
+++ b/playground/alias/package.json
@@ -0,0 +1,20 @@
+{
+ "name": "@vitejs/test-alias",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build",
+ "debug": "node --inspect-brk ../../packages/vite/bin/vite",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "aliased-module": "file:./dir/module",
+ "vue": "^3.5.27",
+ "@vue/shared": "^3.5.27"
+ },
+ "devDependencies": {
+ "@vitejs/test-resolve-linked": "workspace:*"
+ }
+}
diff --git a/packages/playground/alias/test.js b/playground/alias/test.js
similarity index 100%
rename from packages/playground/alias/test.js
rename to playground/alias/test.js
diff --git a/playground/alias/vite.config.js b/playground/alias/vite.config.js
new file mode 100644
index 00000000000000..56f128c2a3d57e
--- /dev/null
+++ b/playground/alias/vite.config.js
@@ -0,0 +1,46 @@
+import path from 'node:path'
+import module from 'node:module'
+import { defineConfig } from 'vite'
+
+const require = module.createRequire(import.meta.url)
+
+export default defineConfig({
+ resolve: {
+ alias: [
+ { find: 'fs', replacement: path.resolve(import.meta.dirname, 'test.js') },
+ { find: 'fs-dir', replacement: path.resolve(import.meta.dirname, 'dir') },
+ { find: 'dep', replacement: '@vitejs/test-resolve-linked' },
+ {
+ find: /^regex\/(.*)/,
+ replacement: `${path.resolve(import.meta.dirname, 'dir')}/$1`,
+ },
+ { find: '/@', replacement: path.resolve(import.meta.dirname, 'dir') },
+ // aliasing a pattern that conflicts with url schemes
+ { find: /^\/\//, replacement: path.join(import.meta.dirname, 'dir/') },
+ // aliasing an optimized dep
+ { find: 'vue', replacement: 'vue/dist/vue.esm-bundler.js' },
+ // aliasing an optimized dep to absolute URL
+ {
+ find: '@vue/shared',
+ replacement: require.resolve('@vue/shared/dist/shared.cjs.prod.js'),
+ },
+ // aliasing one unoptimized dep to an optimized dep
+ { find: 'foo', replacement: 'vue' },
+ {
+ find: 'custom-resolver',
+ replacement: path.resolve(import.meta.dirname, 'test.js'),
+ customResolver(id) {
+ return id.replace('test.js', 'customResolver.js')
+ },
+ },
+ ],
+ },
+ build: {
+ minify: false,
+ },
+ define: {
+ __VUE_OPTIONS_API__: true,
+ __VUE_PROD_DEVTOOLS__: true,
+ __VUE_PROD_HYDRATION_MISMATCH_DETAILS__: false,
+ },
+})
diff --git a/playground/assets-sanitize/+circle.svg b/playground/assets-sanitize/+circle.svg
new file mode 100644
index 00000000000000..81ff39ee185e2e
--- /dev/null
+++ b/playground/assets-sanitize/+circle.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/packages/playground/fs-serve/root/src/.env b/playground/assets-sanitize/.env
similarity index 100%
rename from packages/playground/fs-serve/root/src/.env
rename to playground/assets-sanitize/.env
diff --git a/playground/assets-sanitize/__tests__/assets-sanitize.spec.ts b/playground/assets-sanitize/__tests__/assets-sanitize.spec.ts
new file mode 100644
index 00000000000000..a64292c7d7d78b
--- /dev/null
+++ b/playground/assets-sanitize/__tests__/assets-sanitize.spec.ts
@@ -0,0 +1,32 @@
+import { expect, test } from 'vitest'
+import { getBg, isBuild, page, readManifest } from '~utils'
+
+if (!isBuild) {
+ test('importing asset with special char in filename works in dev', async () => {
+ expect(await getBg('.plus-circle')).toContain('+circle.svg')
+ expect(await page.textContent('.plus-circle')).toMatch('+circle.svg')
+ expect(await getBg('.underscore-circle')).toContain('_circle.svg')
+ expect(await page.textContent('.underscore-circle')).toMatch('_circle.svg')
+ })
+} else {
+ test('importing asset with special char in filename works in build', async () => {
+ const manifest = readManifest()
+ const plusCircleAsset = manifest['+circle.svg'].file
+ const underscoreCircleAsset = manifest['_circle.svg'].file
+ expect(await getBg('.plus-circle')).toMatch(plusCircleAsset)
+ expect(await page.textContent('.plus-circle')).toMatch(plusCircleAsset)
+ expect(await getBg('.underscore-circle')).toMatch(underscoreCircleAsset)
+ expect(await page.textContent('.underscore-circle')).toMatch(
+ underscoreCircleAsset,
+ )
+ expect(plusCircleAsset).toMatch('/_circle')
+ expect(underscoreCircleAsset).toMatch('/_circle')
+ expect(plusCircleAsset).not.toEqual(underscoreCircleAsset)
+ expect(Object.keys(manifest).length).toBe(3) // 2 svg, 1 index.js
+ })
+}
+
+test.runIf(!isBuild)('denied .env', async () => {
+ expect(await page.textContent('.unsafe-dotenv')).toBe('403')
+ expect(await page.textContent('.unsafe-dotenv-double-slash')).toBe('200') // SPA fallback
+})
diff --git a/playground/assets-sanitize/_circle.svg b/playground/assets-sanitize/_circle.svg
new file mode 100644
index 00000000000000..f8e310c6148d42
--- /dev/null
+++ b/playground/assets-sanitize/_circle.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/playground/assets-sanitize/index.html b/playground/assets-sanitize/index.html
new file mode 100644
index 00000000000000..0e634695c584fb
--- /dev/null
+++ b/playground/assets-sanitize/index.html
@@ -0,0 +1,40 @@
+
+
+test elements below should show circles and their url
+
+
+
+Denied .env
+
+
+
+
diff --git a/playground/assets-sanitize/index.js b/playground/assets-sanitize/index.js
new file mode 100644
index 00000000000000..bac3b3b83e6b1d
--- /dev/null
+++ b/playground/assets-sanitize/index.js
@@ -0,0 +1,9 @@
+import plusCircle from './+circle.svg'
+import underscoreCircle from './_circle.svg'
+function setData(classname, file) {
+ const el = document.body.querySelector(classname)
+ el.style.backgroundImage = `url(${file})`
+ el.textContent = file
+}
+setData('.plus-circle', plusCircle)
+setData('.underscore-circle', underscoreCircle)
diff --git a/playground/assets-sanitize/package.json b/playground/assets-sanitize/package.json
new file mode 100644
index 00000000000000..1a110936d06db9
--- /dev/null
+++ b/playground/assets-sanitize/package.json
@@ -0,0 +1,12 @@
+{
+ "name": "@vitejs/test-assets-sanitize",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build",
+ "debug": "node --inspect-brk ../../packages/vite/bin/vite",
+ "preview": "vite preview"
+ }
+}
diff --git a/playground/assets-sanitize/vite.config.js b/playground/assets-sanitize/vite.config.js
new file mode 100644
index 00000000000000..7eeb717a97b6b5
--- /dev/null
+++ b/playground/assets-sanitize/vite.config.js
@@ -0,0 +1,11 @@
+import { defineConfig } from 'vite'
+
+export default defineConfig({
+ build: {
+ //speed up build
+ minify: false,
+ target: 'esnext',
+ assetsInlineLimit: 0,
+ manifest: true,
+ },
+})
diff --git a/playground/assets/__tests__/assets.spec.ts b/playground/assets/__tests__/assets.spec.ts
new file mode 100644
index 00000000000000..8c8cd4ffe74f3d
--- /dev/null
+++ b/playground/assets/__tests__/assets.spec.ts
@@ -0,0 +1,777 @@
+import path from 'node:path'
+import { describe, expect, test } from 'vitest'
+import {
+ browserLogs,
+ editFile,
+ findAssetFile,
+ getBg,
+ getColor,
+ isBuild,
+ isServe,
+ listAssets,
+ notifyRebuildComplete,
+ page,
+ readFile,
+ readManifest,
+ serverLogs,
+ viteTestUrl,
+ watcher,
+} from '~utils'
+
+const assetMatch = isBuild
+ ? /\/foo\/bar\/assets\/asset-[-\w]{8}\.png/
+ : '/foo/bar/nested/asset.png'
+
+const encodedAssetMatch = isBuild
+ ? /\/foo\/bar\/assets\/asset_small_-[-\w]{8}\.png/
+ : '/foo/bar/nested/asset[small].png'
+
+const iconMatch = `/foo/bar/icon.png`
+
+const fetchPath = (p: string) => {
+ return fetch(path.posix.join(viteTestUrl, p), {
+ headers: { Accept: 'text/html,*/*' },
+ })
+}
+
+test('should have no 404s', () => {
+ browserLogs.forEach((msg) => {
+ expect(msg).not.toMatch('404')
+ })
+})
+
+test.runIf(isBuild)(
+ 'should not warn about VITE_ASSET tokens in image-set',
+ async () => {
+ expect(serverLogs).toStrictEqual(
+ expect.not.arrayContaining([
+ expect.stringMatching(/VITE_ASSET__.*?didn't resolve at build time/),
+ ]),
+ )
+ },
+)
+
+test('should get a 404 when using incorrect case', async () => {
+ expect((await fetchPath('icon.png')).headers.get('Content-Type')).toBe(
+ 'image/png',
+ )
+ // fallback to index.html
+ const iconPngResult = await fetchPath('ICON.png')
+ expect(iconPngResult.headers.get('Content-Type')).toBe('text/html')
+ expect(iconPngResult.status).toBe(200)
+
+ expect((await fetchPath('bar')).headers.get('Content-Type')).toBe('')
+ // fallback to index.html
+ const barResult = await fetchPath('BAR')
+ expect(barResult.headers.get('Content-Type')).toContain('text/html')
+ expect(barResult.status).toBe(200)
+})
+
+test('should fallback to index.html when accessing non-existent html file', async () => {
+ expect((await fetchPath('doesnt-exist.html')).status).toBe(200)
+})
+
+describe.runIf(isServe)('outside base', () => {
+ test('should get a 404 with html', async () => {
+ const res = await fetch(new URL('/baz', viteTestUrl), {
+ headers: { Accept: 'text/html,*/*' },
+ })
+ expect(res.status).toBe(404)
+ expect(res.headers.get('Content-Type')).toBe('text/html')
+ })
+
+ test('should get a 404 with text', async () => {
+ const res = await fetch(new URL('/baz', viteTestUrl))
+ expect(res.status).toBe(404)
+ expect(res.headers.get('Content-Type')).toBe('text/plain')
+ })
+})
+
+describe('injected scripts', () => {
+ test('@vite/client', async () => {
+ const hasClient = await page.$(
+ 'script[type="module"][src="/foo/bar/@vite/client"]',
+ )
+ if (isBuild) {
+ expect(hasClient).toBeFalsy()
+ } else {
+ expect(hasClient).toBeTruthy()
+ }
+ })
+
+ test('html-proxy', async () => {
+ const hasHtmlProxy = await page.$(
+ 'script[type="module"][src^="/foo/bar/index.html?html-proxy"]',
+ )
+ if (isBuild) {
+ expect(hasHtmlProxy).toBeFalsy()
+ } else {
+ expect(hasHtmlProxy).toBeTruthy()
+ }
+ })
+})
+
+describe('raw references from /public', () => {
+ test('load raw js from /public', async () => {
+ expect(await page.textContent('.raw-js')).toMatch('[success]')
+ })
+
+ test('load raw css from /public', async () => {
+ expect(await getColor('.raw-css')).toBe('red')
+ })
+})
+
+test('import-expression from simple script', async () => {
+ expect(await page.textContent('.import-expression')).toMatch(
+ '[success][success]',
+ )
+})
+
+describe('asset imports from js', () => {
+ test('relative', async () => {
+ expect(await page.textContent('.asset-import-relative')).toMatch(assetMatch)
+ })
+
+ test('absolute', async () => {
+ expect(await page.textContent('.asset-import-absolute')).toMatch(assetMatch)
+ })
+
+ test('from /public', async () => {
+ expect(await page.textContent('.public-import')).toMatch(iconMatch)
+ })
+
+ test('from /public (json)', async () => {
+ expect(await page.textContent('.public-json-import')).toMatch(
+ '/foo/bar/foo.json',
+ )
+ expect(await page.textContent('.public-json-import-content'))
+ .toMatchInlineSnapshot(`
+ "{
+ "foo": "bar"
+ }
+ "
+ `)
+ })
+
+ test('from /public (js)', async () => {
+ expect(await page.textContent('.public-js-import')).toMatch(
+ '/foo/bar/raw.js',
+ )
+ expect(await page.textContent('.public-js-import-content'))
+ .toMatchInlineSnapshot(`
+ "document.querySelector('.raw-js').textContent =
+ '[success] Raw js from /public loaded'
+ "
+ `)
+ expect(await page.textContent('.public-js-import-content-type')).toMatch(
+ 'text/javascript',
+ )
+ })
+
+ test('from /public (ts)', async () => {
+ expect(await page.textContent('.public-ts-import')).toMatch(
+ '/foo/bar/raw.ts',
+ )
+ expect(await page.textContent('.public-ts-import-content'))
+ .toMatchInlineSnapshot(`
+ "export default function other() {
+ return 1 + 2
+ }
+ "
+ `)
+ // NOTE: users should configure the mime type for .ts files for preview server
+ if (isServe) {
+ expect(await page.textContent('.public-ts-import-content-type')).toMatch(
+ 'text/javascript',
+ )
+ }
+ })
+
+ test('from /public (mts)', async () => {
+ expect(await page.textContent('.public-mts-import')).toMatch(
+ '/foo/bar/raw.mts',
+ )
+ expect(await page.textContent('.public-mts-import-content'))
+ .toMatchInlineSnapshot(`
+ "export default function foobar() {
+ return 1 + 2
+ }
+ "
+ `)
+ // NOTE: users should configure the mime type for .ts files for preview server
+ if (isServe) {
+ expect(await page.textContent('.public-mts-import-content-type')).toMatch(
+ 'text/javascript',
+ )
+ }
+ })
+})
+
+describe('css url() references', () => {
+ test('fonts', async () => {
+ expect(
+ await page.evaluate(() => {
+ return (document as any).fonts.check('700 32px Inter')
+ }),
+ ).toBe(true)
+ })
+
+ test('relative', async () => {
+ expect(await getBg('.css-url-relative')).toMatch(assetMatch)
+ })
+
+ test('encoded', async () => {
+ expect(await getBg('.css-url-encoded')).toMatch(encodedAssetMatch)
+ })
+
+ test('image-set relative', async () => {
+ const imageSet = await getBg('.css-image-set-relative')
+ imageSet.split(', ').forEach((s) => {
+ expect(s).toMatch(assetMatch)
+ })
+ })
+
+ test('image-set without the url() call', async () => {
+ const imageSet = await getBg('.css-image-set-without-url-call')
+ imageSet.split(', ').forEach((s) => {
+ expect(s).toMatch(assetMatch)
+ })
+ })
+
+ test('image-set with var', async () => {
+ const imageSet = await getBg('.css-image-set-with-var')
+ imageSet.split(', ').forEach((s) => {
+ expect(s).toMatch(assetMatch)
+ })
+ })
+
+ test('image-set with mix', async () => {
+ const imageSet = await getBg('.css-image-set-mix-url-var')
+ imageSet.split(', ').forEach((s) => {
+ expect(s).toMatch(assetMatch)
+ })
+ })
+
+ test('image-set with base64', async () => {
+ const imageSet = await getBg('.css-image-set-base64')
+ expect(imageSet).toContain('image-set(url("data:image/png;base64,')
+ })
+
+ test('image-set with gradient', async () => {
+ const imageSet = await getBg('.css-image-set-gradient')
+ expect(imageSet).toContain('image-set(url("data:image/png;base64,')
+ })
+
+ test('image-set with multiple descriptor', async () => {
+ const imageSet = await getBg('.css-image-set-multiple-descriptor')
+ imageSet.split(', ').forEach((s) => {
+ expect(s).toMatch(assetMatch)
+ })
+ })
+
+ test('image-set with multiple descriptor as inline style', async () => {
+ const imageSet = await getBg(
+ '.css-image-set-multiple-descriptor-inline-style',
+ )
+ imageSet.split(', ').forEach((s) => {
+ expect(s).toMatch(assetMatch)
+ })
+ })
+
+ test('image-set and url exist at the same time.', async () => {
+ const imageSet = await getBg('.image-set-and-url-exsiting-at-same-time')
+ expect(imageSet).toMatch(assetMatch)
+ })
+
+ test('relative in @import', async () => {
+ expect(await getBg('.css-url-relative-at-imported')).toMatch(assetMatch)
+ })
+
+ test('absolute', async () => {
+ expect(await getBg('.css-url-absolute')).toMatch(assetMatch)
+ })
+
+ test('from /public', async () => {
+ expect(await getBg('.css-url-public')).toMatch(iconMatch)
+ })
+
+ test('base64 inline', async () => {
+ const match = isBuild ? `data:image/png;base64` : `/foo/bar/nested/icon.png`
+ expect(await getBg('.css-url-base64-inline')).toMatch(match)
+ expect(await getBg('.css-url-quotes-base64-inline')).toMatch(match)
+ })
+
+ test('no base64 inline for icon and manifest links', async () => {
+ const iconEl = await page.$(`link.ico`)
+ const href = await iconEl.getAttribute('href')
+ expect(href).toMatch(
+ isBuild ? /\/foo\/bar\/assets\/favicon-[-\w]{8}\.ico/ : 'favicon.ico',
+ )
+
+ const manifestEl = await page.$(`link[rel="manifest"]`)
+ const manifestHref = await manifestEl.getAttribute('href')
+ expect(manifestHref).toMatch(
+ isBuild ? /\/foo\/bar\/assets\/manifest-[-\w]{8}\.json/ : 'manifest.json',
+ )
+ })
+
+ test('multiple urls on the same line', async () => {
+ const bg = await getBg('.css-url-same-line')
+ expect(bg).toMatch(assetMatch)
+ expect(bg).toMatch(iconMatch)
+ })
+
+ test('aliased', async () => {
+ const bg = await getBg('.css-url-aliased')
+ expect(bg).toMatch(assetMatch)
+ })
+
+ test('preinlined SVG', async () => {
+ expect(await getBg('.css-url-preinlined-svg')).toMatch(
+ /data:image\/svg\+xml,.+/,
+ )
+ })
+
+ test.runIf(isBuild)('generated paths in CSS', () => {
+ const css = findAssetFile(/index-[-\w]{8}\.css$/, 'foo')
+
+ // preserve postfix query/hash
+ expect(css).toMatch(`woff2?#iefix`)
+
+ // generate non-relative base for public path in CSS
+ expect(css).not.toMatch(`../icon.png`)
+ })
+
+ test('url() with svg', async () => {
+ const bg = await getBg('.css-url-svg')
+ expect(bg).toMatch(/data:image\/svg\+xml,.+/)
+ expect(bg).toContain('blue')
+ expect(bg).not.toContain('red')
+
+ if (isServe) {
+ editFile('nested/fragment-bg-hmr.svg', (code) =>
+ code.replace('fill="blue"', 'fill="red"'),
+ )
+ await expect.poll(() => getBg('.css-url-svg')).toMatch('red')
+ }
+ })
+
+ test('image-set() with svg', async () => {
+ expect(await getBg('.css-image-set-svg')).toMatch(/data:image\/svg\+xml,.+/)
+ })
+
+ test('url() with svg in .css?url', async () => {
+ const bg = await getBg('.css-url-svg-in-url')
+ expect(bg).toMatch(/data:image\/svg\+xml,.+/)
+ expect(bg).toContain('blue')
+ expect(bg).not.toContain('red')
+
+ if (isServe) {
+ editFile('nested/fragment-bg-hmr2.svg', (code) =>
+ code.replace('fill="blue"', 'fill="red"'),
+ )
+ await expect.poll(() => getBg('.css-url-svg')).toMatch('red')
+ }
+ })
+
+ test.runIf(isServe)('non inlined url() HMR', async () => {
+ const bg = await getBg('.css-url-non-inline-hmr')
+ editFile('nested/donuts-large.svg', (code) =>
+ code.replace('fill="blue"', 'fill="red"'),
+ )
+ await expect.poll(() => getBg('.css-url-non-inline-hmr')).not.toBe(bg)
+ })
+})
+
+describe('image', () => {
+ test('src', async () => {
+ const img = await page.$('.img-src')
+ const src = await img.getAttribute('src')
+ expect(src).toMatch(
+ isBuild
+ ? /\/foo\/bar\/assets\/html-only-asset-[-\w]{8}\.jpg/
+ : /\/foo\/bar\/nested\/html-only-asset.jpg/,
+ )
+ })
+
+ test('src inline', async () => {
+ const img = await page.$('.img-src-inline')
+ const src = await img.getAttribute('src')
+ expect(src).toMatch(
+ isBuild
+ ? /^data:image\/svg\+xml,%3csvg/
+ : /\/foo\/bar\/nested\/inlined.svg/,
+ )
+ })
+
+ test('srcset', async () => {
+ const img = await page.$('.img-src-set')
+ const srcset = await img.getAttribute('srcset')
+ srcset.split(', ').forEach((s) => {
+ expect(s).toMatch(
+ isBuild
+ ? /\/foo\/bar\/assets\/asset-[-\w]{8}\.png \dx/
+ : /\/foo\/bar\/nested\/asset.png \dx/,
+ )
+ })
+ })
+
+ test('srcset (public)', async () => {
+ const img = await page.$('.img-src-set-public')
+ const srcset = await img.getAttribute('srcset')
+ srcset.split(', ').forEach((s) => {
+ expect(s).toMatch(/\/foo\/bar\/icon\.png \dx/)
+ })
+ })
+
+ test('srcset (mixed)', async () => {
+ const img = await page.$('.img-src-set-mixed')
+ const srcset = await img.getAttribute('srcset')
+ const srcs = srcset.split(', ')
+ expect(srcs[1]).toMatch(
+ isBuild
+ ? /\/foo\/bar\/assets\/asset-[-\w]{8}\.png \dx/
+ : /\/foo\/bar\/nested\/asset.png \dx/,
+ )
+ })
+})
+
+describe('meta', () => {
+ test('og image', async () => {
+ const meta = await page.$('.meta-og-image')
+ const content = await meta.getAttribute('content')
+ expect(content).toMatch(
+ isBuild
+ ? /\/foo\/bar\/assets\/asset-\w{8}\.png/
+ : /\/foo\/bar\/nested\/asset.png/,
+ )
+ })
+})
+
+describe('svg fragments', () => {
+ // 404 is checked already, so here we just ensure the urls end with #fragment
+ test('img url', async () => {
+ const img = await page.$('.svg-frag-img')
+ expect(await img.getAttribute('src')).toMatch(/svg#icon-clock-view$/)
+ })
+
+ test('via css url()', async () => {
+ expect(await getBg('.icon')).toMatch(/svg#icon-clock-view"\)$/)
+ })
+
+ test('from js import', async () => {
+ const img = await page.$('.svg-frag-import')
+ expect(await img.getAttribute('src')).toMatch(
+ // Assert trimmed (data URI starts with < and ends with >)
+ /^data:image\/svg\+xml,%3c.*%3e#icon-heart-view$/,
+ )
+ })
+
+ test('url with an alias', async () => {
+ expect(await getBg('.icon-clock-alias')).toMatch(
+ /\.svg#icon-clock-view"\)$/,
+ )
+ })
+})
+
+test('Unknown extension assets import', async () => {
+ expect(await page.textContent('.unknown-ext')).toMatch(
+ isBuild ? 'data:application/octet-stream;' : '/nested/foo.unknown',
+ )
+})
+
+test('?raw import', async () => {
+ expect(await page.textContent('.raw')).toMatch('SVG')
+ expect(await page.textContent('.raw-html')).toBe('partial
\n')
+
+ if (isBuild) return
+ editFile('nested/partial.html', (code) =>
+ code.replace('partial
', 'partial updated
'),
+ )
+ await expect
+ .poll(() => page.textContent('.raw-html'))
+ .toBe('partial updated
\n')
+ expect(browserLogs).toStrictEqual(
+ expect.arrayContaining([
+ expect.stringContaining('hot updated: /nested/partial.html?raw via'),
+ ]),
+ )
+})
+
+test('?no-inline svg import', async () => {
+ expect(await page.textContent('.no-inline-svg')).toMatch(
+ isBuild
+ ? /\/foo\/bar\/assets\/fragment-[-\w]{8}\.svg/
+ : '/foo/bar/nested/fragment.svg?no-inline',
+ )
+})
+
+test('?no-inline svg import -- multiple postfix', async () => {
+ expect(await page.textContent('.no-inline-svg-mp')).toMatch(
+ isBuild
+ ? /\/foo\/bar\/assets\/fragment-[-\w]{8}\.svg\?foo=bar/
+ : '/foo/bar/nested/fragment.svg?no-inline&foo=bar',
+ )
+})
+
+test('?inline png import', async () => {
+ expect(await page.textContent('.inline-png')).toMatch(
+ /^data:image\/png;base64,/,
+ )
+})
+
+test('?inline public png import', async () => {
+ expect(await page.textContent('.inline-public-png')).toMatch(
+ /^data:image\/png;base64,/,
+ )
+})
+
+test('?inline public json import', async () => {
+ expect(await page.textContent('.inline-public-json')).toMatch(
+ /^data:application\/json;base64,/,
+ )
+})
+
+test('?url import', async () => {
+ const src = readFile('foo.js')
+ expect(await page.textContent('.url')).toMatch(
+ isBuild
+ ? `data:text/javascript;base64,${Buffer.from(src).toString('base64')}`
+ : `/foo/bar/foo.js`,
+ )
+})
+
+test('?url import on css', async () => {
+ const txt = await page.textContent('.url-css')
+ expect(txt).toMatch(
+ isBuild
+ ? /\/foo\/bar\/assets\/icons-[-\w]{8}\.css/
+ : '/foo/bar/css/icons.css',
+ )
+})
+
+describe('unicode url', () => {
+ test('from js import', async () => {
+ const src = readFile('テスト-測試-white space.js')
+ expect(await page.textContent('.unicode-url')).toMatch(
+ isBuild
+ ? `data:text/javascript;base64,${Buffer.from(src).toString('base64')}`
+ : encodeURI(`/foo/bar/テスト-測試-white space.js`),
+ )
+ })
+})
+
+describe.runIf(isBuild)('encodeURI', () => {
+ test('img src with encodeURI', async () => {
+ const img = await page.$('.encodeURI')
+ expect(await img.getAttribute('src')).toMatch(/^data:image\/png;base64,/)
+ })
+})
+
+test('new URL(..., import.meta.url)', async () => {
+ const imgMatch = isBuild
+ ? /\/foo\/bar\/assets\/img-[-\w]{8}\.png/
+ : '/foo/bar/import-meta-url/img.png'
+
+ expect(await page.textContent('.import-meta-url')).toMatch(imgMatch)
+ if (isServe) {
+ const loadPromise = page.waitForEvent('load')
+ const newContent = readFile('import-meta-url/img-update.png', null)
+ let oldContent: Buffer
+ editFile('import-meta-url/img.png', null, (_oldContent) => {
+ oldContent = _oldContent
+ return newContent
+ })
+ await loadPromise // expect reload
+ await expect
+ .poll(() => page.textContent('.import-meta-url'))
+ .toMatch(imgMatch)
+
+ const loadPromise2 = page.waitForEvent('load')
+ editFile('import-meta-url/img.png', null, (_) => oldContent)
+ await loadPromise2 // expect reload
+ await expect
+ .poll(() => page.textContent('.import-meta-url'))
+ .toMatch(imgMatch)
+ }
+})
+
+test('new URL("@/...", import.meta.url)', async () => {
+ expect(await page.textContent('.import-meta-url-dep')).toMatch(assetMatch)
+})
+
+test('new URL("/...", import.meta.url)', async () => {
+ expect(await page.textContent('.import-meta-url-base-path')).toMatch(
+ iconMatch,
+ )
+})
+
+test('new URL("data:...", import.meta.url)', async () => {
+ const img = await page.$('.import-meta-url-data-uri-img')
+ expect(await img.getAttribute('src')).toMatch(/^data:image\/png;base64,/)
+ expect(await page.textContent('.import-meta-url-data-uri')).toMatch(
+ /^data:image\/png;base64,/,
+ )
+})
+
+test('new URL(..., import.meta.url) without extension', async () => {
+ expect(await page.textContent('.import-meta-url-without-extension')).toMatch(
+ isBuild ? 'data:text/javascript' : 'nested/test.js',
+ )
+ expect(
+ await page.textContent('.import-meta-url-content-without-extension'),
+ ).toContain('export default class')
+})
+
+test('new URL(`${dynamic}`, import.meta.url)', async () => {
+ expect(await page.textContent('.dynamic-import-meta-url-1')).toMatch(
+ isBuild ? 'data:image/png;base64' : '/foo/bar/nested/icon.png',
+ )
+ expect(await page.textContent('.dynamic-import-meta-url-2')).toMatch(
+ assetMatch,
+ )
+ expect(await page.textContent('.dynamic-import-meta-url-js')).toMatch(
+ isBuild ? 'data:text/javascript;base64' : '/foo/bar/nested/test.js',
+ )
+})
+
+test('new URL(`./${dynamic}?abc`, import.meta.url)', async () => {
+ expect(await page.textContent('.dynamic-import-meta-url-1-query')).toMatch(
+ isBuild ? 'data:image/png;base64' : '/foo/bar/nested/icon.png?abc',
+ )
+ expect(await page.textContent('.dynamic-import-meta-url-2-query')).toMatch(
+ isBuild
+ ? /\/foo\/bar\/assets\/asset-[-\w]{8}\.png\?abc/
+ : '/foo/bar/nested/asset.png?abc',
+ )
+})
+
+test('new URL(`./${1 === 0 ? static : dynamic}?abc`, import.meta.url)', async () => {
+ expect(await page.textContent('.dynamic-import-meta-url-1-ternary')).toMatch(
+ isBuild ? 'data:image/png;base64' : '/foo/bar/nested/icon.png?abc',
+ )
+ expect(await page.textContent('.dynamic-import-meta-url-2-ternary')).toMatch(
+ isBuild
+ ? /\/foo\/bar\/assets\/asset-[-\w]{8}\.png\?abc/
+ : '/foo/bar/nested/asset.png?abc',
+ )
+})
+
+test("new URL(/* @vite-ignore */ 'non-existent', import.meta.url)", async () => {
+ // the inlined script tag is extracted in a separate file
+ const importMetaUrl = new URL(
+ isBuild ? '/foo/bar/assets/index.js' : '/foo/bar/index.html',
+ page.url(),
+ )
+ expect(await page.textContent('.non-existent-import-meta-url')).toMatch(
+ new URL('non-existent', importMetaUrl).pathname,
+ )
+ expect(serverLogs).not.toContainEqual(
+ expect.stringContaining("doesn't exist at build time"),
+ )
+})
+
+test('new URL(..., import.meta.url) (multiline)', async () => {
+ const assetMatch = isBuild
+ ? /\/foo\/bar\/assets\/asset-[-\w]{8}\.png/
+ : '/foo/bar/nested/asset.png'
+
+ expect(await page.textContent('.import-meta-url-multiline')).toMatch(
+ assetMatch,
+ )
+})
+
+test.runIf(isBuild)('manifest', async () => {
+ const manifest = readManifest('foo')
+ const entry = manifest['index.html']
+
+ for (const file of listAssets('foo')) {
+ if (file.endsWith('.css')) {
+ // ignore icons-*.css and css-url-url-*.css as it's imported with ?url
+ if (file.includes('icons-') || file.includes('css-url-url-')) continue
+ expect(entry.css).toContain(`assets/${file}`)
+ } else if (!file.endsWith('.js')) {
+ expect(entry.assets).toContain(`assets/${file}`)
+ }
+ }
+})
+
+describe.runIf(isBuild)('css and assets in css in build watch', () => {
+ test('css will not be lost and css does not contain undefined', async () => {
+ editFile('index.html', (code) => code.replace('Assets', 'assets'))
+ await notifyRebuildComplete(watcher)
+ const cssFile = findAssetFile(/index-[-\w]+\.css$/, 'foo')
+ expect(cssFile).not.toBe('')
+ expect(cssFile).not.toMatch(/undefined/)
+ })
+
+ test('import module.css', async () => {
+ expect(await getColor('#foo')).toBe('red')
+ editFile('css/foo.module.css', (code) => code.replace('red', 'blue'))
+ await notifyRebuildComplete(watcher)
+ await page.reload()
+ expect(await getColor('#foo')).toBe('blue')
+ })
+
+ test('import with raw query', async () => {
+ expect(await page.textContent('.raw-query')).toBe('foo')
+ editFile('static/foo.txt', (code) => code.replace('foo', 'zoo'))
+ await notifyRebuildComplete(watcher)
+ await page.reload()
+ expect(await page.textContent('.raw-query')).toBe('zoo')
+ })
+})
+
+test('inline style test', async () => {
+ expect(await getBg('.inline-style')).toMatch(assetMatch)
+ expect(await getBg('.style-url-assets')).toMatch(assetMatch)
+})
+
+if (!isBuild) {
+ test('@import in html style tag hmr', async () => {
+ await expect.poll(() => getColor('.import-css')).toBe('rgb(0, 136, 255)')
+ const loadPromise = page.waitForEvent('load')
+ editFile('./css/import.css', (code) => code.replace('#0088ff', '#00ff88'))
+ await loadPromise
+ await expect.poll(() => getColor('.import-css')).toBe('rgb(0, 255, 136)')
+ })
+}
+
+test('html import word boundary', async () => {
+ expect(await page.textContent('.obj-import-express')).toMatch(
+ 'ignore object import prop',
+ )
+ expect(await page.textContent('.string-import-express')).toMatch('no load')
+})
+
+test('relative path in html asset', async () => {
+ expect(await page.textContent('.relative-js')).toMatch('hello')
+ expect(await getColor('.relative-css')).toMatch('red')
+})
+
+test('url() contains file in publicDir, in
+
+ inline style
+
+use style class
+
+base64
+
+
+ inline style
+
+use style class
+from publicDir
+
+
+ inline style
+
+use style class
+
+@import
+
+
+
+ @import CSS from publicDir should load (this should be red)
+
+import module css
+
+
+
+style in svg
+
+
+
+
+
+
+
+
+assets in noscript
+
+
+
+
+assets in template
+
+
+
+
+
+link style
+
+
+
+
+
diff --git a/playground/assets/manifest.json b/playground/assets/manifest.json
new file mode 100644
index 00000000000000..0967ef424bce67
--- /dev/null
+++ b/playground/assets/manifest.json
@@ -0,0 +1 @@
+{}
diff --git a/playground/assets/multiline-import-meta-url.js b/playground/assets/multiline-import-meta-url.js
new file mode 100644
index 00000000000000..4b40342522fc82
--- /dev/null
+++ b/playground/assets/multiline-import-meta-url.js
@@ -0,0 +1,8 @@
+// Test for multiline expressions
+// This is a separate file to ensure the regex filter in assetImportMetaUrlPlugin
+// correctly detects and processes multiline expressions that span multiple lines.
+export const multilineUrl = new URL(
+ './nested/asset.png',
+
+ import.meta.url,
+)
diff --git a/packages/playground/assets/nested/asset.png b/playground/assets/nested/asset.png
similarity index 100%
rename from packages/playground/assets/nested/asset.png
rename to playground/assets/nested/asset.png
diff --git a/playground/assets/nested/asset[small].png b/playground/assets/nested/asset[small].png
new file mode 100644
index 00000000000000..cf5a52d15fc95e
Binary files /dev/null and b/playground/assets/nested/asset[small].png differ
diff --git a/playground/assets/nested/donuts-large.svg b/playground/assets/nested/donuts-large.svg
new file mode 100644
index 00000000000000..022554c81ef3bd
--- /dev/null
+++ b/playground/assets/nested/donuts-large.svg
@@ -0,0 +1,5 @@
+
+
+ longtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtextlongtext
+
+
diff --git a/playground/assets/nested/foo.unknown b/playground/assets/nested/foo.unknown
new file mode 100644
index 00000000000000..e24f83b664c55c
--- /dev/null
+++ b/playground/assets/nested/foo.unknown
@@ -0,0 +1 @@
+custom file
diff --git a/playground/assets/nested/fragment-bg-hmr.svg b/playground/assets/nested/fragment-bg-hmr.svg
new file mode 100644
index 00000000000000..44e4248f924d70
--- /dev/null
+++ b/playground/assets/nested/fragment-bg-hmr.svg
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/playground/assets/nested/fragment-bg-hmr2.svg b/playground/assets/nested/fragment-bg-hmr2.svg
new file mode 100644
index 00000000000000..44e4248f924d70
--- /dev/null
+++ b/playground/assets/nested/fragment-bg-hmr2.svg
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/playground/assets/nested/fragment-bg.svg b/playground/assets/nested/fragment-bg.svg
similarity index 100%
rename from packages/playground/assets/nested/fragment-bg.svg
rename to playground/assets/nested/fragment-bg.svg
diff --git a/packages/playground/assets/nested/fragment.svg b/playground/assets/nested/fragment.svg
similarity index 100%
rename from packages/playground/assets/nested/fragment.svg
rename to playground/assets/nested/fragment.svg
diff --git a/playground/assets/nested/html-only-asset.jpg b/playground/assets/nested/html-only-asset.jpg
new file mode 100644
index 00000000000000..289e5ae196ad0e
Binary files /dev/null and b/playground/assets/nested/html-only-asset.jpg differ
diff --git a/packages/playground/assets/nested/icon.png b/playground/assets/nested/icon.png
similarity index 100%
rename from packages/playground/assets/nested/icon.png
rename to playground/assets/nested/icon.png
diff --git a/playground/assets/nested/inlined.svg b/playground/assets/nested/inlined.svg
new file mode 100644
index 00000000000000..e00a25209ebd4e
--- /dev/null
+++ b/playground/assets/nested/inlined.svg
@@ -0,0 +1,12 @@
+
+
+
diff --git a/playground/assets/nested/partial.html b/playground/assets/nested/partial.html
new file mode 100644
index 00000000000000..3d8c6454a288fa
--- /dev/null
+++ b/playground/assets/nested/partial.html
@@ -0,0 +1 @@
+partial
diff --git a/playground/assets/nested/test.js b/playground/assets/nested/test.js
new file mode 100644
index 00000000000000..1a292f36ac7916
--- /dev/null
+++ b/playground/assets/nested/test.js
@@ -0,0 +1,3 @@
+export default class a {
+ name = 'a'
+}
diff --git a/playground/assets/nested/with-single'quote.png b/playground/assets/nested/with-single'quote.png
new file mode 100644
index 00000000000000..cb1c88d48c090a
Binary files /dev/null and b/playground/assets/nested/with-single'quote.png differ
diff --git a/packages/playground/css/ok.png "b/playground/assets/nested/\343\203\206\343\202\271\343\203\210-\346\270\254\350\251\246-white space.png"
similarity index 100%
rename from packages/playground/css/ok.png
rename to "playground/assets/nested/\343\203\206\343\202\271\343\203\210-\346\270\254\350\251\246-white space.png"
diff --git a/playground/assets/package.json b/playground/assets/package.json
new file mode 100644
index 00000000000000..e90a090605bd61
--- /dev/null
+++ b/playground/assets/package.json
@@ -0,0 +1,24 @@
+{
+ "name": "@vitejs/test-assets",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "scripts": {
+ "debug": "node --inspect-brk ../../packages/vite/bin/vite",
+ "dev": "vite",
+ "build": "vite build",
+ "preview": "vite preview",
+ "dev:encoded-base": "vite --config ./vite.config-encoded-base.js dev",
+ "build:encoded-base": "vite --config ./vite.config-encoded-base.js build",
+ "preview:encoded-base": "vite --config ./vite.config-encoded-base.js preview",
+ "dev:relative-base": "vite --config ./vite.config-relative-base.js dev",
+ "build:relative-base": "vite --config ./vite.config-relative-base.js build",
+ "preview:relative-base": "vite --config ./vite.config-relative-base.js preview",
+ "dev:runtime-base": "vite --config ./vite.config-runtime-base.js dev",
+ "build:runtime-base": "vite --config ./vite.config-runtime-base.js build",
+ "preview:runtime-base": "vite --config ./vite.config-runtime-base.js preview",
+ "dev:url-base": "vite --config ./vite.config-url-base.js dev",
+ "build:url-base": "vite --config ./vite.config-url-base.js build",
+ "preview:url-base": "vite --config ./vite.config-url-base.js preview"
+ }
+}
diff --git a/playground/assets/static/bar b/playground/assets/static/bar
new file mode 100644
index 00000000000000..5716ca5987cbf9
--- /dev/null
+++ b/playground/assets/static/bar
@@ -0,0 +1 @@
+bar
diff --git a/packages/playground/assets/static/foo.css b/playground/assets/static/foo.css
similarity index 100%
rename from packages/playground/assets/static/foo.css
rename to playground/assets/static/foo.css
diff --git a/playground/assets/static/foo.json b/playground/assets/static/foo.json
new file mode 100644
index 00000000000000..c8c4105eb57cda
--- /dev/null
+++ b/playground/assets/static/foo.json
@@ -0,0 +1,3 @@
+{
+ "foo": "bar"
+}
diff --git a/playground/assets/static/foo.txt b/playground/assets/static/foo.txt
new file mode 100644
index 00000000000000..19102815663d23
--- /dev/null
+++ b/playground/assets/static/foo.txt
@@ -0,0 +1 @@
+foo
\ No newline at end of file
diff --git a/packages/playground/assets/static/icon.png b/playground/assets/static/icon.png
similarity index 100%
rename from packages/playground/assets/static/icon.png
rename to playground/assets/static/icon.png
diff --git a/packages/playground/assets/static/import-expression.js b/playground/assets/static/import-expression.js
similarity index 100%
rename from packages/playground/assets/static/import-expression.js
rename to playground/assets/static/import-expression.js
diff --git a/packages/playground/assets/static/raw.css b/playground/assets/static/raw.css
similarity index 100%
rename from packages/playground/assets/static/raw.css
rename to playground/assets/static/raw.css
diff --git a/packages/playground/assets/static/raw.js b/playground/assets/static/raw.js
similarity index 100%
rename from packages/playground/assets/static/raw.js
rename to playground/assets/static/raw.js
diff --git a/playground/assets/static/raw.mts b/playground/assets/static/raw.mts
new file mode 100644
index 00000000000000..6719e0c239a418
--- /dev/null
+++ b/playground/assets/static/raw.mts
@@ -0,0 +1,3 @@
+export default function foobar() {
+ return 1 + 2
+}
diff --git a/playground/assets/static/raw.ts b/playground/assets/static/raw.ts
new file mode 100644
index 00000000000000..7faded7a1614a6
--- /dev/null
+++ b/playground/assets/static/raw.ts
@@ -0,0 +1,3 @@
+export default function other() {
+ return 1 + 2
+}
diff --git a/playground/assets/vite.config-encoded-base.js b/playground/assets/vite.config-encoded-base.js
new file mode 100644
index 00000000000000..70e86eeb90eb37
--- /dev/null
+++ b/playground/assets/vite.config-encoded-base.js
@@ -0,0 +1,34 @@
+import { defineConfig } from 'vite'
+import baseConfig from './vite.config.js'
+
+/** see `ports` variable in test-utils.ts */
+const port = 9524
+
+export default defineConfig({
+ ...baseConfig,
+ // Vite should auto-encode this as `/foo%20bar/` internally
+ base: '/foo bar/',
+ server: {
+ port,
+ strictPort: true,
+ },
+ build: {
+ ...baseConfig.build,
+ outDir: 'dist/encoded-base',
+ watch: null,
+ minify: false,
+ assetsInlineLimit: 0,
+ rollupOptions: {
+ output: {
+ entryFileNames: 'entries/[name].js',
+ chunkFileNames: 'chunks/[name]-[hash].js',
+ assetFileNames: 'other-assets/[name]-[hash][extname]',
+ },
+ },
+ },
+ preview: {
+ port,
+ strictPort: true,
+ },
+ cacheDir: 'node_modules/.vite-encoded-base',
+})
diff --git a/playground/assets/vite.config-relative-base.js b/playground/assets/vite.config-relative-base.js
new file mode 100644
index 00000000000000..dabaedb26136b3
--- /dev/null
+++ b/playground/assets/vite.config-relative-base.js
@@ -0,0 +1,27 @@
+import { defineConfig } from 'vite'
+import baseConfig from './vite.config.js'
+
+export default defineConfig(({ isPreview }) => ({
+ ...baseConfig,
+ base: !isPreview ? './' : '/relative-base/', // relative base to make dist portable
+ build: {
+ ...baseConfig.build,
+ outDir: 'dist/relative-base',
+ watch: null,
+ minify: false,
+ assetsInlineLimit: 0,
+ rollupOptions: {
+ output: {
+ entryFileNames: 'entries/[name].js',
+ chunkFileNames: 'chunks/[name]-[hash].js',
+ assetFileNames: 'other-assets/[name]-[hash][extname]',
+ // manualChunks(id) {
+ // if (id.includes('css/manual-chunks.css')) {
+ // return 'css/manual-chunks'
+ // }
+ // },
+ },
+ },
+ },
+ cacheDir: 'node_modules/.vite-relative-base',
+}))
diff --git a/playground/assets/vite.config-runtime-base.js b/playground/assets/vite.config-runtime-base.js
new file mode 100644
index 00000000000000..5113ccebc68c3d
--- /dev/null
+++ b/playground/assets/vite.config-runtime-base.js
@@ -0,0 +1,61 @@
+import { defineConfig } from 'vite'
+import baseConfig from './vite.config.js'
+
+const dynamicBaseAssetsCode = `
+globalThis.__toAssetUrl = url => '/' + url
+globalThis.__publicBase = '/'
+`
+
+export default defineConfig({
+ ...baseConfig,
+ base: './', // overwrite the original base: '/foo/'
+ build: {
+ ...baseConfig.build,
+ outDir: 'dist/runtime-base',
+ watch: null,
+ minify: false,
+ assetsInlineLimit: 0,
+ rollupOptions: {
+ output: {
+ entryFileNames: 'entries/[name].js',
+ chunkFileNames: 'chunks/[name]-[hash].js',
+ assetFileNames: 'other-assets/[name]-[hash][extname]',
+ },
+ },
+ },
+ plugins: [
+ {
+ name: 'dynamic-base-assets-globals',
+ transformIndexHtml(_, ctx) {
+ if (ctx.bundle) {
+ // Only inject during build
+ return [
+ {
+ tag: 'script',
+ attrs: { type: 'module' },
+ children: dynamicBaseAssetsCode,
+ },
+ ]
+ }
+ },
+ },
+ ],
+ experimental: {
+ renderBuiltUrl(filename, { hostType, type }) {
+ if (type === 'asset') {
+ if (hostType === 'js') {
+ return {
+ runtime: `globalThis.__toAssetUrl(${JSON.stringify(filename)})`,
+ }
+ }
+ } else if (type === 'public') {
+ if (hostType === 'js') {
+ return {
+ runtime: `globalThis.__publicBase+${JSON.stringify(filename)}`,
+ }
+ }
+ }
+ },
+ },
+ cacheDir: 'node_modules/.vite-runtime-base',
+})
diff --git a/playground/assets/vite.config-url-base.js b/playground/assets/vite.config-url-base.js
new file mode 100644
index 00000000000000..14d24feae4298d
--- /dev/null
+++ b/playground/assets/vite.config-url-base.js
@@ -0,0 +1,33 @@
+import { defineConfig } from 'vite'
+import baseConfig from './vite.config.js'
+
+/** see `ports` variable in test-utils.ts */
+const port = 9525
+
+export default defineConfig({
+ ...baseConfig,
+ base: `http://localhost:${port}/`,
+ server: {
+ port,
+ strictPort: true,
+ },
+ build: {
+ ...baseConfig.build,
+ outDir: 'dist/url-base',
+ watch: null,
+ minify: false,
+ assetsInlineLimit: 0,
+ rollupOptions: {
+ output: {
+ entryFileNames: 'entries/[name].js',
+ chunkFileNames: 'chunks/[name]-[hash].js',
+ assetFileNames: 'other-assets/[name]-[hash][extname]',
+ },
+ },
+ },
+ preview: {
+ port,
+ strictPort: true,
+ },
+ cacheDir: 'node_modules/.vite-url-base',
+})
diff --git a/playground/assets/vite.config.js b/playground/assets/vite.config.js
new file mode 100644
index 00000000000000..9051e837ddb366
--- /dev/null
+++ b/playground/assets/vite.config.js
@@ -0,0 +1,20 @@
+import path from 'node:path'
+import { defineConfig } from 'vite'
+
+export default defineConfig({
+ base: '/foo/bar',
+ publicDir: 'static',
+ resolve: {
+ alias: {
+ '@': path.resolve(import.meta.dirname, 'nested'),
+ fragment: path.resolve(import.meta.dirname, 'nested/fragment-bg.svg'),
+ },
+ },
+ assetsInclude: ['**/*.unknown'],
+ build: {
+ outDir: 'dist/foo',
+ assetsInlineLimit: 8000, // 8 kB
+ manifest: true,
+ watch: {},
+ },
+})
diff --git "a/playground/assets/\343\203\206\343\202\271\343\203\210-\346\270\254\350\251\246-white space.js" "b/playground/assets/\343\203\206\343\202\271\343\203\210-\346\270\254\350\251\246-white space.js"
new file mode 100644
index 00000000000000..81de0358864984
--- /dev/null
+++ "b/playground/assets/\343\203\206\343\202\271\343\203\210-\346\270\254\350\251\246-white space.js"
@@ -0,0 +1 @@
+console.log('test Unicode')
diff --git a/playground/backend-integration/__tests__/backend-integration.spec.ts b/playground/backend-integration/__tests__/backend-integration.spec.ts
new file mode 100644
index 00000000000000..7f470d06b08c11
--- /dev/null
+++ b/playground/backend-integration/__tests__/backend-integration.spec.ts
@@ -0,0 +1,134 @@
+import { describe, expect, test, vi } from 'vitest'
+import {
+ browserErrors,
+ browserLogs,
+ editFile,
+ getColor,
+ isBuild,
+ isServe,
+ listAssets,
+ page,
+ ports,
+ readManifest,
+ serverLogs,
+ untilBrowserLogAfter,
+} from '~utils'
+
+test('should have no 404s', () => {
+ browserLogs.forEach((msg) => {
+ expect(msg).not.toMatch('404')
+ })
+})
+
+describe('asset imports from js', () => {
+ test('file outside root', async () => {
+ // assert valid image src https://github.com/microsoft/playwright/issues/6046#issuecomment-1799585719
+ await vi.waitUntil(() =>
+ page
+ .locator('.asset-reference.outside-root .asset-preview')
+ .evaluate((el: HTMLImageElement) => el.naturalWidth > 0),
+ )
+
+ const text = await page.textContent(
+ '.asset-reference.outside-root .asset-url',
+ )
+ if (isBuild) {
+ expect(text).toMatch(/\/dev\/assets\/logo-[-\w]{8}\.png/)
+ } else {
+ // asset url is prefixed with server.origin
+ expect(text).toMatch(
+ `http://localhost:${ports['backend-integration']}/dev/@fs/`,
+ )
+ expect(text).toMatch(/\/dev\/@fs\/.+?\/images\/logo\.png/)
+ }
+ })
+})
+
+describe.runIf(isBuild)('build', () => {
+ test('manifest', async () => {
+ const manifest = readManifest('dev')
+ const htmlEntry = manifest['index.html']
+ const mainTsEntry = manifest['main.ts']
+ const cssAssetEntry = manifest['global.css']
+ const pcssAssetEntry = manifest['foo.pcss']
+ const scssAssetEntry = manifest['nested/blue.scss']
+ const imgAssetEntry = manifest['../images/logo.png']
+ const dirFooAssetEntry = manifest['../../dir/foo.css']
+ const customNameAssetEntry = manifest['../../dir/custom.css']
+ const iconEntrypointEntry = manifest['icon.png']
+ const waterContainerEntry = manifest['water-container.svg']
+ expect(htmlEntry.css.length).toEqual(1)
+ expect(htmlEntry.assets.length).toEqual(1)
+ expect(mainTsEntry.assets?.length ?? 0).toBeGreaterThanOrEqual(1)
+ expect(mainTsEntry.assets).toContainEqual(
+ expect.stringMatching(/assets\/url-[-\w]{8}\.css/),
+ )
+ expect(cssAssetEntry?.file).not.toBeUndefined()
+ expect(cssAssetEntry?.isEntry).toEqual(true)
+ expect(pcssAssetEntry?.file).not.toBeUndefined()
+ expect(pcssAssetEntry?.isEntry).toEqual(true)
+ expect(scssAssetEntry?.file).not.toBeUndefined()
+ expect(scssAssetEntry?.src).toEqual('nested/blue.scss')
+ expect(scssAssetEntry?.isEntry).toEqual(true)
+ expect(imgAssetEntry?.file).not.toBeUndefined()
+ expect(imgAssetEntry?.isEntry).toBeUndefined()
+ expect(dirFooAssetEntry).not.toBeUndefined() // '\\' should not be used even on windows
+ // use the entry name
+ expect(dirFooAssetEntry.file).toMatch('assets/bar-')
+ expect(dirFooAssetEntry.name).toStrictEqual('bar.css')
+ expect(dirFooAssetEntry.assets.length).toEqual(1)
+ expect(customNameAssetEntry.name).toStrictEqual('bar.custom')
+ expect(iconEntrypointEntry?.file).not.toBeUndefined()
+ expect(waterContainerEntry?.file).not.toBeUndefined()
+ })
+
+ test('CSS imported from JS entry should have a non-nested chunk name', () => {
+ const manifest = readManifest('dev')
+ const mainTsEntryCss = manifest['nested/sub.ts'].css
+ expect(mainTsEntryCss.length).toBe(1)
+ expect(mainTsEntryCss[0].replace('assets/', '')).not.toContain('/')
+ })
+
+ test('entrypoint assets should not generate empty JS file', () => {
+ expect(serverLogs).not.toContainEqual(
+ 'Generated an empty chunk: "icon.png".',
+ )
+
+ const assets = listAssets('dev')
+ expect(assets).not.toContainEqual(
+ expect.stringMatching(/icon.png-[-\w]{8}\.js$/),
+ )
+ })
+})
+
+describe.runIf(isServe)('serve', () => {
+ test('No ReferenceError', async () => {
+ browserErrors.forEach((error) => {
+ expect(error.name).not.toBe('ReferenceError')
+ })
+ })
+
+ test('preserve the base in CSS HMR', async () => {
+ await expect.poll(() => getColor('body')).toBe('black') // sanity check
+ editFile('frontend/entrypoints/global.css', (code) =>
+ code.replace('black', 'red'),
+ )
+ await expect.poll(() => getColor('body')).toBe('red') // successful HMR
+
+ // Verify that the base (/dev/) was added during the css-update
+ const link = await page.$('link[rel="stylesheet"]:last-of-type')
+ expect(await link.getAttribute('href')).toContain('/dev/global.css?t=')
+ })
+
+ test('CSS dependencies are tracked for HMR', async () => {
+ const el = await page.$('h1')
+ await untilBrowserLogAfter(
+ () =>
+ editFile('frontend/entrypoints/main.ts', (code) =>
+ code.replace('text-black', 'text-[rgb(204,0,0)]'),
+ ),
+ '[vite] css hot updated: /global.css',
+ )
+ await expect.poll(() => getColor(el)).toBe('rgb(204, 0, 0)')
+ })
+})
diff --git a/playground/backend-integration/dir/custom.css b/playground/backend-integration/dir/custom.css
new file mode 100644
index 00000000000000..086058a71f67e2
--- /dev/null
+++ b/playground/backend-integration/dir/custom.css
@@ -0,0 +1,3 @@
+.custom {
+ color: red;
+}
diff --git a/playground/backend-integration/dir/foo-background.svg b/playground/backend-integration/dir/foo-background.svg
new file mode 100644
index 00000000000000..c6d2b33a58b888
--- /dev/null
+++ b/playground/backend-integration/dir/foo-background.svg
@@ -0,0 +1,5 @@
+
+
+
+
diff --git a/playground/backend-integration/dir/foo.css b/playground/backend-integration/dir/foo.css
new file mode 100644
index 00000000000000..1315b3cd1f62e8
--- /dev/null
+++ b/playground/backend-integration/dir/foo.css
@@ -0,0 +1,6 @@
+.windows-path-foo {
+ color: blue;
+}
+.foo-background-image {
+ background-image: url('./foo-background.svg?no-inline');
+}
diff --git a/playground/backend-integration/frontend/entrypoints/foo.pcss b/playground/backend-integration/frontend/entrypoints/foo.pcss
new file mode 100644
index 00000000000000..a49e02c1cc73bf
--- /dev/null
+++ b/playground/backend-integration/frontend/entrypoints/foo.pcss
@@ -0,0 +1,3 @@
+.foo_pcss {
+ color: blue;
+}
diff --git a/packages/playground/backend-integration/frontend/entrypoints/global.css b/playground/backend-integration/frontend/entrypoints/global.css
similarity index 100%
rename from packages/playground/backend-integration/frontend/entrypoints/global.css
rename to playground/backend-integration/frontend/entrypoints/global.css
diff --git a/packages/playground/css/nested/icon.png b/playground/backend-integration/frontend/entrypoints/icon.png
similarity index 100%
rename from packages/playground/css/nested/icon.png
rename to playground/backend-integration/frontend/entrypoints/icon.png
diff --git a/playground/backend-integration/frontend/entrypoints/index.html b/playground/backend-integration/frontend/entrypoints/index.html
new file mode 100644
index 00000000000000..bc75dbc4b898e9
--- /dev/null
+++ b/playground/backend-integration/frontend/entrypoints/index.html
@@ -0,0 +1,57 @@
+
+
+
+
+Backend Integration
+
+
+ This test configures the root to simulate a Laravel/Rails setup.
+
+
+JS Asset References
+
+
+
+CSS Asset References
+
+
+
+ Background URL with Alias:
+
+
+
+ Background URL with Relative Path:
+
+
+
+
+CSS imported from JS
+
+text
+
+
+
+
diff --git a/playground/backend-integration/frontend/entrypoints/main.ts b/playground/backend-integration/frontend/entrypoints/main.ts
new file mode 100644
index 00000000000000..f32a8084f2a90d
--- /dev/null
+++ b/playground/backend-integration/frontend/entrypoints/main.ts
@@ -0,0 +1,25 @@
+import 'vite/modulepreload-polyfill'
+import cssUrl from '../styles/url.css?url'
+import waterContainer from './water-container.svg'
+
+const cssLink = document.createElement('link')
+cssLink.rel = 'stylesheet'
+cssLink.href = cssUrl
+document.querySelector('head').prepend(cssLink)
+
+const dummyMeta = document.createElement('meta')
+dummyMeta.name = 'dummy'
+dummyMeta.content = waterContainer
+document.querySelector('head').append(dummyMeta)
+
+export const colorClass = 'text-black'
+
+export function colorHeading() {
+ document.querySelector('h1').className = colorClass
+}
+
+colorHeading()
+
+if (import.meta.hot) {
+ import.meta.hot.accept()
+}
diff --git a/playground/backend-integration/frontend/entrypoints/nested/blue.scss b/playground/backend-integration/frontend/entrypoints/nested/blue.scss
new file mode 100644
index 00000000000000..67be4f93044cce
--- /dev/null
+++ b/playground/backend-integration/frontend/entrypoints/nested/blue.scss
@@ -0,0 +1,5 @@
+$primary: #cc0000;
+
+.text-primary {
+ color: $primary;
+}
diff --git a/playground/backend-integration/frontend/entrypoints/nested/sub.ts b/playground/backend-integration/frontend/entrypoints/nested/sub.ts
new file mode 100644
index 00000000000000..9f9f2d70dc674a
--- /dev/null
+++ b/playground/backend-integration/frontend/entrypoints/nested/sub.ts
@@ -0,0 +1 @@
+import '../../styles/imported.css'
diff --git a/playground/backend-integration/frontend/entrypoints/water-container.svg b/playground/backend-integration/frontend/entrypoints/water-container.svg
new file mode 100644
index 00000000000000..39e4aaaf282cb7
--- /dev/null
+++ b/playground/backend-integration/frontend/entrypoints/water-container.svg
@@ -0,0 +1 @@
+
diff --git a/packages/playground/backend-integration/frontend/images/logo.png b/playground/backend-integration/frontend/images/logo.png
similarity index 100%
rename from packages/playground/backend-integration/frontend/images/logo.png
rename to playground/backend-integration/frontend/images/logo.png
diff --git a/packages/playground/backend-integration/frontend/styles/background.css b/playground/backend-integration/frontend/styles/background.css
similarity index 100%
rename from packages/playground/backend-integration/frontend/styles/background.css
rename to playground/backend-integration/frontend/styles/background.css
diff --git a/playground/backend-integration/frontend/styles/imported.css b/playground/backend-integration/frontend/styles/imported.css
new file mode 100644
index 00000000000000..65af30a2064c86
--- /dev/null
+++ b/playground/backend-integration/frontend/styles/imported.css
@@ -0,0 +1,3 @@
+.imported {
+ color: green;
+}
diff --git a/playground/backend-integration/frontend/styles/tailwind.css b/playground/backend-integration/frontend/styles/tailwind.css
new file mode 100644
index 00000000000000..d4b5078586e291
--- /dev/null
+++ b/playground/backend-integration/frontend/styles/tailwind.css
@@ -0,0 +1 @@
+@import 'tailwindcss';
diff --git a/playground/backend-integration/frontend/styles/url.css b/playground/backend-integration/frontend/styles/url.css
new file mode 100644
index 00000000000000..6c9daf3ed51d1f
--- /dev/null
+++ b/playground/backend-integration/frontend/styles/url.css
@@ -0,0 +1,3 @@
+.url {
+ color: red;
+}
diff --git a/playground/backend-integration/package.json b/playground/backend-integration/package.json
new file mode 100644
index 00000000000000..197c5bf43b0da1
--- /dev/null
+++ b/playground/backend-integration/package.json
@@ -0,0 +1,18 @@
+{
+ "name": "@vitejs/test-backend-integration",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build",
+ "debug": "node --inspect-brk ../../packages/vite/bin/vite",
+ "preview": "vite preview"
+ },
+ "devDependencies": {
+ "@tailwindcss/vite": "^4.1.18",
+ "sass": "^1.97.3",
+ "tailwindcss": "^4.1.18",
+ "tinyglobby": "^0.2.15"
+ }
+}
diff --git a/packages/playground/backend-integration/references.css b/playground/backend-integration/references.css
similarity index 100%
rename from packages/playground/backend-integration/references.css
rename to playground/backend-integration/references.css
diff --git a/playground/backend-integration/vite.config.js b/playground/backend-integration/vite.config.js
new file mode 100644
index 00000000000000..d2a8a868471f78
--- /dev/null
+++ b/playground/backend-integration/vite.config.js
@@ -0,0 +1,62 @@
+import path from 'node:path'
+import { globSync } from 'tinyglobby'
+import { defineConfig, normalizePath } from 'vite'
+import tailwind from '@tailwindcss/vite'
+
+/**
+ * @returns {import('vite').Plugin}
+ */
+function BackendIntegrationExample() {
+ return {
+ name: 'backend-integration',
+ config() {
+ const projectRoot = import.meta.dirname
+ const sourceCodeDir = path.join(projectRoot, 'frontend')
+ const root = path.join(sourceCodeDir, 'entrypoints')
+ const outDir = path.relative(root, path.join(projectRoot, 'dist/dev'))
+
+ const entrypoints = globSync(`${normalizePath(root)}/**/*`, {
+ absolute: true,
+ expandDirectories: false,
+ onlyFiles: true,
+ }).map((filename) => [path.relative(root, filename), filename])
+
+ entrypoints.push(['tailwindcss-colors', 'tailwindcss/colors.js'])
+ entrypoints.push(['bar.css', path.resolve(projectRoot, './dir/foo.css')])
+ entrypoints.push([
+ 'bar.custom',
+ path.resolve(projectRoot, './dir/custom.css'),
+ ])
+
+ return {
+ server: {
+ // same port in playground/test-utils.ts
+ port: 5009,
+ strictPort: true,
+ origin: 'http://localhost:5009',
+ },
+ preview: {
+ port: 5009,
+ },
+ build: {
+ manifest: true,
+ outDir,
+ rollupOptions: {
+ input: Object.fromEntries(entrypoints),
+ },
+ },
+ root,
+ resolve: {
+ alias: {
+ '~': sourceCodeDir,
+ },
+ },
+ }
+ },
+ }
+}
+
+export default defineConfig({
+ base: '/dev/',
+ plugins: [BackendIntegrationExample(), tailwind()],
+})
diff --git a/playground/base-conflict/__tests__/base-conflict.spec.ts b/playground/base-conflict/__tests__/base-conflict.spec.ts
new file mode 100644
index 00000000000000..05c74866abaebd
--- /dev/null
+++ b/playground/base-conflict/__tests__/base-conflict.spec.ts
@@ -0,0 +1,6 @@
+import { expect, test } from 'vitest'
+import { page } from '~utils'
+
+test('absolute imports keep base prefix', async () => {
+ await expect.poll(() => page.textContent('.message')).toBe('absolute import')
+})
diff --git a/playground/base-conflict/index.html b/playground/base-conflict/index.html
new file mode 100644
index 00000000000000..9b5fb8c290bc5a
--- /dev/null
+++ b/playground/base-conflict/index.html
@@ -0,0 +1,11 @@
+
+
+
+
+ Base Conflict
+
+
+
+
+
+
diff --git a/playground/base-conflict/package.json b/playground/base-conflict/package.json
new file mode 100644
index 00000000000000..412ee3ce1f51cf
--- /dev/null
+++ b/playground/base-conflict/package.json
@@ -0,0 +1,12 @@
+{
+ "name": "@vitejs/test-base-conflict",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build",
+ "debug": "node --inspect-brk ../../packages/vite/bin/vite",
+ "preview": "vite preview"
+ }
+}
diff --git a/playground/base-conflict/src/importee.ts b/playground/base-conflict/src/importee.ts
new file mode 100644
index 00000000000000..1874734d97828b
--- /dev/null
+++ b/playground/base-conflict/src/importee.ts
@@ -0,0 +1 @@
+export default 'absolute import'
diff --git a/playground/base-conflict/src/main.ts b/playground/base-conflict/src/main.ts
new file mode 100644
index 00000000000000..53cf31aeb4cf17
--- /dev/null
+++ b/playground/base-conflict/src/main.ts
@@ -0,0 +1,3 @@
+import message from 'absolute-importer'
+
+document.querySelector('.message')!.textContent = message
diff --git a/playground/base-conflict/src/virtualModules.d.ts b/playground/base-conflict/src/virtualModules.d.ts
new file mode 100644
index 00000000000000..ead02941e4bb80
--- /dev/null
+++ b/playground/base-conflict/src/virtualModules.d.ts
@@ -0,0 +1,4 @@
+declare module 'absolute-importer' {
+ const msg: string
+ export default msg
+}
diff --git a/playground/base-conflict/vite.config.ts b/playground/base-conflict/vite.config.ts
new file mode 100644
index 00000000000000..178770db70cc09
--- /dev/null
+++ b/playground/base-conflict/vite.config.ts
@@ -0,0 +1,32 @@
+import { defineConfig, normalizePath } from 'vite'
+
+const rootPath = normalizePath(import.meta.dirname)
+const absoluteRoot = rootPath.startsWith('/') ? rootPath : `/${rootPath}`
+const [firstSegment] = absoluteRoot.split('/').filter(Boolean)
+const base = firstSegment ? `/${firstSegment}/` : '/'
+
+const VIRTUAL_MODULE_ID = 'absolute-importer'
+const RESOLVED_VIRTUAL_MODULE_ID = '\0' + VIRTUAL_MODULE_ID
+
+const absoluteDepPath = `${rootPath}/src/importee.ts`
+export default defineConfig({
+ base,
+ plugins: [
+ {
+ name: 'absolute-path-import',
+ resolveId(id) {
+ if (id === VIRTUAL_MODULE_ID) {
+ return RESOLVED_VIRTUAL_MODULE_ID
+ }
+ },
+ load(id) {
+ if (id === RESOLVED_VIRTUAL_MODULE_ID) {
+ return (
+ `import dep from ${JSON.stringify(absoluteDepPath)}\n` +
+ `export default dep`
+ )
+ }
+ },
+ },
+ ],
+})
diff --git a/playground/build-old/__tests__/build-old.spec.ts b/playground/build-old/__tests__/build-old.spec.ts
new file mode 100644
index 00000000000000..ea7da1e32ada88
--- /dev/null
+++ b/playground/build-old/__tests__/build-old.spec.ts
@@ -0,0 +1,22 @@
+import { describe, expect, test } from 'vitest'
+import { findAssetFile, isBuild, page } from '~utils'
+
+describe('syntax preserve', () => {
+ test('import.meta.url', async () => {
+ await expect.poll(() => page.textContent('.import-meta-url')).toBe('string')
+ })
+ test('dynamic import', async () => {
+ await expect.poll(() => page.textContent('.dynamic-import')).toBe('success')
+ })
+})
+
+describe('syntax is lowered', () => {
+ test('private field', async () => {
+ await expect.poll(() => page.textContent('.private-field')).toBe('private')
+
+ if (isBuild) {
+ const content = findAssetFile(/index-[-\w]{8}\.js/)
+ expect(content).not.toMatch(/this\.#\w+/)
+ }
+ })
+})
diff --git a/playground/build-old/dynamic.js b/playground/build-old/dynamic.js
new file mode 100644
index 00000000000000..739bb26e01b765
--- /dev/null
+++ b/playground/build-old/dynamic.js
@@ -0,0 +1 @@
+export default 'success'
diff --git a/playground/build-old/index.html b/playground/build-old/index.html
new file mode 100644
index 00000000000000..3dc75c83bd8206
--- /dev/null
+++ b/playground/build-old/index.html
@@ -0,0 +1,30 @@
+Build Old
+
+import meta url
+
+
+dynamic import
+
+
+private field
+
+
+
diff --git a/playground/build-old/package.json b/playground/build-old/package.json
new file mode 100644
index 00000000000000..695d5e6f28fbc7
--- /dev/null
+++ b/playground/build-old/package.json
@@ -0,0 +1,12 @@
+{
+ "name": "@vitejs/test-build-old",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build",
+ "debug": "node --inspect-brk ../../packages/vite/bin/vite",
+ "preview": "vite preview"
+ }
+}
diff --git a/playground/build-old/vite.config.js b/playground/build-old/vite.config.js
new file mode 100644
index 00000000000000..6c5d26db8c0ee7
--- /dev/null
+++ b/playground/build-old/vite.config.js
@@ -0,0 +1,8 @@
+import { defineConfig } from 'vite'
+
+export default defineConfig({
+ build: {
+ // old browsers only
+ target: ['chrome60'],
+ },
+})
diff --git a/playground/cli-module/__tests__/cli-module.spec.ts b/playground/cli-module/__tests__/cli-module.spec.ts
new file mode 100644
index 00000000000000..7eccaaa74b8578
--- /dev/null
+++ b/playground/cli-module/__tests__/cli-module.spec.ts
@@ -0,0 +1,26 @@
+import { expect, test } from 'vitest'
+import { port } from './serve'
+import { page } from '~utils'
+
+test('cli should work in "type":"module" package', async () => {
+ // this test uses a custom serve implementation, so regular helpers for browserLogs and goto don't work
+ // do the same thing manually
+ const logs = []
+ const onConsole = (msg) => {
+ logs.push(msg.text())
+ }
+ try {
+ page.on('console', onConsole)
+ await page.goto(`http://localhost:${port}/`)
+ expect(await page.textContent('.app')).toBe(
+ 'vite cli in "type":"module" package works!',
+ )
+ expect(
+ logs.some((msg) =>
+ msg.match('vite cli in "type":"module" package works!'),
+ ),
+ ).toBe(true)
+ } finally {
+ page.off('console', onConsole)
+ }
+})
diff --git a/playground/cli-module/__tests__/serve.ts b/playground/cli-module/__tests__/serve.ts
new file mode 100644
index 00000000000000..eff81c7725efb3
--- /dev/null
+++ b/playground/cli-module/__tests__/serve.ts
@@ -0,0 +1,156 @@
+// this is automatically detected by playground/vitestSetup.ts and will replace
+// the default e2e test serve behavior
+
+import { stripVTControlCharacters } from 'node:util'
+import { execaCommand } from 'execa'
+import kill from 'kill-port'
+import {
+ isBuild,
+ isWindows,
+ killProcess,
+ ports,
+ rootDir,
+ viteBinPath,
+} from '~utils'
+
+export const port = ports['cli-module']
+
+export async function serve() {
+ // collect stdout and stderr streams from child processes here to avoid interfering with regular vitest output
+ const streams = {
+ build: { out: [], err: [] },
+ server: { out: [], err: [] },
+ }
+ // helpers to collect streams
+ const collectStreams = (name, process) => {
+ process.stdout.on('data', (d) => streams[name].out.push(d.toString()))
+ process.stderr.on('data', (d) => streams[name].err.push(d.toString()))
+ }
+ const collectErrorStreams = (name, e) => {
+ e.stdout && streams[name].out.push(e.stdout)
+ e.stderr && streams[name].err.push(e.stderr)
+ }
+
+ // helper to output stream content on error
+ const printStreamsToConsole = async (name) => {
+ const std = streams[name]
+ if (std.out && std.out.length > 0) {
+ console.log(`stdout of ${name}\n${std.out.join('\n')}\n`)
+ }
+ if (std.err && std.err.length > 0) {
+ console.log(`stderr of ${name}\n${std.err.join('\n')}\n`)
+ }
+ }
+
+ // only run `vite build` when needed
+ if (isBuild) {
+ const buildCommand = `${viteBinPath} build`
+ try {
+ const buildProcess = execaCommand(buildCommand, {
+ cwd: rootDir,
+ stdio: 'pipe',
+ })
+ collectStreams('build', buildProcess)
+ await buildProcess
+ } catch (e) {
+ console.error(`error while executing cli command "${buildCommand}":`, e)
+ collectErrorStreams('build', e)
+ await printStreamsToConsole('build')
+ throw e
+ }
+ }
+
+ await kill(port)
+
+ // run `vite --port x` or `vite preview --port x` to start server
+ const viteServerArgs = ['--port', `${port}`, '--strict-port']
+ if (isBuild) {
+ viteServerArgs.unshift('preview')
+ }
+ const serverCommand = `${viteBinPath} ${viteServerArgs.join(' ')}`
+ const serverProcess = execaCommand(serverCommand, {
+ cwd: rootDir,
+ stdio: 'pipe',
+ forceKillAfterDelay: 3000,
+ })
+ collectStreams('server', serverProcess)
+
+ // close server helper, send SIGTERM followed by SIGKILL if needed, give up after 3sec
+ const close = async () => {
+ if (serverProcess) {
+ const timeoutError = `server process still alive after 3s`
+ try {
+ await killProcess(serverProcess)
+ await resolvedOrTimeout(serverProcess, 10000, timeoutError)
+ } catch (e) {
+ if (e === timeoutError || (!serverProcess.killed && !isWindows)) {
+ collectErrorStreams('server', e)
+ console.error(
+ `error while killing cli command "${serverCommand}":`,
+ e,
+ )
+ await printStreamsToConsole('server')
+ }
+ }
+ }
+ }
+
+ try {
+ await startedOnPort(serverProcess, port, 5173)
+ return { close }
+ } catch (e) {
+ collectErrorStreams('server', e)
+ console.error(`error while executing cli command "${serverCommand}":`, e)
+ await printStreamsToConsole('server')
+ try {
+ await close()
+ } catch (e1) {
+ console.error(
+ `error while killing cli command after failed execute "${serverCommand}":`,
+ e1,
+ )
+ }
+ }
+}
+
+// helper to validate that server was started on the correct port
+async function startedOnPort(serverProcess, port, timeout) {
+ let checkPort
+ const startedPromise = new Promise((resolve, reject) => {
+ checkPort = (data) => {
+ const str = stripVTControlCharacters(data.toString())
+ const match = str.match(
+ /http:\/\/(?:localhost|127\.0\.0\.1|\[::1\]):(\d{4})/,
+ )
+ if (match) {
+ const startedPort = parseInt(match[1], 10)
+ if (startedPort === port) {
+ resolve()
+ } else {
+ const msg = `server listens on port ${startedPort} instead of ${port}`
+ reject(msg)
+ }
+ }
+ }
+ serverProcess.stdout.on('data', checkPort)
+ })
+ return resolvedOrTimeout(
+ startedPromise,
+ timeout,
+ `failed to start within ${timeout}ms`,
+ ).finally(() => serverProcess.stdout.off('data', checkPort))
+}
+
+// helper function that rejects with errorMessage if promise isn't settled within ms
+async function resolvedOrTimeout(promise, ms, errorMessage) {
+ let timer
+ return Promise.race([
+ promise,
+ new Promise((_, reject) => {
+ timer = setTimeout(() => reject(errorMessage), ms)
+ }),
+ ]).finally(() => {
+ clearTimeout(timer)
+ timer = null
+ })
+}
diff --git a/packages/playground/cli-module/index.html b/playground/cli-module/index.html
similarity index 100%
rename from packages/playground/cli-module/index.html
rename to playground/cli-module/index.html
diff --git a/packages/playground/cli-module/index.js b/playground/cli-module/index.js
similarity index 100%
rename from packages/playground/cli-module/index.js
rename to playground/cli-module/index.js
diff --git a/playground/cli-module/package.json b/playground/cli-module/package.json
new file mode 100644
index 00000000000000..e281a6ecfedb8b
--- /dev/null
+++ b/playground/cli-module/package.json
@@ -0,0 +1,15 @@
+{
+ "name": "@vitejs/test-cli-module",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build",
+ "debug": "node --inspect-brk ../../packages/vite/bin/vite",
+ "serve": "vite preview"
+ },
+ "devDependencies": {
+ "url": "^0.11.4"
+ }
+}
diff --git a/playground/cli-module/vite.config.js b/playground/cli-module/vite.config.js
new file mode 100644
index 00000000000000..a6751c893ea244
--- /dev/null
+++ b/playground/cli-module/vite.config.js
@@ -0,0 +1,18 @@
+// eslint-disable-next-line n/prefer-node-protocol
+import { URL } from 'url'
+import { defineConfig } from 'vite'
+
+// make sure bundling works even if `url` refers to the locally installed
+// `url` package instead of the built-in `url` nodejs module
+globalThis.__test_url = URL
+
+export default defineConfig({
+ server: {
+ host: 'localhost',
+ },
+ build: {
+ //speed up build
+ minify: false,
+ target: 'esnext',
+ },
+})
diff --git a/playground/cli/__tests__/cli.spec.ts b/playground/cli/__tests__/cli.spec.ts
new file mode 100644
index 00000000000000..bd27984cd30259
--- /dev/null
+++ b/playground/cli/__tests__/cli.spec.ts
@@ -0,0 +1,41 @@
+import { expect, test } from 'vitest'
+import { port, streams } from './serve'
+import { editFile, isServe, page } from '~utils'
+
+test('cli should work', async () => {
+ // this test uses a custom serve implementation, so regular helpers for browserLogs and goto don't work
+ // do the same thing manually
+ const logs = []
+ const onConsole = (msg) => {
+ logs.push(msg.text())
+ }
+ try {
+ page.on('console', onConsole)
+ await page.goto(`http://localhost:${port}/`)
+
+ expect(await page.textContent('.app')).toBe('vite cli works!')
+ expect(logs.some((msg) => msg.match('vite cli works!'))).toBe(true)
+ } finally {
+ page.off('console', onConsole)
+ }
+})
+
+test.runIf(isServe)('should restart', async () => {
+ const logsLengthBeforeEdit = streams.server.out.length
+ editFile('./vite.config.js', (content) => content)
+ await expect
+ .poll(() => {
+ const logs = streams.server.out.slice(logsLengthBeforeEdit)
+ expect(logs).toEqual(
+ expect.arrayContaining([expect.stringMatching('server restarted')]),
+ )
+ // Don't reprint the server URLs as they are the same
+ expect(logs).not.toEqual(
+ expect.arrayContaining([expect.stringMatching('http://localhost')]),
+ )
+ expect(logs).not.toEqual(
+ expect.arrayContaining([expect.stringMatching('error')]),
+ )
+ })
+ .toSatisfy(() => true)
+})
diff --git a/playground/cli/__tests__/serve.ts b/playground/cli/__tests__/serve.ts
new file mode 100644
index 00000000000000..352fd5f887cd2b
--- /dev/null
+++ b/playground/cli/__tests__/serve.ts
@@ -0,0 +1,159 @@
+// this is automatically detected by playground/vitestSetup.ts and will replace
+// the default e2e test serve behavior
+
+import { stripVTControlCharacters } from 'node:util'
+import { execaCommand } from 'execa'
+import kill from 'kill-port'
+import {
+ isBuild,
+ isWindows,
+ killProcess,
+ ports,
+ rootDir,
+ viteBinPath,
+} from '~utils'
+
+export const port = ports.cli
+export const streams = {} as {
+ build: { out: string[]; err: string[] }
+ server: { out: string[]; err: string[] }
+}
+export async function serve() {
+ // collect stdout and stderr streams from child processes here to avoid interfering with regular vitest output
+ Object.assign(streams, {
+ build: { out: [], err: [] },
+ server: { out: [], err: [] },
+ })
+ // helpers to collect streams
+ const collectStreams = (name, process) => {
+ process.stdout.on('data', (d) => streams[name].out.push(d.toString()))
+ process.stderr.on('data', (d) => streams[name].err.push(d.toString()))
+ }
+ const collectErrorStreams = (name, e) => {
+ e.stdout && streams[name].out.push(e.stdout)
+ e.stderr && streams[name].err.push(e.stderr)
+ }
+
+ // helper to output stream content on error
+ const printStreamsToConsole = async (name) => {
+ const std = streams[name]
+ if (std.out && std.out.length > 0) {
+ console.log(`stdout of ${name}\n${std.out.join('\n')}\n`)
+ }
+ if (std.err && std.err.length > 0) {
+ console.log(`stderr of ${name}\n${std.err.join('\n')}\n`)
+ }
+ }
+
+ // only run `vite build` when needed
+ if (isBuild) {
+ const buildCommand = `${viteBinPath} build`
+ try {
+ const buildProcess = execaCommand(buildCommand, {
+ cwd: rootDir,
+ stdio: 'pipe',
+ })
+ collectStreams('build', buildProcess)
+ await buildProcess
+ } catch (e) {
+ console.error(`error while executing cli command "${buildCommand}":`, e)
+ collectErrorStreams('build', e)
+ await printStreamsToConsole('build')
+ throw e
+ }
+ }
+
+ await kill(port)
+
+ // run `vite --port x` or `vite preview --port x` to start server
+ const viteServerArgs = ['--port', `${port}`, '--strict-port']
+ if (isBuild) {
+ viteServerArgs.unshift('preview')
+ }
+ const serverCommand = `${viteBinPath} ${viteServerArgs.join(' ')}`
+ const serverProcess = execaCommand(serverCommand, {
+ cwd: rootDir,
+ stdio: 'pipe',
+ forceKillAfterDelay: 3000,
+ })
+ collectStreams('server', serverProcess)
+
+ // close server helper, send SIGTERM followed by SIGKILL if needed, give up after 3sec
+ const close = async () => {
+ if (serverProcess) {
+ const timeoutError = `server process still alive after 3s`
+ try {
+ await killProcess(serverProcess)
+ await resolvedOrTimeout(serverProcess, 5173, timeoutError)
+ } catch (e) {
+ if (e === timeoutError || (!serverProcess.killed && !isWindows)) {
+ collectErrorStreams('server', e)
+ console.error(
+ `error while killing cli command "${serverCommand}":`,
+ e,
+ )
+ await printStreamsToConsole('server')
+ }
+ }
+ }
+ }
+
+ try {
+ await startedOnPort(serverProcess, port, 5173)
+ return { close }
+ } catch (e) {
+ collectErrorStreams('server', e)
+ console.error(`error while executing cli command "${serverCommand}":`, e)
+ await printStreamsToConsole('server')
+ try {
+ await close()
+ } catch (e1) {
+ console.error(
+ `error while killing cli command after failed execute "${serverCommand}":`,
+ e1,
+ )
+ }
+ }
+}
+
+// helper to validate that server was started on the correct port
+async function startedOnPort(serverProcess, port, timeout) {
+ let checkPort
+ const startedPromise = new Promise((resolve, reject) => {
+ checkPort = (data) => {
+ const str = stripVTControlCharacters(data.toString())
+ const match = str.match(
+ /http:\/\/(?:localhost|127\.0\.0\.1|\[::1\]):(\d{4})/,
+ )
+ if (match) {
+ const startedPort = parseInt(match[1], 10)
+ if (startedPort === port) {
+ resolve()
+ } else {
+ const msg = `server listens on port ${startedPort} instead of ${port}`
+ reject(msg)
+ }
+ }
+ }
+ serverProcess.stdout.on('data', checkPort)
+ })
+ return resolvedOrTimeout(
+ startedPromise,
+ timeout,
+ `failed to start within ${timeout}ms`,
+ ).finally(() => serverProcess.stdout.off('data', checkPort))
+}
+
+// helper function that rejects with errorMessage if promise isn't settled within ms
+async function resolvedOrTimeout(promise, ms, errorMessage) {
+ let timer
+ return Promise.race([
+ promise,
+ new Promise((_, reject) => {
+ timer = setTimeout(() => reject(errorMessage), ms)
+ }),
+ ]).finally(() => {
+ clearTimeout(timer)
+ timer = null
+ })
+}
diff --git a/packages/playground/cli/index.html b/playground/cli/index.html
similarity index 100%
rename from packages/playground/cli/index.html
rename to playground/cli/index.html
diff --git a/packages/playground/cli/index.js b/playground/cli/index.js
similarity index 100%
rename from packages/playground/cli/index.js
rename to playground/cli/index.js
diff --git a/playground/cli/package.json b/playground/cli/package.json
new file mode 100644
index 00000000000000..09b97d52574733
--- /dev/null
+++ b/playground/cli/package.json
@@ -0,0 +1,12 @@
+{
+ "name": "@vitejs/test-cli",
+ "private": true,
+ "version": "0.0.0",
+ "type": "commonjs",
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build",
+ "debug": "node --inspect-brk ../../packages/vite/bin/vite",
+ "preview": "vite preview"
+ }
+}
diff --git a/playground/cli/vite.config.js b/playground/cli/vite.config.js
new file mode 100644
index 00000000000000..a5ffac7859b2f1
--- /dev/null
+++ b/playground/cli/vite.config.js
@@ -0,0 +1,15 @@
+import { defineConfig } from 'vite'
+
+export default defineConfig({
+ server: {
+ host: 'localhost',
+ headers: {
+ 'Cache-Control': 'no-store',
+ },
+ },
+ build: {
+ //speed up build
+ minify: false,
+ target: 'esnext',
+ },
+})
diff --git a/playground/client-reload/__tests__/client-reload.spec.ts b/playground/client-reload/__tests__/client-reload.spec.ts
new file mode 100644
index 00000000000000..0872a2e17d4294
--- /dev/null
+++ b/playground/client-reload/__tests__/client-reload.spec.ts
@@ -0,0 +1,75 @@
+import path from 'node:path'
+import { type ServerOptions, type ViteDevServer, createServer } from 'vite'
+import { afterEach, describe, expect, test } from 'vitest'
+import { hmrPorts, isServe, page, ports } from '~utils'
+
+let server: ViteDevServer
+
+afterEach(async () => {
+ await server?.close()
+})
+
+async function testClientReload(serverOptions: ServerOptions) {
+ // start server
+ server = await createServer({
+ root: path.resolve(import.meta.dirname, '..'),
+ logLevel: 'silent',
+ server: {
+ strictPort: true,
+ ...serverOptions,
+ },
+ })
+
+ await server.listen()
+ const serverUrl = server.resolvedUrls.local[0]
+
+ // open page and wait for connection
+ const connectedPromise = page.waitForEvent('console', {
+ predicate: (message) => message.text().includes('[vite] connected.'),
+ timeout: 5000,
+ })
+ await page.goto(serverUrl)
+ await connectedPromise
+
+ // input state
+ await page.locator('input').fill('hello')
+
+ // restart and wait for reconnection after reload
+ const reConnectedPromise = page.waitForEvent('console', {
+ predicate: (message) => message.text().includes('[vite] connected.'),
+ timeout: 5000,
+ })
+ await server.restart()
+ await reConnectedPromise
+ expect(await page.textContent('input')).toBe('')
+}
+
+describe.runIf(isServe)('client-reload', () => {
+ test('default', async () => {
+ await testClientReload({
+ port: ports['client-reload'],
+ })
+ })
+
+ test('custom hmr port', async () => {
+ await testClientReload({
+ port: ports['client-reload/hmr-port'],
+ hmr: {
+ port: hmrPorts['client-reload/hmr-port'],
+ },
+ })
+ })
+
+ test('custom hmr port and cross origin isolation', async () => {
+ await testClientReload({
+ port: ports['client-reload/cross-origin'],
+ hmr: {
+ port: hmrPorts['client-reload/cross-origin'],
+ },
+ headers: {
+ 'Cross-Origin-Embedder-Policy': 'require-corp',
+ 'Cross-Origin-Opener-Policy': 'same-origin',
+ },
+ })
+ })
+})
diff --git a/playground/client-reload/__tests__/serve.ts b/playground/client-reload/__tests__/serve.ts
new file mode 100644
index 00000000000000..1d33d8064a44b4
--- /dev/null
+++ b/playground/client-reload/__tests__/serve.ts
@@ -0,0 +1,6 @@
+// do nothing here since server is managed inside spec
+export async function serve(): Promise<{ close(): Promise }> {
+ return {
+ close: () => Promise.resolve(),
+ }
+}
diff --git a/playground/client-reload/index.html b/playground/client-reload/index.html
new file mode 100644
index 00000000000000..7e78f23e2d5f54
--- /dev/null
+++ b/playground/client-reload/index.html
@@ -0,0 +1,4 @@
+
+ Test Client Reload
+
+
diff --git a/playground/client-reload/package.json b/playground/client-reload/package.json
new file mode 100644
index 00000000000000..a6fa570c64ffe7
--- /dev/null
+++ b/playground/client-reload/package.json
@@ -0,0 +1,12 @@
+{
+ "name": "@vitejs/test-client-reload",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "scripts": {
+ "debug": "node --inspect-brk ../../packages/vite/bin/vite",
+ "dev": "vite",
+ "build": "vite build",
+ "preview": "vite preview"
+ }
+}
diff --git a/playground/client-reload/vite.config.ts b/playground/client-reload/vite.config.ts
new file mode 100644
index 00000000000000..4c9c4be6ba0c82
--- /dev/null
+++ b/playground/client-reload/vite.config.ts
@@ -0,0 +1,5 @@
+import { defineConfig } from 'vite'
+
+export default defineConfig({
+ server: {},
+})
diff --git a/playground/csp/__tests__/csp.spec.ts b/playground/csp/__tests__/csp.spec.ts
new file mode 100644
index 00000000000000..f0c8e510fbf87c
--- /dev/null
+++ b/playground/csp/__tests__/csp.spec.ts
@@ -0,0 +1,45 @@
+import { expect, test } from 'vitest'
+import { getColor, page } from '~utils'
+
+test('linked css', async () => {
+ expect(await getColor('.linked')).toBe('blue')
+})
+
+test('inline style tag', async () => {
+ expect(await getColor('.inline')).toBe('green')
+})
+
+test('imported css', async () => {
+ expect(await getColor('.from-js')).toBe('blue')
+})
+
+test('dynamic css', async () => {
+ expect(await getColor('.dynamic')).toBe('red')
+})
+
+test('script tag', async () => {
+ await expect.poll(() => page.textContent('.js')).toBe('js: ok')
+})
+
+test('dynamic js', async () => {
+ await expect
+ .poll(() => page.textContent('.dynamic-js'))
+ .toBe('dynamic-js: ok')
+})
+
+test('inline js', async () => {
+ await expect.poll(() => page.textContent('.inline-js')).toBe('inline-js: ok')
+})
+
+test('nonce attributes are not repeated', async () => {
+ const htmlSource = await page.content()
+ expect(htmlSource).not.toContain(/nonce=""[^>]*nonce=""/)
+ await expect
+ .poll(() => page.textContent('.double-nonce-js'))
+ .toBe('double-nonce-js: ok')
+})
+
+test('meta[property=csp-nonce] is injected', async () => {
+ const meta = await page.$('meta[property=csp-nonce]')
+ expect(await (await meta.getProperty('nonce')).jsonValue()).not.toBe('')
+})
diff --git a/playground/csp/dynamic.css b/playground/csp/dynamic.css
new file mode 100644
index 00000000000000..ca5140e1c23d94
--- /dev/null
+++ b/playground/csp/dynamic.css
@@ -0,0 +1,3 @@
+.dynamic {
+ color: red;
+}
diff --git a/playground/csp/dynamic.js b/playground/csp/dynamic.js
new file mode 100644
index 00000000000000..3d3e3a413e5677
--- /dev/null
+++ b/playground/csp/dynamic.js
@@ -0,0 +1,3 @@
+import './dynamic.css'
+
+document.querySelector('.dynamic-js').textContent = 'dynamic-js: ok'
diff --git a/playground/csp/from-js.css b/playground/csp/from-js.css
new file mode 100644
index 00000000000000..fb48429dc60ab4
--- /dev/null
+++ b/playground/csp/from-js.css
@@ -0,0 +1,3 @@
+.from-js {
+ color: blue;
+}
diff --git a/playground/csp/index.html b/playground/csp/index.html
new file mode 100644
index 00000000000000..45a508e76a2cd3
--- /dev/null
+++ b/playground/csp/index.html
@@ -0,0 +1,23 @@
+
+
+
+direct
+inline
+from-js
+dynamic
+js: error
+dynamic-js: error
+inline-js: error
+double-nonce-js: error
+
+
diff --git a/playground/csp/index.js b/playground/csp/index.js
new file mode 100644
index 00000000000000..465359baca8297
--- /dev/null
+++ b/playground/csp/index.js
@@ -0,0 +1,5 @@
+import './from-js.css'
+
+document.querySelector('.js').textContent = 'js: ok'
+
+import('./dynamic.js')
diff --git a/playground/csp/linked.css b/playground/csp/linked.css
new file mode 100644
index 00000000000000..51636e6cfad81f
--- /dev/null
+++ b/playground/csp/linked.css
@@ -0,0 +1,3 @@
+.linked {
+ color: blue;
+}
diff --git a/playground/csp/package.json b/playground/csp/package.json
new file mode 100644
index 00000000000000..e8a834d93abd25
--- /dev/null
+++ b/playground/csp/package.json
@@ -0,0 +1,12 @@
+{
+ "name": "@vitejs/test-csp",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "scripts": {
+ "debug": "node --inspect-brk ../../packages/vite/bin/vite",
+ "dev": "vite",
+ "build": "vite build",
+ "preview": "vite preview"
+ }
+}
diff --git a/playground/csp/vite.config.js b/playground/csp/vite.config.js
new file mode 100644
index 00000000000000..6b255abdd30df8
--- /dev/null
+++ b/playground/csp/vite.config.js
@@ -0,0 +1,67 @@
+import fs from 'node:fs/promises'
+import path from 'node:path'
+import crypto from 'node:crypto'
+import { defineConfig } from 'vite'
+
+const noncePlaceholder = '#$NONCE$#'
+const createNonce = () => crypto.randomBytes(16).toString('base64')
+
+/**
+ * @param {import('node:http').ServerResponse} res
+ * @param {string} nonce
+ */
+const setNonceHeader = (res, nonce) => {
+ res.setHeader(
+ 'Content-Security-Policy',
+ `default-src 'nonce-${nonce}'; connect-src 'self'`,
+ )
+}
+
+/**
+ * @param {string} file
+ * @param {(input: string, originalUrl: string) => Promise} transform
+ * @returns {import('vite').Connect.NextHandleFunction}
+ */
+const createMiddleware = (file, transform) => async (req, res) => {
+ const nonce = createNonce()
+ setNonceHeader(res, nonce)
+ const content = await fs.readFile(
+ path.join(import.meta.dirname, file),
+ 'utf-8',
+ )
+ const transformedContent = await transform(content, req.originalUrl)
+ res.setHeader('Content-Type', 'text/html')
+ res.end(transformedContent.replaceAll(noncePlaceholder, nonce))
+}
+
+export default defineConfig({
+ plugins: [
+ {
+ name: 'nonce-inject',
+ config() {
+ return {
+ appType: 'custom',
+ html: {
+ cspNonce: noncePlaceholder,
+ },
+ }
+ },
+ configureServer({ transformIndexHtml, middlewares }) {
+ return () => {
+ middlewares.use(
+ createMiddleware('./index.html', (input, originalUrl) =>
+ transformIndexHtml(originalUrl, input),
+ ),
+ )
+ }
+ },
+ configurePreviewServer({ middlewares }) {
+ return () => {
+ middlewares.use(
+ createMiddleware('./dist/index.html', async (input) => input),
+ )
+ }
+ },
+ },
+ ],
+})
diff --git a/playground/css-codesplit-cjs/__tests__/css-codesplit-cjs.spec.ts b/playground/css-codesplit-cjs/__tests__/css-codesplit-cjs.spec.ts
new file mode 100644
index 00000000000000..9efe9d2ef5a543
--- /dev/null
+++ b/playground/css-codesplit-cjs/__tests__/css-codesplit-cjs.spec.ts
@@ -0,0 +1,21 @@
+import { describe, expect, test } from 'vitest'
+import { findAssetFile, getColor, isBuild, readManifest } from '~utils'
+
+test.skip('should load both stylesheets', async () => {
+ expect(await getColor('h1')).toBe('red')
+ expect(await getColor('h2')).toBe('blue')
+})
+
+describe.runIf(isBuild).skip('build', () => {
+ test('should remove empty chunk', async () => {
+ expect(findAssetFile(/style.*\.js$/)).toBeUndefined()
+ expect(findAssetFile('main.*.js$')).toMatch(`/* empty css`)
+ expect(findAssetFile('other.*.js$')).toMatch(`/* empty css`)
+ })
+
+ test('should generate correct manifest', async () => {
+ const manifest = readManifest()
+ expect(manifest['index.html'].css.length).toBe(2)
+ expect(manifest['other.js'].css.length).toBe(1)
+ })
+})
diff --git a/packages/playground/css-codesplit-cjs/index.html b/playground/css-codesplit-cjs/index.html
similarity index 100%
rename from packages/playground/css-codesplit-cjs/index.html
rename to playground/css-codesplit-cjs/index.html
diff --git a/packages/playground/css-codesplit-cjs/main.css b/playground/css-codesplit-cjs/main.css
similarity index 100%
rename from packages/playground/css-codesplit-cjs/main.css
rename to playground/css-codesplit-cjs/main.css
diff --git a/playground/css-codesplit-cjs/main.js b/playground/css-codesplit-cjs/main.js
new file mode 100644
index 00000000000000..766759f9bd79f4
--- /dev/null
+++ b/playground/css-codesplit-cjs/main.js
@@ -0,0 +1,5 @@
+import './style.css'
+import './main.css'
+
+document.getElementById('app').innerHTML =
+ `This should be red This should be blue `
diff --git a/packages/playground/css-codesplit-cjs/other.js b/playground/css-codesplit-cjs/other.js
similarity index 100%
rename from packages/playground/css-codesplit-cjs/other.js
rename to playground/css-codesplit-cjs/other.js
diff --git a/playground/css-codesplit-cjs/package.json b/playground/css-codesplit-cjs/package.json
new file mode 100644
index 00000000000000..e305007adfcb98
--- /dev/null
+++ b/playground/css-codesplit-cjs/package.json
@@ -0,0 +1,12 @@
+{
+ "name": "@vitejs/test-css-codesplit-cjs",
+ "private": true,
+ "version": "0.0.0",
+ "type": "commonjs",
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build",
+ "debug": "node --inspect-brk ../../packages/vite/bin/vite",
+ "preview": "vite preview"
+ }
+}
diff --git a/packages/playground/css-codesplit-cjs/style.css b/playground/css-codesplit-cjs/style.css
similarity index 100%
rename from packages/playground/css-codesplit-cjs/style.css
rename to playground/css-codesplit-cjs/style.css
diff --git a/playground/css-codesplit-cjs/vite.config.js b/playground/css-codesplit-cjs/vite.config.js
new file mode 100644
index 00000000000000..243476ae5bdcb0
--- /dev/null
+++ b/playground/css-codesplit-cjs/vite.config.js
@@ -0,0 +1,21 @@
+import { resolve } from 'node:path'
+import { defineConfig } from 'vite'
+
+export default defineConfig({
+ build: {
+ outDir: './dist',
+ manifest: true,
+ rollupOptions: {
+ input: {
+ main: resolve(import.meta.dirname, './index.html'),
+ other: resolve(import.meta.dirname, './other.js'),
+ },
+ treeshake: false,
+ output: {
+ format: 'cjs',
+ // freeze: false,
+ externalLiveBindings: false,
+ },
+ },
+ },
+})
diff --git a/playground/css-codesplit/__tests__/css-codesplit-consistent.spec.ts b/playground/css-codesplit/__tests__/css-codesplit-consistent.spec.ts
new file mode 100644
index 00000000000000..9b112e2f1548fa
--- /dev/null
+++ b/playground/css-codesplit/__tests__/css-codesplit-consistent.spec.ts
@@ -0,0 +1,15 @@
+import { beforeEach, describe, expect, test } from 'vitest'
+import { findAssetFile, isBuild, startDefaultServe } from '~utils'
+
+beforeEach(async () => {
+ await startDefaultServe()
+})
+
+for (let i = 0; i < 5; i++) {
+ describe.runIf(isBuild)('css-codesplit build', () => {
+ test('should be consistent with same content', () => {
+ expect(findAssetFile(/style-.+\.css/)).toBeUndefined()
+ expect(findAssetFile(/style2-.+\.css/)).toMatch('h2{color:#00f}')
+ })
+ })
+}
diff --git a/playground/css-codesplit/__tests__/css-codesplit.spec.ts b/playground/css-codesplit/__tests__/css-codesplit.spec.ts
new file mode 100644
index 00000000000000..904da085bb569d
--- /dev/null
+++ b/playground/css-codesplit/__tests__/css-codesplit.spec.ts
@@ -0,0 +1,69 @@
+import { describe, expect, test } from 'vitest'
+import {
+ findAssetFile,
+ getColor,
+ isBuild,
+ listAssets,
+ page,
+ readManifest,
+} from '~utils'
+
+test('should load all stylesheets', async () => {
+ expect(await getColor('h1')).toBe('red')
+ expect(await getColor('h2')).toBe('blue')
+ expect(await getColor('.dynamic')).toBe('green')
+ expect(await getColor('.async-js')).toBe('blue')
+ expect(await getColor('.chunk')).toBe('magenta')
+})
+
+test('should load dynamic import with inline', async () => {
+ const css = await page.textContent('.dynamic-inline')
+ expect(css).toMatch('.inline')
+
+ expect(await getColor('.inline')).not.toBe('yellow')
+})
+
+test('should load dynamic import with module', async () => {
+ const css = await page.textContent('.dynamic-module')
+ expect(css).toMatch('_mod_')
+
+ expect(await getColor('.mod')).toBe('yellow')
+})
+
+test('style order should be consistent when style tag is inserted by JS', async () => {
+ expect(await getColor('.order-bulk')).toBe('orange')
+ await page.click('.order-bulk-update')
+ await expect.poll(() => getColor('.order-bulk')).toBe('green')
+})
+
+describe.runIf(isBuild)('build', () => {
+ test('should remove empty chunk', async () => {
+ expect(findAssetFile(/style-.*\.js$/)).toBeUndefined()
+ expect(findAssetFile('main.*.js$')).toMatch(`/* empty css`)
+ expect(findAssetFile('other.*.js$')).toMatch(`/* empty css`)
+ expect(findAssetFile(/async-[-\w]{8}\.js$/)).toBeUndefined()
+
+ const assets = listAssets()
+ expect(assets).not.toContainEqual(
+ expect.stringMatching(/async-js-[-\w]{8}\.js$/),
+ )
+ })
+
+ test('should remove empty chunk, HTML without JS', async () => {
+ const sharedCSSWithJSChunk = findAssetFile('shared-css-with-js.*.js$')
+ expect(sharedCSSWithJSChunk).toMatch(`/* empty css`)
+ // there are functions and modules in the src code that should be tree-shaken
+ expect(sharedCSSWithJSChunk).not.toMatch('function')
+ expect(sharedCSSWithJSChunk).not.toMatch(/import(?!\s*".\/modulepreload)/)
+ })
+
+ test('should generate correct manifest', async () => {
+ const manifest = readManifest()
+ expect(manifest['index.html'].css.length).toBe(2)
+ expect(manifest['other.js'].css.length).toBe(1)
+ })
+
+ test('should not mark a css chunk with ?url and normal import as pure css chunk', () => {
+ expect(findAssetFile(/chunk-.*\.js$/)).toBeTruthy()
+ })
+})
diff --git a/playground/css-codesplit/async-js.css b/playground/css-codesplit/async-js.css
new file mode 100644
index 00000000000000..ed61a7f513c277
--- /dev/null
+++ b/playground/css-codesplit/async-js.css
@@ -0,0 +1,3 @@
+.async-js {
+ color: blue;
+}
diff --git a/playground/css-codesplit/async-js.js b/playground/css-codesplit/async-js.js
new file mode 100644
index 00000000000000..2ce31a1e741d2d
--- /dev/null
+++ b/playground/css-codesplit/async-js.js
@@ -0,0 +1,2 @@
+// a JS file that becomes an empty file but imports CSS files
+import './async-js.css'
diff --git a/playground/css-codesplit/async.css b/playground/css-codesplit/async.css
new file mode 100644
index 00000000000000..4902b2e7bee811
--- /dev/null
+++ b/playground/css-codesplit/async.css
@@ -0,0 +1,3 @@
+.dynamic {
+ color: green;
+}
diff --git a/playground/css-codesplit/chunk.css b/playground/css-codesplit/chunk.css
new file mode 100644
index 00000000000000..a8aa47c2d96134
--- /dev/null
+++ b/playground/css-codesplit/chunk.css
@@ -0,0 +1,3 @@
+.chunk {
+ color: magenta;
+}
diff --git a/playground/css-codesplit/index.html b/playground/css-codesplit/index.html
new file mode 100644
index 00000000000000..38885fa7ccb5ed
--- /dev/null
+++ b/playground/css-codesplit/index.html
@@ -0,0 +1,19 @@
+This should be red
+This should be blue
+
+This should be green
+This should be blue
+This should not be yellow
+
+This should be yellow
+
+
+
+ This should be orange
+ change to green
+
+
+This should be magenta
+
+
+
diff --git a/playground/css-codesplit/inline.css b/playground/css-codesplit/inline.css
new file mode 100644
index 00000000000000..b2a2b5f1ead51f
--- /dev/null
+++ b/playground/css-codesplit/inline.css
@@ -0,0 +1,3 @@
+.inline {
+ color: yellow;
+}
diff --git a/packages/playground/css-codesplit/main.css b/playground/css-codesplit/main.css
similarity index 100%
rename from packages/playground/css-codesplit/main.css
rename to playground/css-codesplit/main.css
diff --git a/playground/css-codesplit/main.js b/playground/css-codesplit/main.js
new file mode 100644
index 00000000000000..ec266fa003156d
--- /dev/null
+++ b/playground/css-codesplit/main.js
@@ -0,0 +1,23 @@
+import './style.css'
+import './main.css'
+import './order'
+
+import './chunk.css'
+import chunkCssUrl from './chunk.css?url'
+
+// use this to not treeshake
+globalThis.__test_chunkCssUrl = chunkCssUrl
+
+import('./async.css')
+import('./async-js')
+
+import('./inline.css?inline').then((css) => {
+ document.querySelector('.dynamic-inline').textContent = css.default
+})
+
+import('./mod.module.css').then((css) => {
+ document.querySelector('.dynamic-module').textContent = JSON.stringify(
+ css.default,
+ )
+ document.querySelector('.mod').classList.add(css.default.mod)
+})
diff --git a/playground/css-codesplit/mod.module.css b/playground/css-codesplit/mod.module.css
new file mode 100644
index 00000000000000..7f84410485a32c
--- /dev/null
+++ b/playground/css-codesplit/mod.module.css
@@ -0,0 +1,3 @@
+.mod {
+ color: yellow;
+}
diff --git a/playground/css-codesplit/order/base.css b/playground/css-codesplit/order/base.css
new file mode 100644
index 00000000000000..a08c84388f2079
--- /dev/null
+++ b/playground/css-codesplit/order/base.css
@@ -0,0 +1,3 @@
+.order-bulk {
+ color: blue;
+}
diff --git a/playground/css-codesplit/order/dynamic.css b/playground/css-codesplit/order/dynamic.css
new file mode 100644
index 00000000000000..f460d283759a88
--- /dev/null
+++ b/playground/css-codesplit/order/dynamic.css
@@ -0,0 +1,3 @@
+.order-bulk {
+ color: green;
+}
diff --git a/playground/css-codesplit/order/index.js b/playground/css-codesplit/order/index.js
new file mode 100644
index 00000000000000..dab4e8e5962b11
--- /dev/null
+++ b/playground/css-codesplit/order/index.js
@@ -0,0 +1,6 @@
+import './insert' // inserts "color: orange"
+import './base.css' // includes "color: blue"
+
+document.querySelector('.order-bulk-update').addEventListener('click', () => {
+ import('./dynamic.css') // includes "color: green"
+})
diff --git a/playground/css-codesplit/order/insert.js b/playground/css-codesplit/order/insert.js
new file mode 100644
index 00000000000000..2ccda105650412
--- /dev/null
+++ b/playground/css-codesplit/order/insert.js
@@ -0,0 +1,3 @@
+const style = document.createElement('style')
+style.textContent = '.order-bulk { color: orange; }'
+document.head.appendChild(style)
diff --git a/playground/css-codesplit/other.js b/playground/css-codesplit/other.js
new file mode 100644
index 00000000000000..4560c4d53c29ac
--- /dev/null
+++ b/playground/css-codesplit/other.js
@@ -0,0 +1,6 @@
+import './style.css'
+import './chunk.css'
+import chunkCssUrl from './chunk.css?url'
+
+// use this to not treeshake
+globalThis.__test_chunkCssUrl = chunkCssUrl
diff --git a/playground/css-codesplit/package.json b/playground/css-codesplit/package.json
new file mode 100644
index 00000000000000..f7edd868783ebf
--- /dev/null
+++ b/playground/css-codesplit/package.json
@@ -0,0 +1,12 @@
+{
+ "name": "@vitejs/test-css-codesplit",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build",
+ "debug": "node --inspect-brk ../../packages/vite/bin/vite",
+ "preview": "vite preview"
+ }
+}
diff --git a/playground/css-codesplit/shared-css-empty-1.js b/playground/css-codesplit/shared-css-empty-1.js
new file mode 100644
index 00000000000000..80636d362c52d5
--- /dev/null
+++ b/playground/css-codesplit/shared-css-empty-1.js
@@ -0,0 +1,4 @@
+function shouldBeTreeshaken_1() {
+ // This function should be treeshaken, even if { moduleSideEffects: 'no-treeshake' }
+ // was used in the JS corresponding to the HTML entrypoint.
+}
diff --git a/playground/css-codesplit/shared-css-empty-2.js b/playground/css-codesplit/shared-css-empty-2.js
new file mode 100644
index 00000000000000..7ce6d30628268d
--- /dev/null
+++ b/playground/css-codesplit/shared-css-empty-2.js
@@ -0,0 +1,4 @@
+export default function shouldBeTreeshaken_2() {
+ // This function should be treeshaken, even if { moduleSideEffects: 'no-treeshake' }
+ // was used in the JS corresponding to the HTML entrypoint.
+}
diff --git a/playground/css-codesplit/shared-css-main.js b/playground/css-codesplit/shared-css-main.js
new file mode 100644
index 00000000000000..639861b66321f9
--- /dev/null
+++ b/playground/css-codesplit/shared-css-main.js
@@ -0,0 +1,10 @@
+import shouldTreeshake from './shared-css-empty-2.js'
+document.querySelector('#app').innerHTML = `
+
+
Shared CSS, with JS
+
+`
+function shouldBeTreeshaken_0() {
+ // This function should be treeshaken, even if { moduleSideEffects: 'no-treeshake' }
+ // was used in the JS corresponding to the HTML entrypoint.
+}
diff --git a/playground/css-codesplit/shared-css-no-js.html b/playground/css-codesplit/shared-css-no-js.html
new file mode 100644
index 00000000000000..27c666af881f15
--- /dev/null
+++ b/playground/css-codesplit/shared-css-no-js.html
@@ -0,0 +1,4 @@
+
+
+ Share CSS, no JS
+
diff --git a/packages/playground/html/nested/nested.css b/playground/css-codesplit/shared-css-theme.css
similarity index 100%
rename from packages/playground/html/nested/nested.css
rename to playground/css-codesplit/shared-css-theme.css
diff --git a/playground/css-codesplit/shared-css-with-js.html b/playground/css-codesplit/shared-css-with-js.html
new file mode 100644
index 00000000000000..aaa856f2c6c5ef
--- /dev/null
+++ b/playground/css-codesplit/shared-css-with-js.html
@@ -0,0 +1,6 @@
+
+
+
+
+ Replaced by shared-css-main.js
+
diff --git a/packages/playground/css-codesplit/style.css b/playground/css-codesplit/style.css
similarity index 100%
rename from packages/playground/css-codesplit/style.css
rename to playground/css-codesplit/style.css
diff --git a/playground/css-codesplit/style2.css b/playground/css-codesplit/style2.css
new file mode 100644
index 00000000000000..2b4bb3671e654b
--- /dev/null
+++ b/playground/css-codesplit/style2.css
@@ -0,0 +1,3 @@
+h2 {
+ color: blue;
+}
diff --git a/playground/css-codesplit/style2.js b/playground/css-codesplit/style2.js
new file mode 100644
index 00000000000000..ab7ebb9eb632a6
--- /dev/null
+++ b/playground/css-codesplit/style2.js
@@ -0,0 +1 @@
+import './style2.css'
diff --git a/playground/css-codesplit/vite.config.js b/playground/css-codesplit/vite.config.js
new file mode 100644
index 00000000000000..064f5d8487371b
--- /dev/null
+++ b/playground/css-codesplit/vite.config.js
@@ -0,0 +1,36 @@
+import { resolve } from 'node:path'
+import { defineConfig } from 'vite'
+
+const dirname = import.meta.dirname
+
+export default defineConfig({
+ build: {
+ manifest: true,
+ rollupOptions: {
+ input: {
+ main: resolve(dirname, './index.html'),
+ other: resolve(dirname, './other.js'),
+ style2: resolve(dirname, './style2.js'),
+ 'shared-css-with-js': resolve(dirname, 'shared-css-with-js.html'),
+ 'shared-css-no-js': resolve(dirname, 'shared-css-no-js.html'),
+ },
+ output: {
+ // manualChunks(id) {
+ // // make `chunk.css` it's own chunk for easier testing of pure css chunks
+ // if (id.includes('chunk.css')) {
+ // return 'chunk'
+ // }
+ // },
+ codeSplitting: {
+ groups: [
+ // make `chunk.css` it's own chunk for easier testing of pure css chunks
+ {
+ name: 'chunk',
+ test: 'chunk.css',
+ },
+ ],
+ },
+ },
+ },
+ },
+})
diff --git a/playground/css-dynamic-import/__tests__/css-dynamic-import.spec.ts b/playground/css-dynamic-import/__tests__/css-dynamic-import.spec.ts
new file mode 100644
index 00000000000000..4b473d985f136a
--- /dev/null
+++ b/playground/css-dynamic-import/__tests__/css-dynamic-import.spec.ts
@@ -0,0 +1,93 @@
+import type { InlineConfig } from 'vite'
+import { build, createServer, preview } from 'vite'
+import { expect, test } from 'vitest'
+import { getColor, isBuild, isServe, page, ports, rootDir } from '~utils'
+
+const baseOptions = [
+ { base: '', label: 'relative' },
+ { base: '/', label: 'absolute' },
+]
+
+const getConfig = (base: string): InlineConfig => ({
+ base,
+ root: rootDir,
+ logLevel: 'silent',
+ server: { port: ports['css/dynamic-import'] },
+ preview: { port: ports['css/dynamic-import'] },
+ build: { assetsInlineLimit: 0 },
+})
+
+async function withBuild(base: string, fn: () => Promise) {
+ const config = getConfig(base)
+ await build(config)
+ const server = await preview(config)
+
+ try {
+ await page.goto(server.resolvedUrls.local[0])
+ await fn()
+ } finally {
+ server.httpServer.close()
+ }
+}
+
+async function withServe(base: string, fn: () => Promise) {
+ const config = getConfig(base)
+ const server = await createServer(config)
+ await server.listen()
+
+ try {
+ await page.goto(server.resolvedUrls.local[0])
+ await fn()
+ } finally {
+ await page.goto('about:blank') // move to a different page to avoid auto-refresh after server start
+ await server.close()
+ }
+}
+
+async function getLinks() {
+ const links = await page.$$('link')
+ return await Promise.all(
+ links.map((handle) => {
+ return handle.evaluate((link) => ({
+ pathname: new URL(link.href).pathname,
+ rel: link.rel,
+ as: link.as,
+ }))
+ }),
+ )
+}
+
+baseOptions.forEach(({ base, label }) => {
+ test.runIf(isBuild)(
+ `doesn't duplicate dynamically imported css files when built with ${label} base`,
+ async () => {
+ await withBuild(base, async () => {
+ await page.waitForSelector('.loaded', { state: 'attached' })
+
+ expect(await getColor('.css-dynamic-import')).toBe('green')
+ const linkUrls = (await getLinks()).map((link) => link.pathname)
+ const uniqueLinkUrls = [...new Set(linkUrls)]
+ expect(linkUrls).toStrictEqual(uniqueLinkUrls)
+ })
+ },
+ )
+
+ test.runIf(isServe)(
+ `doesn't duplicate dynamically imported css files when served with ${label} base`,
+ async () => {
+ await withServe(base, async () => {
+ await page.waitForSelector('.loaded', { state: 'attached' })
+
+ expect(await getColor('.css-dynamic-import')).toBe('green')
+ // in serve there is no preloading
+ expect(await getLinks()).toEqual([
+ {
+ pathname: '/dynamic.css',
+ rel: 'preload',
+ as: 'style',
+ },
+ ])
+ })
+ },
+ )
+})
diff --git a/playground/css-dynamic-import/__tests__/serve.ts b/playground/css-dynamic-import/__tests__/serve.ts
new file mode 100644
index 00000000000000..48f55eee4f48ec
--- /dev/null
+++ b/playground/css-dynamic-import/__tests__/serve.ts
@@ -0,0 +1,10 @@
+// this is automatically detected by playground/vitestSetup.ts and will replace
+// the default e2e test serve behavior
+
+// The server is started in the test, so we need to have a custom serve
+// function or a default server will be created
+export async function serve() {
+ return {
+ close: () => Promise.resolve(),
+ }
+}
diff --git a/playground/css-dynamic-import/dynamic.css b/playground/css-dynamic-import/dynamic.css
new file mode 100644
index 00000000000000..6212a63c31fa19
--- /dev/null
+++ b/playground/css-dynamic-import/dynamic.css
@@ -0,0 +1,3 @@
+.css-dynamic-import {
+ color: green;
+}
diff --git a/playground/css-dynamic-import/dynamic.js b/playground/css-dynamic-import/dynamic.js
new file mode 100644
index 00000000000000..0d0aeb3aec229c
--- /dev/null
+++ b/playground/css-dynamic-import/dynamic.js
@@ -0,0 +1,6 @@
+import './dynamic.css'
+
+export const lazyLoad = async () => {
+ await import('./static.js')
+ document.body.classList.add('loaded')
+}
diff --git a/playground/css-dynamic-import/index.html b/playground/css-dynamic-import/index.html
new file mode 100644
index 00000000000000..d9f9fedbbda752
--- /dev/null
+++ b/playground/css-dynamic-import/index.html
@@ -0,0 +1,3 @@
+This should be green
+
+
diff --git a/playground/css-dynamic-import/index.js b/playground/css-dynamic-import/index.js
new file mode 100644
index 00000000000000..5a0c724da737db
--- /dev/null
+++ b/playground/css-dynamic-import/index.js
@@ -0,0 +1,10 @@
+import './static.js'
+
+const link = document.head.appendChild(document.createElement('link'))
+link.rel = 'preload'
+link.as = 'style'
+link.href = new URL('./dynamic.css', import.meta.url).href
+
+import('./dynamic.js').then(async ({ lazyLoad }) => {
+ await lazyLoad()
+})
diff --git a/playground/css-dynamic-import/package.json b/playground/css-dynamic-import/package.json
new file mode 100644
index 00000000000000..2b5339f8c72760
--- /dev/null
+++ b/playground/css-dynamic-import/package.json
@@ -0,0 +1,6 @@
+{
+ "name": "@vitejs/test-css-dynamic-import",
+ "private": true,
+ "type": "module",
+ "version": "0.0.0"
+}
diff --git a/playground/css-dynamic-import/static.css b/playground/css-dynamic-import/static.css
new file mode 100644
index 00000000000000..4efb84fdfea550
--- /dev/null
+++ b/playground/css-dynamic-import/static.css
@@ -0,0 +1,3 @@
+.css-dynamic-import {
+ color: red;
+}
diff --git a/playground/css-dynamic-import/static.js b/playground/css-dynamic-import/static.js
new file mode 100644
index 00000000000000..1688198fba4227
--- /dev/null
+++ b/playground/css-dynamic-import/static.js
@@ -0,0 +1,3 @@
+import './static.css'
+
+export const foo = 'foo'
diff --git a/playground/css-lightningcss-proxy/__tests__/css-lightningcss-proxy.spec.ts b/playground/css-lightningcss-proxy/__tests__/css-lightningcss-proxy.spec.ts
new file mode 100644
index 00000000000000..3d5cbc2ebaf142
--- /dev/null
+++ b/playground/css-lightningcss-proxy/__tests__/css-lightningcss-proxy.spec.ts
@@ -0,0 +1,13 @@
+import { describe, expect, test } from 'vitest'
+import { port } from './serve'
+import { getColor, isServe, page } from '~utils'
+
+const url = `http://localhost:${port}`
+
+describe.runIf(isServe)('injected inline style', () => {
+ test('injected inline style is present', async () => {
+ await page.goto(url)
+ const el = await page.$('.ssr-proxy')
+ expect(await getColor(el)).toBe('coral')
+ })
+})
diff --git a/playground/css-lightningcss-proxy/__tests__/serve.ts b/playground/css-lightningcss-proxy/__tests__/serve.ts
new file mode 100644
index 00000000000000..ee933ecd507a8c
--- /dev/null
+++ b/playground/css-lightningcss-proxy/__tests__/serve.ts
@@ -0,0 +1,38 @@
+// this is automatically detected by playground/vitestSetup.ts and will replace
+// the default e2e test serve behavior
+
+import path from 'node:path'
+import kill from 'kill-port'
+import { hmrPorts, ports, rootDir } from '~utils'
+
+export const port = ports['css/lightningcss-proxy']
+
+export async function serve(): Promise<{ close(): Promise }> {
+ await kill(port)
+
+ const { createServer } = await import(path.resolve(rootDir, 'server.js'))
+ const { app, vite } = await createServer(
+ rootDir,
+ hmrPorts['css/lightningcss-proxy'],
+ )
+
+ return new Promise((resolve, reject) => {
+ try {
+ const server = app.listen(port, () => {
+ resolve({
+ // for test teardown
+ async close() {
+ await new Promise((resolve) => {
+ server.close(resolve)
+ })
+ if (vite) {
+ await vite.close()
+ }
+ },
+ })
+ })
+ } catch (e) {
+ reject(e)
+ }
+ })
+}
diff --git a/playground/css-lightningcss-proxy/index.html b/playground/css-lightningcss-proxy/index.html
new file mode 100644
index 00000000000000..a017cc0d01b93c
--- /dev/null
+++ b/playground/css-lightningcss-proxy/index.html
@@ -0,0 +1,5 @@
+
+
+
Injected inline style with SSR Proxy
+
This should be coral
+
diff --git a/playground/css-lightningcss-proxy/package.json b/playground/css-lightningcss-proxy/package.json
new file mode 100644
index 00000000000000..c31519e711fcc4
--- /dev/null
+++ b/playground/css-lightningcss-proxy/package.json
@@ -0,0 +1,15 @@
+{
+ "name": "@vitejs/test-css-lightningcss-proxy",
+ "private": true,
+ "type": "module",
+ "scripts": {
+ "dev": "node server",
+ "serve": "NODE_ENV=production node server",
+ "debug": "node --inspect-brk server",
+ "preview": "vite preview"
+ },
+ "devDependencies": {
+ "lightningcss": "^1.31.1",
+ "express": "^5.2.1"
+ }
+}
diff --git a/playground/css-lightningcss-proxy/server.js b/playground/css-lightningcss-proxy/server.js
new file mode 100644
index 00000000000000..c50a423b72b43b
--- /dev/null
+++ b/playground/css-lightningcss-proxy/server.js
@@ -0,0 +1,85 @@
+import fs from 'node:fs'
+import path from 'node:path'
+import { fileURLToPath } from 'node:url'
+import express from 'express'
+
+const isTest = process.env.VITEST
+
+const DYNAMIC_STYLES = `
+
+`
+
+export async function createServer(root = process.cwd(), hmrPort) {
+ const resolve = (p) => path.resolve(import.meta.dirname, p)
+
+ const app = express()
+
+ /**
+ * @type {import('vite').ViteDevServer}
+ */
+ const vite = await (
+ await import('vite')
+ ).createServer({
+ root,
+ logLevel: isTest ? 'error' : 'info',
+ css: {
+ transformer: 'lightningcss',
+ },
+ server: {
+ middlewareMode: true,
+ watch: {
+ // During tests we edit the files too fast and sometimes chokidar
+ // misses change events, so enforce polling for consistency
+ usePolling: true,
+ interval: 100,
+ },
+ hmr: {
+ port: hmrPort,
+ },
+ },
+ appType: 'custom',
+ })
+ // use vite's connect instance as middleware
+ app.use(vite.middlewares)
+
+ app.use('*all', async (req, res, next) => {
+ try {
+ let [url] = req.originalUrl.split('?')
+ if (url.endsWith('/')) url += 'index.html'
+
+ if (url.startsWith('/favicon.ico')) {
+ return res.status(404).end('404')
+ }
+
+ const htmlLoc = resolve(`.${url}`)
+ let template = fs.readFileSync(htmlLoc, 'utf-8')
+
+ template = template.replace('', DYNAMIC_STYLES)
+
+ // Force calling transformIndexHtml with url === '/', to simulate
+ // usage by ecosystem that was recommended in the SSR documentation
+ // as `const url = req.originalUrl`
+ const html = await vite.transformIndexHtml('/', template)
+
+ res.status(200).set({ 'Content-Type': 'text/html' }).end(html)
+ } catch (e) {
+ vite && vite.ssrFixStacktrace(e)
+ console.log(e.stack)
+ res.status(500).end(e.stack)
+ }
+ })
+
+ return { app, vite }
+}
+
+if (!isTest) {
+ createServer().then(({ app }) =>
+ app.listen(5173, () => {
+ console.log('http://localhost:5173')
+ }),
+ )
+}
diff --git a/playground/css-lightningcss-root/__tests__/css-lightningcss-root.spec.ts b/playground/css-lightningcss-root/__tests__/css-lightningcss-root.spec.ts
new file mode 100644
index 00000000000000..786d17f124ab87
--- /dev/null
+++ b/playground/css-lightningcss-root/__tests__/css-lightningcss-root.spec.ts
@@ -0,0 +1,9 @@
+import { expect, test } from 'vitest'
+import { getBg, isBuild, page, viteTestUrl } from '~utils'
+
+test('url dependency', async () => {
+ const css = await page.$('.url-dep')
+ expect(await getBg(css)).toMatch(
+ isBuild ? /ok-[-\w]+\.png/ : `${viteTestUrl}/ok.png`,
+ )
+})
diff --git a/playground/css-lightningcss-root/package.json b/playground/css-lightningcss-root/package.json
new file mode 100644
index 00000000000000..c1d4ab5a8acb8f
--- /dev/null
+++ b/playground/css-lightningcss-root/package.json
@@ -0,0 +1,14 @@
+{
+ "name": "@vitejs/test-css-lightningcss-root",
+ "private": true,
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build",
+ "debug": "node --inspect-brk ../../packages/vite/bin/vite",
+ "preview": "vite preview"
+ },
+ "devDependencies": {
+ "lightningcss": "^1.31.1"
+ }
+}
diff --git a/playground/css-lightningcss-root/root/index.html b/playground/css-lightningcss-root/root/index.html
new file mode 100644
index 00000000000000..df2820bbe74c59
--- /dev/null
+++ b/playground/css-lightningcss-root/root/index.html
@@ -0,0 +1,3 @@
+url() dependency
+
+
diff --git a/playground/css-lightningcss-root/root/main.js b/playground/css-lightningcss-root/root/main.js
new file mode 100644
index 00000000000000..fe93503f500dd8
--- /dev/null
+++ b/playground/css-lightningcss-root/root/main.js
@@ -0,0 +1 @@
+import './url-dep.css'
diff --git a/playground/css-lightningcss-root/root/ok.png b/playground/css-lightningcss-root/root/ok.png
new file mode 100644
index 00000000000000..a8d1e52510c41c
Binary files /dev/null and b/playground/css-lightningcss-root/root/ok.png differ
diff --git a/playground/css-lightningcss-root/root/url-dep.css b/playground/css-lightningcss-root/root/url-dep.css
new file mode 100644
index 00000000000000..f56470afc0bb51
--- /dev/null
+++ b/playground/css-lightningcss-root/root/url-dep.css
@@ -0,0 +1,7 @@
+.url-dep {
+ background-image: url('./ok.png');
+ background-size: cover;
+ width: 50px;
+ height: 50px;
+ border: 1px solid black;
+}
diff --git a/playground/css-lightningcss-root/vite.config.js b/playground/css-lightningcss-root/vite.config.js
new file mode 100644
index 00000000000000..6e42d3e20e28be
--- /dev/null
+++ b/playground/css-lightningcss-root/vite.config.js
@@ -0,0 +1,8 @@
+import { defineConfig } from 'vite'
+
+export default defineConfig({
+ root: 'root',
+ css: {
+ transformer: 'lightningcss',
+ },
+})
diff --git a/playground/css-lightningcss/__tests__/css-lightningcss.spec.ts b/playground/css-lightningcss/__tests__/css-lightningcss.spec.ts
new file mode 100644
index 00000000000000..ded8d9e9fd5da5
--- /dev/null
+++ b/playground/css-lightningcss/__tests__/css-lightningcss.spec.ts
@@ -0,0 +1,94 @@
+import { expect, test } from 'vitest'
+import {
+ editFile,
+ findAssetFile,
+ getBg,
+ getColor,
+ isBuild,
+ page,
+ viteTestUrl,
+} from '~utils'
+
+// note: tests should retrieve the element at the beginning of test and reuse it
+// in later assertions to ensure CSS HMR doesn't reload the page
+test('linked css', async () => {
+ const linked = await page.$('.linked')
+ const atImport = await page.$('.linked-at-import')
+
+ expect(await getColor(linked)).toBe('blue')
+ expect(await getColor(atImport)).toBe('red')
+
+ if (isBuild) return
+ editFile('linked.css', (code) => code.replace('color: blue', 'color: red'))
+ await expect.poll(() => getColor(linked)).toBe('red')
+
+ editFile('linked-at-import.css', (code) =>
+ code.replace('color: red', 'color: blue'),
+ )
+ await expect.poll(() => getColor(atImport)).toBe('blue')
+})
+
+test('css import from js', async () => {
+ const imported = await page.$('.imported')
+ const atImport = await page.$('.imported-at-import')
+
+ expect(await getColor(imported)).toBe('green')
+ expect(await getColor(atImport)).toBe('purple')
+
+ if (isBuild) return
+ editFile('imported.css', (code) => code.replace('color: green', 'color: red'))
+ await expect.poll(() => getColor(imported)).toBe('red')
+
+ editFile('imported-at-import.css', (code) =>
+ code.replace('color: purple', 'color: blue'),
+ )
+ await expect.poll(() => getColor(atImport)).toBe('blue')
+})
+
+test('css modules', async () => {
+ const imported = await page.$('.modules')
+ expect(await getColor(imported)).toBe('turquoise')
+
+ expect(await imported.getAttribute('class')).toMatch(/\w{6}_apply-color/)
+
+ if (isBuild) return
+ editFile('mod.module.css', (code) =>
+ code.replace('color: turquoise', 'color: red'),
+ )
+ await expect.poll(() => getColor(imported)).toBe('red')
+})
+
+test('inline css modules', async () => {
+ const css = await page.textContent('.modules-inline')
+ expect(css).toMatch(/\._?\w{6}_apply-color-inline/)
+})
+
+test.runIf(isBuild)('minify css', async () => {
+ // should keep the rgba() syntax
+ const cssFile = findAssetFile(/index-[-\w]+\.css$/)
+ expect(cssFile).toMatch('rgba(')
+ expect(cssFile).not.toMatch('#ffff00b3')
+})
+
+test('css with external url', async () => {
+ const css = await page.$('.external')
+ expect(await getBg(css)).toMatch('url("https://vite.dev/logo.svg")')
+})
+
+test('nested css with relative asset', async () => {
+ const css = await page.$('.nested-css-relative-asset')
+ expect(await getBg(css)).toMatch(
+ isBuild ? /ok-[-\w]+\.png/ : `${viteTestUrl}/ok.png`,
+ )
+})
+
+test('aliased asset', async () => {
+ const bg = await getBg('.css-url-aliased')
+ expect(bg).toMatch('data:image/svg+xml,')
+})
+
+test('preinlined SVG', async () => {
+ expect(await getBg('.css-url-preinlined-svg')).toMatch(
+ /data:image\/svg\+xml,.+/,
+ )
+})
diff --git a/packages/playground/css/composed.module.css b/playground/css-lightningcss/composed.module.css
similarity index 100%
rename from packages/playground/css/composed.module.css
rename to playground/css-lightningcss/composed.module.css
diff --git a/playground/css-lightningcss/composes-path-resolving.module.css b/playground/css-lightningcss/composes-path-resolving.module.css
new file mode 100644
index 00000000000000..2873293f9c7605
--- /dev/null
+++ b/playground/css-lightningcss/composes-path-resolving.module.css
@@ -0,0 +1,3 @@
+.path-resolving-css {
+ composes: apply-color from './composed.module.css';
+}
diff --git a/playground/css-lightningcss/css-url.css b/playground/css-lightningcss/css-url.css
new file mode 100644
index 00000000000000..6695ad2b9b0bd3
--- /dev/null
+++ b/playground/css-lightningcss/css-url.css
@@ -0,0 +1,9 @@
+.css-url-aliased {
+ background: url('@/fragment.svg');
+ background-size: 10px;
+}
+
+.css-url-preinlined-svg {
+ background: url('data:image/svg+xml, ');
+ background-size: 20px;
+}
diff --git a/playground/css-lightningcss/external-url.css b/playground/css-lightningcss/external-url.css
new file mode 100644
index 00000000000000..d1c2ef28a6fe80
--- /dev/null
+++ b/playground/css-lightningcss/external-url.css
@@ -0,0 +1,7 @@
+.external {
+ background-image: url('https://vite.dev/logo.svg');
+ background-size: 100%;
+ width: 200px;
+ height: 200px;
+ background-color: #bed;
+}
diff --git a/packages/playground/css/imported-at-import.css b/playground/css-lightningcss/imported-at-import.css
similarity index 100%
rename from packages/playground/css/imported-at-import.css
rename to playground/css-lightningcss/imported-at-import.css
diff --git a/playground/css-lightningcss/imported.css b/playground/css-lightningcss/imported.css
new file mode 100644
index 00000000000000..929e8995d196af
--- /dev/null
+++ b/playground/css-lightningcss/imported.css
@@ -0,0 +1,13 @@
+@import url('./nested/nested.css');
+@import './imported-at-import.css';
+
+.imported {
+ color: green;
+}
+
+pre {
+ background-color: #eee;
+ width: 500px;
+ padding: 1em 1.5em;
+ border-radius: 10px;
+}
diff --git a/playground/css-lightningcss/index.html b/playground/css-lightningcss/index.html
new file mode 100644
index 00000000000000..c0756b11314831
--- /dev/null
+++ b/playground/css-lightningcss/index.html
@@ -0,0 +1,41 @@
+
+
+
+
Lightning CSS
+
+
<link>: This should be blue
+
@import in <link>: This should be red
+
+
import from js: This should be green
+
+ @import in import from js: This should be purple
+
+
+
CSS modules: this should be turquoise
+
Imported CSS module:
+
+
+
Imported compose/from CSS module:
+
+ CSS modules composes path resolving: this should be turquoise
+
+
+
+
Inline CSS module:
+
+
+
External URL
+
+
+
Assets relative to nested CSS
+
+
+
+ CSS background (aliased)
+
+
+ CSS background (pre inlined SVG)
+
+
+
+
diff --git a/playground/css-lightningcss/inline.module.css b/playground/css-lightningcss/inline.module.css
new file mode 100644
index 00000000000000..9566e21e2cd1af
--- /dev/null
+++ b/playground/css-lightningcss/inline.module.css
@@ -0,0 +1,3 @@
+.apply-color-inline {
+ color: turquoise;
+}
diff --git a/packages/playground/css/inlined.css b/playground/css-lightningcss/inlined.css
similarity index 100%
rename from packages/playground/css/inlined.css
rename to playground/css-lightningcss/inlined.css
diff --git a/packages/playground/css/linked-at-import.css b/playground/css-lightningcss/linked-at-import.css
similarity index 100%
rename from packages/playground/css/linked-at-import.css
rename to playground/css-lightningcss/linked-at-import.css
diff --git a/playground/css-lightningcss/linked.css b/playground/css-lightningcss/linked.css
new file mode 100644
index 00000000000000..49f677d1e6462a
--- /dev/null
+++ b/playground/css-lightningcss/linked.css
@@ -0,0 +1,8 @@
+@import './linked-at-import.css';
+
+/* test nesting */
+.wrapper {
+ .linked {
+ color: blue;
+ }
+}
diff --git a/playground/css-lightningcss/main.js b/playground/css-lightningcss/main.js
new file mode 100644
index 00000000000000..bc452b3cd42c2e
--- /dev/null
+++ b/playground/css-lightningcss/main.js
@@ -0,0 +1,33 @@
+import './minify.css'
+import './imported.css'
+import mod from './mod.module.css'
+import './external-url.css'
+import './css-url.css'
+
+document.querySelector('.modules').classList.add(mod['apply-color'])
+text('.modules-code', JSON.stringify(mod, null, 2))
+
+import composesPathResolvingMod from './composes-path-resolving.module.css'
+document
+ .querySelector('.path-resolved-modules-css')
+ .classList.add(...composesPathResolvingMod['path-resolving-css'].split(' '))
+text(
+ '.path-resolved-modules-code',
+ JSON.stringify(composesPathResolvingMod, null, 2),
+)
+
+import inlineMod from './inline.module.css?inline'
+text('.modules-inline', inlineMod)
+
+function text(el, text) {
+ document.querySelector(el).textContent = text
+}
+
+if (import.meta.hot) {
+ import.meta.hot.accept('./mod.module.css', (newMod) => {
+ const list = document.querySelector('.modules').classList
+ list.remove(mod.applyColor)
+ list.add(newMod.applyColor)
+ text('.modules-code', JSON.stringify(newMod.default, null, 2))
+ })
+}
diff --git a/packages/playground/css/minify.css b/playground/css-lightningcss/minify.css
similarity index 100%
rename from packages/playground/css/minify.css
rename to playground/css-lightningcss/minify.css
diff --git a/packages/playground/css/mod.module.css b/playground/css-lightningcss/mod.module.css
similarity index 100%
rename from packages/playground/css/mod.module.css
rename to playground/css-lightningcss/mod.module.css
diff --git a/packages/playground/vue/assets/fragment.svg b/playground/css-lightningcss/nested/fragment.svg
similarity index 100%
rename from packages/playground/vue/assets/fragment.svg
rename to playground/css-lightningcss/nested/fragment.svg
diff --git a/playground/css-lightningcss/nested/nested.css b/playground/css-lightningcss/nested/nested.css
new file mode 100644
index 00000000000000..7d123cdd106de1
--- /dev/null
+++ b/playground/css-lightningcss/nested/nested.css
@@ -0,0 +1,5 @@
+.nested-css-relative-asset {
+ background-image: url('../ok.png');
+ width: 50px;
+ height: 50px;
+}
diff --git a/playground/css-lightningcss/ok.png b/playground/css-lightningcss/ok.png
new file mode 100644
index 00000000000000..a8d1e52510c41c
Binary files /dev/null and b/playground/css-lightningcss/ok.png differ
diff --git a/playground/css-lightningcss/package.json b/playground/css-lightningcss/package.json
new file mode 100644
index 00000000000000..4bd1cb46c46cb5
--- /dev/null
+++ b/playground/css-lightningcss/package.json
@@ -0,0 +1,14 @@
+{
+ "name": "@vitejs/test-css-lightningcss",
+ "private": true,
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build",
+ "debug": "node --inspect-brk ../../packages/vite/bin/vite",
+ "preview": "vite preview"
+ },
+ "devDependencies": {
+ "lightningcss": "^1.31.1"
+ }
+}
diff --git a/playground/css-lightningcss/vite.config.js b/playground/css-lightningcss/vite.config.js
new file mode 100644
index 00000000000000..116489f5189919
--- /dev/null
+++ b/playground/css-lightningcss/vite.config.js
@@ -0,0 +1,17 @@
+import path from 'node:path'
+import { defineConfig } from 'vite'
+
+export default defineConfig({
+ css: {
+ transformer: 'lightningcss',
+ },
+ resolve: {
+ alias: {
+ '@': path.resolve(import.meta.dirname, 'nested'),
+ },
+ },
+ build: {
+ cssTarget: ['chrome61'],
+ cssMinify: 'lightningcss',
+ },
+})
diff --git a/playground/css-no-codesplit/__tests__/css-no-codesplit.spec.ts b/playground/css-no-codesplit/__tests__/css-no-codesplit.spec.ts
new file mode 100644
index 00000000000000..4ce2f2715e2c2e
--- /dev/null
+++ b/playground/css-no-codesplit/__tests__/css-no-codesplit.spec.ts
@@ -0,0 +1,17 @@
+import { describe, expect, test } from 'vitest'
+import { getColor, isBuild, listAssets } from '~utils'
+
+test('should load all stylesheets', async () => {
+ expect(await getColor('.shared-linked')).toBe('blue')
+ await expect.poll(() => getColor('.async-js')).toBe('blue')
+})
+
+describe.runIf(isBuild)('build', () => {
+ test('should remove empty chunk', async () => {
+ const assets = listAssets()
+ expect(assets).not.toContainEqual(
+ expect.stringMatching(/shared-linked-.*\.js$/),
+ )
+ expect(assets).not.toContainEqual(expect.stringMatching(/async-js-.*\.js$/))
+ })
+})
diff --git a/playground/css-no-codesplit/async-js.css b/playground/css-no-codesplit/async-js.css
new file mode 100644
index 00000000000000..ed61a7f513c277
--- /dev/null
+++ b/playground/css-no-codesplit/async-js.css
@@ -0,0 +1,3 @@
+.async-js {
+ color: blue;
+}
diff --git a/playground/css-no-codesplit/async-js.js b/playground/css-no-codesplit/async-js.js
new file mode 100644
index 00000000000000..2ce31a1e741d2d
--- /dev/null
+++ b/playground/css-no-codesplit/async-js.js
@@ -0,0 +1,2 @@
+// a JS file that becomes an empty file but imports CSS files
+import './async-js.css'
diff --git a/playground/css-no-codesplit/index.html b/playground/css-no-codesplit/index.html
new file mode 100644
index 00000000000000..e7673c84e45933
--- /dev/null
+++ b/playground/css-no-codesplit/index.html
@@ -0,0 +1,5 @@
+
+
+
+shared linked: this should be blue
+async JS importing CSS: this should be blue
diff --git a/playground/css-no-codesplit/index.js b/playground/css-no-codesplit/index.js
new file mode 100644
index 00000000000000..44b33fda36a9cd
--- /dev/null
+++ b/playground/css-no-codesplit/index.js
@@ -0,0 +1 @@
+import('./async-js')
diff --git a/playground/css-no-codesplit/package.json b/playground/css-no-codesplit/package.json
new file mode 100644
index 00000000000000..61d806d3d264fa
--- /dev/null
+++ b/playground/css-no-codesplit/package.json
@@ -0,0 +1,12 @@
+{
+ "name": "@vitejs/test-css-no-codesplit",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build",
+ "debug": "node --inspect-brk ../../packages/vite/bin/vite",
+ "preview": "vite preview"
+ }
+}
diff --git a/playground/css-no-codesplit/shared-linked.css b/playground/css-no-codesplit/shared-linked.css
new file mode 100644
index 00000000000000..51857a50efca1f
--- /dev/null
+++ b/playground/css-no-codesplit/shared-linked.css
@@ -0,0 +1,3 @@
+.shared-linked {
+ color: blue;
+}
diff --git a/playground/css-no-codesplit/sub.html b/playground/css-no-codesplit/sub.html
new file mode 100644
index 00000000000000..f535a771d06482
--- /dev/null
+++ b/playground/css-no-codesplit/sub.html
@@ -0,0 +1 @@
+
diff --git a/playground/css-no-codesplit/vite.config.js b/playground/css-no-codesplit/vite.config.js
new file mode 100644
index 00000000000000..0607cf2600bb2c
--- /dev/null
+++ b/playground/css-no-codesplit/vite.config.js
@@ -0,0 +1,14 @@
+import { resolve } from 'node:path'
+import { defineConfig } from 'vite'
+
+export default defineConfig({
+ build: {
+ cssCodeSplit: false,
+ rollupOptions: {
+ input: {
+ index: resolve(import.meta.dirname, './index.html'),
+ sub: resolve(import.meta.dirname, './sub.html'),
+ },
+ },
+ },
+})
diff --git a/playground/css-sourcemap/__tests__/css-sourcemap.spec.ts b/playground/css-sourcemap/__tests__/css-sourcemap.spec.ts
new file mode 100644
index 00000000000000..ec343e011ca79e
--- /dev/null
+++ b/playground/css-sourcemap/__tests__/css-sourcemap.spec.ts
@@ -0,0 +1,275 @@
+import { URL } from 'node:url'
+import { describe, expect, test } from 'vitest'
+import {
+ extractSourcemap,
+ formatSourcemapForSnapshot,
+ isBuild,
+ isServe,
+ page,
+ serverLogs,
+} from '~utils'
+
+test.runIf(isBuild)('should not output sourcemap warning (#4939)', () => {
+ serverLogs.forEach((log) => {
+ expect(log).not.toMatch('Sourcemap is likely to be incorrect')
+ })
+})
+
+describe.runIf(isServe)('serve', () => {
+ const getStyleTagContentIncluding = async (content: string) => {
+ const styles = await page.$$('style')
+ for (const style of styles) {
+ const text = await style.textContent()
+ if (text.includes(content)) {
+ return text
+ }
+ }
+ throw new Error('Not found')
+ }
+
+ test('linked css', async () => {
+ const res = await page.request.get(
+ new URL('./linked.css', page.url()).href,
+ {
+ headers: {
+ accept: 'text/css',
+ },
+ },
+ )
+ const css = await res.text()
+ expect(css).not.toContain('sourceMappingURL')
+ })
+
+ test('linked css with import', async () => {
+ const res = await page.request.get(
+ new URL('./linked-with-import.css', page.url()).href,
+ {
+ headers: {
+ accept: 'text/css',
+ },
+ },
+ )
+ const css = await res.text()
+ const map = extractSourcemap(css)
+ expect(formatSourcemapForSnapshot(map, css)).toMatchInlineSnapshot(`
+ SourceMap {
+ content: {
+ "mappings": "AAAA;EACE,UAAU;AACZ;;ACAA;EACE,UAAU;AACZ",
+ "sources": [
+ "be-imported.css",
+ "linked-with-import.css",
+ ],
+ "sourcesContent": [
+ ".be-imported {
+ color: red;
+ }
+ ",
+ "@import '@/be-imported.css';
+
+ .linked-with-import {
+ color: red;
+ }
+ ",
+ ],
+ "version": 3,
+ },
+ visualization: "https://evanw.github.io/source-map-visualization/#NzAALmJlLWltcG9ydGVkIHsKICBjb2xvcjogcmVkOwp9CgoubGlua2VkLXdpdGgtaW1wb3J0IHsKICBjb2xvcjogcmVkOwp9CjI1NAB7Im1hcHBpbmdzIjoiQUFBQTtFQUNFLFVBQVU7QUFDWjs7QUNBQTtFQUNFLFVBQVU7QUFDWiIsInNvdXJjZXMiOlsiYmUtaW1wb3J0ZWQuY3NzIiwibGlua2VkLXdpdGgtaW1wb3J0LmNzcyJdLCJzb3VyY2VzQ29udGVudCI6WyIuYmUtaW1wb3J0ZWQge1xuICBjb2xvcjogcmVkO1xufVxuIiwiQGltcG9ydCAnQC9iZS1pbXBvcnRlZC5jc3MnO1xuXG4ubGlua2VkLXdpdGgtaW1wb3J0IHtcbiAgY29sb3I6IHJlZDtcbn1cbiJdLCJ2ZXJzaW9uIjozfQ=="
+ }
+ `)
+ })
+
+ test.runIf(isServe)(
+ 'js .css request does not include sourcemap',
+ async () => {
+ const res = await page.request.get(
+ new URL('./linked-with-import.css', page.url()).href,
+ )
+ const content = await res.text()
+ expect(content).not.toMatch('//#s*sourceMappingURL')
+ },
+ )
+
+ test('imported css', async () => {
+ const css = await getStyleTagContentIncluding('.imported ')
+ const map = extractSourcemap(css)
+ expect(formatSourcemapForSnapshot(map, css)).toMatchInlineSnapshot(`
+ SourceMap {
+ content: {
+ "mappings": "AAAA,CAAC,QAAQ,CAAC;AACV,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG;AACZ;",
+ "sources": [
+ "/root/imported.css",
+ ],
+ "sourcesContent": [
+ ".imported {
+ color: red;
+ }
+ ",
+ ],
+ "version": 3,
+ },
+ visualization: "https://evanw.github.io/source-map-visualization/#MjgALmltcG9ydGVkIHsKICBjb2xvcjogcmVkOwp9CjE3MwB7InZlcnNpb24iOjMsInNvdXJjZXMiOlsiL3Jvb3QvaW1wb3J0ZWQuY3NzIl0sInNvdXJjZXNDb250ZW50IjpbIi5pbXBvcnRlZCB7XG4gIGNvbG9yOiByZWQ7XG59XG4iXSwibWFwcGluZ3MiOiJBQUFBLENBQUMsUUFBUSxDQUFDO0FBQ1YsQ0FBQyxDQUFDLEtBQUssQ0FBQyxDQUFDLEdBQUc7QUFDWjsifQ=="
+ }
+ `)
+ })
+
+ test('imported css with import', async () => {
+ const css = await getStyleTagContentIncluding('.imported-with-import ')
+ const map = extractSourcemap(css)
+ expect(formatSourcemapForSnapshot(map, css)).toMatchInlineSnapshot(`
+ SourceMap {
+ content: {
+ "mappings": "AAAA;EACE,UAAU;AACZ;;ACAA;EACE,UAAU;AACZ",
+ "sources": [
+ "/root/be-imported.css",
+ "/root/imported-with-import.css",
+ ],
+ "sourcesContent": [
+ ".be-imported {
+ color: red;
+ }
+ ",
+ "@import '@/be-imported.css';
+
+ .imported-with-import {
+ color: red;
+ }
+ ",
+ ],
+ "version": 3,
+ },
+ visualization: "https://evanw.github.io/source-map-visualization/#NzIALmJlLWltcG9ydGVkIHsKICBjb2xvcjogcmVkOwp9CgouaW1wb3J0ZWQtd2l0aC1pbXBvcnQgewogIGNvbG9yOiByZWQ7Cn0KMjcwAHsibWFwcGluZ3MiOiJBQUFBO0VBQ0UsVUFBVTtBQUNaOztBQ0FBO0VBQ0UsVUFBVTtBQUNaIiwic291cmNlcyI6WyIvcm9vdC9iZS1pbXBvcnRlZC5jc3MiLCIvcm9vdC9pbXBvcnRlZC13aXRoLWltcG9ydC5jc3MiXSwic291cmNlc0NvbnRlbnQiOlsiLmJlLWltcG9ydGVkIHtcbiAgY29sb3I6IHJlZDtcbn1cbiIsIkBpbXBvcnQgJ0AvYmUtaW1wb3J0ZWQuY3NzJztcblxuLmltcG9ydGVkLXdpdGgtaW1wb3J0IHtcbiAgY29sb3I6IHJlZDtcbn1cbiJdLCJ2ZXJzaW9uIjozfQ=="
+ }
+ `)
+ })
+
+ test('imported sass', async () => {
+ const css = await getStyleTagContentIncluding('.imported-sass ')
+ const map = extractSourcemap(css)
+ expect(formatSourcemapForSnapshot(map, css)).toMatchInlineSnapshot(`
+ SourceMap {
+ content: {
+ "mappings": "AAGE;EACE,OCJM",
+ "sourceRoot": "",
+ "sources": [
+ "/root/imported.sass",
+ "/root/imported-nested.sass",
+ ],
+ "sourcesContent": [
+ "@use "/imported-nested.sass"
+
+ .imported
+ &-sass
+ color: imported-nested.$primary
+ ",
+ "$primary: red
+ ",
+ ],
+ "version": 3,
+ },
+ visualization: "https://evanw.github.io/source-map-visualization/#MzMALmltcG9ydGVkLXNhc3MgewogIGNvbG9yOiByZWQ7Cn0KMjUyAHsidmVyc2lvbiI6Mywic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiL3Jvb3QvaW1wb3J0ZWQuc2FzcyIsIi9yb290L2ltcG9ydGVkLW5lc3RlZC5zYXNzIl0sIm1hcHBpbmdzIjoiQUFHRTtFQUNFLE9DSk0iLCJzb3VyY2VzQ29udGVudCI6WyJAdXNlIFwiL2ltcG9ydGVkLW5lc3RlZC5zYXNzXCJcblxuLmltcG9ydGVkXG4gICYtc2Fzc1xuICAgIGNvbG9yOiBpbXBvcnRlZC1uZXN0ZWQuJHByaW1hcnlcbiIsIiRwcmltYXJ5OiByZWRcbiJdfQ=="
+ }
+ `)
+ })
+
+ test('imported sass module', async () => {
+ const css = await getStyleTagContentIncluding('._imported-sass-module_')
+ const map = extractSourcemap(css)
+ expect(formatSourcemapForSnapshot(map, css)).toMatchInlineSnapshot(`
+ SourceMap {
+ content: {
+ "ignoreList": [],
+ "mappings": "AACE;EACE",
+ "sources": [
+ "/root/imported.module.sass",
+ ],
+ "sourcesContent": [
+ ".imported
+ &-sass-module
+ color: red
+ ",
+ ],
+ "version": 3,
+ },
+ visualization: "https://evanw.github.io/source-map-visualization/#NDkALl9pbXBvcnRlZC1zYXNzLW1vZHVsZV9yMXFjcF8xIHsKICBjb2xvcjogcmVkOwp9CjE1OQB7InZlcnNpb24iOjMsIm1hcHBpbmdzIjoiQUFDRTtFQUNFIiwiaWdub3JlTGlzdCI6W10sInNvdXJjZXMiOlsiL3Jvb3QvaW1wb3J0ZWQubW9kdWxlLnNhc3MiXSwic291cmNlc0NvbnRlbnQiOlsiLmltcG9ydGVkXG4gICYtc2Fzcy1tb2R1bGVcbiAgICBjb2xvcjogcmVkXG4iXX0="
+ }
+ `)
+ })
+
+ test('imported less', async () => {
+ const css = await getStyleTagContentIncluding('.imported-less ')
+ const map = extractSourcemap(css)
+ expect(formatSourcemapForSnapshot(map, css)).toMatchInlineSnapshot(`
+ SourceMap {
+ content: {
+ "ignoreList": [],
+ "mappings": "AACE,SAAC;EACC",
+ "sources": [
+ "/root/imported.less",
+ ],
+ "sourcesContent": [
+ ".imported {
+ &-less {
+ color: @color;
+ }
+ }
+ ",
+ ],
+ "version": 3,
+ },
+ visualization: "https://evanw.github.io/source-map-visualization/#MzMALmltcG9ydGVkLWxlc3MgewogIGNvbG9yOiByZWQ7Cn0KMTY2AHsidmVyc2lvbiI6MywibWFwcGluZ3MiOiJBQUNFLFNBQUM7RUFDQyIsImlnbm9yZUxpc3QiOltdLCJzb3VyY2VzIjpbIi9yb290L2ltcG9ydGVkLmxlc3MiXSwic291cmNlc0NvbnRlbnQiOlsiLmltcG9ydGVkIHtcbiAgJi1sZXNzIHtcbiAgICBjb2xvcjogQGNvbG9yO1xuICB9XG59XG4iXX0="
+ }
+ `)
+ })
+
+ test('imported stylus', async () => {
+ const css = await getStyleTagContentIncluding('.imported-stylus ')
+ const map = extractSourcemap(css)
+ expect(formatSourcemapForSnapshot(map, css)).toMatchInlineSnapshot(`
+ SourceMap {
+ content: {
+ "ignoreList": [],
+ "mappings": "AACE;EACE,OAAM,QAAN",
+ "sources": [
+ "/root/imported.styl",
+ ],
+ "sourcesContent": [
+ ".imported
+ &-stylus
+ color blue-red-mixed
+ ",
+ ],
+ "version": 3,
+ },
+ visualization: "https://evanw.github.io/source-map-visualization/#MzkALmltcG9ydGVkLXN0eWx1cyB7CiAgY29sb3I6ICM4MDAwODA7Cn0KMTY3AHsidmVyc2lvbiI6MywibWFwcGluZ3MiOiJBQUNFO0VBQ0UsT0FBTSxRQUFOIiwiaWdub3JlTGlzdCI6W10sInNvdXJjZXMiOlsiL3Jvb3QvaW1wb3J0ZWQuc3R5bCJdLCJzb3VyY2VzQ29udGVudCI6WyIuaW1wb3J0ZWRcbiAgJi1zdHlsdXNcbiAgICBjb2xvciBibHVlLXJlZC1taXhlZFxuIl19"
+ }
+ `)
+ })
+
+ test('imported sugarss', async () => {
+ const css = await getStyleTagContentIncluding('.imported-sugarss ')
+ const map = extractSourcemap(css)
+ expect(formatSourcemapForSnapshot(map, css)).toMatchInlineSnapshot(`
+ SourceMap {
+ content: {
+ "mappings": "AAAA;EACE;AADe",
+ "sources": [
+ "/root/imported.sss",
+ ],
+ "sourcesContent": [
+ ".imported-sugarss
+ color: red
+ ",
+ ],
+ "version": 3,
+ },
+ visualization: "https://evanw.github.io/source-map-visualization/#MzUALmltcG9ydGVkLXN1Z2Fyc3MgewogIGNvbG9yOiByZWQKfQoxMjkAeyJtYXBwaW5ncyI6IkFBQUE7RUFDRTtBQURlIiwic291cmNlcyI6WyIvcm9vdC9pbXBvcnRlZC5zc3MiXSwic291cmNlc0NvbnRlbnQiOlsiLmltcG9ydGVkLXN1Z2Fyc3NcbiAgY29sb3I6IHJlZFxuIl0sInZlcnNpb24iOjN9"
+ }
+ `)
+ })
+
+ test('should not output missing source file warning', () => {
+ serverLogs.forEach((log) => {
+ expect(log).not.toMatch(/Sourcemap for .+ points to missing source files/)
+ })
+ })
+})
diff --git a/playground/css-sourcemap/__tests__/lib-entry/css-sourcemap-lib-entry.spec.ts b/playground/css-sourcemap/__tests__/lib-entry/css-sourcemap-lib-entry.spec.ts
new file mode 100644
index 00000000000000..6266a2d413419a
--- /dev/null
+++ b/playground/css-sourcemap/__tests__/lib-entry/css-sourcemap-lib-entry.spec.ts
@@ -0,0 +1,8 @@
+import { describe, expect, test } from 'vitest'
+import { findAssetFile, isBuild } from '~utils'
+
+describe.runIf(isBuild)('css lib entry', () => {
+ test('remove useless js sourcemap', async () => {
+ expect(findAssetFile('linked.js.map', 'lib-entry', './')).toBeUndefined()
+ })
+})
diff --git a/playground/css-sourcemap/__tests__/lightningcss/lightningcss.spec.ts b/playground/css-sourcemap/__tests__/lightningcss/lightningcss.spec.ts
new file mode 100644
index 00000000000000..2bc2a29235e803
--- /dev/null
+++ b/playground/css-sourcemap/__tests__/lightningcss/lightningcss.spec.ts
@@ -0,0 +1,276 @@
+import { URL } from 'node:url'
+import { describe, expect, test } from 'vitest'
+import {
+ extractSourcemap,
+ formatSourcemapForSnapshot,
+ isBuild,
+ isServe,
+ page,
+ serverLogs,
+} from '~utils'
+
+test.runIf(isBuild)('should not output sourcemap warning (#4939)', () => {
+ serverLogs.forEach((log) => {
+ expect(log).not.toMatch('Sourcemap is likely to be incorrect')
+ })
+})
+
+describe.runIf(isServe)('serve', () => {
+ const getStyleTagContentIncluding = async (content: string) => {
+ const styles = await page.$$('style')
+ for (const style of styles) {
+ const text = await style.textContent()
+ if (text.includes(content)) {
+ return text
+ }
+ }
+ throw new Error('Not found')
+ }
+
+ test('linked css', async () => {
+ const res = await page.request.get(
+ new URL('./linked.css', page.url()).href,
+ {
+ headers: {
+ accept: 'text/css',
+ },
+ },
+ )
+ const css = await res.text()
+ expect(css).not.toContain('sourceMappingURL')
+ })
+
+ test('linked css with import', async () => {
+ const res = await page.request.get(
+ new URL('./linked-with-import.css', page.url()).href,
+ {
+ headers: {
+ accept: 'text/css',
+ },
+ },
+ )
+ const css = await res.text()
+ const map = extractSourcemap(css)
+ expect(formatSourcemapForSnapshot(map, css)).toMatchInlineSnapshot(`
+ SourceMap {
+ content: {
+ "mappings": "ACAA;;;;ADEA",
+ "sourceRoot": null,
+ "sources": [
+ "linked-with-import.css",
+ "be-imported.css",
+ ],
+ "sourcesContent": [
+ "@import '@/be-imported.css';
+
+ .linked-with-import {
+ color: red;
+ }
+ ",
+ ".be-imported {
+ color: red;
+ }
+ ",
+ ],
+ "version": 3,
+ },
+ visualization: "https://evanw.github.io/source-map-visualization/#NzAALmJlLWltcG9ydGVkIHsKICBjb2xvcjogcmVkOwp9CgoubGlua2VkLXdpdGgtaW1wb3J0IHsKICBjb2xvcjogcmVkOwp9CjI0NAB7InZlcnNpb24iOjMsInNvdXJjZVJvb3QiOm51bGwsIm1hcHBpbmdzIjoiQUNBQTs7OztBREVBIiwic291cmNlcyI6WyJsaW5rZWQtd2l0aC1pbXBvcnQuY3NzIiwiYmUtaW1wb3J0ZWQuY3NzIl0sInNvdXJjZXNDb250ZW50IjpbIkBpbXBvcnQgJ0AvYmUtaW1wb3J0ZWQuY3NzJztcblxuLmxpbmtlZC13aXRoLWltcG9ydCB7XG4gIGNvbG9yOiByZWQ7XG59XG4iLCIuYmUtaW1wb3J0ZWQge1xuICBjb2xvcjogcmVkO1xufVxuIl19"
+ }
+ `)
+ })
+
+ test.runIf(isServe)(
+ 'js .css request does not include sourcemap',
+ async () => {
+ const res = await page.request.get(
+ new URL('./linked-with-import.css', page.url()).href,
+ )
+ const content = await res.text()
+ expect(content).not.toMatch('//#s*sourceMappingURL')
+ },
+ )
+
+ test('imported css', async () => {
+ const css = await getStyleTagContentIncluding('.imported ')
+ const map = extractSourcemap(css)
+ expect(formatSourcemapForSnapshot(map, css)).toMatchInlineSnapshot(`
+ SourceMap {
+ content: {
+ "mappings": "AAAA",
+ "sourceRoot": null,
+ "sources": [
+ "imported.css",
+ ],
+ "sourcesContent": [
+ ".imported {
+ color: red;
+ }
+ ",
+ ],
+ "version": 3,
+ },
+ visualization: "https://evanw.github.io/source-map-visualization/#MjgALmltcG9ydGVkIHsKICBjb2xvcjogcmVkOwp9CjEyOQB7InZlcnNpb24iOjMsInNvdXJjZVJvb3QiOm51bGwsIm1hcHBpbmdzIjoiQUFBQSIsInNvdXJjZXMiOlsiaW1wb3J0ZWQuY3NzIl0sInNvdXJjZXNDb250ZW50IjpbIi5pbXBvcnRlZCB7XG4gIGNvbG9yOiByZWQ7XG59XG4iXX0="
+ }
+ `)
+ })
+
+ test('imported css with import', async () => {
+ const css = await getStyleTagContentIncluding('.imported-with-import ')
+ const map = extractSourcemap(css)
+ expect(formatSourcemapForSnapshot(map, css)).toMatchInlineSnapshot(`
+ SourceMap {
+ content: {
+ "mappings": "ACAA;;;;ADEA",
+ "sourceRoot": null,
+ "sources": [
+ "imported-with-import.css",
+ "be-imported.css",
+ ],
+ "sourcesContent": [
+ "@import '@/be-imported.css';
+
+ .imported-with-import {
+ color: red;
+ }
+ ",
+ ".be-imported {
+ color: red;
+ }
+ ",
+ ],
+ "version": 3,
+ },
+ visualization: "https://evanw.github.io/source-map-visualization/#NzIALmJlLWltcG9ydGVkIHsKICBjb2xvcjogcmVkOwp9CgouaW1wb3J0ZWQtd2l0aC1pbXBvcnQgewogIGNvbG9yOiByZWQ7Cn0KMjQ4AHsidmVyc2lvbiI6Mywic291cmNlUm9vdCI6bnVsbCwibWFwcGluZ3MiOiJBQ0FBOzs7O0FERUEiLCJzb3VyY2VzIjpbImltcG9ydGVkLXdpdGgtaW1wb3J0LmNzcyIsImJlLWltcG9ydGVkLmNzcyJdLCJzb3VyY2VzQ29udGVudCI6WyJAaW1wb3J0ICdAL2JlLWltcG9ydGVkLmNzcyc7XG5cbi5pbXBvcnRlZC13aXRoLWltcG9ydCB7XG4gIGNvbG9yOiByZWQ7XG59XG4iLCIuYmUtaW1wb3J0ZWQge1xuICBjb2xvcjogcmVkO1xufVxuIl19"
+ }
+ `)
+ })
+
+ test('imported sass', async () => {
+ const css = await getStyleTagContentIncluding('.imported-sass ')
+ const map = extractSourcemap(css)
+ expect(formatSourcemapForSnapshot(map, css)).toMatchInlineSnapshot(`
+ SourceMap {
+ content: {
+ "ignoreList": [],
+ "mappings": "AAGE",
+ "sources": [
+ "/root/imported.sass",
+ ],
+ "sourcesContent": [
+ "@use "/imported-nested.sass"
+
+ .imported
+ &-sass
+ color: imported-nested.$primary
+ ",
+ ],
+ "version": 3,
+ },
+ visualization: "https://evanw.github.io/source-map-visualization/#MzMALmltcG9ydGVkLXNhc3MgewogIGNvbG9yOiByZWQ7Cn0KMTk1AHsidmVyc2lvbiI6MywibWFwcGluZ3MiOiJBQUdFIiwiaWdub3JlTGlzdCI6W10sInNvdXJjZXMiOlsiL3Jvb3QvaW1wb3J0ZWQuc2FzcyJdLCJzb3VyY2VzQ29udGVudCI6WyJAdXNlIFwiL2ltcG9ydGVkLW5lc3RlZC5zYXNzXCJcblxuLmltcG9ydGVkXG4gICYtc2Fzc1xuICAgIGNvbG9yOiBpbXBvcnRlZC1uZXN0ZWQuJHByaW1hcnlcbiJdfQ=="
+ }
+ `)
+ })
+
+ test('imported sass module', async () => {
+ const css = await getStyleTagContentIncluding('_imported-sass-module')
+ const map = extractSourcemap(css)
+ expect(formatSourcemapForSnapshot(map, css)).toMatchInlineSnapshot(`
+ SourceMap {
+ content: {
+ "ignoreList": [],
+ "mappings": "AACE",
+ "sources": [
+ "/root/imported.module.sass",
+ ],
+ "sourcesContent": [
+ ".imported
+ &-sass-module
+ color: red
+ ",
+ ],
+ "version": 3,
+ },
+ visualization: "https://evanw.github.io/source-map-visualization/#NDcALmhvUU10V19pbXBvcnRlZC1zYXNzLW1vZHVsZSB7CiAgY29sb3I6IHJlZDsKfQoxNTQAeyJ2ZXJzaW9uIjozLCJtYXBwaW5ncyI6IkFBQ0UiLCJpZ25vcmVMaXN0IjpbXSwic291cmNlcyI6WyIvcm9vdC9pbXBvcnRlZC5tb2R1bGUuc2FzcyJdLCJzb3VyY2VzQ29udGVudCI6WyIuaW1wb3J0ZWRcbiAgJi1zYXNzLW1vZHVsZVxuICAgIGNvbG9yOiByZWRcbiJdfQ=="
+ }
+ `)
+ })
+
+ test('imported less', async () => {
+ const css = await getStyleTagContentIncluding('.imported-less ')
+ const map = extractSourcemap(css)
+ expect(formatSourcemapForSnapshot(map, css)).toMatchInlineSnapshot(`
+ SourceMap {
+ content: {
+ "ignoreList": [],
+ "mappings": "AACE",
+ "sources": [
+ "/root/imported.less",
+ ],
+ "sourcesContent": [
+ ".imported {
+ &-less {
+ color: @color;
+ }
+ }
+ ",
+ ],
+ "version": 3,
+ },
+ visualization: "https://evanw.github.io/source-map-visualization/#MzMALmltcG9ydGVkLWxlc3MgewogIGNvbG9yOiByZWQ7Cn0KMTU2AHsidmVyc2lvbiI6MywibWFwcGluZ3MiOiJBQUNFIiwiaWdub3JlTGlzdCI6W10sInNvdXJjZXMiOlsiL3Jvb3QvaW1wb3J0ZWQubGVzcyJdLCJzb3VyY2VzQ29udGVudCI6WyIuaW1wb3J0ZWQge1xuICAmLWxlc3Mge1xuICAgIGNvbG9yOiBAY29sb3I7XG4gIH1cbn1cbiJdfQ=="
+ }
+ `)
+ })
+
+ test('imported stylus', async () => {
+ const css = await getStyleTagContentIncluding('.imported-stylus ')
+ const map = extractSourcemap(css)
+ expect(formatSourcemapForSnapshot(map, css)).toMatchInlineSnapshot(`
+ SourceMap {
+ content: {
+ "ignoreList": [],
+ "mappings": "AACE",
+ "sources": [
+ "/root/imported.styl",
+ ],
+ "sourcesContent": [
+ ".imported
+ &-stylus
+ color blue-red-mixed
+ ",
+ ],
+ "version": 3,
+ },
+ visualization: "https://evanw.github.io/source-map-visualization/#MzgALmltcG9ydGVkLXN0eWx1cyB7CiAgY29sb3I6IHB1cnBsZTsKfQoxNTIAeyJ2ZXJzaW9uIjozLCJtYXBwaW5ncyI6IkFBQ0UiLCJpZ25vcmVMaXN0IjpbXSwic291cmNlcyI6WyIvcm9vdC9pbXBvcnRlZC5zdHlsIl0sInNvdXJjZXNDb250ZW50IjpbIi5pbXBvcnRlZFxuICAmLXN0eWx1c1xuICAgIGNvbG9yIGJsdWUtcmVkLW1peGVkXG4iXX0="
+ }
+ `)
+ })
+
+ test('imported sugarss', async () => {
+ const css = await getStyleTagContentIncluding('.imported-sugarss ')
+ const map = extractSourcemap(css)
+ expect(formatSourcemapForSnapshot(map, css)).toMatchInlineSnapshot(`
+ SourceMap {
+ content: {
+ "ignoreList": [],
+ "mappings": "AAAA",
+ "sources": [
+ "/root/imported.sss",
+ ],
+ "sourcesContent": [
+ ".imported-sugarss
+ color: red
+ ",
+ ],
+ "version": 3,
+ },
+ visualization: "https://evanw.github.io/source-map-visualization/#MzYALmltcG9ydGVkLXN1Z2Fyc3MgewogIGNvbG9yOiByZWQ7Cn0KMTM1AHsidmVyc2lvbiI6MywibWFwcGluZ3MiOiJBQUFBIiwiaWdub3JlTGlzdCI6W10sInNvdXJjZXMiOlsiL3Jvb3QvaW1wb3J0ZWQuc3NzIl0sInNvdXJjZXNDb250ZW50IjpbIi5pbXBvcnRlZC1zdWdhcnNzXG4gIGNvbG9yOiByZWRcbiJdfQ=="
+ }
+ `)
+ })
+
+ test('should not output missing source file warning', () => {
+ serverLogs.forEach((log) => {
+ expect(log).not.toMatch(/Sourcemap for .+ points to missing source files/)
+ })
+ })
+})
diff --git a/playground/css-sourcemap/be-imported.css b/playground/css-sourcemap/be-imported.css
new file mode 100644
index 00000000000000..a29e5f77e3cb5d
--- /dev/null
+++ b/playground/css-sourcemap/be-imported.css
@@ -0,0 +1,3 @@
+.be-imported {
+ color: red;
+}
diff --git a/playground/css-sourcemap/imported-nested.sass b/playground/css-sourcemap/imported-nested.sass
new file mode 100644
index 00000000000000..b5f10d672c1cc5
--- /dev/null
+++ b/playground/css-sourcemap/imported-nested.sass
@@ -0,0 +1 @@
+$primary: red
diff --git a/playground/css-sourcemap/imported-with-import.css b/playground/css-sourcemap/imported-with-import.css
new file mode 100644
index 00000000000000..6a1ed3c3772698
--- /dev/null
+++ b/playground/css-sourcemap/imported-with-import.css
@@ -0,0 +1,5 @@
+@import '@/be-imported.css';
+
+.imported-with-import {
+ color: red;
+}
diff --git a/playground/css-sourcemap/imported.css b/playground/css-sourcemap/imported.css
new file mode 100644
index 00000000000000..9c9b32924962dc
--- /dev/null
+++ b/playground/css-sourcemap/imported.css
@@ -0,0 +1,3 @@
+.imported {
+ color: red;
+}
diff --git a/playground/css-sourcemap/imported.less b/playground/css-sourcemap/imported.less
new file mode 100644
index 00000000000000..e71b15eb102441
--- /dev/null
+++ b/playground/css-sourcemap/imported.less
@@ -0,0 +1,5 @@
+.imported {
+ &-less {
+ color: @color;
+ }
+}
diff --git a/playground/css-sourcemap/imported.module.sass b/playground/css-sourcemap/imported.module.sass
new file mode 100644
index 00000000000000..448a5e7e31f75a
--- /dev/null
+++ b/playground/css-sourcemap/imported.module.sass
@@ -0,0 +1,3 @@
+.imported
+ &-sass-module
+ color: red
diff --git a/playground/css-sourcemap/imported.sass b/playground/css-sourcemap/imported.sass
new file mode 100644
index 00000000000000..e80f906f0e5f51
--- /dev/null
+++ b/playground/css-sourcemap/imported.sass
@@ -0,0 +1,5 @@
+@use "/imported-nested.sass"
+
+.imported
+ &-sass
+ color: imported-nested.$primary
diff --git a/playground/css-sourcemap/imported.sss b/playground/css-sourcemap/imported.sss
new file mode 100644
index 00000000000000..56084992472c47
--- /dev/null
+++ b/playground/css-sourcemap/imported.sss
@@ -0,0 +1,2 @@
+.imported-sugarss
+ color: red
diff --git a/playground/css-sourcemap/imported.styl b/playground/css-sourcemap/imported.styl
new file mode 100644
index 00000000000000..83c7cf517acf4d
--- /dev/null
+++ b/playground/css-sourcemap/imported.styl
@@ -0,0 +1,3 @@
+.imported
+ &-stylus
+ color blue-red-mixed
diff --git a/playground/css-sourcemap/index.html b/playground/css-sourcemap/index.html
new file mode 100644
index 00000000000000..8260ae75ed65ca
--- /dev/null
+++ b/playground/css-sourcemap/index.html
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
CSS Sourcemap
+
+
<inline>
+
+
<linked>: no import
+
<linked>: with import
+
+
<imported>: no import
+
<imported>: with import
+
+
<imported sass>
+
<imported sass> with module
+
+
<imported less> with string additionalData
+
+
<imported stylus>
+
+
+
+
<input source-map>
+
+
+
+
+
diff --git a/playground/css-sourcemap/index.js b/playground/css-sourcemap/index.js
new file mode 100644
index 00000000000000..c05f09558ddaf0
--- /dev/null
+++ b/playground/css-sourcemap/index.js
@@ -0,0 +1 @@
+export default 'hello'
diff --git a/playground/css-sourcemap/input-map.css b/playground/css-sourcemap/input-map.css
new file mode 100644
index 00000000000000..575a1751c2cbca
--- /dev/null
+++ b/playground/css-sourcemap/input-map.css
@@ -0,0 +1,4 @@
+.input-map {
+ color: #00f;
+}
+/*# sourceMappingURL=input-map.css.map */
diff --git a/playground/css-sourcemap/input-map.css.map b/playground/css-sourcemap/input-map.css.map
new file mode 100644
index 00000000000000..05502b8ce18685
--- /dev/null
+++ b/playground/css-sourcemap/input-map.css.map
@@ -0,0 +1,7 @@
+{
+ "version": 3,
+ "sources": ["input-map.src.css"],
+ "sourcesContent": [".input-map {\n color: blue;\n}"],
+ "mappings": "AAAA,WACE",
+ "names": []
+}
diff --git a/playground/css-sourcemap/input-map.src.css b/playground/css-sourcemap/input-map.src.css
new file mode 100644
index 00000000000000..90b9565e271633
--- /dev/null
+++ b/playground/css-sourcemap/input-map.src.css
@@ -0,0 +1,3 @@
+.input-map {
+ color: blue;
+}
diff --git a/playground/css-sourcemap/linked-with-import.css b/playground/css-sourcemap/linked-with-import.css
new file mode 100644
index 00000000000000..6f65d92441fa49
--- /dev/null
+++ b/playground/css-sourcemap/linked-with-import.css
@@ -0,0 +1,5 @@
+@import '@/be-imported.css';
+
+.linked-with-import {
+ color: red;
+}
diff --git a/playground/css-sourcemap/linked.css b/playground/css-sourcemap/linked.css
new file mode 100644
index 00000000000000..e3b67c83872ac0
--- /dev/null
+++ b/playground/css-sourcemap/linked.css
@@ -0,0 +1,3 @@
+.linked {
+ color: red;
+}
diff --git a/playground/css-sourcemap/package.json b/playground/css-sourcemap/package.json
new file mode 100644
index 00000000000000..5d1c9e001eb197
--- /dev/null
+++ b/playground/css-sourcemap/package.json
@@ -0,0 +1,20 @@
+{
+ "name": "@vitejs/test-css-sourcemap",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build",
+ "debug": "node --inspect-brk ../../packages/vite/bin/vite",
+ "preview": "vite preview"
+ },
+ "devDependencies": {
+ "less": "^4.5.1",
+ "lightningcss": "^1.31.1",
+ "magic-string": "^0.30.21",
+ "sass": "^1.97.3",
+ "stylus": "^0.64.0",
+ "sugarss": "^5.0.1"
+ }
+}
diff --git a/playground/css-sourcemap/vite.config-lib-entry.js b/playground/css-sourcemap/vite.config-lib-entry.js
new file mode 100644
index 00000000000000..600b7414a48b75
--- /dev/null
+++ b/playground/css-sourcemap/vite.config-lib-entry.js
@@ -0,0 +1,13 @@
+import { defineConfig } from 'vite'
+
+export default defineConfig({
+ build: {
+ cssCodeSplit: true,
+ sourcemap: true,
+ outDir: 'dist/lib-entry',
+ lib: {
+ entry: ['./index.js', './linked.css'],
+ formats: ['es'],
+ },
+ },
+})
diff --git a/playground/css-sourcemap/vite.config-lightningcss.js b/playground/css-sourcemap/vite.config-lightningcss.js
new file mode 100644
index 00000000000000..cf987bc98f032e
--- /dev/null
+++ b/playground/css-sourcemap/vite.config-lightningcss.js
@@ -0,0 +1,11 @@
+import { defineConfig, mergeConfig } from 'vite'
+import baseConfig from './vite.config.js'
+
+export default mergeConfig(
+ baseConfig,
+ defineConfig({
+ css: {
+ transformer: 'lightningcss',
+ },
+ }),
+)
diff --git a/playground/css-sourcemap/vite.config.js b/playground/css-sourcemap/vite.config.js
new file mode 100644
index 00000000000000..40723e6302e0eb
--- /dev/null
+++ b/playground/css-sourcemap/vite.config.js
@@ -0,0 +1,59 @@
+import { defineConfig } from 'vite'
+import MagicString from 'magic-string'
+
+export default defineConfig({
+ resolve: {
+ alias: {
+ '@': import.meta.dirname,
+ },
+ },
+ css: {
+ devSourcemap: true,
+ preprocessorOptions: {
+ less: {
+ additionalData: '@color: red;',
+ },
+ styl: {
+ additionalData: (content, filename) => {
+ const ms = new MagicString(content, { filename })
+
+ const willBeReplaced = 'blue-red-mixed'
+ const start = content.indexOf(willBeReplaced)
+ ms.overwrite(start, start + willBeReplaced.length, 'purple')
+
+ const map = ms.generateMap({ hires: 'boundary' })
+ map.file = filename
+ map.sources = [filename]
+
+ return {
+ content: ms.toString(),
+ map,
+ }
+ },
+ },
+ },
+ },
+ build: {
+ sourcemap: true,
+ },
+ plugins: [
+ {
+ name: 'virtual-html',
+ configureServer(server) {
+ server.middlewares.use(async (req, res, next) => {
+ if (req.url === '/virtual.html') {
+ const t = await server.transformIndexHtml(
+ '/virtual.html',
+ ' virtual html
',
+ )
+ res.setHeader('Content-Type', 'text/html')
+ res.statusCode = 200
+ res.end(t)
+ return
+ }
+ next()
+ })
+ },
+ },
+ ],
+})
diff --git a/playground/css/__tests__/css.spec.ts b/playground/css/__tests__/css.spec.ts
new file mode 100644
index 00000000000000..c5506838313f49
--- /dev/null
+++ b/playground/css/__tests__/css.spec.ts
@@ -0,0 +1,3 @@
+import { tests } from './tests'
+
+tests(false)
diff --git a/playground/css/__tests__/lightningcss/lightningcss.spec.ts b/playground/css/__tests__/lightningcss/lightningcss.spec.ts
new file mode 100644
index 00000000000000..9b9b35db56e605
--- /dev/null
+++ b/playground/css/__tests__/lightningcss/lightningcss.spec.ts
@@ -0,0 +1,4 @@
+// NOTE: a separate directory from `playground/css` is created by playground/vitestGlobalSetup.ts
+import { tests } from '../tests'
+
+tests(true)
diff --git a/playground/css/__tests__/no-css-minify/css-no-css-minify.spec.ts b/playground/css/__tests__/no-css-minify/css-no-css-minify.spec.ts
new file mode 100644
index 00000000000000..eb70df313e1670
--- /dev/null
+++ b/playground/css/__tests__/no-css-minify/css-no-css-minify.spec.ts
@@ -0,0 +1,14 @@
+import { describe, expect, test } from 'vitest'
+import { findAssetFile, isBuild } from '~utils'
+
+describe.runIf(isBuild)('no css minify', () => {
+ test('js minified but css not minified', () => {
+ expect(findAssetFile(/index-[-\w]+\.js$/, 'no-css-minify')).not.toMatch(
+ '(function polyfill() {',
+ )
+ expect(findAssetFile(/index-[-\w]+\.css$/, 'no-css-minify')).toMatch(`\
+.test-minify {
+ color: rgba(255, 255, 0, 0.7);
+}`)
+ })
+})
diff --git a/playground/css/__tests__/postcss-plugins-different-dir/css-postcss-plugins-different-dir.spec.ts b/playground/css/__tests__/postcss-plugins-different-dir/css-postcss-plugins-different-dir.spec.ts
new file mode 100644
index 00000000000000..cd2b474ce7baf2
--- /dev/null
+++ b/playground/css/__tests__/postcss-plugins-different-dir/css-postcss-plugins-different-dir.spec.ts
@@ -0,0 +1,30 @@
+import path from 'node:path'
+import { createServer } from 'vite'
+import { expect, test } from 'vitest'
+import { getBgColor, getColor, isServe, page, ports } from '~utils'
+
+// Regression test for https://github.com/vitejs/vite/issues/4000
+test.runIf(isServe)('postcss plugins in different dir', async () => {
+ const port = ports['css/postcss-plugins-different-dir']
+ const server = await createServer({
+ root: path.join(import.meta.dirname, '..', '..', '..', 'tailwind'),
+ logLevel: 'silent',
+ server: {
+ port,
+ strictPort: true,
+ },
+ build: {
+ // skip transpilation during tests to make it faster
+ target: 'esnext',
+ },
+ })
+ await server.listen()
+ try {
+ await page.goto(`http://localhost:${port}`)
+ const tailwindStyle = page.locator('#tailwind-style')
+ expect(await getBgColor(tailwindStyle)).toBe('oklch(0.936 0.032 17.717)')
+ expect(await getColor(tailwindStyle)).toBe('rgb(136, 136, 136)')
+ } finally {
+ await server.close()
+ }
+})
diff --git a/playground/css/__tests__/postcss-plugins-different-dir/serve.ts b/playground/css/__tests__/postcss-plugins-different-dir/serve.ts
new file mode 100644
index 00000000000000..195bb47d8520c7
--- /dev/null
+++ b/playground/css/__tests__/postcss-plugins-different-dir/serve.ts
@@ -0,0 +1,10 @@
+// this is automatically detected by playground/vitestSetup.ts and will replace
+// the default e2e test serve behavior
+
+// The server is started in the test, so we need to have a custom serve
+// function or a default server will be created
+export async function serve(): Promise<{ close(): Promise }> {
+ return {
+ close: () => Promise.resolve(),
+ }
+}
diff --git a/playground/css/__tests__/same-file-name/css-same-file-name.spec.ts b/playground/css/__tests__/same-file-name/css-same-file-name.spec.ts
new file mode 100644
index 00000000000000..a0c9a115288135
--- /dev/null
+++ b/playground/css/__tests__/same-file-name/css-same-file-name.spec.ts
@@ -0,0 +1,19 @@
+import { beforeEach, describe, expect, test } from 'vitest'
+import { findAssetFile, isBuild, startDefaultServe } from '~utils'
+
+beforeEach(async () => {
+ await startDefaultServe()
+})
+
+for (let i = 0; i < 5; i++) {
+ describe.runIf(isBuild)('css files has same basename', () => {
+ test('emit file name should consistent', () => {
+ expect(findAssetFile('sub.css', 'same-file-name', '.')).toMatch(
+ '.sub1-sub',
+ )
+ expect(findAssetFile('sub2.css', 'same-file-name', '.')).toMatch(
+ '.sub2-sub',
+ )
+ })
+ })
+}
diff --git a/playground/css/__tests__/sass-modern-compiler-build/sass-modern-compiler.spec.ts b/playground/css/__tests__/sass-modern-compiler-build/sass-modern-compiler.spec.ts
new file mode 100644
index 00000000000000..98bba744b175d5
--- /dev/null
+++ b/playground/css/__tests__/sass-modern-compiler-build/sass-modern-compiler.spec.ts
@@ -0,0 +1,15 @@
+import { expect, test } from 'vitest'
+import { findAssetFile, isBuild } from '~utils'
+
+test.runIf(isBuild)('sass modern compiler build multiple entries', () => {
+ expect(findAssetFile(/entry1/, 'sass-modern-compiler-build'))
+ .toMatchInlineSnapshot(`
+ ".entry1{color:red}
+ "
+ `)
+ expect(findAssetFile(/entry2/, 'sass-modern-compiler-build'))
+ .toMatchInlineSnapshot(`
+ ".entry2{color:#00f}
+ "
+ `)
+})
diff --git a/playground/css/__tests__/sass-tests.ts b/playground/css/__tests__/sass-tests.ts
new file mode 100644
index 00000000000000..721690dbd5bdad
--- /dev/null
+++ b/playground/css/__tests__/sass-tests.ts
@@ -0,0 +1,135 @@
+import { expect, test } from 'vitest'
+import { editFile, getBg, getColor, isBuild, page, viteTestUrl } from '~utils'
+
+export const sassTest = () => {
+ test('sass', async () => {
+ const imported = await page.$('.sass')
+ const atImport = await page.$('.sass-at-import')
+ const atImportAlias = await page.$('.sass-at-import-alias')
+ const atImportRelative = await page.$('.sass-at-import-relative')
+ const atImportReplacementAlias = await page.$(
+ '.sass-at-import-replacement-alias',
+ )
+ const urlStartsWithVariable = await page.$('.sass-url-starts-with-variable')
+ const urlStartsWithVariableInterpolation1 = await page.$(
+ '.sass-url-starts-with-interpolation1',
+ )
+ const urlStartsWithVariableInterpolation2 = await page.$(
+ '.sass-url-starts-with-interpolation2',
+ )
+ const urlStartsWithVariableConcat = await page.$(
+ '.sass-url-starts-with-variable-concat',
+ )
+ const urlStartsWithFunctionCall = await page.$(
+ '.sass-url-starts-with-function-call',
+ )
+ const partialImport = await page.$('.sass-partial')
+
+ expect(await getColor(imported)).toBe('orange')
+ expect(await getColor(atImport)).toBe('olive')
+ expect(await getBg(atImport)).toMatch(
+ isBuild ? /base64/ : '/nested/icon.png',
+ )
+ expect(await getColor(atImportAlias)).toBe('olive')
+ expect(await getBg(atImportAlias)).toMatch(
+ isBuild ? /base64/ : '/nested/icon.png',
+ )
+ expect(await getColor(atImportRelative)).toBe('olive')
+ expect(await getBg(atImportRelative)).toMatch(
+ isBuild ? /base64/ : '/nested/icon.png',
+ )
+ expect(await getColor(atImportReplacementAlias)).toBe('olive')
+ expect(await getBg(urlStartsWithVariable)).toMatch(
+ isBuild ? /ok-[-\w]+\.png/ : `${viteTestUrl}/ok.png`,
+ )
+ expect(await getBg(urlStartsWithVariableInterpolation1)).toMatch(
+ isBuild ? /ok-[-\w]+\.png/ : `${viteTestUrl}/ok.png`,
+ )
+ expect(await getBg(urlStartsWithVariableInterpolation2)).toMatch(
+ isBuild ? /ok-[-\w]+\.png/ : `${viteTestUrl}/ok.png`,
+ )
+ expect(await getBg(urlStartsWithVariableConcat)).toMatch(
+ isBuild ? /ok-[-\w]+\.png/ : `${viteTestUrl}/ok.png`,
+ )
+ expect(await getBg(urlStartsWithFunctionCall)).toMatch(
+ isBuild ? /ok-[-\w]+\.png/ : `${viteTestUrl}/ok.png`,
+ )
+ expect(await getColor(partialImport)).toBe('orchid')
+ expect(await getColor(await page.$('.sass-file-absolute'))).toBe('orange')
+ expect(await getColor(await page.$('.sass-dir-index'))).toBe('orange')
+ expect(await getColor(await page.$('.sass-root-relative'))).toBe('orange')
+
+ if (isBuild) return
+
+ editFile('sass.scss', (code) =>
+ code.replace('color: $injectedColor', 'color: red'),
+ )
+ await expect.poll(() => getColor(imported)).toBe('red')
+
+ editFile('nested/_index.scss', (code) =>
+ code.replace('color: olive', 'color: blue'),
+ )
+ await expect.poll(() => getColor(atImport)).toBe('blue')
+
+ editFile('nested/_partial.scss', (code) =>
+ code.replace('color: orchid', 'color: green'),
+ )
+ await expect.poll(() => getColor(partialImport)).toBe('green')
+ })
+}
+
+export const sassModuleTests = (enableHmrTests = false) => {
+ test('sass modules composes/from path resolving', async () => {
+ const imported = await page.$('.path-resolved-modules-sass')
+ expect(await getColor(imported)).toBe('orangered')
+
+ // check if the generated CSS module class name is indeed using the
+ // format specified in vite.config.js
+ expect(await imported.getAttribute('class')).toMatch(
+ /.composed-module__apply-color___[\w-]{5}/,
+ )
+
+ expect(await imported.getAttribute('class')).toMatch(
+ /.composes-path-resolving-module__path-resolving-sass___[\w-]{5}/,
+ )
+
+ // @todo HMR is not working on this situation.
+ // editFile('composed.module.scss', (code) =>
+ // code.replace('color: orangered', 'color: red')
+ // )
+ // await expect.poll(() => getColor(imported)).toMatch('red')
+ })
+
+ test('css modules w/ sass', async () => {
+ const imported = await page.$('.modules-sass')
+ expect(await getColor(imported)).toBe('orangered')
+ expect(await imported.getAttribute('class')).toMatch(
+ /.mod-module__apply-color___[\w-]{5}/,
+ )
+
+ if (isBuild) return
+
+ editFile('mod.module.scss', (code) =>
+ code.replace('color: orangered', 'color: blue'),
+ )
+ await expect.poll(() => getColor(imported)).toBe('blue')
+ })
+}
+
+export const sassOtherTests = () => {
+ test('@import dependency w/ sass entry', async () => {
+ expect(await getColor('.css-dep-sass')).toBe('orange')
+ })
+
+ test('@import dependency w/ sass export mapping', async () => {
+ expect(await getColor('.css-dep-exports-sass')).toBe('orange')
+ })
+
+ test('@import dependency w/ sass export mapping (deep)', async () => {
+ expect(await getColor('.css-dep-exports-deep-sass')).toBe('orange')
+ })
+
+ test('@import dependency w/out package scss', async () => {
+ expect(await getColor('.sass-dep')).toBe('lavender')
+ })
+}
diff --git a/playground/css/__tests__/tests.ts b/playground/css/__tests__/tests.ts
new file mode 100644
index 00000000000000..a41bc4183bb2d1
--- /dev/null
+++ b/playground/css/__tests__/tests.ts
@@ -0,0 +1,556 @@
+import { readFileSync } from 'node:fs'
+import { expect, test } from 'vitest'
+import { sassModuleTests, sassOtherTests, sassTest } from './sass-tests'
+import {
+ editFile,
+ findAssetFile,
+ getBg,
+ getBgColor,
+ getColor,
+ isBuild,
+ page,
+ removeFile,
+ serverLogs,
+ viteTestUrl,
+} from '~utils'
+
+export const tests = (isLightningCSS: boolean) => {
+ // note: tests should retrieve the element at the beginning of test and reuse it
+ // in later assertions to ensure CSS HMR doesn't reload the page
+ test('imported css', async () => {
+ const glob = await page.textContent('.imported-css-glob')
+ expect(glob).toContain('.dir-import')
+ const globEager = await page.textContent('.imported-css-globEager')
+ expect(globEager).toContain('.dir-import')
+ })
+
+ test('linked css', async () => {
+ const linked = await page.$('.linked')
+ const atImport = await page.$('.linked-at-import')
+
+ expect(await getColor(linked)).toBe('blue')
+ expect(await getColor(atImport)).toBe('red')
+
+ if (isBuild) return
+
+ editFile('linked.css', (code) => code.replace('color: blue', 'color: red'))
+ await expect.poll(() => getColor(linked)).toBe('red')
+
+ editFile('linked-at-import.css', (code) =>
+ code.replace('color: red', 'color: blue'),
+ )
+ await expect.poll(() => getColor(atImport)).toBe('blue')
+ })
+
+ test('css import from js', async () => {
+ const imported = await page.$('.imported')
+ const atImport = await page.$('.imported-at-import')
+
+ expect(await getColor(imported)).toBe('green')
+ expect(await getColor(atImport)).toBe('purple')
+
+ if (isBuild) return
+
+ editFile('imported.css', (code) =>
+ code.replace('color: green', 'color: red'),
+ )
+ await expect.poll(() => getColor(imported)).toBe('red')
+
+ editFile('imported-at-import.css', (code) =>
+ code.replace('color: purple', 'color: blue'),
+ )
+ await expect.poll(() => getColor(atImport)).toBe('blue')
+ })
+
+ test('css import asset with space', async () => {
+ const importedWithSpace = await page.$('.import-with-space')
+
+ expect(await getBg(importedWithSpace)).toMatch(/.*\/ok.*\.png/)
+ })
+
+ test('postcss config', async () => {
+ const imported = await page.$('.postcss .nesting')
+ expect(await getColor(imported)).toBe('pink')
+
+ if (isBuild) return
+
+ editFile('imported.css', (code) =>
+ code.replace('color: pink', 'color: red'),
+ )
+ await expect.poll(() => getColor(imported)).toBe('red')
+ })
+
+ test('postcss plugin that injects url()', async () => {
+ const imported = await page.$('.inject-url')
+ // alias should be resolved
+ expect(await getBg(imported)).toMatch(
+ /localhost(?::\d+)?\/(?:assets\/)?ok.*\.png/,
+ )
+ })
+
+ sassTest()
+
+ test('less', async () => {
+ const imported = await page.$('.less')
+ const atImport = await page.$('.less-at-import')
+ const atImportAlias = await page.$('.less-at-import-alias')
+ const atImportUrlOmmer = await page.$('.less-at-import-url-ommer')
+ const urlStartsWithVariable = await page.$('.less-url-starts-with-variable')
+ const urlStartsWithInterpolation = await page.$(
+ '.less-url-starts-with-interpolation',
+ )
+
+ expect(await getColor(imported)).toBe('blue')
+ expect(await getColor(atImport)).toBe('darkslateblue')
+ expect(await getBg(atImport)).toMatch(
+ isBuild ? /base64/ : '/nested/icon.png',
+ )
+ expect(await getColor(atImportAlias)).toBe('darkslateblue')
+ expect(await getBg(atImportAlias)).toMatch(
+ isBuild ? /base64/ : '/nested/icon.png',
+ )
+ expect(await getColor(atImportUrlOmmer)).toBe('darkorange')
+ expect(await getBg(urlStartsWithVariable)).toMatch(
+ isBuild ? /ok-[-\w]+\.png/ : `${viteTestUrl}/ok.png`,
+ )
+ expect(await getBg(urlStartsWithInterpolation)).toMatch(
+ isBuild ? /ok-[-\w]+\.png/ : `${viteTestUrl}/ok.png`,
+ )
+
+ if (isBuild) return
+
+ editFile('less.less', (code) => code.replace('@color: blue', '@color: red'))
+ await expect.poll(() => getColor(imported)).toBe('red')
+
+ editFile('nested/nested.less', (code) =>
+ code.replace('color: darkslateblue', 'color: blue'),
+ )
+ await expect.poll(() => getColor(atImport)).toBe('blue')
+ })
+
+ test('less-plugin', async () => {
+ const body = await page.$('.less-js-plugin')
+ expect(await getBg(body)).toBe(
+ 'url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAA1JREFUGFdjYGD4/x8AAwIB/8myre4AAAAASUVORK5CYII=")',
+ )
+ })
+
+ test('stylus', async () => {
+ const imported = await page.$('.stylus')
+ const additionalData = await page.$('.stylus-additional-data')
+ const relativeImport = await page.$('.stylus-import')
+ const relativeImportAlias = await page.$('.stylus-import-alias')
+ const optionsRelativeImport = await page.$(
+ '.stylus-options-relative-import',
+ )
+ const optionsAbsoluteImport = await page.$(
+ '.stylus-options-absolute-import',
+ )
+ const optionsDefineVar = await page.$('.stylus-options-define-var')
+ const optionsDefineFunc = await page.$('.stylus-options-define-func')
+
+ expect(await getColor(imported)).toBe('blue')
+ expect(await getColor(additionalData)).toBe('orange')
+ expect(await getColor(relativeImport)).toBe('darkslateblue')
+ expect(await getColor(relativeImportAlias)).toBe('darkslateblue')
+ expect(await getBg(relativeImportAlias)).toMatch(
+ isBuild ? /base64/ : '/nested/icon.png',
+ )
+ expect(await getColor(optionsRelativeImport)).toBe('green')
+ expect(await getColor(optionsAbsoluteImport)).toBe('red')
+ expect(await getColor(optionsDefineVar)).toBe('rgb(51, 197, 255)')
+ expect(await getColor(optionsDefineFunc)).toBe('rgb(255, 0, 98)')
+
+ if (isBuild) return
+
+ editFile('stylus.styl', (code) =>
+ code.replace('$color ?= blue', '$color ?= red'),
+ )
+ await expect.poll(() => getColor(imported)).toBe('red')
+
+ editFile('nested/nested.styl', (code) =>
+ code.replace('color darkslateblue', 'color blue'),
+ )
+ await expect.poll(() => getColor(relativeImport)).toBe('blue')
+ })
+
+ test('css modules', async () => {
+ const imported = await page.$('.modules')
+ expect(await getColor(imported)).toBe('turquoise')
+
+ // check if the generated CSS module class name is indeed using the
+ // format specified in vite.config.js
+ expect(await imported.getAttribute('class')).toMatch(
+ /.mod-module__apply-color___[\w-]{5}/,
+ )
+
+ if (isBuild) return
+
+ editFile('mod.module.css', (code) =>
+ code.replace('color: turquoise', 'color: red'),
+ )
+ await expect.poll(() => getColor(imported)).toBe('red')
+ })
+
+ test('css modules composes/from path resolving', async () => {
+ const imported = await page.$('.path-resolved-modules-css')
+ expect(await getColor(imported)).toBe('turquoise')
+
+ // check if the generated CSS module class name is indeed using the
+ // format specified in vite.config.js
+ expect(await imported.getAttribute('class')).toMatch(
+ /.composed-module__apply-color___[\w-]{5}/,
+ )
+
+ expect(await imported.getAttribute('class')).toMatch(
+ /.composes-path-resolving-module__path-resolving-css___[\w-]{5}/,
+ )
+
+ // @todo HMR is not working on this situation.
+ // editFile('composed.module.css', (code) =>
+ // code.replace('color: turquoise', 'color: red')
+ // )
+ // await expect.poll(() => getColor(imported)).toBe('red')
+ })
+
+ sassModuleTests()
+
+ test('less modules composes/from path resolving', async () => {
+ const imported = await page.$('.path-resolved-modules-less')
+ expect(await getColor(imported)).toBe('blue')
+
+ // check if the generated CSS module class name is indeed using the
+ // format specified in vite.config.js
+ expect(await imported.getAttribute('class')).toMatch(
+ /.composed-module__apply-color___[\w-]{5}/,
+ )
+
+ expect(await imported.getAttribute('class')).toMatch(
+ /.composes-path-resolving-module__path-resolving-less___[\w-]{5}/,
+ )
+
+ // @todo HMR is not working on this situation.
+ // editFile('composed.module.scss', (code) =>
+ // code.replace('color: orangered', 'color: red')
+ // )
+ // await expect.poll(() => getColor(imported)).toBe('red')
+ })
+
+ test('inline css modules', async () => {
+ const css = await page.textContent('.modules-inline')
+ expect(css).toMatch(/\.inline-module__apply-color-inline___[\w-]{5}/)
+ })
+
+ test.runIf(isBuild)('@charset hoist', async () => {
+ serverLogs.forEach((log) => {
+ // no warning from esbuild css minifier
+ expect(log).not.toMatch('"@charset" must be the first rule in the file')
+ })
+ })
+
+ test('layers', async () => {
+ expect(await getColor('.layers-blue')).toMatch('blue')
+ expect(await getColor('.layers-green')).toMatch('green')
+ })
+
+ test('@import dependency w/ style entry', async () => {
+ expect(await getColor('.css-dep')).toBe('purple')
+ })
+
+ test('@import dependency w/ style export mapping', async () => {
+ expect(await getColor('.css-dep-exports')).toBe('purple')
+ })
+
+ test('@import dependency that @import another dependency', async () => {
+ expect(await getColor('.css-proxy-dep')).toBe('purple')
+ })
+
+ test('@import scss dependency that has @import with a css extension pointing to another dependency', async () => {
+ expect(await getColor('.scss-proxy-dep')).toBe('purple')
+ })
+
+ sassOtherTests()
+
+ test('async chunk', async () => {
+ const el = await page.$('.async')
+ expect(await getColor(el)).toBe('teal')
+
+ if (isBuild) {
+ // assert that the css is extracted into its own file instead of in the
+ // main css file
+ expect(findAssetFile(/index-[-\w]{8}\.css$/)).not.toMatch('teal')
+ expect(findAssetFile(/async-[-\w]{8}\.css$/)).toMatch(
+ '.async{color:teal}',
+ )
+ } else {
+ // test hmr
+ editFile('async.css', (code) =>
+ code.replace('color: teal', 'color: blue'),
+ )
+ await expect.poll(() => getColor(el)).toBe('blue')
+ }
+ })
+
+ test('treeshaken async chunk', async () => {
+ if (isBuild) {
+ // should be absent in prod
+ expect(
+ await page.evaluate(() => {
+ return document.querySelector('.async-treeshaken')
+ }),
+ ).toBeNull()
+ // assert that the css is not present anywhere
+ expect(findAssetFile(/\.css$/)).not.toMatch('plum')
+ expect(findAssetFile(/index-[-\w]+\.js$/)).not.toMatch(
+ '.async{color:plum}',
+ )
+ expect(findAssetFile(/async-[-\w]+\.js$/)).not.toMatch(
+ '.async{color:plum}',
+ )
+ // should have no chunk!
+ expect(findAssetFile(/async-treeshaken/)).toBeUndefined()
+ } else {
+ // should be present in dev
+ const el = await page.$('.async-treeshaken')
+ editFile('async-treeshaken.css', (code) =>
+ code.replace('color: plum', 'color: blue'),
+ )
+ await expect.poll(() => getColor(el)).toBe('blue')
+ }
+ })
+
+ test('PostCSS dir-dependency', async () => {
+ const el1 = await page.$('.dir-dep')
+ const el2 = await page.$('.dir-dep-2')
+ const el3 = await page.$('.dir-dep-3')
+
+ expect(await getColor(el1)).toBe('grey')
+ expect(await getColor(el2)).toBe('grey')
+ expect(await getColor(el3)).toBe('grey')
+
+ // NOTE: lightningcss does not support registering dependencies in plugins
+ if (!isBuild && !isLightningCSS) {
+ editFile('glob-dep/foo.css', (code) =>
+ code.replace('color: grey', 'color: blue'),
+ )
+ await expect.poll(() => getColor(el1)).toBe('blue')
+ expect(await getColor(el2)).toBe('grey')
+
+ editFile('glob-dep/bar.css', (code) =>
+ code.replace('color: grey', 'color: red'),
+ )
+ await expect.poll(() => getColor(el2)).toBe('red')
+ expect(await getColor(el1)).toBe('blue')
+
+ editFile('glob-dep/nested (dir)/baz.css', (code) =>
+ code.replace('color: grey', 'color: green'),
+ )
+ await expect.poll(() => getColor(el3)).toBe('green')
+ expect(await getColor(el1)).toBe('blue')
+ expect(await getColor(el2)).toBe('red')
+
+ // test add/remove
+ removeFile('glob-dep/bar.css')
+ await expect.poll(() => getColor(el2)).toBe('black')
+ }
+ })
+
+ test('import dependency includes css import', async () => {
+ expect(await getColor('.css-js-dep')).toBe('green')
+ expect(await getColor('.css-js-dep-module')).toBe('green')
+ })
+
+ test('URL separation', async () => {
+ const urlSeparated = await page.$('.url-separated')
+ const baseUrl = 'url(images/dog.webp)'
+ const cases = new Array(5)
+ .fill('')
+ .flatMap((_, i) =>
+ [',', ' ,', ', ', ' , '].map(
+ (sep) =>
+ `background-image:${new Array(i + 1).fill(baseUrl).join(sep)};`,
+ ),
+ )
+
+ // Insert the base case
+ cases.unshift('background-image:url(images/cat.webp),url(images/dog.webp)')
+
+ for (const [c, i] of cases.map((c, i) => [c, i]) as [string, number][]) {
+ // Replace the previous case
+ if (i > 0)
+ editFile('imported.css', (code) => code.replace(cases[i - 1], c))
+
+ expect(await getBg(urlSeparated)).toMatch(
+ /^url\(.+\)(?:\s*,\s*url\(.+\))*$/,
+ )
+ }
+ })
+
+ test('inlined', async () => {
+ // should not insert css
+ expect(await getColor('.inlined')).toBe('black')
+ })
+
+ test('inlined-code', async () => {
+ const code = await page.textContent('.inlined-code')
+ // should resolve assets
+ expect(code).toContain('background:')
+ expect(code).not.toContain('__VITE_ASSET__')
+
+ if (isBuild) {
+ expect(code.trim()).not.toContain('\n') // check minified
+ }
+ })
+
+ test('minify css', async () => {
+ if (!isBuild) {
+ return
+ }
+
+ // should keep the rgba() syntax
+ const cssFile = findAssetFile(/index-[-\w]+\.css$/)
+ expect(cssFile).toMatch('rgba(')
+ expect(cssFile).not.toMatch('#ffff00b3')
+ })
+
+ test('?url', async () => {
+ expect(await getColor('.url-imported-css')).toBe('yellow')
+ })
+
+ test('?raw', async () => {
+ const rawImportCss = await page.$('.raw-imported-css')
+
+ expect(await rawImportCss.textContent()).toBe(
+ readFileSync(require.resolve('../raw-imported.css'), 'utf-8'),
+ )
+
+ if (!isBuild) {
+ editFile('raw-imported.css', (code) =>
+ code.replace('color: yellow', 'color: blue'),
+ )
+ await expect
+ .poll(() => page.textContent('.raw-imported-css'))
+ .toMatch('color: blue')
+ }
+ })
+
+ test('import css in less', async () => {
+ expect(await getColor('.css-in-less')).toBe('yellow')
+ expect(await getColor('.css-in-less-2')).toBe('blue')
+ })
+
+ test("relative path rewritten in Less's data-uri", async () => {
+ // relative path passed to Less's data-uri is rewritten to absolute,
+ // the Less inlines it
+ expect(await getBg('.form-box-data-uri')).toMatch(
+ /^url\("data:image\/svg\+xml,%3Csvg/,
+ )
+ })
+
+ test('PostCSS source.input.from includes query', async () => {
+ const code = await page.textContent('.postcss-source-input')
+ // should resolve assets
+ expect(code).toContain('/postcss-source-input.css?inline&query=foo')
+ })
+
+ test('aliased css has content', async () => {
+ expect(await getColor('.aliased')).toBe('blue')
+ // skipped: currently not supported see #8936
+ // expect(await page.textContent('.aliased-content')).toMatch('.aliased')
+ expect(await getColor('.aliased-module')).toBe('blue')
+ })
+
+ test('resolve imports field in CSS', async () => {
+ expect(await getColor('.imports-field')).toBe('red')
+ })
+
+ test.runIf(isBuild)(
+ 'warning can be suppressed by esbuild.logOverride',
+ () => {
+ serverLogs.forEach((log) => {
+ // no warning from esbuild css minifier
+ expect(log).not.toMatch('unsupported-css-property')
+ })
+ },
+ )
+
+ test('sugarss', async () => {
+ const imported = await page.$('.sugarss')
+ const atImport = await page.$('.sugarss-at-import')
+ const atImportAlias = await page.$('.sugarss-at-import-alias')
+
+ expect(await getColor(imported)).toBe('blue')
+ expect(await getColor(atImport)).toBe('darkslateblue')
+ expect(await getBg(atImport)).toMatch(
+ isBuild ? /base64/ : '/nested/icon.png',
+ )
+ expect(await getColor(atImportAlias)).toBe('darkslateblue')
+ expect(await getBg(atImportAlias)).toMatch(
+ isBuild ? /base64/ : '/nested/icon.png',
+ )
+
+ if (isBuild) return
+
+ editFile('sugarss.sss', (code) =>
+ code.replace('color: blue', 'color: coral'),
+ )
+ await expect.poll(() => getColor(imported)).toBe('coral')
+
+ editFile('nested/nested.sss', (code) =>
+ code.replace('color: darkslateblue', 'color: blue'),
+ )
+ await expect.poll(() => getColor(atImport)).toBe('blue')
+ })
+
+ // NOTE: the match inline snapshot should generate by build mode
+ test('async css order', async () => {
+ await expect.poll(() => getColor('.async-green')).toBe('green')
+ await expect.poll(() => getColor('.async-blue')).toBe('blue')
+ })
+
+ test('async css order with css modules', async () => {
+ await expect.poll(() => getColor('.modules-pink')).toBe('pink')
+ })
+
+ test('@import scss', async () => {
+ expect(await getColor('.at-import-scss')).toBe('red')
+ })
+
+ // TODO: skipped because of https://github.com/rolldown/rolldown/issues/7315
+ test.runIf(isBuild).skip('manual chunk path', async () => {
+ // assert that the manual-chunk css is output in the directory specified in manualChunk (#12072)
+ expect(
+ findAssetFile(
+ /manual-chunk-[-\w]{8}\.css$/,
+ undefined,
+ 'assets/dir/dir2',
+ ),
+ ).not.toBeUndefined()
+ })
+
+ test.runIf(isBuild)('CSS modules should be treeshaken if not used', () => {
+ const css = findAssetFile(/\.css$/, undefined, undefined, true)
+ expect(css).not.toContain('treeshake-module-b')
+ })
+
+ test.runIf(isBuild)('Scoped CSS via cssScopeTo should be treeshaken', () => {
+ const css = findAssetFile(/\.css$/, undefined, undefined, true)
+ expect(css).not.toMatch(/\btreeshake-scoped-b\b/)
+ expect(css).not.toMatch(/\btreeshake-scoped-c\b/)
+ })
+
+ test('Scoped CSS should have a correct order', async () => {
+ await page.goto(viteTestUrl + '/treeshake-scoped/')
+ expect(await getColor('.treeshake-scoped-order')).toBe('red')
+ expect(await getBgColor('.treeshake-scoped-order')).toBe('blue')
+ })
+
+ test.runIf(isBuild)(
+ 'empty CSS files should generate .css assets, not .js assets',
+ () => {
+ // Check that empty CSS entry point generates a .css file, not a .js file
+ expect(findAssetFile(/empty-[-\w]{8}\.css$/)).not.toBeUndefined()
+ expect(findAssetFile(/empty-[-\w]{8}\.js$/)).toBeUndefined()
+ },
+ )
+}
diff --git a/playground/css/aliased/bar.module.css b/playground/css/aliased/bar.module.css
new file mode 100644
index 00000000000000..e4e46f3306a02e
--- /dev/null
+++ b/playground/css/aliased/bar.module.css
@@ -0,0 +1,3 @@
+.aliasedModule {
+ color: blue;
+}
diff --git a/playground/css/aliased/foo.css b/playground/css/aliased/foo.css
new file mode 100644
index 00000000000000..7e32cb71a8f375
--- /dev/null
+++ b/playground/css/aliased/foo.css
@@ -0,0 +1,3 @@
+.aliased {
+ color: blue;
+}
diff --git a/packages/playground/css/async-treeshaken.css b/playground/css/async-treeshaken.css
similarity index 100%
rename from packages/playground/css/async-treeshaken.css
rename to playground/css/async-treeshaken.css
diff --git a/packages/playground/css/async-treeshaken.js b/playground/css/async-treeshaken.js
similarity index 100%
rename from packages/playground/css/async-treeshaken.js
rename to playground/css/async-treeshaken.js
diff --git a/packages/playground/css/async.css b/playground/css/async.css
similarity index 100%
rename from packages/playground/css/async.css
rename to playground/css/async.css
diff --git a/packages/playground/css/async.js b/playground/css/async.js
similarity index 100%
rename from packages/playground/css/async.js
rename to playground/css/async.js
diff --git a/playground/css/async/async-1.css b/playground/css/async/async-1.css
new file mode 100644
index 00000000000000..9af99eec7843fe
--- /dev/null
+++ b/playground/css/async/async-1.css
@@ -0,0 +1,3 @@
+.async-blue {
+ color: blue;
+}
diff --git a/playground/css/async/async-1.js b/playground/css/async/async-1.js
new file mode 100644
index 00000000000000..8187dc3b9307e7
--- /dev/null
+++ b/playground/css/async/async-1.js
@@ -0,0 +1,4 @@
+import { createButton } from './base'
+import './async-1.css'
+
+createButton('async-blue')
diff --git a/playground/css/async/async-2.css b/playground/css/async/async-2.css
new file mode 100644
index 00000000000000..941e034da37389
--- /dev/null
+++ b/playground/css/async/async-2.css
@@ -0,0 +1,3 @@
+.async-green {
+ color: green;
+}
diff --git a/playground/css/async/async-2.js b/playground/css/async/async-2.js
new file mode 100644
index 00000000000000..157eafdc4bff79
--- /dev/null
+++ b/playground/css/async/async-2.js
@@ -0,0 +1,4 @@
+import { createButton } from './base'
+import './async-2.css'
+
+createButton('async-green')
diff --git a/playground/css/async/async-3.js b/playground/css/async/async-3.js
new file mode 100644
index 00000000000000..b5dd6da1f326d2
--- /dev/null
+++ b/playground/css/async/async-3.js
@@ -0,0 +1,4 @@
+import { createButton } from './base'
+import styles from './async-3.module.css'
+
+createButton(`${styles['async-pink']} modules-pink`)
diff --git a/playground/css/async/async-3.module.css b/playground/css/async/async-3.module.css
new file mode 100644
index 00000000000000..7f43f88d754252
--- /dev/null
+++ b/playground/css/async/async-3.module.css
@@ -0,0 +1,3 @@
+.async-pink {
+ color: pink;
+}
diff --git a/playground/css/async/base.css b/playground/css/async/base.css
new file mode 100644
index 00000000000000..cc6f88ddccdf10
--- /dev/null
+++ b/playground/css/async/base.css
@@ -0,0 +1,3 @@
+.btn {
+ color: black;
+}
diff --git a/playground/css/async/base.js b/playground/css/async/base.js
new file mode 100644
index 00000000000000..1a409d7e32e4c9
--- /dev/null
+++ b/playground/css/async/base.js
@@ -0,0 +1,8 @@
+import './base.css'
+
+export function createButton(className) {
+ const button = document.createElement('button')
+ button.className = `btn ${className}`
+ document.body.appendChild(button)
+ button.textContent = `button ${getComputedStyle(button).color}`
+}
diff --git a/playground/css/async/index.js b/playground/css/async/index.js
new file mode 100644
index 00000000000000..20d6975ab9d23a
--- /dev/null
+++ b/playground/css/async/index.js
@@ -0,0 +1,3 @@
+import('./async-1.js')
+import('./async-2.js')
+import('./async-3.js')
diff --git a/playground/css/charset.css b/playground/css/charset.css
new file mode 100644
index 00000000000000..5c42b279f8404c
--- /dev/null
+++ b/playground/css/charset.css
@@ -0,0 +1,5 @@
+@charset "utf-8";
+
+.utf8 {
+ color: green;
+}
diff --git a/playground/css/composed.module.css b/playground/css/composed.module.css
new file mode 100644
index 00000000000000..b2ae0e967dced1
--- /dev/null
+++ b/playground/css/composed.module.css
@@ -0,0 +1,3 @@
+.apply-color {
+ color: turquoise;
+}
diff --git a/packages/playground/css/composed.module.less b/playground/css/composed.module.less
similarity index 100%
rename from packages/playground/css/composed.module.less
rename to playground/css/composed.module.less
diff --git a/packages/playground/css/composed.module.scss b/playground/css/composed.module.scss
similarity index 100%
rename from packages/playground/css/composed.module.scss
rename to playground/css/composed.module.scss
diff --git a/playground/css/composes-path-resolving.module.css b/playground/css/composes-path-resolving.module.css
new file mode 100644
index 00000000000000..a5a5172eb4104c
--- /dev/null
+++ b/playground/css/composes-path-resolving.module.css
@@ -0,0 +1,11 @@
+.path-resolving-css {
+ composes: apply-color from '=/composed.module.css';
+}
+
+.path-resolving-sass {
+ composes: apply-color from '=/composed.module.scss';
+}
+
+.path-resolving-less {
+ composes: apply-color from '=/composed.module.less';
+}
diff --git a/playground/css/css-dep-exports/foo1.scss b/playground/css/css-dep-exports/foo1.scss
new file mode 100644
index 00000000000000..0292f7d6ad9f6b
--- /dev/null
+++ b/playground/css/css-dep-exports/foo1.scss
@@ -0,0 +1,3 @@
+.css-dep-exports-deep-sass {
+ color: orange;
+}
diff --git a/packages/playground/css/css-dep/index.js b/playground/css/css-dep-exports/index.js
similarity index 100%
rename from packages/playground/css/css-dep/index.js
rename to playground/css/css-dep-exports/index.js
diff --git a/playground/css/css-dep-exports/package.json b/playground/css/css-dep-exports/package.json
new file mode 100644
index 00000000000000..54adce62a88c4a
--- /dev/null
+++ b/playground/css/css-dep-exports/package.json
@@ -0,0 +1,15 @@
+{
+ "name": "@vitejs/test-css-dep-exports",
+ "private": true,
+ "version": "1.0.0",
+ "exports": {
+ ".": {
+ "sass": "./style.scss",
+ "style": "./style.css",
+ "import": "./index.js"
+ },
+ "./foo.scss": {
+ "sass": "./foo1.scss"
+ }
+ }
+}
diff --git a/playground/css/css-dep-exports/style.css b/playground/css/css-dep-exports/style.css
new file mode 100644
index 00000000000000..838a8afbe4d435
--- /dev/null
+++ b/playground/css/css-dep-exports/style.css
@@ -0,0 +1,3 @@
+.css-dep-exports {
+ color: purple;
+}
diff --git a/playground/css/css-dep-exports/style.scss b/playground/css/css-dep-exports/style.scss
new file mode 100644
index 00000000000000..37df38d7d49d24
--- /dev/null
+++ b/playground/css/css-dep-exports/style.scss
@@ -0,0 +1,3 @@
+.css-dep-exports-sass {
+ color: orange;
+}
diff --git a/packages/playground/css/css-dep/index.css b/playground/css/css-dep/index.css
similarity index 100%
rename from packages/playground/css/css-dep/index.css
rename to playground/css/css-dep/index.css
diff --git a/playground/css/css-dep/index.js b/playground/css/css-dep/index.js
new file mode 100644
index 00000000000000..47b55353d03edb
--- /dev/null
+++ b/playground/css/css-dep/index.js
@@ -0,0 +1 @@
+throw new Error('should not be imported')
diff --git a/packages/playground/css/css-dep/index.scss b/playground/css/css-dep/index.scss
similarity index 100%
rename from packages/playground/css/css-dep/index.scss
rename to playground/css/css-dep/index.scss
diff --git a/packages/playground/css/css-dep/index.styl b/playground/css/css-dep/index.styl
similarity index 100%
rename from packages/playground/css/css-dep/index.styl
rename to playground/css/css-dep/index.styl
diff --git a/playground/css/css-dep/package.json b/playground/css/css-dep/package.json
new file mode 100644
index 00000000000000..204e8c71b0ebba
--- /dev/null
+++ b/playground/css/css-dep/package.json
@@ -0,0 +1,8 @@
+{
+ "name": "@vitejs/test-css-dep",
+ "private": true,
+ "version": "1.0.0",
+ "main": "index.js",
+ "style": "index.css",
+ "sass": "index.scss"
+}
diff --git a/playground/css/css-js-dep/bar.module.css b/playground/css/css-js-dep/bar.module.css
new file mode 100644
index 00000000000000..9d62f66761fa3d
--- /dev/null
+++ b/playground/css/css-js-dep/bar.module.css
@@ -0,0 +1,3 @@
+.cssJsDepModule {
+ color: green;
+}
diff --git a/playground/css/css-js-dep/foo.css b/playground/css/css-js-dep/foo.css
new file mode 100644
index 00000000000000..515ee7693bff3f
--- /dev/null
+++ b/playground/css/css-js-dep/foo.css
@@ -0,0 +1,3 @@
+.css-js-dep {
+ color: green;
+}
diff --git a/playground/css/css-js-dep/index.js b/playground/css/css-js-dep/index.js
new file mode 100644
index 00000000000000..853094b806fa97
--- /dev/null
+++ b/playground/css/css-js-dep/index.js
@@ -0,0 +1,4 @@
+import './foo.css'
+import barModuleClasses from './bar.module.css'
+
+export { barModuleClasses }
diff --git a/playground/css/css-js-dep/package.json b/playground/css/css-js-dep/package.json
new file mode 100644
index 00000000000000..ce96e1e3c2b3f9
--- /dev/null
+++ b/playground/css/css-js-dep/package.json
@@ -0,0 +1,7 @@
+{
+ "name": "@vitejs/test-css-js-dep",
+ "private": true,
+ "type": "module",
+ "version": "1.0.0",
+ "main": "index.js"
+}
diff --git a/playground/css/css-proxy-dep-nested/index.css b/playground/css/css-proxy-dep-nested/index.css
new file mode 100644
index 00000000000000..ad0654a130d2e5
--- /dev/null
+++ b/playground/css/css-proxy-dep-nested/index.css
@@ -0,0 +1,3 @@
+.css-proxy-dep {
+ color: purple;
+}
diff --git a/playground/css/css-proxy-dep-nested/package.json b/playground/css/css-proxy-dep-nested/package.json
new file mode 100644
index 00000000000000..06cb23332c7c56
--- /dev/null
+++ b/playground/css/css-proxy-dep-nested/package.json
@@ -0,0 +1,6 @@
+{
+ "name": "@vitejs/test-css-proxy-dep-nested",
+ "private": true,
+ "version": "1.0.0",
+ "style": "index.css"
+}
diff --git a/playground/css/css-proxy-dep/index.css b/playground/css/css-proxy-dep/index.css
new file mode 100644
index 00000000000000..9b31759a8a326d
--- /dev/null
+++ b/playground/css/css-proxy-dep/index.css
@@ -0,0 +1 @@
+@import '@vitejs/test-css-proxy-dep-nested';
diff --git a/playground/css/css-proxy-dep/package.json b/playground/css/css-proxy-dep/package.json
new file mode 100644
index 00000000000000..60256b6f4e2486
--- /dev/null
+++ b/playground/css/css-proxy-dep/package.json
@@ -0,0 +1,9 @@
+{
+ "name": "@vitejs/test-css-proxy-dep",
+ "private": true,
+ "version": "1.0.0",
+ "style": "index.css",
+ "dependencies": {
+ "@vitejs/test-css-proxy-dep-nested": "file:../css-proxy-dep-nested"
+ }
+}
diff --git a/playground/css/dep.css b/playground/css/dep.css
new file mode 100644
index 00000000000000..2577e5b8cb3578
--- /dev/null
+++ b/playground/css/dep.css
@@ -0,0 +1,3 @@
+@import '@vitejs/test-css-dep';
+@import '@vitejs/test-css-dep-exports';
+@import '@vitejs/test-css-proxy-dep';
diff --git a/playground/css/empty.css b/playground/css/empty.css
new file mode 100644
index 00000000000000..e69de29bb2d1d6
diff --git a/playground/css/file-absolute.scss b/playground/css/file-absolute.scss
new file mode 100644
index 00000000000000..508930e3678f6e
--- /dev/null
+++ b/playground/css/file-absolute.scss
@@ -0,0 +1,3 @@
+.sass-file-absolute {
+ color: orange;
+}
diff --git a/playground/css/folder with space/ok.png b/playground/css/folder with space/ok.png
new file mode 100644
index 00000000000000..a8d1e52510c41c
Binary files /dev/null and b/playground/css/folder with space/ok.png differ
diff --git a/playground/css/folder with space/space.css b/playground/css/folder with space/space.css
new file mode 100644
index 00000000000000..55a8532da32a94
--- /dev/null
+++ b/playground/css/folder with space/space.css
@@ -0,0 +1,5 @@
+.import-with-space {
+ color: green;
+ background: url(spacefolder/ok.png);
+ background-position: center;
+}
diff --git a/packages/playground/css/glob-dep.css b/playground/css/glob-dep.css
similarity index 100%
rename from packages/playground/css/glob-dep.css
rename to playground/css/glob-dep.css
diff --git a/packages/playground/css/glob-dep/bar.css b/playground/css/glob-dep/bar.css
similarity index 100%
rename from packages/playground/css/glob-dep/bar.css
rename to playground/css/glob-dep/bar.css
diff --git a/packages/playground/css/glob-dep/foo.css b/playground/css/glob-dep/foo.css
similarity index 100%
rename from packages/playground/css/glob-dep/foo.css
rename to playground/css/glob-dep/foo.css
diff --git a/playground/css/glob-dep/nested (dir)/baz.css b/playground/css/glob-dep/nested (dir)/baz.css
new file mode 100644
index 00000000000000..9a8b0f0ba47dc5
--- /dev/null
+++ b/playground/css/glob-dep/nested (dir)/baz.css
@@ -0,0 +1,3 @@
+.dir-dep-3 {
+ color: grey;
+}
diff --git a/packages/playground/css/glob-import/bar.css b/playground/css/glob-import/bar.css
similarity index 100%
rename from packages/playground/css/glob-import/bar.css
rename to playground/css/glob-import/bar.css
diff --git a/packages/playground/css/glob-import/foo.css b/playground/css/glob-import/foo.css
similarity index 100%
rename from packages/playground/css/glob-import/foo.css
rename to playground/css/glob-import/foo.css
diff --git a/playground/css/imported-at-import.css b/playground/css/imported-at-import.css
new file mode 100644
index 00000000000000..b71ef00e65b27a
--- /dev/null
+++ b/playground/css/imported-at-import.css
@@ -0,0 +1,3 @@
+.imported-at-import {
+ color: purple;
+}
diff --git a/playground/css/imported.css b/playground/css/imported.css
new file mode 100644
index 00000000000000..7d582995fab9fd
--- /dev/null
+++ b/playground/css/imported.css
@@ -0,0 +1,26 @@
+@import './imported-at-import.css';
+@import 'spacefolder/space.css';
+
+.imported {
+ color: green;
+}
+
+pre {
+ background-color: #eee;
+ width: 500px;
+ padding: 1em 1.5em;
+ border-radius: 10px;
+}
+
+/* test postcss nesting */
+.postcss {
+ .nesting {
+ color: pink;
+ }
+}
+
+/* test url comma separation */
+.url-separated {
+ /* prettier-ignore */
+ background-image:url(images/cat.webp),url(images/dog.webp);
+}
diff --git a/playground/css/imported.scss b/playground/css/imported.scss
new file mode 100644
index 00000000000000..eee442a32c9eb5
--- /dev/null
+++ b/playground/css/imported.scss
@@ -0,0 +1,5 @@
+$color: red;
+
+.at-import-scss {
+ color: $color;
+}
diff --git a/playground/css/imports-field.css b/playground/css/imports-field.css
new file mode 100644
index 00000000000000..9120b6c04f9150
--- /dev/null
+++ b/playground/css/imports-field.css
@@ -0,0 +1,3 @@
+.imports-field {
+ color: red;
+}
diff --git a/playground/css/imports-imports-field.css b/playground/css/imports-imports-field.css
new file mode 100644
index 00000000000000..2f4167c3033ec4
--- /dev/null
+++ b/playground/css/imports-imports-field.css
@@ -0,0 +1 @@
+@import '#imports';
diff --git a/playground/css/index.html b/playground/css/index.html
new file mode 100644
index 00000000000000..c6f13efdc0013f
--- /dev/null
+++ b/playground/css/index.html
@@ -0,0 +1,243 @@
+
+
+
+
CSS
+
+
<link>: This should be blue
+
@import in <link>: This should be red
+
+
import from js: This should be green
+
+ @import in import from js: This should be purple
+
+
+ @import from file with space: This should be green and have a background
+ image
+
+
Imported css string:
+
+
+
+
Imported scoped CSS
+
+
+ PostCSS nesting plugin: this should be pink
+
+
PostCSS plugin: this should have a background image
+
+
SASS: This should be orange
+
+ @import from SASS _index: This should be olive and have bg image
+
+
+ @import from SASS _index: This should be olive and have bg image which url
+ contains alias
+
+
+ @import from SASS relative: This should be olive and have bg image
+
+
+ @import with replacement alias from SASS: This should be olive
+
+
@import from SASS _partial: This should be orchid
+
url starts with variable
+
+ url starts with interpolation 1
+
+
+ url starts with interpolation 2
+
+
+ url starts with variable and contains concat
+
+
+ url starts with function call
+
+
Imported SASS string:
+
+ @import dependency w/ no scss entrypoint: this should be lavender
+
+
+ @import "file:///xxx/absolute-path.scss" should be orange
+
+
@import "./dir" should be orange
+
+ @import "/nested/root-relative.scss" should be orange
+
+
+
Less: This should be blue
+
+ @import from Less: This should be darkslateblue and have bg image
+
+
+ @import from Less: This should be darkslateblue and have bg image which url
+ contains alias
+
+
+ @import url() from Less: This should be darkorange
+
+
url starts with variable
+
+ url starts with interpolation
+
+
+
+ tests Less's `data-uri()` function with relative image paths
+
+
+ url in Less's JS plugin: This should have a blue square below
+
+
+
+
Stylus: This should be blue
+
+ Stylus additionalData: This should be orange
+
+
@import from Stylus: This should be darkslateblue
+
+ @import from Stylus: This should be darkslateblue and have bg image which
+ url contains alias
+
+
+ Stylus import (relative path) via vite config preprocessor options: This
+ should be green
+
+
+ Stylus import (absolute path) via vite config preprocessor options: This
+ should be red
+
+
+ Stylus define variable via vite config preprocessor options: This should be
+ rgb(51, 197, 255)
+
+
+ Stylus define function via vite config preprocessor options: This should be
+ rgb(255, 0, 98)
+
+
+
+
+
+
+
CSS modules: this should be turquoise
+
Imported CSS module:
+
+
+
CSS modules w/ SASS: this should be orangered
+
Imported SASS module:
+
+
+
CSS modules should treeshake in build
+
+
Imported compose/from CSS/SASS module:
+
+ CSS modules composes path resolving: this should be turquoise
+
+
+ CSS modules composes path resolving: this should be orangered
+
+
+ CSS modules composes path resolving: this should be blue
+
+
+
+
Inline CSS module:
+
+
+
CSS with @charset:
+
+
+
+ @import with layers:
+ blue
+ green
+
+
+
+ @import dependency w/ style entrypoints: this should be purple
+
+
+ @import dependency w/ sass entrypoints: this should be orange
+
+
+
+ @import dependency w/ style export mapping: this should be purple
+
+
+ @import dependency w/ sass export mapping: this should be orange
+
+
+ @import dependency w/ sass export mapping (deep): this should be orange
+
+
+
+ @import dependency that @import another dependency: this should be purple
+
+
+ @import dependency that has @import with a css extension pointing to another
+ dependency: this should be purple
+
+
+
PostCSS dir-dependency: this should be grey
+
+ PostCSS dir-dependency (file 2): this should be grey too
+
+
+ PostCSS dir-dependency (file 3): this should be grey too
+
+
+
+ import dependency includes 'import "./foo.css"': this should be green
+
+
+ import dependency includes 'import "./bar.module.css"': this should be green
+
+
+
+ URL separation preservation: should have valid background-image
+
+
+
Inlined import - this should NOT be red.
+
+
+ test import css in less, this color will be yellow
+
+
+ test for import less in less, this color will be blue
+
+
+
+ test import css in scss, this color will be orange
+
+
+
+
+
URL Support
+
+
Raw Support
+
+
+
PostCSS source.input.from. Should include query
+
+
+
Import from jsfile.css.js without the extension
+
+
+
Aliased
+
import '#alias': this should be blue
+
+
import '#alias-module': this should be blue
+
+
Imports field
+
import '#imports': this should be red
+
+
+@import scss: this should be red
+
diff --git a/playground/css/inline.module.css b/playground/css/inline.module.css
new file mode 100644
index 00000000000000..9566e21e2cd1af
--- /dev/null
+++ b/playground/css/inline.module.css
@@ -0,0 +1,3 @@
+.apply-color-inline {
+ color: turquoise;
+}
diff --git a/playground/css/inlined.css b/playground/css/inlined.css
new file mode 100644
index 00000000000000..1b08950784ee76
--- /dev/null
+++ b/playground/css/inlined.css
@@ -0,0 +1,4 @@
+.inlined {
+ color: green;
+ background: url('./ok.png');
+}
diff --git a/playground/css/jsfile.css.js b/playground/css/jsfile.css.js
new file mode 100644
index 00000000000000..025674a66f3b16
--- /dev/null
+++ b/playground/css/jsfile.css.js
@@ -0,0 +1,2 @@
+const message = 'from jsfile.css.js'
+export default message
diff --git a/playground/css/layered/blue.css b/playground/css/layered/blue.css
new file mode 100644
index 00000000000000..faa644dd73ce2d
--- /dev/null
+++ b/playground/css/layered/blue.css
@@ -0,0 +1,5 @@
+@media screen {
+ .layers-blue {
+ color: blue;
+ }
+}
diff --git a/playground/css/layered/green.css b/playground/css/layered/green.css
new file mode 100644
index 00000000000000..15a762b7572e0b
--- /dev/null
+++ b/playground/css/layered/green.css
@@ -0,0 +1,5 @@
+@media screen {
+ .layers-green {
+ color: green;
+ }
+}
diff --git a/playground/css/layered/index.css b/playground/css/layered/index.css
new file mode 100644
index 00000000000000..49756673b674d4
--- /dev/null
+++ b/playground/css/layered/index.css
@@ -0,0 +1,13 @@
+@layer base;
+
+@import './blue.css' layer;
+@import './green.css' layer;
+
+@layer base {
+ .layers-blue {
+ color: black;
+ }
+ .layers-green {
+ color: black;
+ }
+}
diff --git a/playground/css/less-plugin.less b/playground/css/less-plugin.less
new file mode 100644
index 00000000000000..0b256dec683f1c
--- /dev/null
+++ b/playground/css/less-plugin.less
@@ -0,0 +1,7 @@
+@plugin "less-plugin/test.js";
+
+.less-js-plugin {
+ height: 1em;
+ width: 1em;
+ background-image: test();
+}
diff --git a/playground/css/less-plugin/test.js b/playground/css/less-plugin/test.js
new file mode 100644
index 00000000000000..e261eaf335e4da
--- /dev/null
+++ b/playground/css/less-plugin/test.js
@@ -0,0 +1,5 @@
+functions.add('test', function test() {
+ const transparentPng =
+ 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAA1JREFUGFdjYGD4/x8AAwIB/8myre4AAAAASUVORK5CYII='
+ return `url(${transparentPng})`
+})
diff --git a/playground/css/less.less b/playground/css/less.less
new file mode 100644
index 00000000000000..f5f6fa52b36740
--- /dev/null
+++ b/playground/css/less.less
@@ -0,0 +1,11 @@
+@import '=/nested/nested';
+@import './nested/css-in-less.less';
+
+// Test data-uri calls with relative images.
+@import './less/components/form.less';
+
+@color: blue;
+
+.less {
+ color: @color;
+}
diff --git a/playground/css/less/components/form.less b/playground/css/less/components/form.less
new file mode 100644
index 00000000000000..99cdc4d5d1d118
--- /dev/null
+++ b/playground/css/less/components/form.less
@@ -0,0 +1,6 @@
+@import url('../../less/ommer.less');
+
+.form-box-data-uri {
+ // data-uri() calls with relative paths should be replaced just like urls.
+ background-image: data-uri('../images/backgrounds/form-select.svg');
+}
diff --git a/playground/css/less/images/backgrounds/form-select.svg b/playground/css/less/images/backgrounds/form-select.svg
new file mode 100644
index 00000000000000..8aaf69c09e03f4
--- /dev/null
+++ b/playground/css/less/images/backgrounds/form-select.svg
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/playground/css/less/ommer.less b/playground/css/less/ommer.less
new file mode 100644
index 00000000000000..a0ef4ce9dda922
--- /dev/null
+++ b/playground/css/less/ommer.less
@@ -0,0 +1,3 @@
+.less-at-import-url-ommer {
+ color: darkorange;
+}
diff --git a/playground/css/lightningcss-plugins.js b/playground/css/lightningcss-plugins.js
new file mode 100644
index 00000000000000..4aa462d1e6db5b
--- /dev/null
+++ b/playground/css/lightningcss-plugins.js
@@ -0,0 +1,210 @@
+import path from 'node:path'
+import { normalizePath } from 'vite'
+import { bundle as bundleWithLightningCss } from 'lightningcss'
+import { globSync } from 'tinyglobby'
+
+/**
+ * @param {string} filename
+ * @returns {import('lightningcss').StyleSheet}
+ *
+ * based on https://github.com/sardinedev/lightningcss-plugins/blob/9fb379486e402a4b4b8950d09e655b4cbf8a118b/packages/global-custom-queries/src/globalCustomQueries.ts#L9-L29
+ * https://github.com/sardinedev/lightningcss-plugins/blob/main/LICENSE
+ */
+function obtainLightningCssAst(filename) {
+ let ast
+ try {
+ bundleWithLightningCss({
+ filename,
+ visitor: {
+ StyleSheet(stylesheet) {
+ ast = stylesheet
+ },
+ },
+ })
+ return ast
+ } catch (error) {
+ throw Error(`failed to obtain lightning css AST`, { cause: error })
+ }
+}
+
+/** @returns {import('lightningcss').Visitor} */
+export function testDirDep() {
+ /** @type {string[]} */
+ let currentStyleSheetSources
+ return {
+ StyleSheet(stylesheet) {
+ currentStyleSheetSources = stylesheet.sources
+ },
+ Rule: {
+ unknown: {
+ test(rule) {
+ const location = rule.loc
+ const from = currentStyleSheetSources[location.source_index]
+ const pattern = normalizePath(
+ path.resolve(path.dirname(from), './glob-dep/**/*.css'),
+ )
+ // FIXME: there's no way to add a dependency
+ const files = globSync(pattern, {
+ expandDirectories: false,
+ absolute: true,
+ })
+ return files.flatMap((file) => obtainLightningCssAst(file).rules)
+ },
+ },
+ },
+ }
+}
+
+/** @returns {import('lightningcss').Visitor} */
+export function testSourceInput() {
+ /** @type {string[]} */
+ let currentStyleSheetSources
+ return {
+ StyleSheet(stylesheet) {
+ currentStyleSheetSources = stylesheet.sources
+ },
+ Rule: {
+ unknown: {
+ 'source-input': (rule) => {
+ const location = rule.loc
+ const from = currentStyleSheetSources[location.source_index]
+ return [
+ {
+ type: 'style',
+ value: {
+ // .source-input::before
+ selectors: [
+ [
+ { type: 'class', name: 'source-input' },
+ { type: 'pseudo-element', kind: 'before' },
+ ],
+ ],
+ // content: ${JSON.stringify(from)};
+ declarations: {
+ declarations: [
+ {
+ property: 'custom',
+ value:
+ /** @satisfies {import('lightningcss').CustomProperty} */ ({
+ name: 'content',
+ value: [
+ {
+ type: 'token',
+ value: { type: 'string', value: from },
+ },
+ ],
+ }),
+ },
+ ],
+ },
+ loc: rule.loc,
+ },
+ },
+ ]
+ },
+ },
+ },
+ }
+}
+
+/**
+ * really simplified implementation of https://github.com/postcss/postcss-nested
+ *
+ * @returns {import('lightningcss').Visitor}
+ */
+export function nestedLikePlugin() {
+ return {
+ Rule: {
+ style(rule) {
+ // NOTE: multiple selectors are not supported
+ if (rule.value.selectors.length > 1) {
+ return
+ }
+ const parentSelector = rule.value.selectors[0]
+
+ const nestedRules = rule.value.rules
+ /** @type {import('lightningcss').Rule[]} */
+ const additionalRules = []
+ if (nestedRules) {
+ const filteredNestedRules = []
+ for (const nestedRule of nestedRules) {
+ if (nestedRule.type === 'style') {
+ const selectors = nestedRule.value.selectors
+ // NOTE: multiple selectors are not supported
+ if (selectors.length === 1) {
+ const selector = selectors[0]
+ if (
+ selector.length >= 2 &&
+ selector[0].type === 'nesting' &&
+ selector[1].type === 'type'
+ ) {
+ const lastParentSelectorComponent =
+ parentSelector[parentSelector.length - 1]
+ if ('name' in lastParentSelectorComponent) {
+ const newSelector = [
+ ...parentSelector.slice(0, -1),
+ {
+ ...lastParentSelectorComponent,
+ name:
+ lastParentSelectorComponent.name + selector[1].name,
+ },
+ ]
+ additionalRules.push({
+ type: 'style',
+ value: {
+ selectors: [newSelector],
+ declarations: nestedRule.value.declarations,
+ loc: nestedRule.value.loc,
+ },
+ })
+ continue
+ }
+ }
+ }
+ }
+ filteredNestedRules.push(nestedRule)
+ }
+ rule.value.rules = filteredNestedRules
+ }
+ return [rule, ...additionalRules]
+ },
+ },
+ }
+}
+
+/** @returns {import('lightningcss').Visitor} */
+export function testInjectUrl() {
+ return {
+ Rule: {
+ unknown: {
+ 'inject-url': (rule) => {
+ return [
+ {
+ type: 'style',
+ value: {
+ selectors: [[{ type: 'class', name: 'inject-url' }]],
+ declarations: {
+ declarations: [
+ {
+ property: 'background-image',
+ value: [
+ {
+ type: 'url',
+ value: {
+ url: '=/ok.png',
+ loc: rule.loc,
+ },
+ },
+ ],
+ },
+ ],
+ },
+ loc: rule.loc,
+ },
+ },
+ ]
+ },
+ },
+ },
+ }
+}
diff --git a/playground/css/linked-at-import.css b/playground/css/linked-at-import.css
new file mode 100644
index 00000000000000..a778a3c754bb1b
--- /dev/null
+++ b/playground/css/linked-at-import.css
@@ -0,0 +1,3 @@
+.linked-at-import {
+ color: red;
+}
diff --git a/playground/css/linked.css b/playground/css/linked.css
new file mode 100644
index 00000000000000..55b11f672fc500
--- /dev/null
+++ b/playground/css/linked.css
@@ -0,0 +1,8 @@
+@import '=/linked-at-import.css';
+
+/* test postcss nesting */
+.wrapper {
+ .linked {
+ color: blue;
+ }
+}
diff --git a/playground/css/main.js b/playground/css/main.js
new file mode 100644
index 00000000000000..aab2b499ec01b6
--- /dev/null
+++ b/playground/css/main.js
@@ -0,0 +1,140 @@
+import './minify.css'
+import './imported.css'
+import './sugarss.sss'
+import './sass.scss'
+import './less.less'
+import './less-plugin.less'
+import './stylus.styl'
+import './manual-chunk.css'
+import './postcss-inject-url.css'
+
+import urlCss from './url-imported.css?url'
+appendLinkStylesheet(urlCss)
+
+import rawCss from './raw-imported.css?raw'
+text('.raw-imported-css', rawCss)
+
+import { cUsed, a as treeshakeScopedA } from './treeshake-scoped/index.js'
+document.querySelector('.scoped').classList.add(treeshakeScopedA(), cUsed())
+
+import mod from './mod.module.css'
+document.querySelector('.modules').classList.add(mod['apply-color'])
+text('.modules-code', JSON.stringify(mod, null, 2))
+
+import sassMod from './mod.module.scss'
+document.querySelector('.modules-sass').classList.add(sassMod['apply-color'])
+text('.modules-sass-code', JSON.stringify(sassMod, null, 2))
+
+import { a as treeshakeMod } from './treeshake-module/index.js'
+document
+ .querySelector('.modules-treeshake')
+ .classList.add(treeshakeMod()['treeshake-module-a'])
+
+import composesPathResolvingMod from './composes-path-resolving.module.css'
+document
+ .querySelector('.path-resolved-modules-css')
+ .classList.add(...composesPathResolvingMod['path-resolving-css'].split(' '))
+document
+ .querySelector('.path-resolved-modules-sass')
+ .classList.add(...composesPathResolvingMod['path-resolving-sass'].split(' '))
+document
+ .querySelector('.path-resolved-modules-less')
+ .classList.add(...composesPathResolvingMod['path-resolving-less'].split(' '))
+text(
+ '.path-resolved-modules-code',
+ JSON.stringify(composesPathResolvingMod, null, 2),
+)
+
+import inlineMod from './inline.module.css?inline'
+text('.modules-inline', inlineMod)
+
+import charset from './charset.css?inline'
+text('.charset-css', charset)
+
+import './layered/index.css'
+
+import './dep.css'
+import './glob-dep.css'
+
+// eslint-disable-next-line import-x/order
+import { barModuleClasses } from '@vitejs/test-css-js-dep'
+document
+ .querySelector('.css-js-dep-module')
+ .classList.add(barModuleClasses.cssJsDepModule)
+
+function text(el, text) {
+ document.querySelector(el).textContent = text
+}
+
+function appendLinkStylesheet(href) {
+ const link = document.createElement('link')
+ link.rel = 'stylesheet'
+ link.href = href
+ document.head.appendChild(link)
+}
+
+if (import.meta.hot) {
+ import.meta.hot.accept('./mod.module.css', (newMod) => {
+ const list = document.querySelector('.modules').classList
+ list.remove(mod.applyColor)
+ list.add(newMod.applyColor)
+ text('.modules-code', JSON.stringify(newMod.default, null, 2))
+ })
+
+ import.meta.hot.accept('./mod.module.scss', (newMod) => {
+ const list = document.querySelector('.modules-sass').classList
+ list.remove(mod.applyColor)
+ list.add(newMod.applyColor)
+ text('.modules-sass-code', JSON.stringify(newMod.default, null, 2))
+ })
+}
+
+// async
+import('./async')
+
+if (import.meta.env.DEV) {
+ import('./async-treeshaken')
+}
+
+// inlined
+import inlined from './inlined.css?inline'
+text('.inlined-code', inlined)
+
+// glob
+const glob = import.meta.glob('./glob-import/*.css', { query: '?inline' })
+Promise.all(
+ Object.keys(glob).map((key) => glob[key]().then((i) => i.default)),
+).then((res) => {
+ text('.imported-css-glob', JSON.stringify(res, null, 2))
+})
+
+// globEager
+const globEager = import.meta.glob('./glob-import/*.css', {
+ eager: true,
+ query: '?inline',
+})
+text('.imported-css-globEager', JSON.stringify(globEager, null, 2))
+
+import postcssSourceInput from './postcss-source-input.css?inline&query=foo'
+text('.postcss-source-input', postcssSourceInput)
+
+// The file is jsfile.css.js, and we should be able to import it without extension
+import jsFileMessage from './jsfile.css'
+text('.jsfile-css-js', jsFileMessage)
+
+import '#alias'
+import aliasContent from '#alias?inline'
+text('.aliased-content', aliasContent)
+import aliasModule from '#alias-module'
+document
+ .querySelector('.aliased-module')
+ .classList.add(aliasModule.aliasedModule)
+
+import './unsupported.css'
+
+import './async/index'
+
+import('./same-name/sub1/sub')
+import('./same-name/sub2/sub')
+
+import './imports-imports-field.css'
diff --git a/playground/css/manual-chunk.css b/playground/css/manual-chunk.css
new file mode 100644
index 00000000000000..dc41883115cc1d
--- /dev/null
+++ b/playground/css/manual-chunk.css
@@ -0,0 +1,3 @@
+.manual-chunk {
+ color: blue;
+}
diff --git a/playground/css/minify.css b/playground/css/minify.css
new file mode 100644
index 00000000000000..ada062407cdb38
--- /dev/null
+++ b/playground/css/minify.css
@@ -0,0 +1,3 @@
+.test-minify {
+ color: rgba(255, 255, 0, 0.7);
+}
diff --git a/playground/css/mod.module.css b/playground/css/mod.module.css
new file mode 100644
index 00000000000000..b2ae0e967dced1
--- /dev/null
+++ b/playground/css/mod.module.css
@@ -0,0 +1,3 @@
+.apply-color {
+ color: turquoise;
+}
diff --git a/packages/playground/css/mod.module.scss b/playground/css/mod.module.scss
similarity index 100%
rename from packages/playground/css/mod.module.scss
rename to playground/css/mod.module.scss
diff --git a/playground/css/nested/_index.scss b/playground/css/nested/_index.scss
new file mode 100644
index 00000000000000..193828696a1004
--- /dev/null
+++ b/playground/css/nested/_index.scss
@@ -0,0 +1,44 @@
+@use 'sass:string';
+@use '/nested/root-relative'; // root relative path
+@use '../weapp.wxss'; // test user's custom importer in a file loaded by vite's custom importer
+
+@import './css-in-scss.css';
+
+.sass-at-import {
+ color: olive;
+ background: url(./icon.png) 10px no-repeat;
+}
+
+.sass-at-import-alias {
+ color: olive;
+ background: url(=/nested/icon.png) 10px no-repeat;
+}
+
+$var: '/ok.png';
+.sass-url-starts-with-variable {
+ background: url($var);
+ background-position: center;
+}
+
+.sass-url-starts-with-interpolation1 {
+ background: url(#{$var});
+ background-position: center;
+}
+
+.sass-url-starts-with-interpolation2 {
+ background: url('#{$var}');
+ background-position: center;
+}
+
+$var-c1: '/ok';
+$var-c2: '.png';
+.sass-url-starts-with-variable-concat {
+ background: url($var-c1 + $var-c2);
+ background-position: center;
+}
+
+$var2: '/OK.PNG';
+.sass-url-starts-with-function-call {
+ background: url(string.to-lower-case($var2));
+ background-position: center;
+}
diff --git a/packages/playground/css/nested/_partial.scss b/playground/css/nested/_partial.scss
similarity index 100%
rename from packages/playground/css/nested/_partial.scss
rename to playground/css/nested/_partial.scss
diff --git a/packages/playground/css/nested/css-in-less-2.less b/playground/css/nested/css-in-less-2.less
similarity index 100%
rename from packages/playground/css/nested/css-in-less-2.less
rename to playground/css/nested/css-in-less-2.less
diff --git a/packages/playground/css/nested/css-in-less.css b/playground/css/nested/css-in-less.css
similarity index 100%
rename from packages/playground/css/nested/css-in-less.css
rename to playground/css/nested/css-in-less.css
diff --git a/packages/playground/css/nested/css-in-less.less b/playground/css/nested/css-in-less.less
similarity index 100%
rename from packages/playground/css/nested/css-in-less.less
rename to playground/css/nested/css-in-less.less
diff --git a/packages/playground/css/nested/css-in-scss.css b/playground/css/nested/css-in-scss.css
similarity index 100%
rename from packages/playground/css/nested/css-in-scss.css
rename to playground/css/nested/css-in-scss.css
diff --git a/packages/playground/vue/public/icon.png b/playground/css/nested/icon.png
similarity index 100%
rename from packages/playground/vue/public/icon.png
rename to playground/css/nested/icon.png
diff --git a/playground/css/nested/nested.less b/playground/css/nested/nested.less
new file mode 100644
index 00000000000000..ecd1b9bff4203a
--- /dev/null
+++ b/playground/css/nested/nested.less
@@ -0,0 +1,20 @@
+.less-at-import {
+ color: darkslateblue;
+ background: url(./icon.png) 10px no-repeat;
+}
+
+.less-at-import-alias {
+ color: darkslateblue;
+ background: url(=/nested/icon.png) 10px no-repeat;
+}
+
+@var: '/ok.png';
+.less-url-starts-with-variable {
+ background: url(@var);
+ background-position: center;
+}
+
+.less-url-starts-with-interpolation {
+ background: url('@{var}');
+ background-position: center;
+}
diff --git a/playground/css/nested/nested.sss b/playground/css/nested/nested.sss
new file mode 100644
index 00000000000000..9dc685cb3e50c3
--- /dev/null
+++ b/playground/css/nested/nested.sss
@@ -0,0 +1,8 @@
+.sugarss-at-import
+ color: darkslateblue
+ background: url(./icon.png) 10px no-repeat
+
+
+.sugarss-at-import-alias
+ color: darkslateblue
+ background: url(=/nested/icon.png) 10px no-repeat
diff --git a/playground/css/nested/nested.styl b/playground/css/nested/nested.styl
new file mode 100644
index 00000000000000..8a371948538de0
--- /dev/null
+++ b/playground/css/nested/nested.styl
@@ -0,0 +1,6 @@
+.stylus-import
+ color darkslateblue
+
+.stylus-import-alias
+ color darkslateblue
+ background url('=/nested/icon.png') 10px no-repeat
diff --git a/playground/css/nested/relative.scss b/playground/css/nested/relative.scss
new file mode 100644
index 00000000000000..310c7f30433d3d
--- /dev/null
+++ b/playground/css/nested/relative.scss
@@ -0,0 +1,4 @@
+.sass-at-import-relative {
+ color: olive;
+ background: url(./icon.png) 10px no-repeat;
+}
diff --git a/playground/css/nested/replacement-alias.scss b/playground/css/nested/replacement-alias.scss
new file mode 100644
index 00000000000000..41d521140fc37a
--- /dev/null
+++ b/playground/css/nested/replacement-alias.scss
@@ -0,0 +1,3 @@
+.sass-at-import-replacement-alias {
+ color: olive;
+}
diff --git a/playground/css/nested/root-relative.scss b/playground/css/nested/root-relative.scss
new file mode 100644
index 00000000000000..775dca855743b3
--- /dev/null
+++ b/playground/css/nested/root-relative.scss
@@ -0,0 +1,3 @@
+.sass-root-relative {
+ color: orange;
+}
diff --git a/playground/css/ok.png b/playground/css/ok.png
new file mode 100644
index 00000000000000..a8d1e52510c41c
Binary files /dev/null and b/playground/css/ok.png differ
diff --git a/packages/playground/css/options/absolute-import.styl b/playground/css/options/absolute-import.styl
similarity index 100%
rename from packages/playground/css/options/absolute-import.styl
rename to playground/css/options/absolute-import.styl
diff --git a/packages/playground/css/options/relative-import.styl b/playground/css/options/relative-import.styl
similarity index 100%
rename from packages/playground/css/options/relative-import.styl
rename to playground/css/options/relative-import.styl
diff --git a/playground/css/package.json b/playground/css/package.json
new file mode 100644
index 00000000000000..c35c5a96cd0401
--- /dev/null
+++ b/playground/css/package.json
@@ -0,0 +1,35 @@
+{
+ "name": "@vitejs/test-css",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build",
+ "debug": "node --inspect-brk ../../packages/vite/bin/vite",
+ "preview": "vite preview",
+ "dev:relative-base": "vite --config ./vite.config-relative-base.js dev",
+ "build:relative-base": "vite --config ./vite.config-relative-base.js build",
+ "preview:relative-base": "vite --config ./vite.config-relative-base.js preview",
+ "dev:no-css-minify": "vite --config ./vite.config-no-css-minify.js dev",
+ "build:no-css-minify": "vite --config ./vite.config-no-css-minify.js build",
+ "preview:no-css-minify": "vite --config ./vite.config-no-css-minify.js preview"
+ },
+ "devDependencies": {
+ "@vitejs/test-css-dep": "link:./css-dep",
+ "@vitejs/test-css-dep-exports": "link:./css-dep-exports",
+ "@vitejs/test-css-js-dep": "file:./css-js-dep",
+ "@vitejs/test-css-proxy-dep": "file:./css-proxy-dep",
+ "@vitejs/test-scss-proxy-dep": "file:./scss-proxy-dep",
+ "less": "^4.5.1",
+ "lightningcss": "^1.31.1",
+ "postcss-nested": "^7.0.2",
+ "sass": "^1.97.3",
+ "stylus": "^0.64.0",
+ "sugarss": "^5.0.1",
+ "tinyglobby": "^0.2.15"
+ },
+ "imports": {
+ "#imports": "./imports-field.css"
+ }
+}
diff --git a/packages/playground/css/pkg-dep/_index.scss b/playground/css/pkg-dep/_index.scss
similarity index 100%
rename from packages/playground/css/pkg-dep/_index.scss
rename to playground/css/pkg-dep/_index.scss
diff --git a/playground/css/pkg-dep/index.js b/playground/css/pkg-dep/index.js
new file mode 100644
index 00000000000000..e69de29bb2d1d6
diff --git a/playground/css/pkg-dep/package.json b/playground/css/pkg-dep/package.json
new file mode 100644
index 00000000000000..a6fa57379afe0f
--- /dev/null
+++ b/playground/css/pkg-dep/package.json
@@ -0,0 +1,6 @@
+{
+ "name": "@vitejs/test-pkg-dep",
+ "private": true,
+ "version": "1.0.0",
+ "main": "index.js"
+}
diff --git a/packages/playground/css/postcss-caching/blue-app/imported.css b/playground/css/postcss-caching/blue-app/imported.css
similarity index 100%
rename from packages/playground/css/postcss-caching/blue-app/imported.css
rename to playground/css/postcss-caching/blue-app/imported.css
diff --git a/packages/playground/css/postcss-caching/blue-app/index.html b/playground/css/postcss-caching/blue-app/index.html
similarity index 100%
rename from packages/playground/css/postcss-caching/blue-app/index.html
rename to playground/css/postcss-caching/blue-app/index.html
diff --git a/playground/css/postcss-caching/blue-app/main.js b/playground/css/postcss-caching/blue-app/main.js
new file mode 100644
index 00000000000000..8556576f10e5f3
--- /dev/null
+++ b/playground/css/postcss-caching/blue-app/main.js
@@ -0,0 +1,7 @@
+import './imported.css'
+import css from './imported.css?inline'
+text('.imported-css', css)
+
+function text(el, text) {
+ document.querySelector(el).textContent = text
+}
diff --git a/playground/css/postcss-caching/blue-app/package.json b/playground/css/postcss-caching/blue-app/package.json
new file mode 100644
index 00000000000000..545df92eb6c5c8
--- /dev/null
+++ b/playground/css/postcss-caching/blue-app/package.json
@@ -0,0 +1,12 @@
+{
+ "name": "@vitejs/test-blue-app",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build",
+ "debug": "node --inspect-brk ../../packages/vite/bin/vite",
+ "preview": "vite preview"
+ }
+}
diff --git a/playground/css/postcss-caching/blue-app/postcss.config.js b/playground/css/postcss-caching/blue-app/postcss.config.js
new file mode 100644
index 00000000000000..679b801013ef1a
--- /dev/null
+++ b/playground/css/postcss-caching/blue-app/postcss.config.js
@@ -0,0 +1,15 @@
+export default {
+ plugins: [replacePinkWithBlue],
+}
+
+function replacePinkWithBlue() {
+ return {
+ postcssPlugin: 'replace-pink-with-blue',
+ Declaration(decl) {
+ if (decl.value === 'pink') {
+ decl.value = 'blue'
+ }
+ },
+ }
+}
+replacePinkWithBlue.postcss = true
diff --git a/playground/css/postcss-caching/css.spec.ts b/playground/css/postcss-caching/css.spec.ts
new file mode 100644
index 00000000000000..7ff525828e8973
--- /dev/null
+++ b/playground/css/postcss-caching/css.spec.ts
@@ -0,0 +1,65 @@
+import path from 'node:path'
+import { createServer } from 'vite'
+import { expect, test } from 'vitest'
+import { getColor, isServe, page, ports } from '~utils'
+
+test.runIf(isServe)('postcss config', async () => {
+ const port = ports['css/postcss-caching']
+ const startServer = async (root) => {
+ const server = await createServer({
+ root,
+ logLevel: 'silent',
+ server: {
+ port,
+ strictPort: true,
+ },
+ build: {
+ // skip transpilation during tests to make it faster
+ target: 'esnext',
+ },
+ })
+ await server.listen()
+ return server
+ }
+
+ const blueAppDir = path.join(import.meta.dirname, 'blue-app')
+ const greenAppDir = path.join(import.meta.dirname, 'green-app')
+ let blueApp
+ let greenApp
+ try {
+ const hmrConnectionPromise = page.waitForEvent('console', (msg) =>
+ msg.text().includes('connected'),
+ )
+
+ blueApp = await startServer(blueAppDir)
+
+ await page.goto(`http://localhost:${port}`, { waitUntil: 'load' })
+ const blueA = await page.$('.postcss-a')
+ expect(await getColor(blueA)).toBe('blue')
+ const blueB = await page.$('.postcss-b')
+ expect(await getColor(blueB)).toBe('black')
+
+ // wait for hmr connection because: if server stops before connection, auto reload does not happen
+ await hmrConnectionPromise
+ await blueApp.close()
+ blueApp = null
+
+ const loadPromise = page.waitForEvent('load') // wait for server restart auto reload
+ greenApp = await startServer(greenAppDir)
+ await loadPromise
+
+ const greenA = await page.$('.postcss-a')
+ expect(await getColor(greenA)).toBe('black')
+ const greenB = await page.$('.postcss-b')
+ expect(await getColor(greenB)).toBe('green')
+ await greenApp.close()
+ greenApp = null
+ } finally {
+ if (blueApp) {
+ await blueApp.close()
+ }
+ if (greenApp) {
+ await greenApp.close()
+ }
+ }
+})
diff --git a/packages/playground/css/postcss-caching/green-app/imported.css b/playground/css/postcss-caching/green-app/imported.css
similarity index 100%
rename from packages/playground/css/postcss-caching/green-app/imported.css
rename to playground/css/postcss-caching/green-app/imported.css
diff --git a/packages/playground/css/postcss-caching/green-app/index.html b/playground/css/postcss-caching/green-app/index.html
similarity index 100%
rename from packages/playground/css/postcss-caching/green-app/index.html
rename to playground/css/postcss-caching/green-app/index.html
diff --git a/playground/css/postcss-caching/green-app/main.js b/playground/css/postcss-caching/green-app/main.js
new file mode 100644
index 00000000000000..8556576f10e5f3
--- /dev/null
+++ b/playground/css/postcss-caching/green-app/main.js
@@ -0,0 +1,7 @@
+import './imported.css'
+import css from './imported.css?inline'
+text('.imported-css', css)
+
+function text(el, text) {
+ document.querySelector(el).textContent = text
+}
diff --git a/playground/css/postcss-caching/green-app/package.json b/playground/css/postcss-caching/green-app/package.json
new file mode 100644
index 00000000000000..1653869bec67c0
--- /dev/null
+++ b/playground/css/postcss-caching/green-app/package.json
@@ -0,0 +1,12 @@
+{
+ "name": "@vitejs/test-green-app",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build",
+ "debug": "node --inspect-brk ../../packages/vite/bin/vite",
+ "preview": "vite preview"
+ }
+}
diff --git a/playground/css/postcss-caching/green-app/postcss.config.js b/playground/css/postcss-caching/green-app/postcss.config.js
new file mode 100644
index 00000000000000..c0a74e3676976d
--- /dev/null
+++ b/playground/css/postcss-caching/green-app/postcss.config.js
@@ -0,0 +1,15 @@
+export default {
+ plugins: [replacePinkWithGreen],
+}
+
+function replacePinkWithGreen() {
+ return {
+ postcssPlugin: 'replace-pink-with-green',
+ Declaration(decl) {
+ if (decl.value === 'pink') {
+ decl.value = 'green'
+ }
+ },
+ }
+}
+replacePinkWithGreen.postcss = true
diff --git a/playground/css/postcss-caching/serve.ts b/playground/css/postcss-caching/serve.ts
new file mode 100644
index 00000000000000..195bb47d8520c7
--- /dev/null
+++ b/playground/css/postcss-caching/serve.ts
@@ -0,0 +1,10 @@
+// this is automatically detected by playground/vitestSetup.ts and will replace
+// the default e2e test serve behavior
+
+// The server is started in the test, so we need to have a custom serve
+// function or a default server will be created
+export async function serve(): Promise<{ close(): Promise }> {
+ return {
+ close: () => Promise.resolve(),
+ }
+}
diff --git a/playground/css/postcss-inject-url.css b/playground/css/postcss-inject-url.css
new file mode 100644
index 00000000000000..766ccc8838bf3d
--- /dev/null
+++ b/playground/css/postcss-inject-url.css
@@ -0,0 +1 @@
+@inject-url;
diff --git a/playground/css/postcss-source-input.css b/playground/css/postcss-source-input.css
new file mode 100644
index 00000000000000..c6c3cb0c16dece
--- /dev/null
+++ b/playground/css/postcss-source-input.css
@@ -0,0 +1 @@
+@source-input;
diff --git a/playground/css/postcss.config.js b/playground/css/postcss.config.js
new file mode 100644
index 00000000000000..0953807b15789a
--- /dev/null
+++ b/playground/css/postcss.config.js
@@ -0,0 +1,85 @@
+import fs from 'node:fs'
+import path from 'node:path'
+import { globSync } from 'tinyglobby'
+import { normalizePath } from 'vite'
+import postcssNested from 'postcss-nested'
+
+export default {
+ plugins: [postcssNested, testDirDep, testSourceInput, testInjectUrl],
+}
+
+/**
+ * A plugin for testing the `dir-dependency` message handling.
+ */
+function testDirDep() {
+ return {
+ postcssPlugin: 'dir-dep',
+ AtRule(atRule, { result, Comment }) {
+ if (atRule.name === 'test') {
+ const pattern = normalizePath(
+ path.resolve(path.dirname(result.opts.from), './glob-dep/**/*.css'),
+ )
+ const files = globSync(pattern, { expandDirectories: false })
+ const text = files.map((f) => fs.readFileSync(f, 'utf-8')).join('\n')
+ atRule.parent.insertAfter(atRule, text)
+ atRule.remove()
+
+ result.messages.push({
+ type: 'dir-dependency',
+ plugin: 'dir-dep',
+ dir: './glob-dep',
+ glob: '*.css',
+ parent: result.opts.from,
+ })
+
+ result.messages.push({
+ type: 'dir-dependency',
+ plugin: 'dir-dep',
+ dir: './glob-dep/nested (dir)', // includes special characters in glob
+ glob: '*.css',
+ parent: result.opts.from,
+ })
+ }
+ },
+ }
+}
+testDirDep.postcss = true
+
+function testSourceInput() {
+ return {
+ postcssPlugin: 'source-input',
+ AtRule(atRule) {
+ if (atRule.name === 'source-input') {
+ atRule.after(
+ `.source-input::before { content: ${JSON.stringify(
+ atRule.source.input.from,
+ )}; }`,
+ )
+ atRule.remove()
+ }
+ },
+ }
+}
+testSourceInput.postcss = true
+
+function testInjectUrl() {
+ return {
+ postcssPlugin: 'inject-url',
+ Once(root, { Rule }) {
+ root.walkAtRules('inject-url', (atRule) => {
+ const rule = new Rule({
+ selector: '.inject-url',
+ source: atRule.source,
+ })
+ rule.append({
+ prop: 'background',
+ value: "url('=/ok.png')",
+ source: atRule.source,
+ })
+ atRule.after(rule)
+ atRule.remove()
+ })
+ },
+ }
+}
+testInjectUrl.postcss = true
diff --git a/playground/css/raw-imported.css b/playground/css/raw-imported.css
new file mode 100644
index 00000000000000..ee681e650b0b47
--- /dev/null
+++ b/playground/css/raw-imported.css
@@ -0,0 +1,6 @@
+.raw {
+ /* should not be transformed by postcss */
+ &-imported {
+ color: yellow;
+ }
+}
diff --git a/playground/css/same-name/sub1/sub.css b/playground/css/same-name/sub1/sub.css
new file mode 100644
index 00000000000000..8eca4c3b9a7b6d
--- /dev/null
+++ b/playground/css/same-name/sub1/sub.css
@@ -0,0 +1,3 @@
+.sub1-sub {
+ color: red;
+}
diff --git a/playground/css/same-name/sub1/sub.js b/playground/css/same-name/sub1/sub.js
new file mode 100644
index 00000000000000..abe787e8e3c05d
--- /dev/null
+++ b/playground/css/same-name/sub1/sub.js
@@ -0,0 +1,3 @@
+import './sub.css'
+
+export default 'sub1-name'
diff --git a/playground/css/same-name/sub2/sub.css b/playground/css/same-name/sub2/sub.css
new file mode 100644
index 00000000000000..910bf3898e5bfb
--- /dev/null
+++ b/playground/css/same-name/sub2/sub.css
@@ -0,0 +1,3 @@
+.sub2-sub {
+ color: blue;
+}
diff --git a/playground/css/same-name/sub2/sub.js b/playground/css/same-name/sub2/sub.js
new file mode 100644
index 00000000000000..3d338a64d0649f
--- /dev/null
+++ b/playground/css/same-name/sub2/sub.js
@@ -0,0 +1,3 @@
+import './sub.css'
+
+export default 'sub2-name'
diff --git a/playground/css/sass-modern-compiler-build/entry1.scss b/playground/css/sass-modern-compiler-build/entry1.scss
new file mode 100644
index 00000000000000..e21334eb8337bc
--- /dev/null
+++ b/playground/css/sass-modern-compiler-build/entry1.scss
@@ -0,0 +1,3 @@
+.entry1 {
+ color: red;
+}
diff --git a/playground/css/sass-modern-compiler-build/entry2.scss b/playground/css/sass-modern-compiler-build/entry2.scss
new file mode 100644
index 00000000000000..eca3004c9d247f
--- /dev/null
+++ b/playground/css/sass-modern-compiler-build/entry2.scss
@@ -0,0 +1,3 @@
+.entry2 {
+ color: blue;
+}
diff --git a/playground/css/sass.scss b/playground/css/sass.scss
new file mode 100644
index 00000000000000..ccef5c28b1ab95
--- /dev/null
+++ b/playground/css/sass.scss
@@ -0,0 +1,18 @@
+@use '=/nested'; // alias + custom index resolving -> /nested/_index.scss
+@use '=/nested/partial'; // sass convention: omitting leading _ for partials
+@use './nested/relative'; // relative path
+@use '@vitejs/test-css-dep'; // package w/ sass entry points
+@use '@vitejs/test-css-dep-exports'; // package with a sass export mapping
+@use '@vitejs/test-css-dep-exports/foo.scss'; // package with a sass export mapping (deep)
+@use '@vitejs/test-scss-proxy-dep'; // package with a sass proxy import
+@use 'virtual-dep'; // virtual file added through importer
+@use '=/pkg-dep'; // package w/out sass field
+@use '=/weapp.wxss'; // wxss file
+@use 'virtual-file-absolute';
+@use '=/scss-dir/main.scss'; // "./dir" reference from vite custom importer
+@use '=replace/nested/replacement-alias.scss';
+
+.sass {
+ /* injected via vite.config.js */
+ color: $injectedColor;
+}
diff --git a/playground/css/scss-dir/dir/index.scss b/playground/css/scss-dir/dir/index.scss
new file mode 100644
index 00000000000000..e6bcdc2166b8ab
--- /dev/null
+++ b/playground/css/scss-dir/dir/index.scss
@@ -0,0 +1,3 @@
+.sass-dir-index {
+ color: orange;
+}
diff --git a/playground/css/scss-dir/main.scss b/playground/css/scss-dir/main.scss
new file mode 100644
index 00000000000000..b661030297a451
--- /dev/null
+++ b/playground/css/scss-dir/main.scss
@@ -0,0 +1 @@
+@use './dir';
diff --git a/playground/css/scss-proxy-dep-nested/index.css b/playground/css/scss-proxy-dep-nested/index.css
new file mode 100644
index 00000000000000..4c7e3b16e62597
--- /dev/null
+++ b/playground/css/scss-proxy-dep-nested/index.css
@@ -0,0 +1,3 @@
+.scss-proxy-dep {
+ color: purple;
+}
diff --git a/playground/css/scss-proxy-dep-nested/package.json b/playground/css/scss-proxy-dep-nested/package.json
new file mode 100644
index 00000000000000..4f7a99bb559207
--- /dev/null
+++ b/playground/css/scss-proxy-dep-nested/package.json
@@ -0,0 +1,5 @@
+{
+ "name": "@vitejs/test-scss-proxy-dep-nested",
+ "private": true,
+ "version": "1.0.0"
+}
diff --git a/playground/css/scss-proxy-dep/index.scss b/playground/css/scss-proxy-dep/index.scss
new file mode 100644
index 00000000000000..540353efe1030a
--- /dev/null
+++ b/playground/css/scss-proxy-dep/index.scss
@@ -0,0 +1 @@
+@use '@vitejs/test-scss-proxy-dep-nested/index.css';
diff --git a/playground/css/scss-proxy-dep/package.json b/playground/css/scss-proxy-dep/package.json
new file mode 100644
index 00000000000000..a2749e7ae3e957
--- /dev/null
+++ b/playground/css/scss-proxy-dep/package.json
@@ -0,0 +1,9 @@
+{
+ "name": "@vitejs/test-scss-proxy-dep",
+ "private": true,
+ "version": "1.0.0",
+ "sass": "index.scss",
+ "dependencies": {
+ "@vitejs/test-scss-proxy-dep-nested": "file:../scss-proxy-dep-nested"
+ }
+}
diff --git a/playground/css/stylus.styl b/playground/css/stylus.styl
new file mode 100644
index 00000000000000..a4e61e6c06e2ec
--- /dev/null
+++ b/playground/css/stylus.styl
@@ -0,0 +1,18 @@
+@import './nested/nested'
+
+$color ?= blue
+
+.stylus
+ color $color
+
+.stylus-additional-data
+ /* injected via vite.config.js */
+ color $injectedColor
+
+.stylus-options-define-var
+ /* defined in vite.config.js */
+ color $definedColor
+
+.stylus-options-define-func
+ /* defined in vite.config.js */
+ color definedFunction()
diff --git a/playground/css/sugarss.sss b/playground/css/sugarss.sss
new file mode 100644
index 00000000000000..80cfc3861b9417
--- /dev/null
+++ b/playground/css/sugarss.sss
@@ -0,0 +1,4 @@
+@import '=/nested/nested.sss'
+
+.sugarss
+ color: blue
diff --git a/playground/css/treeshake-module/a.js b/playground/css/treeshake-module/a.js
new file mode 100644
index 00000000000000..7272fa1dc1d9c1
--- /dev/null
+++ b/playground/css/treeshake-module/a.js
@@ -0,0 +1,5 @@
+import style from './a.module.css'
+
+export function a() {
+ return style
+}
diff --git a/playground/css/treeshake-module/a.module.css b/playground/css/treeshake-module/a.module.css
new file mode 100644
index 00000000000000..72ab1a9fdb001a
--- /dev/null
+++ b/playground/css/treeshake-module/a.module.css
@@ -0,0 +1,3 @@
+.treeshake-module-a {
+ color: red;
+}
diff --git a/playground/css/treeshake-module/b.js b/playground/css/treeshake-module/b.js
new file mode 100644
index 00000000000000..b3db996f7f64cd
--- /dev/null
+++ b/playground/css/treeshake-module/b.js
@@ -0,0 +1,5 @@
+import style from './b.module.css'
+
+export function b() {
+ return style
+}
diff --git a/playground/css/treeshake-module/b.module.css b/playground/css/treeshake-module/b.module.css
new file mode 100644
index 00000000000000..5ad402ef7353e8
--- /dev/null
+++ b/playground/css/treeshake-module/b.module.css
@@ -0,0 +1,3 @@
+.treeshake-module-b {
+ color: red;
+}
diff --git a/playground/css/treeshake-module/index.js b/playground/css/treeshake-module/index.js
new file mode 100644
index 00000000000000..67332c5a21eb3d
--- /dev/null
+++ b/playground/css/treeshake-module/index.js
@@ -0,0 +1,2 @@
+export { a } from './a.js'
+export { b } from './b.js'
diff --git a/playground/css/treeshake-scoped/a-scoped.css b/playground/css/treeshake-scoped/a-scoped.css
new file mode 100644
index 00000000000000..e18cbb887f4637
--- /dev/null
+++ b/playground/css/treeshake-scoped/a-scoped.css
@@ -0,0 +1,3 @@
+.treeshake-scoped-a {
+ color: red;
+}
diff --git a/playground/css/treeshake-scoped/a.js b/playground/css/treeshake-scoped/a.js
new file mode 100644
index 00000000000000..819b7d3cf84e1d
--- /dev/null
+++ b/playground/css/treeshake-scoped/a.js
@@ -0,0 +1,5 @@
+import './a-scoped.css' // should be treeshaken away if `a` is not used
+
+export default function a() {
+ return 'treeshake-scoped-a'
+}
diff --git a/playground/css/treeshake-scoped/b-scoped.css b/playground/css/treeshake-scoped/b-scoped.css
new file mode 100644
index 00000000000000..9792a332519a81
--- /dev/null
+++ b/playground/css/treeshake-scoped/b-scoped.css
@@ -0,0 +1,3 @@
+.treeshake-scoped-b {
+ color: red;
+}
diff --git a/playground/css/treeshake-scoped/b.js b/playground/css/treeshake-scoped/b.js
new file mode 100644
index 00000000000000..798ec76741c429
--- /dev/null
+++ b/playground/css/treeshake-scoped/b.js
@@ -0,0 +1,5 @@
+import './b-scoped.css' // should be treeshaken away if `b` is not used
+
+export default function b() {
+ return 'treeshake-scoped-b'
+}
diff --git a/playground/css/treeshake-scoped/c-scoped.css b/playground/css/treeshake-scoped/c-scoped.css
new file mode 100644
index 00000000000000..8901f7303dc9d6
--- /dev/null
+++ b/playground/css/treeshake-scoped/c-scoped.css
@@ -0,0 +1,3 @@
+.treeshake-scoped-c {
+ color: red;
+}
diff --git a/playground/css/treeshake-scoped/c.js b/playground/css/treeshake-scoped/c.js
new file mode 100644
index 00000000000000..8a7e2fb89dbaa2
--- /dev/null
+++ b/playground/css/treeshake-scoped/c.js
@@ -0,0 +1,10 @@
+import './c-scoped.css' // should be treeshaken away if `b` is not used
+
+export default function c() {
+ return 'treeshake-scoped-c'
+}
+
+export function cUsed() {
+ // used but does not depend on scoped css
+ return 'c-used'
+}
diff --git a/playground/css/treeshake-scoped/d-scoped.css b/playground/css/treeshake-scoped/d-scoped.css
new file mode 100644
index 00000000000000..83c0b0ed176271
--- /dev/null
+++ b/playground/css/treeshake-scoped/d-scoped.css
@@ -0,0 +1,3 @@
+.treeshake-scoped-d {
+ color: red;
+}
diff --git a/playground/css/treeshake-scoped/d.js b/playground/css/treeshake-scoped/d.js
new file mode 100644
index 00000000000000..7581688476cf56
--- /dev/null
+++ b/playground/css/treeshake-scoped/d.js
@@ -0,0 +1,5 @@
+import './d-scoped.css' // should be treeshaken away if `d` is not used
+
+export default function d() {
+ return 'treeshake-scoped-d'
+}
diff --git a/playground/css/treeshake-scoped/index.html b/playground/css/treeshake-scoped/index.html
new file mode 100644
index 00000000000000..d5e17c9a6bd772
--- /dev/null
+++ b/playground/css/treeshake-scoped/index.html
@@ -0,0 +1,12 @@
+treeshake-scoped
+Imported scoped CSS
+
+ scoped CSS order (this should be red text with blue background)
+
+
+
diff --git a/playground/css/treeshake-scoped/index.js b/playground/css/treeshake-scoped/index.js
new file mode 100644
index 00000000000000..93bea696056968
--- /dev/null
+++ b/playground/css/treeshake-scoped/index.js
@@ -0,0 +1,4 @@
+export { default as a } from './a.js'
+export { default as b } from './b.js'
+export { default as c, cUsed } from './c.js'
+export { default as d } from './d.js'
diff --git a/playground/css/treeshake-scoped/order/a-scoped.css b/playground/css/treeshake-scoped/order/a-scoped.css
new file mode 100644
index 00000000000000..64b3725097079a
--- /dev/null
+++ b/playground/css/treeshake-scoped/order/a-scoped.css
@@ -0,0 +1,4 @@
+.treeshake-scoped-order {
+ color: red;
+ background: red;
+}
diff --git a/playground/css/treeshake-scoped/order/a.js b/playground/css/treeshake-scoped/order/a.js
new file mode 100644
index 00000000000000..2dfefad9c14b18
--- /dev/null
+++ b/playground/css/treeshake-scoped/order/a.js
@@ -0,0 +1,7 @@
+import './before.css'
+import './a-scoped.css'
+import './after.css'
+
+export default function a() {
+ return 'treeshake-scoped-order-a'
+}
diff --git a/playground/css/treeshake-scoped/order/after.css b/playground/css/treeshake-scoped/order/after.css
new file mode 100644
index 00000000000000..af41d370d9c45f
--- /dev/null
+++ b/playground/css/treeshake-scoped/order/after.css
@@ -0,0 +1,4 @@
+.treeshake-scoped-order {
+ color: red;
+ background: blue;
+}
diff --git a/playground/css/treeshake-scoped/order/before.css b/playground/css/treeshake-scoped/order/before.css
new file mode 100644
index 00000000000000..d5e6bdb1ee3d36
--- /dev/null
+++ b/playground/css/treeshake-scoped/order/before.css
@@ -0,0 +1,3 @@
+.treeshake-scoped-order {
+ color: blue;
+}
diff --git a/playground/css/unsupported.css b/playground/css/unsupported.css
new file mode 100644
index 00000000000000..c17818a3ab33d7
--- /dev/null
+++ b/playground/css/unsupported.css
@@ -0,0 +1,3 @@
+.unsupported {
+ overflow-x: hidden;
+}
diff --git a/playground/css/url-imported.css b/playground/css/url-imported.css
new file mode 100644
index 00000000000000..95fec50ab2c554
--- /dev/null
+++ b/playground/css/url-imported.css
@@ -0,0 +1,6 @@
+.url {
+ /* should be transformed by postcss */
+ &-imported-css {
+ color: yellow;
+ }
+}
diff --git a/playground/css/vite.config-lightningcss.js b/playground/css/vite.config-lightningcss.js
new file mode 100644
index 00000000000000..8d87c785a0a154
--- /dev/null
+++ b/playground/css/vite.config-lightningcss.js
@@ -0,0 +1,29 @@
+import { defineConfig } from 'vite'
+import { composeVisitors } from 'lightningcss'
+import baseConfig from './vite.config.js'
+import {
+ nestedLikePlugin,
+ testDirDep,
+ testInjectUrl,
+ testSourceInput,
+} from './lightningcss-plugins'
+
+export default defineConfig({
+ ...baseConfig,
+ css: {
+ ...baseConfig.css,
+ transformer: 'lightningcss',
+ lightningcss: {
+ cssModules: {
+ pattern: '[name]__[local]___[hash]',
+ },
+ visitor: composeVisitors([
+ nestedLikePlugin(),
+ testDirDep(),
+ testSourceInput(),
+ testInjectUrl(),
+ ]),
+ },
+ },
+ cacheDir: 'node_modules/.vite-no-css-minify',
+})
diff --git a/playground/css/vite.config-no-css-minify.js b/playground/css/vite.config-no-css-minify.js
new file mode 100644
index 00000000000000..dce4815a712b64
--- /dev/null
+++ b/playground/css/vite.config-no-css-minify.js
@@ -0,0 +1,13 @@
+import { defineConfig } from 'vite'
+import baseConfig from './vite.config.js'
+
+export default defineConfig({
+ ...baseConfig,
+ build: {
+ ...baseConfig.build,
+ outDir: 'dist/no-css-minify',
+ minify: true,
+ cssMinify: false,
+ },
+ cacheDir: 'node_modules/.vite-no-css-minify',
+})
diff --git a/playground/css/vite.config-relative-base.js b/playground/css/vite.config-relative-base.js
new file mode 100644
index 00000000000000..451ca7090d023a
--- /dev/null
+++ b/playground/css/vite.config-relative-base.js
@@ -0,0 +1,22 @@
+import { defineConfig } from 'vite'
+import baseConfig from './vite.config.js'
+
+export default defineConfig(({ isPreview }) => ({
+ ...baseConfig,
+ base: !isPreview ? './' : '/relative-base/', // relative base to make dist portable
+ build: {
+ ...baseConfig.build,
+ outDir: 'dist/relative-base',
+ watch: null,
+ minify: false,
+ assetsInlineLimit: 0,
+ rollupOptions: {
+ output: {
+ entryFileNames: 'entries/[name].js',
+ chunkFileNames: 'chunks/[name]-[hash].js',
+ assetFileNames: 'other-assets/[name]-[hash][extname]',
+ },
+ },
+ },
+ cacheDir: 'node_modules/.vite-relative-base',
+}))
diff --git a/playground/css/vite.config-same-file-name.js b/playground/css/vite.config-same-file-name.js
new file mode 100644
index 00000000000000..f8e88d23b973be
--- /dev/null
+++ b/playground/css/vite.config-same-file-name.js
@@ -0,0 +1,17 @@
+import { defineConfig } from 'vite'
+import baseConfig from './vite.config.js'
+
+export default defineConfig({
+ ...baseConfig,
+ build: {
+ ...baseConfig.build,
+ outDir: 'dist/same-file-name',
+ rollupOptions: {
+ output: {
+ entryFileNames: '[name].js',
+ chunkFileNames: '[name].[hash].js',
+ assetFileNames: '[name].[ext]',
+ },
+ },
+ },
+})
diff --git a/playground/css/vite.config-sass-modern-compiler-build.js b/playground/css/vite.config-sass-modern-compiler-build.js
new file mode 100644
index 00000000000000..412be2815eb339
--- /dev/null
+++ b/playground/css/vite.config-sass-modern-compiler-build.js
@@ -0,0 +1,20 @@
+import path from 'node:path'
+import { defineConfig } from 'vite'
+
+export default defineConfig({
+ build: {
+ outDir: 'dist/sass-modern-compiler-build',
+ rollupOptions: {
+ input: {
+ entry1: path.join(
+ import.meta.dirname,
+ 'sass-modern-compiler-build/entry1.scss',
+ ),
+ entry2: path.join(
+ import.meta.dirname,
+ 'sass-modern-compiler-build/entry2.scss',
+ ),
+ },
+ },
+ },
+})
diff --git a/playground/css/vite.config.js b/playground/css/vite.config.js
new file mode 100644
index 00000000000000..76dc4395aeac39
--- /dev/null
+++ b/playground/css/vite.config.js
@@ -0,0 +1,148 @@
+import path from 'node:path'
+import { pathToFileURL } from 'node:url'
+import stylus from 'stylus'
+import { defineConfig } from 'vite'
+
+// trigger scss bug: https://github.com/sass/dart-sass/issues/710
+// make sure Vite handles safely
+// @ts-expect-error refer to https://github.com/vitejs/vite/pull/11079
+globalThis.window = {}
+// @ts-expect-error refer to https://github.com/vitejs/vite/pull/11079
+globalThis.location = new URL('http://localhost/')
+
+const dirname = import.meta.dirname
+
+export default defineConfig({
+ plugins: [
+ {
+ // Emulate a UI framework component where a framework module would import
+ // scoped CSS files that should treeshake if the default export is not used.
+ name: 'treeshake-scoped-css',
+ enforce: 'pre',
+ async resolveId(id, importer) {
+ if (!importer || !id.endsWith('-scoped.css')) return
+
+ const resolved = await this.resolve(id, importer)
+ if (!resolved) return
+
+ return {
+ ...resolved,
+ meta: {
+ vite: {
+ cssScopeTo: [
+ importer,
+ resolved.id.includes('barrel') ? undefined : 'default',
+ ],
+ },
+ },
+ }
+ },
+ },
+ ],
+ build: {
+ cssTarget: 'chrome61',
+ rollupOptions: {
+ input: {
+ index: path.resolve(dirname, './index.html'),
+ treeshakeScoped: path.resolve(dirname, './treeshake-scoped/index.html'),
+ empty: path.resolve(dirname, './empty.css'),
+ },
+ output: {
+ manualChunks(id) {
+ if (id.includes('manual-chunk.css')) {
+ return 'dir/dir2/manual-chunk'
+ }
+ },
+ },
+ },
+ },
+ esbuild: {
+ logOverride: {
+ 'unsupported-css-property': 'silent',
+ },
+ },
+ resolve: {
+ alias: [
+ { find: '=', replacement: dirname },
+ { find: /^=replace\/(.*)/, replacement: `${dirname}/$1` },
+ { find: 'spacefolder', replacement: dirname + '/folder with space' },
+ { find: '#alias', replacement: dirname + '/aliased/foo.css' },
+ {
+ find: '#alias?inline',
+ replacement: dirname + '/aliased/foo.css?inline',
+ },
+ {
+ find: '#alias-module',
+ replacement: dirname + '/aliased/bar.module.css',
+ },
+ ],
+ },
+ css: {
+ modules: {
+ generateScopedName: '[name]__[local]___[hash:base64:5]',
+
+ // example of how getJSON can be used to generate
+ // typescript typings for css modules class names
+
+ // getJSON(cssFileName, json, _outputFileName) {
+ // let typings = 'declare const classNames: {\n'
+ // for (let className in json) {
+ // typings += ` "${className}": string;\n`
+ // }
+ // typings += '};\n'
+ // typings += 'export default classNames;\n'
+ // const { join, dirname, basename } = require('path')
+ // const typingsFile = join(
+ // dirname(cssFileName),
+ // basename(cssFileName) + '.d.ts'
+ // )
+ // require('fs').writeFileSync(typingsFile, typings)
+ // },
+ },
+ preprocessorOptions: {
+ scss: {
+ additionalData: `$injectedColor: orange;`,
+ importers: [
+ {
+ canonicalize(url) {
+ return url === 'virtual-dep' || url.endsWith('.wxss')
+ ? new URL('custom-importer:virtual-dep')
+ : null
+ },
+ load() {
+ return {
+ contents: ``,
+ syntax: 'scss',
+ }
+ },
+ },
+ {
+ canonicalize(url) {
+ return url === 'virtual-file-absolute'
+ ? new URL('custom-importer:virtual-file-absolute')
+ : null
+ },
+ load() {
+ return {
+ contents: `@use "${pathToFileURL(path.join(import.meta.dirname, 'file-absolute.scss')).href}"`,
+ syntax: 'scss',
+ }
+ },
+ },
+ ],
+ },
+ styl: {
+ additionalData: `$injectedColor ?= orange`,
+ imports: [
+ './options/relative-import.styl',
+ path.join(dirname, 'options/absolute-import.styl'),
+ ],
+ define: {
+ $definedColor: new stylus.nodes.RGBA(51, 197, 255, 1),
+ definedFunction: () => new stylus.nodes.RGBA(255, 0, 98, 1),
+ },
+ },
+ },
+ preprocessorMaxWorkers: true,
+ },
+})
diff --git a/playground/css/weapp.wxss b/playground/css/weapp.wxss
new file mode 100644
index 00000000000000..7a6ada48018898
--- /dev/null
+++ b/playground/css/weapp.wxss
@@ -0,0 +1 @@
+this is not css
diff --git a/playground/data-uri/__tests__/data-uri.spec.ts b/playground/data-uri/__tests__/data-uri.spec.ts
new file mode 100644
index 00000000000000..b18012e59d380c
--- /dev/null
+++ b/playground/data-uri/__tests__/data-uri.spec.ts
@@ -0,0 +1,27 @@
+import { expect, test } from 'vitest'
+import { findAssetFile, isBuild, page } from '~utils'
+
+test('plain', async () => {
+ expect(await page.textContent('.plain')).toBe('hi')
+})
+
+test('base64', async () => {
+ expect(await page.textContent('.base64')).toBe('hi')
+})
+
+test('svg data uri minify', async () => {
+ const sqdqs = await page.getByTestId('sqdqs').boundingBox()
+ const sqsdqs = await page.getByTestId('sqsdqs').boundingBox()
+ const dqsqs = await page.getByTestId('dqsqs').boundingBox()
+ const dqssqs = await page.getByTestId('dqssqs').boundingBox()
+
+ expect(sqdqs.height).toBe(100)
+ expect(sqsdqs.height).toBe(100)
+ expect(dqsqs.height).toBe(100)
+ expect(dqssqs.height).toBe(100)
+})
+
+test.runIf(isBuild)('should compile away the import for build', async () => {
+ const file = findAssetFile('index')
+ expect(file).not.toMatch('import')
+})
diff --git a/playground/data-uri/double-quote-in-single-quotes.svg b/playground/data-uri/double-quote-in-single-quotes.svg
new file mode 100644
index 00000000000000..d3a5ffc19e3701
--- /dev/null
+++ b/playground/data-uri/double-quote-in-single-quotes.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/playground/data-uri/double-quotes-in-single-quotes.svg b/playground/data-uri/double-quotes-in-single-quotes.svg
new file mode 100644
index 00000000000000..fb8f151a23a598
--- /dev/null
+++ b/playground/data-uri/double-quotes-in-single-quotes.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/playground/data-uri/index.html b/playground/data-uri/index.html
new file mode 100644
index 00000000000000..3794f74db4ea3e
--- /dev/null
+++ b/playground/data-uri/index.html
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/playground/data-uri/main.js b/playground/data-uri/main.js
new file mode 100644
index 00000000000000..63326fdb1b4781
--- /dev/null
+++ b/playground/data-uri/main.js
@@ -0,0 +1,18 @@
+import sqdqs from './single-quote-in-double-quotes.svg'
+import sqsdqs from './single-quotes-in-double-quotes.svg'
+import dqsqs from './double-quote-in-single-quotes.svg'
+import dqssqs from './double-quotes-in-single-quotes.svg'
+
+document.querySelector('#sqdqs').innerHTML = `
+
+`
+document.querySelector('#sqsdqs').innerHTML = `
+
+`
+
+document.querySelector('#dqsqs').innerHTML = `
+
+`
+document.querySelector('#dqssqs').innerHTML = `
+
+`
diff --git a/playground/data-uri/package.json b/playground/data-uri/package.json
new file mode 100644
index 00000000000000..4e8b2d699f2ac8
--- /dev/null
+++ b/playground/data-uri/package.json
@@ -0,0 +1,12 @@
+{
+ "name": "@vitejs/test-data-uri",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build",
+ "debug": "node --inspect-brk ../../packages/vite/bin/vite",
+ "preview": "vite preview"
+ }
+}
diff --git a/playground/data-uri/single-quote-in-double-quotes.svg b/playground/data-uri/single-quote-in-double-quotes.svg
new file mode 100644
index 00000000000000..69974c97773921
--- /dev/null
+++ b/playground/data-uri/single-quote-in-double-quotes.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/playground/data-uri/single-quotes-in-double-quotes.svg b/playground/data-uri/single-quotes-in-double-quotes.svg
new file mode 100644
index 00000000000000..0489e7b39e8b5a
--- /dev/null
+++ b/playground/data-uri/single-quotes-in-double-quotes.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/playground/data-uri/vite.config.js b/playground/data-uri/vite.config.js
new file mode 100644
index 00000000000000..67d43b61015b13
--- /dev/null
+++ b/playground/data-uri/vite.config.js
@@ -0,0 +1,20 @@
+import { defineConfig } from 'vite'
+
+export default defineConfig({
+ plugins: [
+ {
+ name: 'post-plugin',
+ enforce: 'post',
+ resolveId(id) {
+ if (id.replace(/\?.*$/, '') === 'comma/foo') {
+ return id
+ }
+ },
+ load(id) {
+ if (id.replace(/\?.*$/, '') === 'comma/foo') {
+ return `export const comma = 'hi'`
+ }
+ },
+ },
+ ],
+})
diff --git a/playground/define/__tests__/define.spec.ts b/playground/define/__tests__/define.spec.ts
new file mode 100644
index 00000000000000..b95a00c4171e99
--- /dev/null
+++ b/playground/define/__tests__/define.spec.ts
@@ -0,0 +1,121 @@
+import { expect, test } from 'vitest'
+import viteConfig from '../vite.config'
+import { page } from '~utils'
+
+const defines = viteConfig.define
+const envDefines = viteConfig.environments.client.define
+
+test('string', async () => {
+ expect(await page.textContent('.exp')).toBe(
+ String(typeof eval(defines.__EXP__)),
+ )
+ expect(await page.textContent('.string')).toBe(JSON.parse(defines.__STRING__))
+ expect(await page.textContent('.number')).toBe(String(defines.__NUMBER__))
+ expect(await page.textContent('.boolean')).toBe(String(defines.__BOOLEAN__))
+ expect(await page.textContent('.undefined')).toBe('')
+
+ expect(await page.textContent('.object')).toBe(
+ JSON.stringify(defines.__OBJ__, null, 2),
+ )
+ expect(await page.textContent('.process-node-env')).toBe(
+ JSON.parse(defines['process.env.NODE_ENV']),
+ )
+ expect(await page.textContent('.process-env')).toBe(
+ JSON.stringify(defines['process.env'], null, 2),
+ )
+ expect(await page.textContent('.env-var')).toBe(
+ JSON.parse(defines['process.env.SOMEVAR']),
+ )
+ expect(await page.textContent('.process-as-property')).toBe(
+ defines.__OBJ__.process.env.SOMEVAR,
+ )
+ expect(await page.textContent('.spread-object')).toBe(
+ JSON.stringify({ SOMEVAR: defines['process.env.SOMEVAR'] }),
+ )
+ expect(await page.textContent('.spread-array')).toBe(
+ JSON.stringify([...defines.__STRING__]),
+ )
+ expect(await page.textContent('.dollar-identifier')).toBe(
+ String(defines.$DOLLAR),
+ )
+ expect(await page.textContent('.unicode-identifier')).toBe(
+ String(defines.ÖUNICODE_LETTERɵ),
+ )
+ expect(await page.textContent('.no-identifier-substring')).toBe(String(true))
+ expect(await page.textContent('.no-property')).toBe(String(true))
+ // html wouldn't need to define replacement
+ expect(await page.textContent('.exp-define')).toBe('__EXP__')
+ expect(await page.textContent('.import-json')).toBe('__EXP__')
+ expect(await page.textContent('.define-in-dep')).toBe(
+ defines.__STRINGIFIED_OBJ__,
+ )
+ expect(await page.textContent('.define-in-environment')).toBe(
+ envDefines.__DEFINE_IN_ENVIRONMENT__,
+ )
+})
+
+test('ignores constants in string literals', async () => {
+ expect(
+ await page.textContent('.ignores-string-literals .process-env-dot'),
+ ).toBe('process.env.')
+ expect(
+ await page.textContent('.ignores-string-literals .global-process-env-dot'),
+ ).toBe('global.process.env.')
+ expect(
+ await page.textContent(
+ '.ignores-string-literals .globalThis-process-env-dot',
+ ),
+ ).toBe('globalThis.process.env.')
+ expect(
+ await page.textContent('.ignores-string-literals .process-env-NODE_ENV'),
+ ).toBe('process.env.NODE_ENV')
+ expect(
+ await page.textContent(
+ '.ignores-string-literals .global-process-env-NODE_ENV',
+ ),
+ ).toBe('global.process.env.NODE_ENV')
+ expect(
+ await page.textContent(
+ '.ignores-string-literals .globalThis-process-env-NODE_ENV',
+ ),
+ ).toBe('globalThis.process.env.NODE_ENV')
+ expect(
+ await page.textContent('.ignores-string-literals .import-meta-hot'),
+ ).toBe('import' + '.meta.hot')
+})
+
+test('replaces constants in template literal expressions', async () => {
+ expect(
+ await page.textContent(
+ '.replaces-constants-in-template-literal-expressions .process-env-dot',
+ ),
+ ).toBe(JSON.parse(defines['process.env.SOMEVAR']))
+ expect(
+ await page.textContent(
+ '.replaces-constants-in-template-literal-expressions .process-env-NODE_ENV',
+ ),
+ ).toBe('dev')
+})
+
+test('replace constants on import.meta.env when it is a invalid json', async () => {
+ expect(
+ await page.textContent(
+ '.replace-undefined-constants-on-import-meta-env .import-meta-env-UNDEFINED',
+ ),
+ ).toBe('undefined')
+ expect(
+ await page.textContent(
+ '.replace-undefined-constants-on-import-meta-env .import-meta-env-SOME_IDENTIFIER',
+ ),
+ ).toBe('true')
+})
+
+test('optional values are detected by pattern properly', async () => {
+ expect(await page.textContent('.optional-env')).toBe(
+ JSON.parse(defines['process.env.SOMEVAR']),
+ )
+})
+
+test('env import with query parameters works correctly', async () => {
+ expect(await page.textContent('.env-with-query')).toBe('success')
+})
diff --git a/playground/define/commonjs-dep/index.js b/playground/define/commonjs-dep/index.js
new file mode 100644
index 00000000000000..3525efcea4c5bf
--- /dev/null
+++ b/playground/define/commonjs-dep/index.js
@@ -0,0 +1,3 @@
+module.exports = {
+ defined: __STRINGIFIED_OBJ__,
+}
diff --git a/playground/define/commonjs-dep/package.json b/playground/define/commonjs-dep/package.json
new file mode 100644
index 00000000000000..f8ac503baaf9a9
--- /dev/null
+++ b/playground/define/commonjs-dep/package.json
@@ -0,0 +1,6 @@
+{
+ "name": "@vitejs/test-commonjs-dep",
+ "private": true,
+ "version": "1.0.0",
+ "type": "commonjs"
+}
diff --git a/packages/playground/define/data.json b/playground/define/data.json
similarity index 100%
rename from packages/playground/define/data.json
rename to playground/define/data.json
diff --git a/playground/define/index.html b/playground/define/index.html
new file mode 100644
index 00000000000000..c6ab5b2e2a36d0
--- /dev/null
+++ b/playground/define/index.html
@@ -0,0 +1,190 @@
+
+
+Define
+
+Raw Expression
+String
+Number
+Boolean
+Undefined
+Object
+Env Var
+process node env:
+process env:
+process as property:
+spread object:
+spread array:
+dollar identifier:
+unicode identifier:
+no property:
+no identifier substring:
+define variable in html: __EXP__
+import json:
+define in dep:
+define in environment:
+
+Define ignores string literals
+
+ process.env.
+ global.process.env.
+
+ globalThis.process.env.
+
+ process.env.NODE_ENV
+
+ global.process.env.NODE_ENV
+
+
+
+ globalThis.process.env.NODE_ENV
+
+
+ import.meta.hot
+
+
+Define replaces constants in template literal expressions
+
+ process.env.
+ global.process.env.
+
+ globalThis.process.env.
+
+ process.env.NODE_ENV
+
+ global.process.env.NODE_ENV
+
+
+
+ globalThis.process.env.NODE_ENV
+
+
+ import.meta.hot
+
+
+Define undefined constants on import.meta.env when it's a invalid json
+
+
+Optional values are detected by pattern properly
+
+ process?.env?.SOMEVAR
+
+
+
+Env import with query parameters works correctly
+
+ /@vite/env?foo should be transformed
+
+
+
+
+
+
+
diff --git a/playground/define/optional-env.js b/playground/define/optional-env.js
new file mode 100644
index 00000000000000..eb86cbfe22419c
--- /dev/null
+++ b/playground/define/optional-env.js
@@ -0,0 +1,2 @@
+// separate file to test pattern filter
+export default process?.env?.SOMEVAR
diff --git a/playground/define/package.json b/playground/define/package.json
new file mode 100644
index 00000000000000..a65b36c1c3df67
--- /dev/null
+++ b/playground/define/package.json
@@ -0,0 +1,15 @@
+{
+ "name": "@vitejs/test-define",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build",
+ "debug": "node --inspect-brk ../../packages/vite/bin/vite",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "@vitejs/test-commonjs-dep": "file:./commonjs-dep"
+ }
+}
diff --git a/playground/define/vite.config.js b/playground/define/vite.config.js
new file mode 100644
index 00000000000000..0ccf65b395131d
--- /dev/null
+++ b/playground/define/vite.config.js
@@ -0,0 +1,66 @@
+import { defineConfig } from 'vite'
+
+/**
+ * Plugin to test that env imports with query parameters work correctly (#20997)
+ */
+function testEnvQueryParamsPlugin() {
+ let isBuild = true
+ return {
+ name: 'test-env-query-params',
+ configResolved(config) {
+ isBuild = config.command === 'build'
+ },
+ transform(code, id) {
+ if (
+ id.includes('index.html') &&
+ code.includes('__VITE_ENV_WITH_QUERY__')
+ ) {
+ return code.replace(
+ '__VITE_ENV_WITH_QUERY__',
+ JSON.stringify(isBuild ? 'data:text/javascript,' : '/@vite/env?foo'),
+ )
+ }
+ },
+ }
+}
+
+export default defineConfig({
+ plugins: [testEnvQueryParamsPlugin()],
+ define: {
+ __EXP__: 'false',
+ __STRING__: '"hello"',
+ __NUMBER__: 123,
+ __BOOLEAN__: true,
+ __UNDEFINED__: undefined,
+ __OBJ__: {
+ foo: 1,
+ bar: {
+ baz: 2,
+ },
+ process: {
+ env: {
+ SOMEVAR: '"PROCESS MAY BE PROPERTY"',
+ },
+ },
+ },
+ 'process.env.NODE_ENV': '"dev"',
+ 'process.env.SOMEVAR': '"SOMEVAR"',
+ 'process.env': {
+ NODE_ENV: 'dev',
+ SOMEVAR: 'SOMEVAR',
+ OTHER: 'works',
+ },
+ $DOLLAR: 456,
+ ÖUNICODE_LETTERɵ: 789,
+ __VAR_NAME__: false,
+ __STRINGIFIED_OBJ__: JSON.stringify({ foo: true }),
+ 'import.meta.env.SOME_IDENTIFIER': '__VITE_SOME_IDENTIFIER__',
+ },
+ environments: {
+ client: {
+ define: {
+ __DEFINE_IN_ENVIRONMENT__: '"defined only in client"',
+ },
+ },
+ },
+})
diff --git a/playground/devtools/index.html b/playground/devtools/index.html
new file mode 100644
index 00000000000000..8a0f9c1d64bbb7
--- /dev/null
+++ b/playground/devtools/index.html
@@ -0,0 +1,12 @@
+
+
+
+
+ DevTools
+
+
+
+
+
+
+
diff --git a/playground/devtools/package.json b/playground/devtools/package.json
new file mode 100644
index 00000000000000..eecd2a12b7629f
--- /dev/null
+++ b/playground/devtools/package.json
@@ -0,0 +1,19 @@
+{
+ "name": "@vitejs/test-devtools",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build",
+ "debug": "node --inspect-brk ../../packages/vite/bin/vite",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "vue": "^3.5.25"
+ },
+ "devDependencies": {
+ "vite": "workspace:*",
+ "@vitejs/devtools": "^0.0.0-alpha.24"
+ }
+}
diff --git a/playground/devtools/src/counter.ts b/playground/devtools/src/counter.ts
new file mode 100644
index 00000000000000..c14288be1c2bcc
--- /dev/null
+++ b/playground/devtools/src/counter.ts
@@ -0,0 +1,19 @@
+import { ref } from 'vue'
+
+export function useCounter() {
+ const count = ref(0)
+
+ function increment() {
+ count.value++
+ }
+
+ function decrement() {
+ count.value--
+ }
+
+ return {
+ count,
+ increment,
+ decrement,
+ }
+}
diff --git a/playground/devtools/src/main.ts b/playground/devtools/src/main.ts
new file mode 100644
index 00000000000000..7cb4fb3fad5b60
--- /dev/null
+++ b/playground/devtools/src/main.ts
@@ -0,0 +1,17 @@
+import { watchEffect } from 'vue'
+import { useCounter } from './counter'
+
+const { count, increment, decrement } = useCounter()
+
+watchEffect(() => {
+ document.querySelector('#app')!.textContent = `${count.value}`
+})
+
+const timer = setInterval(() => {
+ if (count.value < 6) {
+ increment()
+ } else {
+ decrement()
+ clearInterval(timer)
+ }
+}, 1000)
diff --git a/playground/devtools/vite.config.ts b/playground/devtools/vite.config.ts
new file mode 100644
index 00000000000000..c503e6e3c857db
--- /dev/null
+++ b/playground/devtools/vite.config.ts
@@ -0,0 +1,11 @@
+// no test exists, this should be tested manually
+
+import { defineConfig } from 'vite'
+
+export default defineConfig({
+ base: '/',
+ devtools: {
+ enabled: false,
+ port: 5173,
+ },
+})
diff --git a/playground/dynamic-import-inline/__tests__/dynamic-import-inline.spec.ts b/playground/dynamic-import-inline/__tests__/dynamic-import-inline.spec.ts
new file mode 100644
index 00000000000000..b752cf4fd91177
--- /dev/null
+++ b/playground/dynamic-import-inline/__tests__/dynamic-import-inline.spec.ts
@@ -0,0 +1,12 @@
+import { expect, test } from 'vitest'
+import { isBuild, serverLogs } from '~utils'
+
+test.runIf(isBuild)(
+ "don't warn when codeSplitting is set to false",
+ async () => {
+ const log = serverLogs.join('\n')
+ expect(log).not.toContain(
+ 'dynamic import will not move module into another chunk',
+ )
+ },
+)
diff --git a/playground/dynamic-import-inline/index.html b/playground/dynamic-import-inline/index.html
new file mode 100644
index 00000000000000..d86d5c08912184
--- /dev/null
+++ b/playground/dynamic-import-inline/index.html
@@ -0,0 +1 @@
+
diff --git a/playground/dynamic-import-inline/package.json b/playground/dynamic-import-inline/package.json
new file mode 100644
index 00000000000000..32a98927237fe7
--- /dev/null
+++ b/playground/dynamic-import-inline/package.json
@@ -0,0 +1,12 @@
+{
+ "name": "@vitejs/test-dynamic-import-inline",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build",
+ "debug": "node --inspect-brk ../../packages/vite/bin/vite",
+ "preview": "vite preview"
+ }
+}
diff --git a/playground/dynamic-import-inline/src/foo.js b/playground/dynamic-import-inline/src/foo.js
new file mode 100644
index 00000000000000..483b987b3b1c9b
--- /dev/null
+++ b/playground/dynamic-import-inline/src/foo.js
@@ -0,0 +1,3 @@
+export default function foo() {
+ return 'foo'
+}
diff --git a/playground/dynamic-import-inline/src/index.js b/playground/dynamic-import-inline/src/index.js
new file mode 100644
index 00000000000000..a151a21a59d840
--- /dev/null
+++ b/playground/dynamic-import-inline/src/index.js
@@ -0,0 +1,9 @@
+import foo from './foo'
+
+const asyncImport = async () => {
+ const { foo } = await import('./foo.js')
+ foo()
+}
+
+foo()
+asyncImport()
diff --git a/playground/dynamic-import-inline/vite.config.js b/playground/dynamic-import-inline/vite.config.js
new file mode 100644
index 00000000000000..8d25f33d0b9512
--- /dev/null
+++ b/playground/dynamic-import-inline/vite.config.js
@@ -0,0 +1,18 @@
+import path from 'node:path'
+import { defineConfig } from 'vite'
+
+export default defineConfig({
+ resolve: {
+ alias: {
+ '@': path.resolve(import.meta.dirname, 'alias'),
+ },
+ },
+ build: {
+ sourcemap: true,
+ rollupOptions: {
+ output: {
+ codeSplitting: false,
+ },
+ },
+ },
+})
diff --git a/playground/dynamic-import/(app)/main.js b/playground/dynamic-import/(app)/main.js
new file mode 100644
index 00000000000000..48f83a961a109a
--- /dev/null
+++ b/playground/dynamic-import/(app)/main.js
@@ -0,0 +1,3 @@
+export function hello() {
+ return 'dynamic-import-with-vars-contains-parenthesis'
+}
diff --git a/playground/dynamic-import/(app)/nest/index.js b/playground/dynamic-import/(app)/nest/index.js
new file mode 100644
index 00000000000000..f2e2ca17f05336
--- /dev/null
+++ b/playground/dynamic-import/(app)/nest/index.js
@@ -0,0 +1,6 @@
+const base = 'main'
+import(`../${base}.js`).then((mod) => {
+ document.querySelector(
+ '.dynamic-import-with-vars-contains-parenthesis',
+ ).textContent = mod.hello()
+})
diff --git a/playground/dynamic-import/__tests__/dynamic-import.spec.ts b/playground/dynamic-import/__tests__/dynamic-import.spec.ts
new file mode 100644
index 00000000000000..384f4190295f46
--- /dev/null
+++ b/playground/dynamic-import/__tests__/dynamic-import.spec.ts
@@ -0,0 +1,196 @@
+import { expect, test } from 'vitest'
+import {
+ browserLogs,
+ findAssetFile,
+ getColor,
+ isBuild,
+ page,
+ serverLogs,
+} from '~utils'
+
+test('should load literal dynamic import', async () => {
+ await page.click('.baz')
+ await expect.poll(() => page.textContent('.view')).toMatch('Baz view')
+})
+
+test('should load full dynamic import from public', async () => {
+ await page.click('.qux')
+ await expect.poll(() => page.textContent('.view')).toMatch('Qux view')
+ // No warning should be logged as we are using @vite-ignore
+ expect(
+ serverLogs.some((log) => log.includes('cannot be analyzed by vite')),
+ ).toBe(false)
+})
+
+test('should load data URL of `blob:`', async () => {
+ await page.click('.issue-2658-1')
+ await expect.poll(() => page.textContent('.view')).toMatch('blob')
+})
+
+test('should load data URL of `data:`', async () => {
+ await page.click('.issue-2658-2')
+ await expect.poll(() => page.textContent('.view')).toMatch('data')
+})
+
+test('should have same reference on static and dynamic js import, .mxd', async () => {
+ await page.click('.mxd')
+ await expect.poll(() => page.textContent('.view')).toMatch('true')
+})
+
+// in this case, it is not possible to detect the correct module
+test('should have same reference on static and dynamic js import, .mxd2', async () => {
+ await page.click('.mxd2')
+ await expect.poll(() => page.textContent('.view')).toMatch('false')
+})
+
+test('should have same reference on static and dynamic js import, .mxdjson', async () => {
+ await page.click('.mxdjson')
+ await expect.poll(() => page.textContent('.view')).toMatch('true')
+})
+
+// since this test has a timeout, it should be put last so that it
+// does not bleed on the last
+test('should load dynamic import with vars', async () => {
+ await page.click('.foo')
+ await expect.poll(() => page.textContent('.view')).toMatch('Foo view')
+
+ await page.click('.bar')
+ await expect.poll(() => page.textContent('.view')).toMatch('Bar view')
+})
+
+// dynamic import css
+test('should load dynamic import with css', async () => {
+ await page.click('.css')
+ await expect.poll(() => getColor('.view')).toBe('red')
+})
+
+test('should load dynamic import with vars', async () => {
+ await expect
+ .poll(() => page.textContent('.dynamic-import-with-vars'))
+ .toMatch('hello')
+})
+
+test('should load dynamic import with vars ignored', async () => {
+ await expect
+ .poll(() => page.textContent('.dynamic-import-with-vars-ignored'))
+ .toMatch('hello')
+ // No warning should be logged as we are using @vite-ignore
+ expect(
+ serverLogs.some((log) =>
+ log.includes('"https" has been externalized for browser compatibility'),
+ ),
+ ).toBe(false)
+})
+
+test('should load dynamic import with double slash ignored', async () => {
+ await expect
+ .poll(() => page.textContent('.dynamic-import-with-double-slash-ignored'))
+ .toMatch('hello')
+})
+
+test('should load dynamic import with vars multiline', async () => {
+ await expect
+ .poll(() => page.textContent('.dynamic-import-with-vars-multiline'))
+ .toMatch('hello')
+})
+
+test('should load dynamic import with vars alias', async () => {
+ await expect
+ .poll(() => page.textContent('.dynamic-import-with-vars-alias'))
+ .toMatch('hi')
+})
+
+test('should load dynamic import with vars raw', async () => {
+ await expect
+ .poll(() => page.textContent('.dynamic-import-with-vars-raw'))
+ .toMatch('export function hello()')
+})
+
+test('should load dynamic import with vars url', async () => {
+ await expect
+ .poll(() => page.textContent('.dynamic-import-with-vars-url'))
+ .toMatch(isBuild ? 'data:text/javascript' : '/alias/url.js')
+})
+
+test('should load dynamic import with vars worker', async () => {
+ await expect
+ .poll(() => page.textContent('.dynamic-import-with-vars-worker'))
+ .toMatch('load worker')
+})
+
+test('should load dynamic import with css in package', async () => {
+ await page.click('.pkg-css')
+ await expect.poll(() => getColor('.pkg-css')).toBe('blue')
+})
+
+test('should work with load ../ and itself directory', async () => {
+ await expect
+ .poll(() => page.textContent('.dynamic-import-self'))
+ .toMatch('dynamic-import-self-content')
+})
+
+test('should work with load ../ and contain itself directory', async () => {
+ await expect
+ .poll(() => page.textContent('.dynamic-import-nested-self'))
+ .toMatch('dynamic-import-nested-self-content')
+})
+
+test('should work a load path that contains parentheses.', async () => {
+ await expect
+ .poll(() =>
+ page.textContent('.dynamic-import-with-vars-contains-parenthesis'),
+ )
+ .toMatch('dynamic-import-with-vars-contains-parenthesis')
+})
+
+test.runIf(isBuild)(
+ 'should rollup warn when static and dynamic import a module in same chunk',
+ // NOTE: this is a warning related to rollup's chunking behavior
+ { skip: true },
+ async () => {
+ const log = serverLogs.join('\n')
+ expect(log).toContain(
+ 'dynamic import will not move module into another chunk',
+ )
+ expect(log).toMatch(
+ /\(!\).*\/dynamic-import\/files\/mxd\.js is dynamically imported by/,
+ )
+ expect(log).toMatch(
+ /\(!\).*\/dynamic-import\/files\/mxd\.json is dynamically imported by/,
+ )
+ expect(log).not.toMatch(
+ /\(!\).*\/dynamic-import\/nested\/shared\.js is dynamically imported by/,
+ )
+ },
+)
+
+test('dynamic import treeshaken log', async () => {
+ const log = browserLogs.join('\n')
+ expect(log).toContain('treeshaken foo')
+ expect(log).toContain('treeshaken bar')
+ expect(log).toContain('treeshaken baz1')
+ expect(log).toContain('treeshaken baz2')
+ expect(log).toContain('treeshaken baz3')
+ expect(log).toContain('treeshaken baz4')
+ expect(log).toContain('treeshaken baz5')
+ expect(log).toContain('treeshaken baz6')
+ expect(log).toContain('treeshaken default')
+
+ expect(log).not.toContain('treeshaken removed')
+})
+
+test('dynamic import syntax parsing', async () => {
+ const log = browserLogs.join('\n')
+ expect(log).toContain('treeshaken syntax foo')
+ expect(log).toContain('treeshaken syntax default')
+})
+
+test.runIf(isBuild)('dynamic import treeshaken file', async () => {
+ expect(findAssetFile(/treeshaken.+\.js$/)).not.toContain('treeshaken removed')
+})
+
+test.runIf(isBuild)('should not preload for non-analyzable urls', () => {
+ const js = findAssetFile(/index-[-\w]{8}\.js$/)
+ // should match e.g. await import(e.jss);o(".view",p===i)
+ expect(js).to.match(/\.jss\);/)
+})
diff --git a/playground/dynamic-import/alias/hello.js b/playground/dynamic-import/alias/hello.js
new file mode 100644
index 00000000000000..b10bde412dbbe1
--- /dev/null
+++ b/playground/dynamic-import/alias/hello.js
@@ -0,0 +1,4 @@
+export function hello() {
+ return 'hello'
+}
+console.log('hello.js')
diff --git a/playground/dynamic-import/alias/hi.js b/playground/dynamic-import/alias/hi.js
new file mode 100644
index 00000000000000..d2cfa4dc305c7b
--- /dev/null
+++ b/playground/dynamic-import/alias/hi.js
@@ -0,0 +1,4 @@
+export function hi() {
+ return 'hi'
+}
+console.log('hi.js')
diff --git a/playground/dynamic-import/alias/url.js b/playground/dynamic-import/alias/url.js
new file mode 100644
index 00000000000000..c9b0c79461d91e
--- /dev/null
+++ b/playground/dynamic-import/alias/url.js
@@ -0,0 +1 @@
+export const url = 'load url'
diff --git a/playground/dynamic-import/alias/worker.js b/playground/dynamic-import/alias/worker.js
new file mode 100644
index 00000000000000..2a8fc242aab315
--- /dev/null
+++ b/playground/dynamic-import/alias/worker.js
@@ -0,0 +1,5 @@
+self.onmessage = (event) => {
+ self.postMessage({
+ msg: 'load worker',
+ })
+}
diff --git a/packages/playground/dynamic-import/css/index.css b/playground/dynamic-import/css/index.css
similarity index 100%
rename from packages/playground/dynamic-import/css/index.css
rename to playground/dynamic-import/css/index.css
diff --git a/packages/playground/dynamic-import/mxd.js b/playground/dynamic-import/files/mxd.js
similarity index 100%
rename from packages/playground/dynamic-import/mxd.js
rename to playground/dynamic-import/files/mxd.js
diff --git a/playground/dynamic-import/files/mxd.json b/playground/dynamic-import/files/mxd.json
new file mode 100644
index 00000000000000..0967ef424bce67
--- /dev/null
+++ b/playground/dynamic-import/files/mxd.json
@@ -0,0 +1 @@
+{}
diff --git a/playground/dynamic-import/index.html b/playground/dynamic-import/index.html
new file mode 100644
index 00000000000000..1289efc3e1ac4e
--- /dev/null
+++ b/playground/dynamic-import/index.html
@@ -0,0 +1,54 @@
+Foo
+Bar
+Baz
+Qux
+Mxd
+Mxd2
+Mxdjson
+Issue 2658 - 1
+Issue 2658 - 2
+css
+pkg-css
+
+dynamic-import-with-vars
+todo
+
+dynamic-import-with-vars-ignored
+todo
+
+dynamic-import-with-double-slash-ignored
+todo
+
+dynamic-import-with-vars-multiline
+todo
+
+dynamic-import-with-vars-alias
+todo
+
+dynamic-import-with-vars-raw
+todo
+
+dynamic-import-with-vars-url
+todo
+
+dynamic-import-with-vars-worker
+todo
+
+dynamic-import-with-vars-contains-parenthesis
+todo
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/playground/dynamic-import/nested/deps.js b/playground/dynamic-import/nested/deps.js
new file mode 100644
index 00000000000000..34668f67263505
--- /dev/null
+++ b/playground/dynamic-import/nested/deps.js
@@ -0,0 +1,3 @@
+/* don't include dynamic import inside this file */
+
+import '@vitejs/test-pkg'
diff --git a/playground/dynamic-import/nested/hello.js b/playground/dynamic-import/nested/hello.js
new file mode 100644
index 00000000000000..67900ef0999962
--- /dev/null
+++ b/playground/dynamic-import/nested/hello.js
@@ -0,0 +1,3 @@
+export function hello() {
+ return 'hello'
+}
diff --git a/playground/dynamic-import/nested/index.js b/playground/dynamic-import/nested/index.js
new file mode 100644
index 00000000000000..6520e1f5a51bb8
--- /dev/null
+++ b/playground/dynamic-import/nested/index.js
@@ -0,0 +1,204 @@
+import mxdStatic from '../files/mxd'
+import mxdStaticJSON from '../files/mxd.json'
+
+async function setView(view) {
+ const { msg } = await import(`../views/${view}.js`)
+ text('.view', msg)
+}
+
+;['foo', 'bar'].forEach((id) => {
+ document.querySelector(`.${id}`).addEventListener('click', () => setView(id))
+})
+
+// literal dynamic
+document.querySelector('.baz').addEventListener('click', async () => {
+ const { msg } = await import('../views/baz.js')
+ text('.view', msg)
+})
+
+// full dynamic
+const arr = ['qux.js']
+const view = `/views/${arr[0]}`
+document.querySelector('.qux').addEventListener('click', async () => {
+ const { msg } = await import(/*@vite-ignore*/ view)
+ text('.view', msg)
+})
+
+// mixed static and dynamic
+document.querySelector('.mxd').addEventListener('click', async () => {
+ const view = 'mxd'
+ const { default: mxdDynamic } = await import(`../files/${view}.js`)
+ text('.view', mxdStatic === mxdDynamic)
+})
+
+document.querySelector('.mxd2').addEventListener('click', async () => {
+ const test = { jss: '../files/mxd.js' }
+ const ttest = test
+ const view = 'mxd'
+ const { default: mxdDynamic } = await import(/*@vite-ignore*/ test.jss)
+ text('.view', mxdStatic === mxdDynamic)
+})
+
+document.querySelector('.mxdjson').addEventListener('click', async () => {
+ const view = 'mxd'
+ const { default: mxdDynamicJSON } = await import(`../files/${view}.json`)
+ text('.view', mxdStaticJSON === mxdDynamicJSON)
+})
+
+// data URLs (`blob:`)
+const code1 = 'export const msg = "blob"'
+const blob = new Blob([code1], { type: 'text/javascript;charset=UTF-8' })
+const blobURL = URL.createObjectURL(blob)
+document.querySelector('.issue-2658-1').addEventListener('click', async () => {
+ const { msg } = await import(/*@vite-ignore*/ blobURL)
+ text('.view', msg)
+})
+
+// data URLs (`data:`)
+const code2 = 'export const msg = "data";'
+const dataURL = `data:text/javascript;charset=utf-8,${encodeURIComponent(
+ code2,
+)}`
+document.querySelector('.issue-2658-2').addEventListener('click', async () => {
+ const { msg } = await import(/*@vite-ignore*/ dataURL)
+ text('.view', msg)
+})
+
+document.querySelector('.css').addEventListener('click', async () => {
+ await import('../css/index.css')
+ text('.view', 'dynamic import css')
+})
+
+document.querySelector('.pkg-css').addEventListener('click', async () => {
+ await import('./deps')
+ text('.view', 'dynamic import css in package')
+})
+
+function text(el, text) {
+ document.querySelector(el).textContent = text
+}
+
+let base = 'hello'
+
+import(`../alias/${base}.js`).then((mod) => {
+ text('.dynamic-import-with-vars', mod.hello())
+})
+
+import(/*@vite-ignore*/ `https://localhost`).catch((mod) => {
+ console.log(mod)
+ text('.dynamic-import-with-vars-ignored', 'hello')
+})
+
+import(/*@vite-ignore*/ `https://localhost//${'test'}`).catch((mod) => {
+ console.log(mod)
+ text('.dynamic-import-with-double-slash-ignored', 'hello')
+})
+
+// prettier-ignore
+import(
+ /* this messes with */
+ `../alias/${base}.js`
+ /* es-module-lexer */
+).then((mod) => {
+ text('.dynamic-import-with-vars-multiline', mod.hello())
+})
+
+import(`../alias/${base}.js?raw`).then((mod) => {
+ text('.dynamic-import-with-vars-raw', JSON.stringify(mod))
+})
+
+base = 'url'
+import(`../alias/${base}.js?url`).then((mod) => {
+ text('.dynamic-import-with-vars-url', JSON.stringify(mod))
+})
+
+base = 'worker'
+import(`../alias/${base}.js?worker`).then((workerMod) => {
+ const worker = new workerMod.default()
+ worker.postMessage('1')
+ worker.addEventListener('message', (ev) => {
+ console.log(ev)
+ text('.dynamic-import-with-vars-worker', JSON.stringify(ev.data))
+ })
+})
+
+base = 'hi'
+import(`@/${base}.js`).then((mod) => {
+ text('.dynamic-import-with-vars-alias', mod.hi())
+})
+
+base = 'self'
+import(`../nested/${base}.js`).then((mod) => {
+ text('.dynamic-import-self', mod.self)
+})
+
+import(`../nested/nested/${base}.js`).then((mod) => {
+ text('.dynamic-import-nested-self', mod.self)
+})
+;(async function () {
+ const { foo } = await import('./treeshaken/treeshaken.js')
+ const { bar, default: tree } = await import('./treeshaken/treeshaken.js')
+ const default2 = (await import('./treeshaken/treeshaken.js')).default
+ const baz1 = (await import('./treeshaken/treeshaken.js')).baz1
+ const baz2 = (await import('./treeshaken/treeshaken.js')).baz2.log
+ const baz3 = (await import('./treeshaken/treeshaken.js')).baz3?.log
+ const baz4 = await import('./treeshaken/treeshaken.js').then(
+ ({ baz4 }) => baz4,
+ )
+ const baz5 = await import('./treeshaken/treeshaken.js').then(function ({
+ baz5,
+ }) {
+ return baz5
+ }),
+ { baz6 } = await import('./treeshaken/treeshaken.js')
+ foo()
+ bar()
+ tree()
+ ;(await import('./treeshaken/treeshaken.js')).default()
+ default2()
+ baz1()
+ baz2()
+ baz3()
+ baz4()
+ baz5()
+ baz6()
+})()
+// Test syntax parsing only
+;(async function () {
+ const default1 = await import('./treeshaken/syntax.js').then(
+ (mod) => mod.default,
+ )
+ const default2 = (await import('./treeshaken/syntax.js')).default,
+ other = () => {}
+ const foo = await import('./treeshaken/syntax.js').then((mod) => mod.foo)
+ const foo2 = await import('./treeshaken/syntax.js').then(
+ ({ foo = {} }) => foo,
+ )
+ const foo3 = await import('./treeshaken/syntax.js').then((m) => m.foo)
+ const e = ('' + window.doesntExist)[3] // to disallow minifier to constant fold
+ const foo4 = await import('./treeshaken/syntax.js')[`th${e}n`]((m) => m.foo)
+ await import('./treeshaken/syntax.js').then((mod) => mod.foo({ foo }))
+ const obj = [
+ '',
+ {
+ async lazy() {
+ const { foo } = await import('./treeshaken/treeshaken.js')
+ return { foo: aaa(foo) }
+ },
+ },
+ ]
+ default1()
+ default2()
+ other()
+ foo()
+ foo2()
+ foo3()
+ foo4()
+ obj[1].lazy()
+})()
+
+import(`../nested/static.js`).then((mod) => {
+ text('.dynamic-import-static', mod.self)
+})
+
+console.log('index.js')
diff --git a/playground/dynamic-import/nested/nested/self.js b/playground/dynamic-import/nested/nested/self.js
new file mode 100644
index 00000000000000..b18321b2044887
--- /dev/null
+++ b/playground/dynamic-import/nested/nested/self.js
@@ -0,0 +1 @@
+export const self = 'dynamic-import-nested-self-content'
diff --git a/playground/dynamic-import/nested/self.js b/playground/dynamic-import/nested/self.js
new file mode 100644
index 00000000000000..46e122535d86e4
--- /dev/null
+++ b/playground/dynamic-import/nested/self.js
@@ -0,0 +1 @@
+export const self = 'dynamic-import-self-content'
diff --git a/packages/playground/dynamic-import/nested/shared.js b/playground/dynamic-import/nested/shared.js
similarity index 100%
rename from packages/playground/dynamic-import/nested/shared.js
rename to playground/dynamic-import/nested/shared.js
diff --git a/playground/dynamic-import/nested/static.js b/playground/dynamic-import/nested/static.js
new file mode 100644
index 00000000000000..02dd476388a6e4
--- /dev/null
+++ b/playground/dynamic-import/nested/static.js
@@ -0,0 +1 @@
+export const self = 'dynamic-import-static'
diff --git a/playground/dynamic-import/nested/treeshaken/syntax.js b/playground/dynamic-import/nested/treeshaken/syntax.js
new file mode 100644
index 00000000000000..7ee55ddefc403d
--- /dev/null
+++ b/playground/dynamic-import/nested/treeshaken/syntax.js
@@ -0,0 +1,6 @@
+export const foo = () => {
+ console.log('treeshaken syntax foo')
+}
+export default () => {
+ console.log('treeshaken syntax default')
+}
diff --git a/playground/dynamic-import/nested/treeshaken/treeshaken.js b/playground/dynamic-import/nested/treeshaken/treeshaken.js
new file mode 100644
index 00000000000000..3fdc9ae7a7808f
--- /dev/null
+++ b/playground/dynamic-import/nested/treeshaken/treeshaken.js
@@ -0,0 +1,34 @@
+export const foo = () => {
+ console.log('treeshaken foo')
+}
+export const bar = () => {
+ console.log('treeshaken bar')
+}
+export const baz1 = () => {
+ console.log('treeshaken baz1')
+}
+export const baz2 = {
+ log: () => {
+ console.log('treeshaken baz2')
+ },
+}
+export const baz3 = {
+ log: () => {
+ console.log('treeshaken baz3')
+ },
+}
+export const baz4 = () => {
+ console.log('treeshaken baz4')
+}
+export const baz5 = () => {
+ console.log('treeshaken baz5')
+}
+export const baz6 = () => {
+ console.log('treeshaken baz6')
+}
+export const removed = () => {
+ console.log('treeshaken removed')
+}
+export default () => {
+ console.log('treeshaken default')
+}
diff --git a/playground/dynamic-import/package.json b/playground/dynamic-import/package.json
new file mode 100644
index 00000000000000..d3ab6846463268
--- /dev/null
+++ b/playground/dynamic-import/package.json
@@ -0,0 +1,15 @@
+{
+ "name": "@vitejs/test-dynamic-import",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build",
+ "debug": "node --inspect-brk ../../packages/vite/bin/vite",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "@vitejs/test-pkg": "file:./pkg"
+ }
+}
diff --git a/playground/dynamic-import/pkg/index.js b/playground/dynamic-import/pkg/index.js
new file mode 100644
index 00000000000000..20f705c0b4a8c9
--- /dev/null
+++ b/playground/dynamic-import/pkg/index.js
@@ -0,0 +1 @@
+import('./pkg.css')
diff --git a/playground/dynamic-import/pkg/package.json b/playground/dynamic-import/pkg/package.json
new file mode 100644
index 00000000000000..fb2e8c8815c8fa
--- /dev/null
+++ b/playground/dynamic-import/pkg/package.json
@@ -0,0 +1,7 @@
+{
+ "name": "@vitejs/test-pkg",
+ "type": "module",
+ "private": true,
+ "version": "1.0.0",
+ "main": "index.js"
+}
diff --git a/playground/dynamic-import/pkg/pkg.css b/playground/dynamic-import/pkg/pkg.css
new file mode 100644
index 00000000000000..349d669b6829bf
--- /dev/null
+++ b/playground/dynamic-import/pkg/pkg.css
@@ -0,0 +1,3 @@
+.pkg-css {
+ color: blue;
+}
diff --git a/packages/playground/dynamic-import/views/bar.js b/playground/dynamic-import/views/bar.js
similarity index 100%
rename from packages/playground/dynamic-import/views/bar.js
rename to playground/dynamic-import/views/bar.js
diff --git a/packages/playground/dynamic-import/views/baz.js b/playground/dynamic-import/views/baz.js
similarity index 100%
rename from packages/playground/dynamic-import/views/baz.js
rename to playground/dynamic-import/views/baz.js
diff --git a/packages/playground/dynamic-import/views/foo.js b/playground/dynamic-import/views/foo.js
similarity index 100%
rename from packages/playground/dynamic-import/views/foo.js
rename to playground/dynamic-import/views/foo.js
diff --git a/packages/playground/dynamic-import/qux.js b/playground/dynamic-import/views/qux.js
similarity index 100%
rename from packages/playground/dynamic-import/qux.js
rename to playground/dynamic-import/views/qux.js
diff --git a/playground/dynamic-import/vite.config.js b/playground/dynamic-import/vite.config.js
new file mode 100644
index 00000000000000..ad68283310617e
--- /dev/null
+++ b/playground/dynamic-import/vite.config.js
@@ -0,0 +1,37 @@
+import fs from 'node:fs'
+import path from 'node:path'
+import { defineConfig } from 'vite'
+
+const dirname = import.meta.dirname
+
+export default defineConfig({
+ plugins: [
+ {
+ name: 'copy',
+ writeBundle() {
+ fs.mkdirSync(path.resolve(dirname, 'dist/views'))
+ fs.mkdirSync(path.resolve(dirname, 'dist/files'))
+ fs.copyFileSync(
+ path.resolve(dirname, 'views/qux.js'),
+ path.resolve(dirname, 'dist/views/qux.js'),
+ )
+ fs.copyFileSync(
+ path.resolve(dirname, 'files/mxd.js'),
+ path.resolve(dirname, 'dist/files/mxd.js'),
+ )
+ fs.copyFileSync(
+ path.resolve(dirname, 'files/mxd.json'),
+ path.resolve(dirname, 'dist/files/mxd.json'),
+ )
+ },
+ },
+ ],
+ resolve: {
+ alias: {
+ '@': path.resolve(dirname, 'alias'),
+ },
+ },
+ build: {
+ sourcemap: true,
+ },
+})
diff --git a/packages/playground/env-nested/.env b/playground/env-nested/.env
similarity index 100%
rename from packages/playground/env-nested/.env
rename to playground/env-nested/.env
diff --git a/packages/playground/env-nested/__tests__/env-nested.spec.ts b/playground/env-nested/__tests__/env-nested.spec.ts
similarity index 83%
rename from packages/playground/env-nested/__tests__/env-nested.spec.ts
rename to playground/env-nested/__tests__/env-nested.spec.ts
index 1ceebde7a044b7..ea4cdeb88e72c4 100644
--- a/packages/playground/env-nested/__tests__/env-nested.spec.ts
+++ b/playground/env-nested/__tests__/env-nested.spec.ts
@@ -1,4 +1,5 @@
-import { isBuild } from 'testUtils'
+import { expect, test } from 'vitest'
+import { isBuild, page } from '~utils'
const mode = isBuild ? `production` : `development`
diff --git a/packages/playground/env-nested/envs/.env.development b/playground/env-nested/envs/.env.development
similarity index 100%
rename from packages/playground/env-nested/envs/.env.development
rename to playground/env-nested/envs/.env.development
diff --git a/packages/playground/env-nested/envs/.env.production b/playground/env-nested/envs/.env.production
similarity index 100%
rename from packages/playground/env-nested/envs/.env.production
rename to playground/env-nested/envs/.env.production
diff --git a/packages/playground/env-nested/index.html b/playground/env-nested/index.html
similarity index 100%
rename from packages/playground/env-nested/index.html
rename to playground/env-nested/index.html
diff --git a/playground/env-nested/package.json b/playground/env-nested/package.json
new file mode 100644
index 00000000000000..060888e998c98d
--- /dev/null
+++ b/playground/env-nested/package.json
@@ -0,0 +1,12 @@
+{
+ "name": "@vitejs/test-env-nested",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build",
+ "debug": "node --inspect-brk ../../packages/vite/bin/vite",
+ "preview": "vite preview"
+ }
+}
diff --git a/playground/env-nested/vite.config.js b/playground/env-nested/vite.config.js
new file mode 100644
index 00000000000000..dc79ce87dcc405
--- /dev/null
+++ b/playground/env-nested/vite.config.js
@@ -0,0 +1,5 @@
+import { defineConfig } from 'vite'
+
+export default defineConfig({
+ envDir: './envs',
+})
diff --git a/playground/env/.env b/playground/env/.env
new file mode 100644
index 00000000000000..db7181dff7c1d3
--- /dev/null
+++ b/playground/env/.env
@@ -0,0 +1,11 @@
+VITE_CUSTOM_ENV_VARIABLE=1
+CUSTOM_PREFIX_ENV_VARIABLE=1
+VITE_EFFECTIVE_MODE_FILE_NAME=.env
+VITE_BOOL=true
+DEPEND_ENV=depend
+VITE_EXPAND_A=$EXPAND
+VITE_EXPAND_B=$DEPEND_ENV
+VITE_ESCAPE_A=escape\$
+VITE_ESCAPE_B=escape$
+IRRELEVANT_ENV=$DEPEND_ENV
+IRRELEVANT_ESCAPE_ENV=irrelevant$
diff --git a/packages/playground/env/.env.development b/playground/env/.env.development
similarity index 100%
rename from packages/playground/env/.env.development
rename to playground/env/.env.development
diff --git a/packages/playground/env/.env.production b/playground/env/.env.production
similarity index 100%
rename from packages/playground/env/.env.production
rename to playground/env/.env.production
diff --git a/playground/env/__tests__/env.spec.ts b/playground/env/__tests__/env.spec.ts
new file mode 100644
index 00000000000000..084d9d887e326d
--- /dev/null
+++ b/playground/env/__tests__/env.spec.ts
@@ -0,0 +1,124 @@
+import { expect, test } from 'vitest'
+import { isBuild, page } from '~utils'
+
+const mode = isBuild ? `production` : `development`
+
+test('base', async () => {
+ expect(await page.textContent('.base')).toBe('/env/')
+})
+
+test('mode', async () => {
+ expect(await page.textContent('.mode')).toBe(mode)
+})
+
+test('dev', async () => {
+ expect(await page.textContent('.dev')).toBe(String(!isBuild))
+})
+
+test('prod', async () => {
+ expect(await page.textContent('.prod')).toBe(String(isBuild))
+})
+
+test('custom', async () => {
+ expect(await page.textContent('.custom')).toBe('1')
+})
+
+test('custom in template literal expression', async () => {
+ expect(await page.textContent('.custom-template-literal-exp')).toBe('1')
+})
+
+test('custom-prefix', async () => {
+ expect(await page.textContent('.custom-prefix')).toBe('1')
+})
+
+test('mode file override', async () => {
+ expect(await page.textContent('.mode-file')).toBe(`.env.${mode}`)
+})
+
+test('inline variables', async () => {
+ expect(await page.textContent('.inline')).toBe(
+ isBuild ? `inline-build` : `inline-serve`,
+ )
+})
+
+test('define', async () => {
+ expect(await page.textContent('.bool')).toBe('boolean')
+ expect(await page.textContent('.number')).toBe('number')
+ expect(await page.textContent('.string')).toBe('string')
+ expect(await page.textContent('.stringify-object')).toBe('object')
+})
+
+test('NODE_ENV', async () => {
+ expect(await page.textContent('.node-env')).toBe(process.env.NODE_ENV)
+ expect(await page.textContent('.global-node-env')).toBe(process.env.NODE_ENV)
+ expect(await page.textContent('.global-this-node-env')).toBe(
+ process.env.NODE_ENV,
+ )
+})
+
+test('expand', async () => {
+ expect(await page.textContent('.expand-a')).toBe('expand')
+ expect(await page.textContent('.expand-b')).toBe('depend')
+})
+
+test('ssr', async () => {
+ expect(await page.textContent('.ssr')).toBe('false')
+})
+
+test('env object', async () => {
+ const env = JSON.parse(await page.textContent('.env-object'))
+ expect(env).not.toHaveProperty([
+ 'DEPEND_ENV',
+ 'IRRELEVANT_ENV',
+ 'IRRELEVANT_ESCAPE_ENV',
+ ])
+ expect(env).toMatchObject({
+ VITE_EFFECTIVE_MODE_FILE_NAME: `.env.${mode}`,
+ CUSTOM_PREFIX_ENV_VARIABLE: '1',
+ VITE_CUSTOM_ENV_VARIABLE: '1',
+ VITE_EXPAND_A: 'expand',
+ VITE_EXPAND_B: 'depend',
+ VITE_ESCAPE_A: 'escape$',
+ VITE_ESCAPE_B: 'escape$',
+ BASE_URL: '/env/',
+ VITE_BOOL: true,
+ SSR: false,
+ MODE: mode,
+ DEV: !isBuild,
+ PROD: isBuild,
+ VITE_NUMBER: 123,
+ VITE_STRING: '{"123",}',
+ VITE_STRINGIFY_OBJECT: {
+ a: '1',
+ b: '2',
+ },
+ })
+})
+
+test('env object in template literal expression', async () => {
+ const envText = await page.textContent('.env-object-in-template-literal-exp')
+ expect(JSON.parse(envText)).toMatchObject({
+ VITE_EFFECTIVE_MODE_FILE_NAME: `.env.${mode}`,
+ CUSTOM_PREFIX_ENV_VARIABLE: '1',
+ VITE_CUSTOM_ENV_VARIABLE: '1',
+ BASE_URL: '/env/',
+ MODE: mode,
+ DEV: !isBuild,
+ PROD: isBuild,
+ })
+})
+
+if (!isBuild) {
+ test('relative url import script return import.meta.url', async () => {
+ expect(await page.textContent('.url')).toMatch('/env/index.js')
+ })
+}
+
+test('ignores import' + '.meta.env in string literals', async () => {
+ expect(await page.textContent('.ignores-literal-import-meta-env-dot')).toBe(
+ 'import' + '.meta.env.',
+ )
+ expect(await page.textContent('.ignores-literal-import-meta-env')).toBe(
+ 'import' + '.meta.env',
+ )
+})
diff --git a/playground/env/index.html b/playground/env/index.html
new file mode 100644
index 00000000000000..442e5dd1fc7f3e
--- /dev/null
+++ b/playground/env/index.html
@@ -0,0 +1,89 @@
+Environment Variables
+import.meta.env.BASE_URL:
+import.meta.env.MODE:
+import.meta.env.DEV:
+import.meta.env.PROD:
+import.meta.env.VITE_CUSTOM_ENV_VARIABLE:
+
+ ${import.meta.env.VITE_CUSTOM_ENV_VARIABLE}:
+
+
+
+ import.meta.env.CUSTOM_PREFIX_ENV_VARIABLE:
+
+
+
+ import.meta.env.VITE_EFFECTIVE_MODE_FILE_NAME:
+
+import.meta.env.VITE_INLINE:
+typeof import.meta.env.VITE_BOOL:
+typeof import.meta.env.VITE_NUMBER:
+typeof import.meta.env.VITE_STRING:
+
+ typeof import.meta.env.VITE_STRINGIFY_OBJECT:
+
+
+process.env.NODE_ENV:
+global.process.env.NODE_ENV:
+
+ globalThis.process.env.NODE_ENV:
+
+import.meta.env.VITE_EXPAND_A:
+import.meta.env.VITE_EXPAND_B:
+import.meta.env.SSR:
+import.meta.env:
+
+ ${import.meta.env}:
+
+
+import.meta.url:
+
+ import.meta.env.
+
+import.meta.env
+
+
+
+
+
diff --git a/playground/env/index.js b/playground/env/index.js
new file mode 100644
index 00000000000000..35e23fd0d2b924
--- /dev/null
+++ b/playground/env/index.js
@@ -0,0 +1,5 @@
+text('.url', import.meta.url)
+
+function text(el, text) {
+ document.querySelector(el).textContent = text
+}
diff --git a/playground/env/package.json b/playground/env/package.json
new file mode 100644
index 00000000000000..a50e47e1c17313
--- /dev/null
+++ b/playground/env/package.json
@@ -0,0 +1,12 @@
+{
+ "name": "@vitejs/test-env",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "VITE_INLINE=inline-serve vite",
+ "build": "VITE_INLINE=inline-build vite build",
+ "debug": "node --inspect-brk ../../packages/vite/bin/vite",
+ "preview": "vite preview"
+ }
+}
diff --git a/playground/env/vite.config.js b/playground/env/vite.config.js
new file mode 100644
index 00000000000000..bca10b34c4c6b5
--- /dev/null
+++ b/playground/env/vite.config.js
@@ -0,0 +1,17 @@
+import { defineConfig } from 'vite'
+
+process.env.EXPAND = 'expand'
+
+export default defineConfig({
+ base: '/env/',
+ envPrefix: ['VITE_', 'CUSTOM_PREFIX_'],
+ build: {
+ outDir: 'dist/env',
+ },
+ define: {
+ 'import.meta.env.VITE_BOOL': true,
+ 'import.meta.env.VITE_NUMBER': '123',
+ 'import.meta.env.VITE_STRING': JSON.stringify('{"123",}'),
+ 'import.meta.env.VITE_STRINGIFY_OBJECT': JSON.stringify({ a: '1', b: '2' }),
+ },
+})
diff --git a/playground/environment-react-ssr/__tests__/environment-react-ssr.spec.ts b/playground/environment-react-ssr/__tests__/environment-react-ssr.spec.ts
new file mode 100644
index 00000000000000..92d0bdbb45d18e
--- /dev/null
+++ b/playground/environment-react-ssr/__tests__/environment-react-ssr.spec.ts
@@ -0,0 +1,98 @@
+import fs from 'node:fs'
+import path from 'node:path'
+import { stripVTControlCharacters } from 'node:util'
+import { describe, expect, onTestFinished, test } from 'vitest'
+import {
+ isBuild,
+ page,
+ readDepOptimizationMetadata,
+ readFile,
+ serverLogs,
+ testDir,
+} from '~utils'
+
+test('basic', async () => {
+ await page.getByText('hydrated: true').isVisible()
+ await page.getByText('Count: 0').isVisible()
+ await page.getByRole('button', { name: '+' }).click()
+ await page.getByText('Count: 1').isVisible()
+})
+
+describe.runIf(!isBuild)('pre-bundling', () => {
+ test('client', async () => {
+ const metaJson = readDepOptimizationMetadata()
+
+ expect(metaJson.optimized['react']).toBeTruthy()
+ expect(metaJson.optimized['react-dom/client']).toBeTruthy()
+ expect(metaJson.optimized['react/jsx-dev-runtime']).toBeTruthy()
+
+ expect(metaJson.optimized['react-dom/server']).toBeFalsy()
+ })
+
+ test('ssr', async () => {
+ const metaJson = readDepOptimizationMetadata('ssr')
+
+ expect(metaJson.optimized['react']).toBeTruthy()
+ expect(metaJson.optimized['react-dom/server']).toBeTruthy()
+ expect(metaJson.optimized['react/jsx-dev-runtime']).toBeTruthy()
+
+ expect(metaJson.optimized['react-dom/client']).toBeFalsy()
+
+ // process.env.NODE_ENV should be kept as keepProcessEnv is true
+ const depsFiles = fs
+ .readdirSync(path.resolve(testDir, 'node_modules/.vite/deps_ssr'), {
+ withFileTypes: true,
+ })
+ .filter((file) => file.isFile() && file.name.endsWith('.js'))
+ .map((file) => path.join(file.parentPath, file.name))
+ const depsFilesWithProcessEnvNodeEnv = depsFiles.filter((file) =>
+ fs.readFileSync(file, 'utf-8').includes('process.env.NODE_ENV'),
+ )
+
+ expect(depsFilesWithProcessEnvNodeEnv.length).toBeGreaterThan(0)
+ })
+
+ test('deps reload', async () => {
+ const envs = ['client', 'server'] as const
+
+ const clientMeta = readDepOptimizationMetadata('client')
+ const ssrMeta = readDepOptimizationMetadata('ssr')
+ expect(clientMeta.optimized['react-fake-client']).toBeFalsy()
+ expect(clientMeta.optimized['react-fake-server']).toBeFalsy()
+ expect(ssrMeta.optimized['react-fake-server']).toBeFalsy()
+ expect(ssrMeta.optimized['react-fake-client']).toBeFalsy()
+
+ envs.forEach((env) => {
+ const filePath = path.resolve(testDir, `src/entry-${env}.tsx`)
+ const originalContent = readFile(filePath)
+ fs.writeFileSync(
+ filePath,
+ `import 'react-fake-${env}'\n${originalContent}`,
+ 'utf-8',
+ )
+ onTestFinished(() => {
+ fs.writeFileSync(filePath, originalContent, 'utf-8')
+ })
+ })
+
+ await expect
+ .poll(() =>
+ serverLogs
+ .map(
+ (log) =>
+ stripVTControlCharacters(log).match(
+ /new dependencies optimized: (react-fake-.*)/,
+ )?.[1],
+ )
+ .filter(Boolean),
+ )
+ .toStrictEqual(['react-fake-server', 'react-fake-client'])
+
+ const clientMetaNew = readDepOptimizationMetadata('client')
+ const ssrMetaNew = readDepOptimizationMetadata('ssr')
+ expect(clientMetaNew.optimized['react-fake-client']).toBeTruthy()
+ expect(clientMetaNew.optimized['react-fake-server']).toBeFalsy()
+ expect(ssrMetaNew.optimized['react-fake-server']).toBeTruthy()
+ expect(ssrMetaNew.optimized['react-fake-client']).toBeFalsy()
+ })
+})
diff --git a/playground/environment-react-ssr/index.html b/playground/environment-react-ssr/index.html
new file mode 100644
index 00000000000000..9f4d44a675c1b1
--- /dev/null
+++ b/playground/environment-react-ssr/index.html
@@ -0,0 +1,14 @@
+
+
+
+
+ environment-react-ssr
+
+
+
+
+
+
diff --git a/playground/environment-react-ssr/package.json b/playground/environment-react-ssr/package.json
new file mode 100644
index 00000000000000..95a4a0ac911396
--- /dev/null
+++ b/playground/environment-react-ssr/package.json
@@ -0,0 +1,19 @@
+{
+ "name": "@vitejs/test-environment-react-ssr",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build --app",
+ "preview": "vite preview"
+ },
+ "devDependencies": {
+ "@types/react": "^19.2.10",
+ "@types/react-dom": "^19.2.3",
+ "react": "^19.2.4",
+ "react-fake-client": "npm:react@^19.2.4",
+ "react-fake-server": "npm:react@^19.2.4",
+ "react-dom": "^19.2.4"
+ }
+}
diff --git a/playground/environment-react-ssr/src/entry-client.tsx b/playground/environment-react-ssr/src/entry-client.tsx
new file mode 100644
index 00000000000000..e33d677abfbab2
--- /dev/null
+++ b/playground/environment-react-ssr/src/entry-client.tsx
@@ -0,0 +1,12 @@
+import ReactDomClient from 'react-dom/client'
+import React from 'react'
+import Root from './root'
+
+async function main() {
+ const el = document.getElementById('root')
+ React.startTransition(() => {
+ ReactDomClient.hydrateRoot(el!, )
+ })
+}
+
+main()
diff --git a/playground/environment-react-ssr/src/entry-server.tsx b/playground/environment-react-ssr/src/entry-server.tsx
new file mode 100644
index 00000000000000..588d365a0ca996
--- /dev/null
+++ b/playground/environment-react-ssr/src/entry-server.tsx
@@ -0,0 +1,24 @@
+import ReactDomServer from 'react-dom/server'
+import type { Connect, ViteDevServer } from 'vite'
+import Root from './root'
+
+const handler: Connect.NextHandleFunction = async (_req, res) => {
+ const ssrHtml = ReactDomServer.renderToString( )
+ let html = await importHtml()
+ html = html.replace(//, `${ssrHtml}
`)
+ res.setHeader('content-type', 'text/html').end(html)
+}
+
+export default handler
+
+declare let __globalServer: ViteDevServer
+
+async function importHtml() {
+ if (import.meta.env.DEV) {
+ const mod = await import('/index.html?raw')
+ return __globalServer.transformIndexHtml('/', mod.default)
+ } else {
+ const mod = await import('/dist/client/index.html?raw')
+ return mod.default
+ }
+}
diff --git a/playground/environment-react-ssr/src/root.tsx b/playground/environment-react-ssr/src/root.tsx
new file mode 100644
index 00000000000000..3d077cafb892ba
--- /dev/null
+++ b/playground/environment-react-ssr/src/root.tsx
@@ -0,0 +1,19 @@
+import React from 'react'
+
+export default function Root() {
+ const [count, setCount] = React.useState(0)
+
+ const [hydrated, setHydrated] = React.useState(false)
+ React.useEffect(() => {
+ setHydrated(true)
+ }, [])
+
+ return (
+
+
hydrated: {String(hydrated)}
+
Count: {count}
+
setCount((v) => v - 1)}>-1
+
setCount((v) => v + 1)}>+1
+
+ )
+}
diff --git a/playground/environment-react-ssr/tsconfig.json b/playground/environment-react-ssr/tsconfig.json
new file mode 100644
index 00000000000000..be3ffda527ca91
--- /dev/null
+++ b/playground/environment-react-ssr/tsconfig.json
@@ -0,0 +1,7 @@
+{
+ "extends": "../tsconfig.json",
+ "include": ["src"],
+ "compilerOptions": {
+ "jsx": "react-jsx"
+ }
+}
diff --git a/playground/environment-react-ssr/vite.config.ts b/playground/environment-react-ssr/vite.config.ts
new file mode 100644
index 00000000000000..7803dc15ae9016
--- /dev/null
+++ b/playground/environment-react-ssr/vite.config.ts
@@ -0,0 +1,121 @@
+import {
+ type Connect,
+ type Plugin,
+ type PluginOption,
+ createServerModuleRunner,
+ defineConfig,
+} from 'vite'
+
+export default defineConfig((env) => ({
+ clearScreen: false,
+ appType: 'custom',
+ plugins: [
+ vitePluginSsrMiddleware({
+ entry: '/src/entry-server',
+ preview: new URL('./dist/server/index.js', import.meta.url).toString(),
+ }),
+ {
+ name: 'global-server',
+ configureServer(server) {
+ Object.assign(globalThis, { __globalServer: server })
+ },
+ },
+ {
+ name: 'build-client',
+ async buildApp(builder) {
+ await builder.build(builder.environments.client)
+ },
+ },
+ ],
+ resolve: {
+ noExternal: true,
+ },
+ environments: {
+ client: {
+ build: {
+ minify: false,
+ sourcemap: true,
+ outDir: 'dist/client',
+ },
+ optimizeDeps: {
+ rolldownOptions: {
+ // manual test for https://github.com/vitejs/rolldown-vite/issues/416
+ transform: {},
+ },
+ },
+ },
+ ssr: {
+ optimizeDeps: {
+ noDiscovery: false,
+ },
+ build: {
+ outDir: 'dist/server',
+ rollupOptions: {
+ input: {
+ index: '/src/entry-server',
+ },
+ },
+ },
+ },
+ },
+
+ builder: {
+ async buildApp(builder) {
+ if (!builder.environments.client.isBuilt) {
+ throw new Error('Client environment should be built first')
+ }
+ await builder.build(builder.environments.ssr)
+ },
+ },
+}))
+
+// vavite-style ssr middleware plugin
+export function vitePluginSsrMiddleware({
+ entry,
+ preview,
+}: {
+ entry: string
+ preview?: string
+}): PluginOption {
+ const plugin: Plugin = {
+ name: vitePluginSsrMiddleware.name,
+
+ configureServer(server) {
+ const runner = createServerModuleRunner(server.environments.ssr, {
+ hmr: { logger: false },
+ })
+ const importWithRetry = async () => {
+ try {
+ return await runner.import(entry)
+ } catch (e) {
+ if (
+ e instanceof Error &&
+ (e as any).code === 'ERR_OUTDATED_OPTIMIZED_DEP'
+ ) {
+ runner.clearCache()
+ return await importWithRetry()
+ }
+ throw e
+ }
+ }
+ const handler: Connect.NextHandleFunction = async (req, res, next) => {
+ try {
+ const mod = await importWithRetry()
+ await mod['default'](req, res, next)
+ } catch (e) {
+ next(e)
+ }
+ }
+ return () => server.middlewares.use(handler)
+ },
+
+ async configurePreviewServer(server) {
+ if (preview) {
+ const mod = await import(preview)
+ return () => server.middlewares.use(mod.default)
+ }
+ return
+ },
+ }
+ return [plugin]
+}
diff --git a/playground/extensions/__tests__/extensions.spec.ts b/playground/extensions/__tests__/extensions.spec.ts
new file mode 100644
index 00000000000000..a2e229ffcd37f3
--- /dev/null
+++ b/playground/extensions/__tests__/extensions.spec.ts
@@ -0,0 +1,13 @@
+import { expect, test } from 'vitest'
+import { browserLogs, page } from '~utils'
+
+test('should have no 404s', () => {
+ browserLogs.forEach((msg) => {
+ expect(msg).not.toMatch('404')
+ })
+})
+
+test('not contain `.mjs`', async () => {
+ const appHtml = await page.content()
+ expect(appHtml).toMatch('Hello Vite!')
+})
diff --git a/packages/playground/extensions/index.html b/playground/extensions/index.html
similarity index 100%
rename from packages/playground/extensions/index.html
rename to playground/extensions/index.html
diff --git a/playground/extensions/package.json b/playground/extensions/package.json
new file mode 100644
index 00000000000000..eb8bb65a6e086c
--- /dev/null
+++ b/playground/extensions/package.json
@@ -0,0 +1,15 @@
+{
+ "name": "@vitejs/test-extensions",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build",
+ "debug": "node --inspect-brk ../../packages/vite/bin/vite",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "vue": "^3.5.27"
+ }
+}
diff --git a/playground/extensions/vite.config.js b/playground/extensions/vite.config.js
new file mode 100644
index 00000000000000..5fdb2c721c7870
--- /dev/null
+++ b/playground/extensions/vite.config.js
@@ -0,0 +1,8 @@
+import { defineConfig } from 'vite'
+
+export default defineConfig({
+ resolve: {
+ alias: [{ find: 'vue', replacement: 'vue/dist/vue.esm-bundler.js' }],
+ extensions: ['.js'],
+ },
+})
diff --git a/playground/external/__tests__/external.spec.ts b/playground/external/__tests__/external.spec.ts
new file mode 100644
index 00000000000000..af307a1ede2c38
--- /dev/null
+++ b/playground/external/__tests__/external.spec.ts
@@ -0,0 +1,26 @@
+import { describe, expect, test } from 'vitest'
+import { browserLogs, isBuild, page } from '~utils'
+
+test('importmap', () => {
+ expect(browserLogs).not.toContain(
+ 'An import map is added after module script load was triggered.',
+ )
+})
+
+test('should have default exports', async () => {
+ expect(await page.textContent('#imported-slash5-exists')).toBe('true')
+ expect(await page.textContent('#imported-slash3-exists')).toBe('true')
+ expect(await page.textContent('#required-slash3-exists')).toBe('true')
+})
+
+describe.runIf(isBuild)('build', () => {
+ test('should externalize imported packages', async () => {
+ // If `vue` is successfully externalized, the page should use the version from the import map
+ expect(await page.textContent('#imported-vue-version')).toBe('3.4.38')
+ })
+
+ test('should externalize required packages', async () => {
+ // If `vue` is successfully externalized, the page should use the version from the import map
+ expect(await page.textContent('#required-vue-version')).toBe('3.4.38')
+ })
+})
diff --git a/playground/external/dep-that-imports/index.js b/playground/external/dep-that-imports/index.js
new file mode 100644
index 00000000000000..096ea934cc4965
--- /dev/null
+++ b/playground/external/dep-that-imports/index.js
@@ -0,0 +1,9 @@
+import { version } from 'vue'
+import slash5 from 'slash5'
+import slash3 from 'slash3'
+
+document.querySelector('#imported-vue-version').textContent = version
+document.querySelector('#imported-slash5-exists').textContent =
+ !!slash5('foo/bar')
+document.querySelector('#imported-slash3-exists').textContent =
+ !!slash3('foo/bar')
diff --git a/playground/external/dep-that-imports/package.json b/playground/external/dep-that-imports/package.json
new file mode 100644
index 00000000000000..a0ba68f2cec635
--- /dev/null
+++ b/playground/external/dep-that-imports/package.json
@@ -0,0 +1,10 @@
+{
+ "name": "@vitejs/test-dep-that-imports",
+ "private": true,
+ "version": "0.0.0",
+ "dependencies": {
+ "slash3": "npm:slash@^3.0.0",
+ "slash5": "npm:slash@^5.1.0",
+ "vue": "^3.5.27"
+ }
+}
diff --git a/playground/external/dep-that-requires/index.js b/playground/external/dep-that-requires/index.js
new file mode 100644
index 00000000000000..6f0dd6124d829d
--- /dev/null
+++ b/playground/external/dep-that-requires/index.js
@@ -0,0 +1,7 @@
+const { version } = require('vue')
+// require('slash5') // cannot require ESM
+const slash3 = require('slash3')
+
+document.querySelector('#required-vue-version').textContent = version
+document.querySelector('#required-slash3-exists').textContent =
+ !!slash3('foo/bar')
diff --git a/playground/external/dep-that-requires/package.json b/playground/external/dep-that-requires/package.json
new file mode 100644
index 00000000000000..a7c3fe4842bb9e
--- /dev/null
+++ b/playground/external/dep-that-requires/package.json
@@ -0,0 +1,10 @@
+{
+ "name": "@vitejs/test-dep-that-requires",
+ "private": true,
+ "version": "0.0.0",
+ "dependencies": {
+ "slash3": "npm:slash@^3.0.0",
+ "slash5": "npm:slash@^5.1.0",
+ "vue": "^3.5.27"
+ }
+}
diff --git a/playground/external/index.html b/playground/external/index.html
new file mode 100644
index 00000000000000..92f3b50e0b6d80
--- /dev/null
+++ b/playground/external/index.html
@@ -0,0 +1,25 @@
+
+
+
+
+
+ Vite App
+
+
+
+ Imported Vue version:
+ Required Vue version:
+ Imported slash5 exists:
+ Imported slash3 exists:
+ Required slash3 exists:
+
+
+
diff --git a/playground/external/package.json b/playground/external/package.json
new file mode 100644
index 00000000000000..57e5efe7296258
--- /dev/null
+++ b/playground/external/package.json
@@ -0,0 +1,23 @@
+{
+ "name": "@vitejs/test-external",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build",
+ "debug": "node --inspect-brk ../../packages/vite/bin/vite",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "@vitejs/test-dep-that-imports": "file:./dep-that-imports",
+ "@vitejs/test-dep-that-requires": "file:./dep-that-requires"
+ },
+ "devDependencies": {
+ "slash3": "npm:slash@^3.0.0",
+ "slash5": "npm:slash@^5.1.0",
+ "vite": "workspace:*",
+ "vue": "^3.5.27",
+ "vue34": "npm:vue@~3.4.38"
+ }
+}
diff --git a/playground/external/public/slash@3.0.0.js b/playground/external/public/slash@3.0.0.js
new file mode 100644
index 00000000000000..754082e97c4f82
--- /dev/null
+++ b/playground/external/public/slash@3.0.0.js
@@ -0,0 +1,5 @@
+/* eslint-disable */
+// copied from https://esm.sh/v133/slash@3.0.0/es2022/slash.mjs to reduce network issues in CI
+
+/* esm.sh - esbuild bundle(slash@3.0.0) es2022 production */
+var a=Object.create;var d=Object.defineProperty;var m=Object.getOwnPropertyDescriptor;var x=Object.getOwnPropertyNames;var g=Object.getPrototypeOf,p=Object.prototype.hasOwnProperty;var A=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports),E=(e,t)=>{for(var r in t)d(e,r,{get:t[r],enumerable:!0})},u=(e,t,r,i)=>{if(t&&typeof t=="object"||typeof t=="function")for(let n of x(t))!p.call(e,n)&&n!==r&&d(e,n,{get:()=>t[n],enumerable:!(i=m(t,n))||i.enumerable});return e},o=(e,t,r)=>(u(e,t,"default"),r&&u(r,t,"default")),c=(e,t,r)=>(r=e!=null?a(g(e)):{},u(t||!e||!e.__esModule?d(r,"default",{value:e,enumerable:!0}):r,e));var f=A((h,_)=>{"use strict";_.exports=e=>{let t=/^\\\\\?\\/.test(e),r=/[^\u0000-\u0080]+/.test(e);return t||r?e:e.replace(/\\/g,"/")}});var s={};E(s,{default:()=>P});var L=c(f());o(s,c(f()));var{default:l,...N}=L,P=l!==void 0?l:N;export{P as default};
diff --git a/playground/external/src/main.js b/playground/external/src/main.js
new file mode 100644
index 00000000000000..db3f5b3ac58c7c
--- /dev/null
+++ b/playground/external/src/main.js
@@ -0,0 +1,3 @@
+import './require-polyfill'
+import '@vitejs/test-dep-that-imports'
+import '@vitejs/test-dep-that-requires'
diff --git a/playground/external/src/require-polyfill.js b/playground/external/src/require-polyfill.js
new file mode 100644
index 00000000000000..ded978f98ab108
--- /dev/null
+++ b/playground/external/src/require-polyfill.js
@@ -0,0 +1,8 @@
+import * as vue from 'vue'
+import slash3 from 'slash3'
+
+export default (id) => {
+ if (id === 'vue') return vue
+ if (id === 'slash3') return slash3
+ throw new Error(`Cannot require "${id}"`)
+}
diff --git a/playground/external/vite.config.js b/playground/external/vite.config.js
new file mode 100644
index 00000000000000..81ccd7d704edc8
--- /dev/null
+++ b/playground/external/vite.config.js
@@ -0,0 +1,55 @@
+import fs from 'node:fs/promises'
+import path from 'node:path'
+import { defineConfig } from 'vite'
+
+const npmDirectServeConfig = {
+ '/vue@3.4.38.js': 'vue34/dist/vue.runtime.esm-browser.js',
+ '/slash@5.js': 'slash5/index.js',
+}
+/** @type {import('vite').Connect.NextHandleFunction} */
+const serveNpmCodeDirectlyMiddleware = async (req, res, next) => {
+ for (const [url, file] of Object.entries(npmDirectServeConfig)) {
+ if (req.originalUrl === url) {
+ const code = await fs.readFile(
+ new URL(`./node_modules/${file}`, import.meta.url),
+ )
+ res.setHeader('Content-Type', 'text/javascript')
+ res.end(code)
+ return
+ }
+ }
+ next()
+}
+
+export default defineConfig({
+ optimizeDeps: {
+ include: ['dep-that-imports', 'dep-that-requires'],
+ exclude: ['vue', 'slash5'],
+ },
+ build: {
+ minify: false,
+ rollupOptions: {
+ external: ['vue', 'slash3', 'slash5'],
+ transform: {
+ inject: {
+ require: path.resolve(import.meta.dirname, 'src/require-polyfill.js'),
+ },
+ },
+ },
+ commonjsOptions: {
+ esmExternals: ['vue', 'slash5'],
+ dynamicRequireTargets: ['test-no-op-fdir-glob'],
+ },
+ },
+ plugins: [
+ {
+ name: 'serve-npm-code-directly',
+ configureServer({ middlewares }) {
+ middlewares.use(serveNpmCodeDirectlyMiddleware)
+ },
+ configurePreviewServer({ middlewares }) {
+ middlewares.use(serveNpmCodeDirectlyMiddleware)
+ },
+ },
+ ],
+})
diff --git a/playground/fs-serve/__tests__/base/fs-serve-base.spec.ts b/playground/fs-serve/__tests__/base/fs-serve-base.spec.ts
new file mode 100644
index 00000000000000..0af740c923f99b
--- /dev/null
+++ b/playground/fs-serve/__tests__/base/fs-serve-base.spec.ts
@@ -0,0 +1,140 @@
+import { beforeAll, describe, expect, test } from 'vitest'
+import testJSON from '../../safe.json'
+import { isServe, page, viteTestUrl } from '~utils'
+
+const stringified = JSON.stringify(testJSON)
+
+describe.runIf(isServe)('main', () => {
+ beforeAll(async () => {
+ const srcPrefix = viteTestUrl.endsWith('/') ? '' : '/'
+ await page.goto(viteTestUrl + srcPrefix + 'src/', {
+ // while networkidle is discouraged, we use here because we're not using playwright's retry-able assertions,
+ // and refactoring the code below to manually retry would be harder to read.
+ waitUntil: 'networkidle',
+ })
+ })
+
+ test('default import', async () => {
+ await expect.poll(() => page.textContent('.full')).toBe(stringified)
+ })
+
+ test('named import', async () => {
+ await expect.poll(() => page.textContent('.named')).toBe(testJSON.msg)
+ })
+
+ test('safe fetch', async () => {
+ expect(await page.textContent('.safe-fetch')).toMatch('KEY=safe')
+ await expect.poll(() => page.textContent('.safe-fetch-status')).toBe('200')
+ })
+
+ test('safe fetch with query', async () => {
+ expect(await page.textContent('.safe-fetch-query')).toMatch('KEY=safe')
+ await expect
+ .poll(() => page.textContent('.safe-fetch-query-status'))
+ .toBe('200')
+ })
+
+ test('safe fetch with special characters', async () => {
+ expect(
+ await page.textContent('.safe-fetch-subdir-special-characters'),
+ ).toMatch('KEY=safe')
+ await expect
+ .poll(() =>
+ page.textContent('.safe-fetch-subdir-special-characters-status'),
+ )
+ .toBe('200')
+ })
+
+ test('unsafe fetch', async () => {
+ expect(await page.textContent('.unsafe-fetch')).toMatch('403 Restricted')
+ await expect
+ .poll(() => page.textContent('.unsafe-fetch-status'))
+ .toBe('403')
+ })
+
+ test('unsafe fetch with special characters (#8498)', async () => {
+ await expect.poll(() => page.textContent('.unsafe-fetch-8498')).toBe('')
+ await expect
+ .poll(() => page.textContent('.unsafe-fetch-8498-status'))
+ .toBe('404')
+ })
+
+ test('unsafe fetch with special characters 2 (#8498)', async () => {
+ await expect.poll(() => page.textContent('.unsafe-fetch-8498-2')).toBe('')
+ await expect
+ .poll(() => page.textContent('.unsafe-fetch-8498-2-status'))
+ .toBe('404')
+ })
+
+ test('safe fs fetch', async () => {
+ await expect
+ .poll(() => page.textContent('.safe-fs-fetch'))
+ .toBe(stringified)
+ await expect
+ .poll(() => page.textContent('.safe-fs-fetch-status'))
+ .toBe('200')
+ })
+
+ test('safe fs fetch', async () => {
+ await expect
+ .poll(() => page.textContent('.safe-fs-fetch-query'))
+ .toBe(stringified)
+ await expect
+ .poll(() => page.textContent('.safe-fs-fetch-query-status'))
+ .toBe('200')
+ })
+
+ test('safe fs fetch with special characters', async () => {
+ await expect
+ .poll(() => page.textContent('.safe-fs-fetch-special-characters'))
+ .toBe(stringified)
+ await expect
+ .poll(() => page.textContent('.safe-fs-fetch-special-characters-status'))
+ .toBe('200')
+ })
+
+ test('unsafe fs fetch', async () => {
+ await expect.poll(() => page.textContent('.unsafe-fs-fetch')).toBe('')
+ await expect
+ .poll(() => page.textContent('.unsafe-fs-fetch-status'))
+ .toBe('403')
+ })
+
+ test('unsafe fs fetch with special characters (#8498)', async () => {
+ await expect.poll(() => page.textContent('.unsafe-fs-fetch-8498')).toBe('')
+ await expect
+ .poll(() => page.textContent('.unsafe-fs-fetch-8498-status'))
+ .toBe('404')
+ })
+
+ test('unsafe fs fetch with special characters 2 (#8498)', async () => {
+ await expect
+ .poll(() => page.textContent('.unsafe-fs-fetch-8498-2'))
+ .toBe('')
+ await expect
+ .poll(() => page.textContent('.unsafe-fs-fetch-8498-2-status'))
+ .toBe('404')
+ })
+
+ test('nested entry', async () => {
+ await expect.poll(() => page.textContent('.nested-entry')).toBe('foobar')
+ })
+
+ test('denied', async () => {
+ await expect.poll(() => page.textContent('.unsafe-dotenv')).toBe('403')
+ })
+
+ test('denied EnV casing', async () => {
+ // It is 403 in case insensitive system, 404 in others
+ await expect
+ .poll(() => page.textContent('.unsafe-dotEnV-casing'))
+ .toStrictEqual(expect.toBeOneOf(['403', '404']))
+ })
+})
+
+describe('fetch', () => {
+ test('serve with configured headers', async () => {
+ const res = await fetch(viteTestUrl + '/src/')
+ expect(res.headers.get('x-served-by')).toBe('vite')
+ })
+})
diff --git a/playground/fs-serve/__tests__/deny/fs-serve-deny.spec.ts b/playground/fs-serve/__tests__/deny/fs-serve-deny.spec.ts
new file mode 100644
index 00000000000000..fb60922e86e1ae
--- /dev/null
+++ b/playground/fs-serve/__tests__/deny/fs-serve-deny.spec.ts
@@ -0,0 +1,17 @@
+import { describe, expect, test } from 'vitest'
+import { isServe, page, viteTestUrl } from '~utils'
+
+describe.runIf(isServe)('main', () => {
+ test('**/deny/** should deny src/deny/deny.txt', async () => {
+ const res = await page.request.fetch(
+ new URL('/src/deny/deny.txt', viteTestUrl).href,
+ )
+ expect(res.status()).toBe(403)
+ })
+ test('**/deny/** should deny src/deny/.deny', async () => {
+ const res = await page.request.fetch(
+ new URL('/src/deny/.deny', viteTestUrl).href,
+ )
+ expect(res.status()).toBe(403)
+ })
+})
diff --git a/playground/fs-serve/__tests__/fs-serve.spec.ts b/playground/fs-serve/__tests__/fs-serve.spec.ts
new file mode 100644
index 00000000000000..16bcf66710ecee
--- /dev/null
+++ b/playground/fs-serve/__tests__/fs-serve.spec.ts
@@ -0,0 +1,660 @@
+import net from 'node:net'
+import path from 'node:path'
+import http from 'node:http'
+import {
+ afterEach,
+ beforeAll,
+ beforeEach,
+ describe,
+ expect,
+ test,
+} from 'vitest'
+import type { Page } from 'playwright-chromium'
+import WebSocket from 'ws'
+import testJSON from '../safe.json'
+import {
+ browser,
+ isServe,
+ isWindows,
+ page,
+ viteServer,
+ viteTestUrl,
+} from '~utils'
+
+const getViteTestIndexHtmlUrl = () => {
+ const srcPrefix = viteTestUrl.endsWith('/') ? '' : '/'
+ // NOTE: viteTestUrl is set lazily
+ return viteTestUrl + srcPrefix + 'src/'
+}
+
+const stringified = JSON.stringify(testJSON)
+
+describe.runIf(isServe)('main', () => {
+ beforeAll(async () => {
+ await page.goto(getViteTestIndexHtmlUrl())
+ })
+
+ test('default import', async () => {
+ await expect.poll(() => page.textContent('.full')).toBe(stringified)
+ })
+
+ test('named import', async () => {
+ await expect.poll(() => page.textContent('.named')).toBe(testJSON.msg)
+ })
+
+ test('virtual svg module', async () => {
+ await expect.poll(() => page.textContent('.virtual-svg')).toMatch(' {
+ await expect.poll(() => page.textContent('.safe-fetch')).toMatch('KEY=safe')
+ await expect.poll(() => page.textContent('.safe-fetch-status')).toBe('200')
+ })
+
+ test('safe fetch with query', async () => {
+ await expect
+ .poll(() => page.textContent('.safe-fetch-query'))
+ .toMatch('KEY=safe')
+ await expect
+ .poll(() => page.textContent('.safe-fetch-query-status'))
+ .toBe('200')
+ })
+
+ test('safe fetch with special characters', async () => {
+ await expect
+ .poll(() => page.textContent('.safe-fetch-subdir-special-characters'))
+ .toMatch('KEY=safe')
+ await expect
+ .poll(() =>
+ page.textContent('.safe-fetch-subdir-special-characters-status'),
+ )
+ .toBe('200')
+ })
+
+ test('unsafe fetch', async () => {
+ await expect
+ .poll(() => page.textContent('.unsafe-fetch'))
+ .toMatch('403 Restricted')
+ await expect
+ .poll(() => page.textContent('.unsafe-fetch-status'))
+ .toBe('403')
+ })
+
+ test('unsafe HTML fetch', async () => {
+ await expect
+ .poll(() => page.textContent('.unsafe-fetch-html'))
+ .toMatch('403 Restricted')
+ await expect
+ .poll(() => page.textContent('.unsafe-fetch-html-status'))
+ .toBe('403')
+ })
+
+ test('unsafe fetch with special characters (#8498)', async () => {
+ await expect.poll(() => page.textContent('.unsafe-fetch-8498')).toBe('')
+ await expect
+ .poll(() => page.textContent('.unsafe-fetch-8498-status'))
+ .toBe('404')
+ })
+
+ test('unsafe fetch with special characters 2 (#8498)', async () => {
+ await expect.poll(() => page.textContent('.unsafe-fetch-8498-2')).toBe('')
+ await expect
+ .poll(() => page.textContent('.unsafe-fetch-8498-2-status'))
+ .toBe('404')
+ })
+
+ test('unsafe fetch import inline', async () => {
+ await expect
+ .poll(() => page.textContent('.unsafe-fetch-import-inline-status'))
+ .toBe('403')
+ })
+
+ test('unsafe fetch raw query import', async () => {
+ await expect
+ .poll(() => page.textContent('.unsafe-fetch-raw-query-import-status'))
+ .toBe('403')
+ })
+
+ test('unsafe fetch ?.svg?import', async () => {
+ await expect
+ .poll(() => page.textContent('.unsafe-fetch-query-dot-svg-import-status'))
+ .toBe('403')
+ })
+
+ test('unsafe fetch .svg?import', async () => {
+ await expect
+ .poll(() => page.textContent('.unsafe-fetch-svg-status'))
+ .toBe('403')
+ })
+
+ test('safe fs fetch', async () => {
+ await expect
+ .poll(() => page.textContent('.safe-fs-fetch'))
+ .toBe(stringified)
+ await expect
+ .poll(() => page.textContent('.safe-fs-fetch-status'))
+ .toBe('200')
+ })
+
+ test('safe fs fetch', async () => {
+ await expect
+ .poll(() => page.textContent('.safe-fs-fetch-query'))
+ .toBe(stringified)
+ await expect
+ .poll(() => page.textContent('.safe-fs-fetch-query-status'))
+ .toBe('200')
+ })
+
+ test('safe fs fetch with special characters', async () => {
+ await expect
+ .poll(() => page.textContent('.safe-fs-fetch-special-characters'))
+ .toBe(stringified)
+ await expect
+ .poll(() => page.textContent('.safe-fs-fetch-special-characters-status'))
+ .toBe('200')
+ })
+
+ test('unsafe fs fetch', async () => {
+ await expect.poll(() => page.textContent('.unsafe-fs-fetch')).toBe('')
+ await expect
+ .poll(() => page.textContent('.unsafe-fs-fetch-status'))
+ .toBe('403')
+ })
+
+ test('unsafe fs fetch', async () => {
+ await expect.poll(() => page.textContent('.unsafe-fs-fetch-raw')).toBe('')
+ await expect
+ .poll(() => page.textContent('.unsafe-fs-fetch-raw-status'))
+ .toBe('403')
+ })
+
+ test('unsafe fs fetch query 1', async () => {
+ await expect
+ .poll(() => page.textContent('.unsafe-fs-fetch-raw-query1'))
+ .toBe('')
+ await expect
+ .poll(() => page.textContent('.unsafe-fs-fetch-raw-query1-status'))
+ .toBe('403')
+ })
+
+ test('unsafe fs fetch query 2', async () => {
+ await expect
+ .poll(() => page.textContent('.unsafe-fs-fetch-raw-query2'))
+ .toBe('')
+ await expect
+ .poll(() => page.textContent('.unsafe-fs-fetch-raw-query2-status'))
+ .toBe('403')
+ })
+
+ test('unsafe fs fetch with special characters (#8498)', async () => {
+ await expect.poll(() => page.textContent('.unsafe-fs-fetch-8498')).toBe('')
+ await expect
+ .poll(() => page.textContent('.unsafe-fs-fetch-8498-status'))
+ .toBe('404')
+ })
+
+ test('unsafe fs fetch with special characters 2 (#8498)', async () => {
+ await expect
+ .poll(() => page.textContent('.unsafe-fs-fetch-8498-2'))
+ .toBe('')
+ await expect
+ .poll(() => page.textContent('.unsafe-fs-fetch-8498-2-status'))
+ .toBe('404')
+ })
+
+ test('unsafe fs fetch import inline', async () => {
+ await expect
+ .poll(() => page.textContent('.unsafe-fs-fetch-import-inline-status'))
+ .toBe('403')
+ })
+
+ test('unsafe fs fetch import inline wasm init', async () => {
+ await expect
+ .poll(() =>
+ page.textContent('.unsafe-fs-fetch-import-inline-wasm-init-status'),
+ )
+ .toBe('403')
+ })
+
+ test('unsafe fs fetch with relative path after query status', async () => {
+ await expect
+ .poll(() =>
+ page.textContent('.unsafe-fs-fetch-relative-path-after-query-status'),
+ )
+ .toBe('404')
+ })
+
+ test('nested entry', async () => {
+ await expect.poll(() => page.textContent('.nested-entry')).toBe('foobar')
+ })
+
+ test('denied', async () => {
+ await expect.poll(() => page.textContent('.unsafe-dotenv')).toBe('403')
+ })
+
+ test('denied EnV casing', async () => {
+ // It is 403 in case insensitive system, 404 in others
+ await expect
+ .poll(() => page.textContent('.unsafe-dotEnV-casing'))
+ .toStrictEqual(expect.toBeOneOf(['403', '404']))
+ })
+
+ test('denied env with ?.svg?.wasm?init', async () => {
+ await expect
+ .poll(() => page.textContent('.unsafe-dotenv-query-dot-svg-wasm-init'))
+ .toBe('403')
+ })
+})
+
+describe('fetch', () => {
+ test('serve with configured headers', async () => {
+ const res = await fetch(viteTestUrl + '/src/')
+ expect(res.headers.get('x-served-by')).toBe('vite')
+ })
+})
+
+describe('cross origin', () => {
+ const fetchStatusFromPage = async (page: Page, url: string) => {
+ return await page.evaluate(async (url: string) => {
+ try {
+ const res = await globalThis.fetch(url)
+ return res.status
+ } catch {
+ return -1
+ }
+ }, url)
+ }
+
+ const connectWebSocketFromPage = async (page: Page, url: string) => {
+ return await page.evaluate(async (url: string) => {
+ try {
+ const ws = new globalThis.WebSocket(url, ['vite-hmr'])
+ await new Promise((resolve, reject) => {
+ ws.addEventListener('open', () => {
+ resolve()
+ ws.close()
+ })
+ ws.addEventListener('error', () => {
+ reject()
+ })
+ })
+ return true
+ } catch {
+ return false
+ }
+ }, url)
+ }
+
+ const connectWebSocketFromServer = async (
+ url: string,
+ host: string,
+ origin: string | undefined,
+ ) => {
+ try {
+ const ws = new WebSocket(url, ['vite-hmr'], {
+ headers: {
+ Host: host,
+ ...(origin ? { Origin: origin } : undefined),
+ },
+ })
+ await new Promise((resolve, reject) => {
+ ws.addEventListener('open', () => {
+ resolve()
+ ws.close()
+ })
+ ws.addEventListener('error', () => {
+ reject()
+ })
+ })
+ return true
+ } catch {
+ return false
+ }
+ }
+
+ describe('allowed for same origin', () => {
+ beforeEach(async () => {
+ await page.goto(getViteTestIndexHtmlUrl())
+ })
+
+ test('fetch HTML file', async () => {
+ const status = await fetchStatusFromPage(page, viteTestUrl + '/src/')
+ expect(status).toBe(200)
+ })
+
+ test.runIf(isServe)('fetch JS file', async () => {
+ const status = await fetchStatusFromPage(
+ page,
+ viteTestUrl + '/src/code.js',
+ )
+ expect(status).toBe(200)
+ })
+
+ test.runIf(isServe)('connect WebSocket with valid token', async () => {
+ const token = viteServer.config.webSocketToken
+ const result = await connectWebSocketFromPage(
+ page,
+ `${viteTestUrl}?token=${token}`,
+ )
+ expect(result).toBe(true)
+ })
+
+ test('fetch with allowed hosts', async () => {
+ const viteTestUrlUrl = new URL(viteTestUrl)
+ const res = await fetch(viteTestUrl + '/src/index.html', {
+ headers: { Host: viteTestUrlUrl.host },
+ })
+ expect(res.status).toBe(200)
+ })
+
+ test.runIf(isServe)(
+ 'connect WebSocket with valid token with allowed hosts',
+ async () => {
+ const viteTestUrlUrl = new URL(viteTestUrl)
+ const token = viteServer.config.webSocketToken
+ const result = await connectWebSocketFromServer(
+ `${viteTestUrl}?token=${token}`,
+ viteTestUrlUrl.host,
+ viteTestUrlUrl.origin,
+ )
+ expect(result).toBe(true)
+ },
+ )
+
+ test.runIf(isServe)(
+ 'connect WebSocket without a token without the origin header',
+ async () => {
+ const viteTestUrlUrl = new URL(viteTestUrl)
+ const result = await connectWebSocketFromServer(
+ viteTestUrl,
+ viteTestUrlUrl.host,
+ undefined,
+ )
+ expect(result).toBe(true)
+ },
+ )
+ })
+
+ describe('denied for different origin', async () => {
+ let page2: Page
+ beforeEach(async () => {
+ page2 = await browser.newPage()
+ await page2.goto('http://vite.dev/404')
+ })
+ afterEach(async () => {
+ await page2.close()
+ })
+
+ test('fetch HTML file', async () => {
+ const status = await fetchStatusFromPage(page2, viteTestUrl + '/src/')
+ expect(status).not.toBe(200)
+ })
+
+ test.runIf(isServe)('fetch JS file', async () => {
+ const status = await fetchStatusFromPage(
+ page2,
+ viteTestUrl + '/src/code.js',
+ )
+ expect(status).not.toBe(200)
+ })
+
+ test.runIf(isServe)('connect WebSocket without token', async () => {
+ const result = await connectWebSocketFromPage(page, viteTestUrl)
+ expect(result).toBe(false)
+
+ const result2 = await connectWebSocketFromPage(
+ page,
+ `${viteTestUrl}?token=`,
+ )
+ expect(result2).toBe(false)
+ })
+
+ test.runIf(isServe)('connect WebSocket with invalid token', async () => {
+ const token = viteServer.config.webSocketToken
+ const result = await connectWebSocketFromPage(
+ page,
+ `${viteTestUrl}?token=${'t'.repeat(token.length)}`,
+ )
+ expect(result).toBe(false)
+
+ const result2 = await connectWebSocketFromPage(
+ page,
+ `${viteTestUrl}?token=${'t'.repeat(token.length)}t`, // different length
+ )
+ expect(result2).toBe(false)
+ })
+
+ test('fetch with non-allowed hosts', async () => {
+ // NOTE: fetch cannot be used here as `fetch` sets the correct `Host` header
+ const res = await new Promise((resolve, reject) => {
+ http
+ .get(
+ viteTestUrl + '/src/index.html',
+ {
+ headers: {
+ Host: 'vite.dev',
+ },
+ },
+ (res) => {
+ resolve(res)
+ },
+ )
+ .on('error', (e) => {
+ reject(e)
+ })
+ })
+ expect(res.statusCode).toBe(403)
+ })
+
+ test.runIf(isServe)(
+ 'connect WebSocket with valid token with non-allowed hosts',
+ async () => {
+ const token = viteServer.config.webSocketToken
+ const result = await connectWebSocketFromServer(
+ `${viteTestUrl}?token=${token}`,
+ 'vite.dev',
+ 'http://vite.dev',
+ )
+ expect(result).toBe(false)
+
+ const result2 = await connectWebSocketFromServer(
+ `${viteTestUrl}?token=${token}`,
+ 'vite.dev',
+ undefined,
+ )
+ expect(result2).toBe(false)
+ },
+ )
+ })
+})
+
+describe.runIf(isServe)('invalid request', () => {
+ const sendRawRequest = async (baseUrl: string, requestTarget: string) => {
+ return new Promise((resolve, reject) => {
+ const parsedUrl = new URL(baseUrl)
+
+ const buf: Buffer[] = []
+ const client = net.createConnection(
+ { port: +parsedUrl.port, host: parsedUrl.hostname },
+ () => {
+ client.write(
+ [
+ `GET ${encodeURI(requestTarget)} HTTP/1.1`,
+ `Host: ${parsedUrl.host}`,
+ 'Connection: Close',
+ '\r\n',
+ ].join('\r\n'),
+ )
+ },
+ )
+ client.on('data', (data) => {
+ buf.push(data)
+ })
+ client.on('end', (hadError) => {
+ if (!hadError) {
+ resolve(Buffer.concat(buf).toString())
+ }
+ })
+ client.on('error', (err) => {
+ reject(err)
+ })
+ })
+ }
+
+ const root = path
+ .resolve(import.meta.dirname.replace('playground', 'playground-temp'), '..')
+ .replace(/\\/g, '/')
+
+ test('request with sendRawRequest should work', async () => {
+ const response = await sendRawRequest(viteTestUrl, '/src/safe.txt')
+ expect(response).toContain('HTTP/1.1 200 OK')
+ expect(response).toContain('KEY=safe')
+ })
+
+ test('request with sendRawRequest should work with /@fs/', async () => {
+ const response = await sendRawRequest(
+ viteTestUrl,
+ path.posix.join('/@fs/', root, 'root/src/safe.txt'),
+ )
+ expect(response).toContain('HTTP/1.1 200 OK')
+ expect(response).toContain('KEY=safe')
+ })
+
+ test('should reject request that has # in request-target', async () => {
+ const response = await sendRawRequest(
+ viteTestUrl,
+ '/src/safe.txt#/../../unsafe.txt',
+ )
+ expect(response).toContain('HTTP/1.1 400 Bad Request')
+ })
+
+ test('should reject request that has # in request-target with /@fs/', async () => {
+ const response = await sendRawRequest(
+ viteTestUrl,
+ path.posix.join('/@fs/', root, 'root/src/safe.txt') +
+ '#/../../unsafe.txt',
+ )
+ expect(response).toContain('HTTP/1.1 400 Bad Request')
+ })
+
+ test('should deny request to denied file when a request has /.', async () => {
+ const response = await sendRawRequest(viteTestUrl, '/src/dummy.crt/.')
+ expect(response).toContain('HTTP/1.1 403 Forbidden')
+ })
+
+ test('should deny request to denied file when a request ends with \\', async () => {
+ const response = await sendRawRequest(viteTestUrl, '/src/.env\\')
+ expect(response).toContain(
+ isWindows ? 'HTTP/1.1 403 Forbidden' : 'HTTP/1.1 404 Not Found',
+ )
+ })
+
+ test('should deny request to denied file when a request ends with \\ with /@fs/', async () => {
+ const response = await sendRawRequest(
+ viteTestUrl,
+ path.posix.join('/@fs/', root, 'root/src/.env') + '\\',
+ )
+ expect(response).toContain(
+ isWindows ? 'HTTP/1.1 403 Forbidden' : 'HTTP/1.1 404 Not Found',
+ )
+ })
+
+ test('should deny request with /@fs/ to denied file when a request has /.', async () => {
+ const response = await sendRawRequest(
+ viteTestUrl,
+ path.posix.join('/@fs/', root, 'root/src/dummy.crt/') + '.',
+ )
+ expect(response).toContain('HTTP/1.1 403 Forbidden')
+ })
+
+ test('should deny request to HTML file outside root by default with relative path', async () => {
+ const response = await sendRawRequest(viteTestUrl, '/../unsafe.html')
+ expect(response).toContain('HTTP/1.1 403 Forbidden')
+ })
+})
+
+describe.runIf(!isServe)('preview HTML', () => {
+ test('unsafe HTML fetch', async () => {
+ await expect.poll(() => page.textContent('.unsafe-fetch-html')).toBe('')
+ await expect
+ .poll(() => page.textContent('.unsafe-fetch-html-status'))
+ .toBe('404')
+ })
+})
+
+test.runIf(isServe)(
+ 'load script with no-cors mode from a different origin',
+ async () => {
+ const viteTestUrlUrl = new URL(viteTestUrl)
+
+ // NOTE: fetch cannot be used here as `fetch` sets some headers automatically
+ const res = await new Promise((resolve, reject) => {
+ http
+ .get(
+ viteTestUrl + '/src/code.js',
+ {
+ headers: {
+ 'Sec-Fetch-Dest': 'script',
+ 'Sec-Fetch-Mode': 'no-cors',
+ 'Sec-Fetch-Site': 'same-site',
+ Origin: 'http://vite.dev',
+ Host: viteTestUrlUrl.host,
+ },
+ },
+ (res) => {
+ resolve(res)
+ },
+ )
+ .on('error', (e) => {
+ reject(e)
+ })
+ })
+ expect(res.statusCode).toBe(403)
+ const body = Buffer.concat(await ArrayFromAsync(res)).toString()
+ expect(body).toContain(
+ 'Cross-origin requests for classic scripts must be made with CORS mode enabled.',
+ )
+ },
+)
+
+test.runIf(isServe)(
+ 'load image with no-cors mode from a different origin should be allowed',
+ async () => {
+ const viteTestUrlUrl = new URL(viteTestUrl)
+
+ // NOTE: fetch cannot be used here as `fetch` sets some headers automatically
+ const res = await new Promise((resolve, reject) => {
+ http
+ .get(
+ viteTestUrl + '/src/code.js',
+ {
+ headers: {
+ 'Sec-Fetch-Dest': 'image',
+ 'Sec-Fetch-Mode': 'no-cors',
+ 'Sec-Fetch-Site': 'same-site',
+ Origin: 'http://vite.dev',
+ Host: viteTestUrlUrl.host,
+ },
+ },
+ (res) => {
+ resolve(res)
+ },
+ )
+ .on('error', (e) => {
+ reject(e)
+ })
+ })
+ expect(res.statusCode).not.toBe(403)
+ },
+)
+
+// Note: Array.fromAsync is only supported in Node.js 22+
+async function ArrayFromAsync(
+ asyncIterable: AsyncIterable,
+): Promise {
+ const chunks = []
+ for await (const chunk of asyncIterable) {
+ chunks.push(chunk)
+ }
+ return chunks
+}
diff --git a/packages/playground/fs-serve/entry.js b/playground/fs-serve/entry.js
similarity index 100%
rename from packages/playground/fs-serve/entry.js
rename to playground/fs-serve/entry.js
diff --git a/packages/playground/fs-serve/nested/foo.js b/playground/fs-serve/nested/foo.js
similarity index 100%
rename from packages/playground/fs-serve/nested/foo.js
rename to playground/fs-serve/nested/foo.js
diff --git a/playground/fs-serve/package.json b/playground/fs-serve/package.json
new file mode 100644
index 00000000000000..7f426509de8a14
--- /dev/null
+++ b/playground/fs-serve/package.json
@@ -0,0 +1,21 @@
+{
+ "name": "@vitejs/test-fs-serve",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite root",
+ "build": "vite build root",
+ "debug": "node --inspect-brk ../../packages/vite/bin/vite",
+ "preview": "vite preview root",
+ "dev:base": "vite root --config ./root/vite.config-base.js",
+ "build:base": "vite build root --config ./root/vite.config-base.js",
+ "preview:base": "vite preview root --config ./root/vite.config-base.js",
+ "dev:deny": "vite root --config ./root/vite.config-deny.js",
+ "build:deny": "vite build root --config ./root/vite.config-deny.js",
+ "preview:deny": "vite preview root --config ./root/vite.config-deny.js"
+ },
+ "devDependencies": {
+ "ws": "^8.19.0"
+ }
+}
diff --git a/packages/playground/fs-serve/root/unsafe.txt b/playground/fs-serve/root/src/.env
similarity index 100%
rename from packages/playground/fs-serve/root/unsafe.txt
rename to playground/fs-serve/root/src/.env
diff --git a/playground/fs-serve/root/src/code.js b/playground/fs-serve/root/src/code.js
new file mode 100644
index 00000000000000..33fd8df878207b
--- /dev/null
+++ b/playground/fs-serve/root/src/code.js
@@ -0,0 +1 @@
+// code.js
diff --git a/playground/fs-serve/root/src/deny/.deny b/playground/fs-serve/root/src/deny/.deny
new file mode 100644
index 00000000000000..73bd3960853c61
--- /dev/null
+++ b/playground/fs-serve/root/src/deny/.deny
@@ -0,0 +1 @@
+.deny
diff --git a/playground/fs-serve/root/src/deny/deny.txt b/playground/fs-serve/root/src/deny/deny.txt
new file mode 100644
index 00000000000000..f9df83416f8a72
--- /dev/null
+++ b/playground/fs-serve/root/src/deny/deny.txt
@@ -0,0 +1 @@
+deny
diff --git a/playground/fs-serve/root/src/dummy.crt b/playground/fs-serve/root/src/dummy.crt
new file mode 100644
index 00000000000000..d97c5eada5d8c5
--- /dev/null
+++ b/playground/fs-serve/root/src/dummy.crt
@@ -0,0 +1 @@
+secret
diff --git a/playground/fs-serve/root/src/index.html b/playground/fs-serve/root/src/index.html
new file mode 100644
index 00000000000000..399dc319107272
--- /dev/null
+++ b/playground/fs-serve/root/src/index.html
@@ -0,0 +1,462 @@
+
+
+Normal Import
+
+
+
+Safe Fetch
+
+
+
+
+
+Safe Fetch Subdirectory
+
+
+
+
+
+Unsafe Fetch
+
+
+
+
+
+
+
+
+
+
+
+
+
+Safe /@fs/ Fetch
+
+
+
+
+
+
+
+Unsafe /@fs/ Fetch
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Nested Entry
+
+
+Denied
+
+
+
+
+Virtual SVG module
+
+
+
diff --git a/packages/playground/fs-serve/root/src/safe.txt b/playground/fs-serve/root/src/safe.txt
similarity index 100%
rename from packages/playground/fs-serve/root/src/safe.txt
rename to playground/fs-serve/root/src/safe.txt
diff --git a/packages/playground/fs-serve/safe.json "b/playground/fs-serve/root/src/special characters \303\245\303\244\303\266/safe.json"
similarity index 100%
rename from packages/playground/fs-serve/safe.json
rename to "playground/fs-serve/root/src/special characters \303\245\303\244\303\266/safe.json"
diff --git a/packages/playground/fs-serve/root/src/subdir/safe.txt "b/playground/fs-serve/root/src/special characters \303\245\303\244\303\266/safe.txt"
similarity index 100%
rename from packages/playground/fs-serve/root/src/subdir/safe.txt
rename to "playground/fs-serve/root/src/special characters \303\245\303\244\303\266/safe.txt"
diff --git a/playground/fs-serve/root/src/subdir/safe.txt b/playground/fs-serve/root/src/subdir/safe.txt
new file mode 100644
index 00000000000000..3f3d0607101642
--- /dev/null
+++ b/playground/fs-serve/root/src/subdir/safe.txt
@@ -0,0 +1 @@
+KEY=safe
diff --git a/playground/fs-serve/root/svgVirtualModulePlugin.ts b/playground/fs-serve/root/svgVirtualModulePlugin.ts
new file mode 100644
index 00000000000000..62eb5c1ad19506
--- /dev/null
+++ b/playground/fs-serve/root/svgVirtualModulePlugin.ts
@@ -0,0 +1,20 @@
+import type { Plugin } from 'vite'
+const svgVirtualModuleId = 'virtual:foo.svg'
+const resolvedSvgVirtualModuleId = '\0' + svgVirtualModuleId
+
+export default function svgVirtualModulePlugin(): Plugin {
+ return {
+ name: 'svg-virtual-module',
+ resolveId(id) {
+ if (id === svgVirtualModuleId) {
+ return resolvedSvgVirtualModuleId
+ }
+ },
+ async load(id, _options) {
+ if (id === resolvedSvgVirtualModuleId) {
+ return `export default ' '`
+ }
+ },
+ enforce: 'pre',
+ }
+}
diff --git a/playground/fs-serve/root/unsafe.html b/playground/fs-serve/root/unsafe.html
new file mode 100644
index 00000000000000..0996062cd32fda
--- /dev/null
+++ b/playground/fs-serve/root/unsafe.html
@@ -0,0 +1 @@
+unsafe
diff --git a/playground/fs-serve/root/unsafe.svg b/playground/fs-serve/root/unsafe.svg
new file mode 100644
index 00000000000000..e80a9f4170bf1f
--- /dev/null
+++ b/playground/fs-serve/root/unsafe.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/playground/fs-serve/root/unsafe.txt b/playground/fs-serve/root/unsafe.txt
new file mode 100644
index 00000000000000..d0e0cfd28cbe57
--- /dev/null
+++ b/playground/fs-serve/root/unsafe.txt
@@ -0,0 +1 @@
+KEY=unsafe
diff --git a/playground/fs-serve/root/vite.config-base.js b/playground/fs-serve/root/vite.config-base.js
new file mode 100644
index 00000000000000..c1ca2a52841143
--- /dev/null
+++ b/playground/fs-serve/root/vite.config-base.js
@@ -0,0 +1,38 @@
+import path from 'node:path'
+import { defineConfig } from 'vite'
+import svgVirtualModulePlugin from './svgVirtualModulePlugin'
+
+const BASE = '/base/'
+
+export default defineConfig({
+ base: BASE,
+ build: {
+ rollupOptions: {
+ input: {
+ main: path.resolve(import.meta.dirname, 'src/index.html'),
+ },
+ },
+ },
+ server: {
+ fs: {
+ strict: true,
+ allow: [path.resolve(import.meta.dirname, 'src')],
+ },
+ hmr: {
+ overlay: false,
+ },
+ headers: {
+ 'x-served-by': 'vite',
+ },
+ },
+ preview: {
+ headers: {
+ 'x-served-by': 'vite',
+ },
+ },
+ define: {
+ ROOT: JSON.stringify(path.dirname(import.meta.dirname).replace(/\\/g, '/')),
+ BASE: JSON.stringify(BASE),
+ },
+ plugins: [svgVirtualModulePlugin()],
+})
diff --git a/playground/fs-serve/root/vite.config-deny.js b/playground/fs-serve/root/vite.config-deny.js
new file mode 100644
index 00000000000000..2772d3adef93cd
--- /dev/null
+++ b/playground/fs-serve/root/vite.config-deny.js
@@ -0,0 +1,24 @@
+import path from 'node:path'
+import { defineConfig } from 'vite'
+import svgVirtualModulePlugin from './svgVirtualModulePlugin'
+
+export default defineConfig({
+ build: {
+ rollupOptions: {
+ input: {
+ main: path.resolve(import.meta.dirname, 'src/index.html'),
+ },
+ },
+ },
+ server: {
+ fs: {
+ strict: true,
+ allow: [path.resolve(import.meta.dirname, 'src')],
+ deny: ['**/deny/**'],
+ },
+ },
+ define: {
+ ROOT: JSON.stringify(path.dirname(import.meta.dirname).replace(/\\/g, '/')),
+ },
+ plugins: [svgVirtualModulePlugin()],
+})
diff --git a/playground/fs-serve/root/vite.config.js b/playground/fs-serve/root/vite.config.js
new file mode 100644
index 00000000000000..43a192eab1cac9
--- /dev/null
+++ b/playground/fs-serve/root/vite.config.js
@@ -0,0 +1,34 @@
+import path from 'node:path'
+import { defineConfig } from 'vite'
+import svgVirtualModulePlugin from './svgVirtualModulePlugin'
+
+export default defineConfig({
+ build: {
+ rollupOptions: {
+ input: {
+ main: path.resolve(import.meta.dirname, 'src/index.html'),
+ },
+ },
+ },
+ server: {
+ fs: {
+ strict: true,
+ allow: [path.resolve(import.meta.dirname, 'src')],
+ },
+ hmr: {
+ overlay: false,
+ },
+ headers: {
+ 'x-served-by': 'vite',
+ },
+ },
+ preview: {
+ headers: {
+ 'x-served-by': 'vite',
+ },
+ },
+ define: {
+ ROOT: JSON.stringify(path.dirname(import.meta.dirname).replace(/\\/g, '/')),
+ },
+ plugins: [svgVirtualModulePlugin()],
+})
diff --git a/playground/fs-serve/safe.json b/playground/fs-serve/safe.json
new file mode 100644
index 00000000000000..84f96593c10bad
--- /dev/null
+++ b/playground/fs-serve/safe.json
@@ -0,0 +1,3 @@
+{
+ "msg": "safe"
+}
diff --git a/playground/fs-serve/unsafe.html b/playground/fs-serve/unsafe.html
new file mode 100644
index 00000000000000..4853bcbcc0e2ad
--- /dev/null
+++ b/playground/fs-serve/unsafe.html
@@ -0,0 +1 @@
+unsafe outside root
diff --git a/packages/playground/fs-serve/unsafe.json b/playground/fs-serve/unsafe.json
similarity index 100%
rename from packages/playground/fs-serve/unsafe.json
rename to playground/fs-serve/unsafe.json
diff --git a/playground/glob-import/__tests__/glob-import.spec.ts b/playground/glob-import/__tests__/glob-import.spec.ts
new file mode 100644
index 00000000000000..81125143c69479
--- /dev/null
+++ b/playground/glob-import/__tests__/glob-import.spec.ts
@@ -0,0 +1,313 @@
+import path from 'node:path'
+import { readdir } from 'node:fs/promises'
+import { expect, test } from 'vitest'
+import {
+ addFile,
+ editFile,
+ findAssetFile,
+ isBuild,
+ page,
+ removeFile,
+} from '~utils'
+
+const filteredResult = {
+ './alias.js': {
+ default: 'hi',
+ },
+ './foo.js': {
+ msg: 'foo',
+ },
+ "./quote'.js": {
+ msg: 'single-quote',
+ },
+}
+
+const json = {
+ msg: 'baz',
+ default: {
+ msg: 'baz',
+ },
+}
+
+const globWithAlias = {
+ '/dir/alias.js': {
+ default: 'hi',
+ },
+}
+
+const allResult = {
+ // JSON file should be properly transformed
+ '/dir/alias.js': {
+ default: 'hi',
+ },
+ '/dir/baz.json': json,
+ '/dir/foo.css': {},
+ '/dir/foo.js': {
+ msg: 'foo',
+ },
+ '/dir/index.js': isBuild
+ ? {
+ modules: filteredResult,
+ globWithAlias,
+ }
+ : {
+ globWithAlias,
+ modules: filteredResult,
+ },
+ '/dir/nested/bar.js': {
+ modules: {
+ '../baz.json': json,
+ },
+ msg: 'bar',
+ },
+ "/dir/quote'.js": {
+ msg: 'single-quote',
+ },
+}
+
+const nodeModulesResult = {
+ '/dir/node_modules/hoge.js': { msg: 'hoge' },
+}
+
+const rawResult = {
+ '/dir/baz.json': {
+ msg: 'baz',
+ },
+}
+
+const relativeRawResult = {
+ './dir/baz.json': {
+ msg: 'baz',
+ },
+}
+
+const baseRawResult = {
+ './baz.json': {
+ msg: 'baz',
+ },
+}
+
+test('should work', async () => {
+ // TODO: extglobs are not supported yet: https://github.com/vitejs/rolldown-vite/issues/365
+ if (process.env._VITE_TEST_JS_PLUGIN) {
+ await expect
+ .poll(async () => JSON.parse(await page.textContent('.result')))
+ .toStrictEqual(allResult)
+ await expect
+ .poll(async () => JSON.parse(await page.textContent('.result-eager')))
+ .toStrictEqual(allResult)
+ }
+ await expect
+ .poll(async () =>
+ JSON.parse(await page.textContent('.result-node_modules')),
+ )
+ .toStrictEqual(nodeModulesResult)
+})
+
+test('import glob raw', async () => {
+ expect(await page.textContent('.globraw')).toBe(
+ JSON.stringify(rawResult, null, 2),
+ )
+})
+
+test('import property access', async () => {
+ expect(await page.textContent('.property-access')).toBe(
+ JSON.stringify(rawResult['/dir/baz.json'], null, 2),
+ )
+})
+
+test('import relative glob raw', async () => {
+ expect(await page.textContent('.relative-glob-raw')).toBe(
+ JSON.stringify(relativeRawResult, null, 2),
+ )
+})
+
+test('unassigned import processes', async () => {
+ expect(await page.textContent('.side-effect-result')).toBe(
+ 'Hello from side effect',
+ )
+})
+
+test('import glob in package', async () => {
+ expect(await page.textContent('.in-package')).toBe(
+ JSON.stringify(['/pkg-pages/foo.js']),
+ )
+})
+
+if (!isBuild) {
+ test('hmr for adding/removing files', async () => {
+ const resultElement = page.locator('.result')
+
+ addFile('dir/a.js', '')
+ await expect
+ .poll(async () => {
+ const actualAdd = await resultElement.textContent()
+ return JSON.parse(actualAdd)
+ })
+ .toStrictEqual({
+ '/dir/a.js': {},
+ ...allResult,
+ '/dir/index.js': {
+ ...allResult['/dir/index.js'],
+ modules: {
+ './a.js': {},
+ ...allResult['/dir/index.js'].modules,
+ },
+ },
+ })
+
+ // edit the added file
+ editFile('dir/a.js', () => 'export const msg ="a"')
+ await expect
+ .poll(async () => {
+ const actualEdit = await resultElement.textContent()
+ return JSON.parse(actualEdit)
+ })
+ .toStrictEqual({
+ '/dir/a.js': {
+ msg: 'a',
+ },
+ ...allResult,
+ '/dir/index.js': {
+ ...allResult['/dir/index.js'],
+ modules: {
+ './a.js': {
+ msg: 'a',
+ },
+ ...allResult['/dir/index.js'].modules,
+ },
+ },
+ })
+
+ removeFile('dir/a.js')
+ await expect
+ .poll(async () => {
+ const actualRemove = await resultElement.textContent()
+ return JSON.parse(actualRemove)
+ })
+ .toStrictEqual(allResult)
+ })
+
+ test('no hmr for adding/removing files', async () => {
+ let request = page.waitForResponse(/dir\/index\.js$/, { timeout: 200 })
+ addFile('nohmr.js', '')
+ let response = await request.catch(() => ({ status: () => -1 }))
+ expect(response.status()).toBe(-1)
+
+ request = page.waitForResponse(/dir\/index\.js$/, { timeout: 200 })
+ removeFile('nohmr.js')
+ response = await request.catch(() => ({ status: () => -1 }))
+ expect(response.status()).toBe(-1)
+ })
+
+ test('hmr for adding/removing files in package', async () => {
+ const resultElement = page.locator('.in-package')
+
+ addFile('pkg-pages/bar.js', '// empty')
+ await expect
+ .poll(async () => JSON.parse(await resultElement.textContent()))
+ .toStrictEqual(['/pkg-pages/foo.js', '/pkg-pages/bar.js'].sort())
+
+ removeFile('pkg-pages/bar.js')
+ await expect
+ .poll(async () => JSON.parse(await resultElement.textContent()))
+ .toStrictEqual(['/pkg-pages/foo.js'])
+ })
+
+ test('hmr for adding/removing files with array patterns and exclusions', async () => {
+ const resultElement = page.locator('.array-result')
+ await expect
+ .poll(async () => JSON.parse(await resultElement.textContent()))
+ .toStrictEqual({
+ './array-test-dir/included.js': 'included',
+ })
+
+ addFile('array-test-dir/new-file.js', 'export default "new"')
+ await expect
+ .poll(async () => JSON.parse(await resultElement.textContent()))
+ .toStrictEqual({
+ './array-test-dir/included.js': 'included',
+ './array-test-dir/new-file.js': 'new',
+ })
+
+ removeFile('array-test-dir/new-file.js')
+ await expect
+ .poll(async () => JSON.parse(await resultElement.textContent()))
+ .toStrictEqual({
+ './array-test-dir/included.js': 'included',
+ })
+ })
+}
+
+test('array pattern with exclusions', async () => {
+ await expect
+ .poll(async () => JSON.parse(await page.textContent('.array-result')))
+ .toStrictEqual({
+ './array-test-dir/included.js': 'included',
+ })
+})
+
+test('tree-shake eager css', async () => {
+ expect(await page.textContent('.no-tree-shake-eager-css-result')).toMatch(
+ '.no-tree-shake-eager-css',
+ )
+
+ if (isBuild) {
+ const content = findAssetFile(/index-[-\w]+\.js/)
+ expect(content).not.toMatch('.tree-shake-eager-css')
+ }
+})
+
+test('escapes special chars in globs without mangling user supplied glob suffix', async () => {
+ // the escape dir contains subdirectories where each has a name that needs escaping for glob safety
+ // inside each of them is a glob.js that exports the result of a relative glob `./**/*.js`
+ // and an alias glob `@escape__mod/**/*.js`. The matching aliases are generated in vite.config.ts
+ // index.html has a script that loads all these glob.js files and prints the globs that returned the expected result
+ // this test finally compares the printed output of index.js with the list of directories with special chars,
+ // expecting that they all work
+ const files = await readdir(path.join(import.meta.dirname, '..', 'escape'), {
+ withFileTypes: true,
+ })
+ const expectedNames = files
+ .filter((f) => f.isDirectory())
+ .map((f) => `/escape/${f.name}/glob.js`)
+ .sort()
+ await expect
+ .poll(async () => {
+ const text = await page.textContent('.escape-relative')
+ return text.split('\n').sort()
+ })
+ .toEqual(expectedNames)
+ await expect
+ .poll(async () => {
+ const text = await page.textContent('.escape-alias')
+ return text.split('\n').sort()
+ })
+ .toEqual(expectedNames)
+})
+
+test('subpath imports', async () => {
+ await expect
+ .poll(async () => await page.textContent('.subpath-imports'))
+ .toMatch('bar foo')
+})
+
+test('#alias imports', async () => {
+ await expect
+ .poll(async () => await page.textContent('.hash-alias-imports'))
+ .toMatch('bar foo')
+})
+
+test('import base glob raw', async () => {
+ await expect
+ .poll(async () => await page.textContent('.result-base'))
+ .toBe(JSON.stringify(baseRawResult, null, 2))
+})
+
+test('import.meta.glob and dynamic import vars transformations should be visible to post transform plugins', async () => {
+ await expect
+ .poll(async () => await page.textContent('.transform-visibility'))
+ .toBe(
+ JSON.stringify({ globTransformed: true, dynamicImportTransformed: true }),
+ )
+})
diff --git a/playground/glob-import/array-test-dir/excluded.js b/playground/glob-import/array-test-dir/excluded.js
new file mode 100644
index 00000000000000..7149314fc08e29
--- /dev/null
+++ b/playground/glob-import/array-test-dir/excluded.js
@@ -0,0 +1 @@
+export default 'excluded'
diff --git a/playground/glob-import/array-test-dir/included.js b/playground/glob-import/array-test-dir/included.js
new file mode 100644
index 00000000000000..7066dd3f83ee79
--- /dev/null
+++ b/playground/glob-import/array-test-dir/included.js
@@ -0,0 +1 @@
+export default 'included'
diff --git a/packages/playground/glob-import/dir/alias.js b/playground/glob-import/dir/alias.js
similarity index 100%
rename from packages/playground/glob-import/dir/alias.js
rename to playground/glob-import/dir/alias.js
diff --git a/packages/playground/glob-import/dir/baz.json b/playground/glob-import/dir/baz.json
similarity index 100%
rename from packages/playground/glob-import/dir/baz.json
rename to playground/glob-import/dir/baz.json
diff --git a/playground/glob-import/dir/foo.css b/playground/glob-import/dir/foo.css
new file mode 100644
index 00000000000000..94ff8d4b4895c6
--- /dev/null
+++ b/playground/glob-import/dir/foo.css
@@ -0,0 +1,3 @@
+.foo {
+ color: blue;
+}
diff --git a/packages/playground/glob-import/dir/foo.js b/playground/glob-import/dir/foo.js
similarity index 100%
rename from packages/playground/glob-import/dir/foo.js
rename to playground/glob-import/dir/foo.js
diff --git a/playground/glob-import/dir/index.js b/playground/glob-import/dir/index.js
new file mode 100644
index 00000000000000..94ca66f1017093
--- /dev/null
+++ b/playground/glob-import/dir/index.js
@@ -0,0 +1,11 @@
+const modules = import.meta.glob('./*.(js|ts)', { eager: true })
+const globWithAlias = import.meta.glob('@dir/al*.js', { eager: true })
+
+// test negative glob
+import.meta.glob(['@dir/*.js', '!@dir/x.js'])
+import.meta.glob(['!@dir/x.js', '@dir/*.js'])
+
+// test for sourcemap
+console.log('hello')
+
+export { modules, globWithAlias }
diff --git a/playground/glob-import/dir/nested/bar.js b/playground/glob-import/dir/nested/bar.js
new file mode 100644
index 00000000000000..bb23a5a141de8e
--- /dev/null
+++ b/playground/glob-import/dir/nested/bar.js
@@ -0,0 +1,4 @@
+const modules = import.meta.glob('../*.json', { eager: true })
+
+export const msg = 'bar'
+export { modules }
diff --git a/packages/playground/glob-import/dir/node_modules/hoge.js b/playground/glob-import/dir/node_modules/hoge.js
similarity index 100%
rename from packages/playground/glob-import/dir/node_modules/hoge.js
rename to playground/glob-import/dir/node_modules/hoge.js
diff --git a/playground/glob-import/dir/quote'.js b/playground/glob-import/dir/quote'.js
new file mode 100644
index 00000000000000..deb63c3bcad695
--- /dev/null
+++ b/playground/glob-import/dir/quote'.js
@@ -0,0 +1 @@
+export const msg = 'single-quote'
diff --git a/playground/glob-import/escape/(parenthesis)/glob.js b/playground/glob-import/escape/(parenthesis)/glob.js
new file mode 100644
index 00000000000000..9e0d925c4d1026
--- /dev/null
+++ b/playground/glob-import/escape/(parenthesis)/glob.js
@@ -0,0 +1,5 @@
+const relative = import.meta.glob('./**/*.js', { eager: true })
+const alias = import.meta.glob('@escape_(parenthesis)_mod/**/*.js', {
+ eager: true,
+})
+export { relative, alias }
diff --git a/playground/glob-import/escape/(parenthesis)/mod/index.js b/playground/glob-import/escape/(parenthesis)/mod/index.js
new file mode 100644
index 00000000000000..4eeb2ac0e1dbb4
--- /dev/null
+++ b/playground/glob-import/escape/(parenthesis)/mod/index.js
@@ -0,0 +1 @@
+export const msg = 'foo'
diff --git a/playground/glob-import/escape/[brackets]/glob.js b/playground/glob-import/escape/[brackets]/glob.js
new file mode 100644
index 00000000000000..320cf021f9db77
--- /dev/null
+++ b/playground/glob-import/escape/[brackets]/glob.js
@@ -0,0 +1,5 @@
+const relative = import.meta.glob('./**/*.js', { eager: true })
+const alias = import.meta.glob('@escape_[brackets]_mod/**/*.js', {
+ eager: true,
+})
+export { relative, alias }
diff --git a/playground/glob-import/escape/[brackets]/mod/index.js b/playground/glob-import/escape/[brackets]/mod/index.js
new file mode 100644
index 00000000000000..4eeb2ac0e1dbb4
--- /dev/null
+++ b/playground/glob-import/escape/[brackets]/mod/index.js
@@ -0,0 +1 @@
+export const msg = 'foo'
diff --git a/playground/glob-import/escape/{curlies}/glob.js b/playground/glob-import/escape/{curlies}/glob.js
new file mode 100644
index 00000000000000..a6d286001567e9
--- /dev/null
+++ b/playground/glob-import/escape/{curlies}/glob.js
@@ -0,0 +1,3 @@
+const relative = import.meta.glob('./**/*.js', { eager: true })
+const alias = import.meta.glob('@escape_{curlies}_mod/**/*.js', { eager: true })
+export { relative, alias }
diff --git a/playground/glob-import/escape/{curlies}/mod/index.js b/playground/glob-import/escape/{curlies}/mod/index.js
new file mode 100644
index 00000000000000..4eeb2ac0e1dbb4
--- /dev/null
+++ b/playground/glob-import/escape/{curlies}/mod/index.js
@@ -0,0 +1 @@
+export const msg = 'foo'
diff --git a/playground/glob-import/import-meta-glob-pkg/index.js b/playground/glob-import/import-meta-glob-pkg/index.js
new file mode 100644
index 00000000000000..44705cf18f9f22
--- /dev/null
+++ b/playground/glob-import/import-meta-glob-pkg/index.js
@@ -0,0 +1,4 @@
+export const g = import.meta.glob('/pkg-pages/*.js')
+document.querySelector('.in-package').textContent = JSON.stringify(
+ Object.keys(g).sort(),
+)
diff --git a/playground/glob-import/import-meta-glob-pkg/package.json b/playground/glob-import/import-meta-glob-pkg/package.json
new file mode 100644
index 00000000000000..7138de851543cf
--- /dev/null
+++ b/playground/glob-import/import-meta-glob-pkg/package.json
@@ -0,0 +1,5 @@
+{
+ "name": "@vitejs/test-import-meta-glob-pkg",
+ "type": "module",
+ "main": "./index.js"
+}
diff --git a/playground/glob-import/imports-path/bar.js b/playground/glob-import/imports-path/bar.js
new file mode 100644
index 00000000000000..4548a26ba14dc8
--- /dev/null
+++ b/playground/glob-import/imports-path/bar.js
@@ -0,0 +1 @@
+export default 'bar'
diff --git a/playground/glob-import/imports-path/foo.js b/playground/glob-import/imports-path/foo.js
new file mode 100644
index 00000000000000..7e942cf45c8a37
--- /dev/null
+++ b/playground/glob-import/imports-path/foo.js
@@ -0,0 +1 @@
+export default 'foo'
diff --git a/playground/glob-import/index.html b/playground/glob-import/index.html
new file mode 100644
index 00000000000000..20d4467361e2d7
--- /dev/null
+++ b/playground/glob-import/index.html
@@ -0,0 +1,216 @@
+Glob import
+Normal
+
+Eager
+
+node_modules
+
+Raw
+
+Property access
+
+Relative raw
+
+Side effect
+
+Tree shake Eager CSS
+Should be orange
+Should be orange
+
+Escape relative glob
+
+Escape alias glob
+
+Subpath imports
+
+#alias imports
+
+In package
+
+Base
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Array Pattern with Exclusions
+
+
+
+
+Transform visibility
+
+
+
diff --git a/playground/glob-import/no-tree-shake.css b/playground/glob-import/no-tree-shake.css
new file mode 100644
index 00000000000000..9075733e138c5a
--- /dev/null
+++ b/playground/glob-import/no-tree-shake.css
@@ -0,0 +1,3 @@
+.no-tree-shake-eager-css {
+ color: orange;
+}
diff --git a/playground/glob-import/package.json b/playground/glob-import/package.json
new file mode 100644
index 00000000000000..d71d01109270f1
--- /dev/null
+++ b/playground/glob-import/package.json
@@ -0,0 +1,18 @@
+{
+ "name": "@vitejs/test-import-context",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "imports": {
+ "#imports/*": "./imports-path/*"
+ },
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build",
+ "debug": "node --inspect-brk ../../packages/vite/bin/vite",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "@vitejs/test-import-meta-glob-pkg": "file:./import-meta-glob-pkg"
+ }
+}
diff --git a/playground/glob-import/pkg-pages/foo.js b/playground/glob-import/pkg-pages/foo.js
new file mode 100644
index 00000000000000..8b1a393741c96c
--- /dev/null
+++ b/playground/glob-import/pkg-pages/foo.js
@@ -0,0 +1 @@
+// empty
diff --git a/playground/glob-import/side-effect/writedom.js b/playground/glob-import/side-effect/writedom.js
new file mode 100644
index 00000000000000..e2ab04ba7f5cbe
--- /dev/null
+++ b/playground/glob-import/side-effect/writedom.js
@@ -0,0 +1,4 @@
+/* global document */
+document &&
+ (document.querySelector('.side-effect-result').textContent =
+ 'Hello from side effect')
diff --git a/playground/glob-import/side-effect/writetodom.js b/playground/glob-import/side-effect/writetodom.js
new file mode 100644
index 00000000000000..e2ab04ba7f5cbe
--- /dev/null
+++ b/playground/glob-import/side-effect/writetodom.js
@@ -0,0 +1,4 @@
+/* global document */
+document &&
+ (document.querySelector('.side-effect-result').textContent =
+ 'Hello from side effect')
diff --git a/playground/glob-import/transform-visibility.js b/playground/glob-import/transform-visibility.js
new file mode 100644
index 00000000000000..fd409c9b419646
--- /dev/null
+++ b/playground/glob-import/transform-visibility.js
@@ -0,0 +1,3 @@
+const name = 'foo'
+export const globResult = import.meta.glob('./dir/*.js')
+export const dynamicResult = import(`./dir/${name}.js`)
diff --git a/playground/glob-import/tree-shake.css b/playground/glob-import/tree-shake.css
new file mode 100644
index 00000000000000..84f24297e4efce
--- /dev/null
+++ b/playground/glob-import/tree-shake.css
@@ -0,0 +1,3 @@
+.tree-shake-eager-css {
+ color: orange;
+}
diff --git a/playground/glob-import/vite.config.ts b/playground/glob-import/vite.config.ts
new file mode 100644
index 00000000000000..3f2c42961c4c32
--- /dev/null
+++ b/playground/glob-import/vite.config.ts
@@ -0,0 +1,54 @@
+import fs from 'node:fs'
+import path from 'node:path'
+import { defineConfig } from 'vite'
+
+const escapeAliases = fs
+ .readdirSync(path.join(import.meta.dirname, 'escape'), {
+ withFileTypes: true,
+ })
+ .filter((f) => f.isDirectory())
+ .map((f) => f.name)
+ .reduce((aliases: Record, dir) => {
+ aliases[`@escape_${dir}_mod`] = path.resolve(
+ import.meta.dirname,
+ `./escape/${dir}/mod`,
+ )
+ return aliases
+ }, {})
+
+const transformVisibilityPlugin = {
+ name: 'test:transform-visibility',
+ enforce: 'post',
+ transform(code: string, id: string) {
+ if (id.endsWith('transform-visibility.js')) {
+ const globTransformed = !code.includes('import.meta.glob')
+ const dynamicImportTransformed = code.includes(
+ '__variableDynamicImportRuntimeHelper',
+ )
+ return `export default ${JSON.stringify({ globTransformed, dynamicImportTransformed })}`
+ }
+ },
+}
+
+export default defineConfig({
+ plugins: [transformVisibilityPlugin],
+ resolve: {
+ alias: {
+ ...escapeAliases,
+ '@dir': path.resolve(import.meta.dirname, './dir/'),
+ '#alias': path.resolve(import.meta.dirname, './imports-path/'),
+ },
+ },
+ build: {
+ sourcemap: true,
+ rollupOptions: {
+ output: {
+ // manualChunks(id) {
+ // if (id.includes('foo.css')) {
+ // return 'foo_css'
+ // }
+ // },
+ },
+ },
+ },
+})
diff --git a/playground/hmr-full-bundle-mode/__tests__/hmr-full-bundle-mode.spec.ts b/playground/hmr-full-bundle-mode/__tests__/hmr-full-bundle-mode.spec.ts
new file mode 100644
index 00000000000000..0c78cb863052e2
--- /dev/null
+++ b/playground/hmr-full-bundle-mode/__tests__/hmr-full-bundle-mode.spec.ts
@@ -0,0 +1,178 @@
+import { setTimeout } from 'node:timers/promises'
+import { expect, test } from 'vitest'
+import { editFile, isBuild, page } from '~utils'
+
+const assetUrl = /asset-[\w-]+\.png/
+
+if (isBuild) {
+ test('should render', async () => {
+ expect(await page.textContent('h1')).toContain('HMR Full Bundle Mode')
+ await expect.poll(() => page.textContent('.app')).toBe('hello')
+ await expect.poll(() => page.textContent('.hmr')).toBe('hello')
+ })
+} else {
+ // INITIAL -> BUNDLING -> BUNDLED
+ test('show bundling in progress', async () => {
+ const reloadPromise = page.waitForEvent('load')
+ await expect
+ .poll(() => page.textContent('body'))
+ .toContain('Bundling in progress')
+ await reloadPromise // page shown after reload
+ await expect.poll(() => page.textContent('h1')).toBe('HMR Full Bundle Mode')
+ await expect.poll(() => page.textContent('.app')).toBe('hello')
+ await expect.poll(() => page.textContent('.asset')).toMatch(assetUrl)
+ await expect
+ .poll(() => page.textContent('.worker-query'))
+ .toBe('worker-query')
+ await expect.poll(() => page.textContent('.worker-url')).toBe('worker-url')
+ })
+
+ // BUNDLED -> GENERATE_HMR_PATCH -> BUNDLING -> BUNDLE_ERROR -> BUNDLING -> BUNDLED
+ test('handle bundle error', async () => {
+ editFile('main.js', (code) =>
+ code.replace("text('.app', 'hello')", "text('.app', 'hello'); text("),
+ )
+ await expect.poll(() => page.isVisible('vite-error-overlay')).toBe(true)
+ editFile('main.js', (code) =>
+ code.replace("text('.app', 'hello'); text(", "text('.app', 'hello')"),
+ )
+ await expect.poll(() => page.isVisible('vite-error-overlay')).toBe(false)
+ await expect.poll(() => page.textContent('.app')).toBe('hello')
+ })
+
+ // BUNDLED -> GENERATE_HMR_PATCH -> BUNDLING -> BUNDLED
+ test('update bundle', async () => {
+ editFile('main.js', (code) =>
+ code.replace("text('.app', 'hello')", "text('.app', 'hello1')"),
+ )
+ await expect.poll(() => page.textContent('.app')).toBe('hello1')
+
+ editFile('main.js', (code) =>
+ code.replace("text('.app', 'hello1')", "text('.app', 'hello')"),
+ )
+ await expect.poll(() => page.textContent('.app')).toBe('hello')
+ await expect.poll(() => page.textContent('.asset')).toMatch(assetUrl)
+ })
+
+ // BUNDLED -> GENERATE_HMR_PATCH -> BUNDLING -> BUNDLING -> BUNDLED
+ test('debounce bundle', async () => {
+ editFile('main.js', (code) =>
+ code.replace(
+ "text('.app', 'hello')",
+ "text('.app', 'hello1')\n" + '// @delay-transform',
+ ),
+ )
+ await setTimeout(100)
+ editFile('main.js', (code) =>
+ code.replace("text('.app', 'hello1')", "text('.app', 'hello2')"),
+ )
+ await expect.poll(() => page.textContent('.app')).toBe('hello2')
+
+ editFile('main.js', (code) =>
+ code.replace(
+ "text('.app', 'hello2')\n" + '// @delay-transform',
+ "text('.app', 'hello')",
+ ),
+ )
+ await expect.poll(() => page.textContent('.app')).toBe('hello')
+ })
+
+ // BUNDLED -> GENERATING_HMR_PATCH -> BUNDLED
+ test('handle generate hmr patch error', async () => {
+ await expect.poll(() => page.textContent('.hmr')).toBe('hello')
+ editFile('hmr.js', (code) =>
+ code.replace("const foo = 'hello'", "const foo = 'hello"),
+ )
+ await expect.poll(() => page.isVisible('vite-error-overlay')).toBe(true)
+
+ editFile('hmr.js', (code) =>
+ code.replace("const foo = 'hello", "const foo = 'hello'"),
+ )
+ await expect.poll(() => page.isVisible('vite-error-overlay')).toBe(false)
+ await expect.poll(() => page.textContent('.hmr')).toContain('hello')
+ })
+
+ // BUNDLED -> GENERATING_HMR_PATCH -> BUNDLED
+ test('generate hmr patch', async () => {
+ await expect.poll(() => page.textContent('.hmr')).toBe('hello')
+ editFile('hmr.js', (code) =>
+ code.replace("const foo = 'hello'", "const foo = 'hello1'"),
+ )
+ await expect.poll(() => page.textContent('.hmr')).toBe('hello1')
+
+ editFile('hmr.js', (code) =>
+ code.replace("const foo = 'hello1'", "const foo = 'hello'"),
+ )
+ await expect.poll(() => page.textContent('.hmr')).toContain('hello')
+ await expect.poll(() => page.textContent('.asset')).toMatch(assetUrl)
+ })
+
+ // BUNDLED -> GENERATING_HMR_PATCH -> GENERATING_HMR_PATCH -> BUNDLED
+ test('continuous generate hmr patch', async () => {
+ editFile('hmr.js', (code) =>
+ code.replace(
+ "const foo = 'hello'",
+ "const foo = 'hello1'\n" + '// @delay-transform',
+ ),
+ )
+ await setTimeout(100)
+ editFile('hmr.js', (code) =>
+ code.replace("const foo = 'hello1'", "const foo = 'hello2'"),
+ )
+ await expect.poll(() => page.textContent('.hmr')).toBe('hello2')
+
+ editFile('hmr.js', (code) =>
+ code.replace(
+ "const foo = 'hello2'\n" + '// @delay-transform',
+ "const foo = 'hello'",
+ ),
+ )
+ await expect.poll(() => page.textContent('.hmr')).toBe('hello')
+ })
+
+ test('worker with ?worker query', async () => {
+ await expect
+ .poll(() => page.textContent('.worker-query'))
+ .toBe('worker-query')
+ editFile('worker-query.js', (code) =>
+ code.replace(
+ "const msg = 'worker-query'",
+ "const msg = 'worker-query-updated'",
+ ),
+ )
+ await expect
+ .poll(() => page.textContent('.worker-query'))
+ .toBe('worker-query-updated')
+
+ editFile('worker-query.js', (code) =>
+ code.replace(
+ "const msg = 'worker-query-updated'",
+ "const msg = 'worker-query'",
+ ),
+ )
+ await expect
+ .poll(() => page.textContent('.worker-query'))
+ .toBe('worker-query')
+ })
+
+ test('worker with new URL', async () => {
+ await expect.poll(() => page.textContent('.worker-url')).toBe('worker-url')
+ editFile('worker-url.js', (code) =>
+ code.replace(
+ "const msg = 'worker-url'",
+ "const msg = 'worker-url-updated'",
+ ),
+ )
+ await expect
+ .poll(() => page.textContent('.worker-url'))
+ .toBe('worker-url-updated')
+
+ editFile('worker-url.js', (code) =>
+ code.replace(
+ "const msg = 'worker-url-updated'",
+ "const msg = 'worker-url'",
+ ),
+ )
+ await expect.poll(() => page.textContent('.worker-url')).toBe('worker-url')
+ })
+}
diff --git a/packages/playground/vue/assets/asset.png b/playground/hmr-full-bundle-mode/asset.png
similarity index 100%
rename from packages/playground/vue/assets/asset.png
rename to playground/hmr-full-bundle-mode/asset.png
diff --git a/playground/hmr-full-bundle-mode/hmr.js b/playground/hmr-full-bundle-mode/hmr.js
new file mode 100644
index 00000000000000..02087aadd2b91c
--- /dev/null
+++ b/playground/hmr-full-bundle-mode/hmr.js
@@ -0,0 +1,18 @@
+export const foo = 'hello'
+text('.hmr', foo)
+
+import assetUrlImported from './asset.png'
+
+export const assetUrl = assetUrlImported
+text('.asset', assetUrl)
+
+function text(el, text) {
+ document.querySelector(el).textContent = text
+}
+
+import.meta.hot?.accept((mod) => {
+ if (mod) {
+ text('.hmr', mod.foo)
+ text('.asset', mod.assetUrl)
+ }
+})
diff --git a/playground/hmr-full-bundle-mode/index.html b/playground/hmr-full-bundle-mode/index.html
new file mode 100644
index 00000000000000..c5259e11e5d184
--- /dev/null
+++ b/playground/hmr-full-bundle-mode/index.html
@@ -0,0 +1,9 @@
+HMR Full Bundle Mode
+
+
+
+
+
+
+
+
diff --git a/playground/hmr-full-bundle-mode/main.js b/playground/hmr-full-bundle-mode/main.js
new file mode 100644
index 00000000000000..9438bf643f1960
--- /dev/null
+++ b/playground/hmr-full-bundle-mode/main.js
@@ -0,0 +1,24 @@
+import './hmr.js'
+import assetUrl from './asset.png'
+import WorkerQuery from './worker-query.js?worker'
+
+text('.app', 'hello')
+text('.asset', assetUrl)
+
+const workerQuery = new WorkerQuery()
+workerQuery.postMessage('ping')
+workerQuery.addEventListener('message', (e) => {
+ text('.worker-query', e.data)
+})
+
+const workerUrl = new Worker(new URL('./worker-url.js', import.meta.url), {
+ type: 'module',
+})
+workerUrl.postMessage('ping')
+workerUrl.addEventListener('message', (e) => {
+ text('.worker-url', e.data)
+})
+
+function text(el, text) {
+ document.querySelector(el).textContent = text
+}
diff --git a/playground/hmr-full-bundle-mode/package.json b/playground/hmr-full-bundle-mode/package.json
new file mode 100644
index 00000000000000..dcd3f5e9ed014b
--- /dev/null
+++ b/playground/hmr-full-bundle-mode/package.json
@@ -0,0 +1,12 @@
+{
+ "name": "@vitejs/test-hmr-full-bundle-mode",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build",
+ "debug": "node --inspect-brk ../../packages/vite/bin/vite",
+ "preview": "vite preview"
+ }
+}
diff --git a/playground/hmr-full-bundle-mode/vite.config.ts b/playground/hmr-full-bundle-mode/vite.config.ts
new file mode 100644
index 00000000000000..5cd8ae52e492ac
--- /dev/null
+++ b/playground/hmr-full-bundle-mode/vite.config.ts
@@ -0,0 +1,59 @@
+import { type Plugin, defineConfig } from 'vite'
+
+export default defineConfig({
+ experimental: {
+ bundledDev: true,
+ },
+ plugins: [waitBundleCompleteUntilAccess(), delayTransformComment()],
+})
+
+function waitBundleCompleteUntilAccess(): Plugin {
+ let resolvers: PromiseWithResolvers
+
+ return {
+ name: 'wait-bundle-complete-until-access',
+ apply: 'serve',
+ configureServer(server) {
+ let accessCount = 0
+ resolvers = promiseWithResolvers()
+
+ server.middlewares.use((_req, _res, next) => {
+ accessCount++
+ if (accessCount === 1) {
+ resolvers.resolve()
+ }
+ next()
+ })
+ },
+ async generateBundle() {
+ await resolvers.promise
+ await new Promise((resolve) => setTimeout(resolve, 300))
+ },
+ }
+}
+
+function delayTransformComment(): Plugin {
+ return {
+ name: 'delay-transform-comment',
+ async transform(code) {
+ if (code.includes('// @delay-transform')) {
+ await new Promise((resolve) => setTimeout(resolve, 300))
+ }
+ },
+ }
+}
+
+interface PromiseWithResolvers {
+ promise: Promise
+ resolve: (value: T | PromiseLike) => void
+ reject: (reason?: any) => void
+}
+function promiseWithResolvers(): PromiseWithResolvers {
+ let resolve: any
+ let reject: any
+ const promise = new Promise((_resolve, _reject) => {
+ resolve = _resolve
+ reject = _reject
+ })
+ return { promise, resolve, reject }
+}
diff --git a/playground/hmr-full-bundle-mode/worker-query.js b/playground/hmr-full-bundle-mode/worker-query.js
new file mode 100644
index 00000000000000..770c157419e430
--- /dev/null
+++ b/playground/hmr-full-bundle-mode/worker-query.js
@@ -0,0 +1,7 @@
+const msg = 'worker-query'
+
+self.onmessage = (e) => {
+ if (e.data === 'ping') {
+ self.postMessage(msg)
+ }
+}
diff --git a/playground/hmr-full-bundle-mode/worker-url.js b/playground/hmr-full-bundle-mode/worker-url.js
new file mode 100644
index 00000000000000..19de9e8e7b6b81
--- /dev/null
+++ b/playground/hmr-full-bundle-mode/worker-url.js
@@ -0,0 +1,7 @@
+const msg = 'worker-url'
+
+self.onmessage = (e) => {
+ if (e.data === 'ping') {
+ self.postMessage(msg)
+ }
+}
diff --git a/playground/hmr-root/__tests__/hmr-root.spec.ts b/playground/hmr-root/__tests__/hmr-root.spec.ts
new file mode 100644
index 00000000000000..5c63d1acc9f3aa
--- /dev/null
+++ b/playground/hmr-root/__tests__/hmr-root.spec.ts
@@ -0,0 +1,10 @@
+import { expect, test } from 'vitest'
+
+import { editFile, isServe, page } from '~utils'
+
+test.runIf(isServe)('should watch files outside root', async () => {
+ expect(await page.textContent('#foo')).toBe('foo')
+ editFile('foo.js', (code) => code.replace("'foo'", "'foobar'"))
+ await page.waitForEvent('load')
+ await expect.poll(() => page.textContent('#foo')).toBe('foobar')
+})
diff --git a/playground/hmr-root/foo.js b/playground/hmr-root/foo.js
new file mode 100644
index 00000000000000..cb356468240d50
--- /dev/null
+++ b/playground/hmr-root/foo.js
@@ -0,0 +1 @@
+export const foo = 'foo'
diff --git a/playground/hmr-root/root/index.html b/playground/hmr-root/root/index.html
new file mode 100644
index 00000000000000..ddf3514623d0b7
--- /dev/null
+++ b/playground/hmr-root/root/index.html
@@ -0,0 +1,7 @@
+
+
+
diff --git a/playground/hmr-root/vite.config.ts b/playground/hmr-root/vite.config.ts
new file mode 100644
index 00000000000000..0170945efc5ade
--- /dev/null
+++ b/playground/hmr-root/vite.config.ts
@@ -0,0 +1,6 @@
+import path from 'node:path'
+import { defineConfig } from 'vite'
+
+export default defineConfig({
+ root: path.join(import.meta.dirname, './root'),
+})
diff --git a/playground/hmr-ssr/__tests__/hmr-ssr.spec.ts b/playground/hmr-ssr/__tests__/hmr-ssr.spec.ts
new file mode 100644
index 00000000000000..89645dd740d6d0
--- /dev/null
+++ b/playground/hmr-ssr/__tests__/hmr-ssr.spec.ts
@@ -0,0 +1,1188 @@
+import fs from 'node:fs'
+import { posix, resolve } from 'node:path'
+import EventEmitter from 'node:events'
+import {
+ afterAll,
+ beforeAll,
+ describe,
+ expect,
+ onTestFinished,
+ test,
+ vi,
+} from 'vitest'
+import type {
+ InlineConfig,
+ Plugin,
+ RunnableDevEnvironment,
+ ViteDevServer,
+} from 'vite'
+import { createRunnableDevEnvironment, createServer } from 'vite'
+import type { ModuleRunner } from 'vite/module-runner'
+import {
+ addFile,
+ createInMemoryLogger,
+ editFile,
+ isBuild,
+ promiseWithResolvers,
+ readFile,
+ removeFile,
+ slash,
+ testDir,
+} from '~utils'
+
+let server: ViteDevServer
+const clientLogs: string[] = []
+const serverLogs: string[] = []
+let runner: ModuleRunner
+
+const logsEmitter = new EventEmitter()
+
+afterAll(async () => {
+ await server?.close()
+})
+
+const hmr = (key: string) => (globalThis.__HMR__[key] as string) || ''
+
+const updated = (file: string, via?: string) => {
+ if (via) {
+ return `[vite] hot updated: ${file} via ${via}`
+ }
+ return `[vite] hot updated: ${file}`
+}
+
+if (!isBuild) {
+ describe('hmr works correctly', () => {
+ const hotEventCounts = { connect: 0, disconnect: 0 }
+
+ beforeAll(async () => {
+ function hotEventsPlugin(): Plugin {
+ return {
+ name: 'hot-events',
+ configureServer(server) {
+ server.environments.ssr.hot.on(
+ 'vite:client:connect',
+ () => hotEventCounts.connect++,
+ )
+ server.environments.ssr.hot.on(
+ 'vite:client:disconnect',
+ () => hotEventCounts.disconnect++,
+ )
+ },
+ }
+ }
+
+ await setupModuleRunner('/hmr.ts', { plugins: [hotEventsPlugin()] })
+ })
+
+ test('should connect', async () => {
+ expect(clientLogs).toContain('[vite] connected.')
+ expect(hotEventCounts).toStrictEqual({ connect: 1, disconnect: 0 })
+ })
+
+ test('self accept', async () => {
+ const el = () => hmr('.app')
+ await untilConsoleLogAfter(
+ () =>
+ editFile('hmr.ts', (code) =>
+ code.replace('const foo = 1', 'const foo = 2'),
+ ),
+ [
+ '>>> vite:beforeUpdate -- update',
+ 'foo was: 1',
+ '(self-accepting 1) foo is now: 2',
+ '(self-accepting 2) foo is now: 2',
+ updated('/hmr.ts'),
+ '>>> vite:afterUpdate -- update',
+ ],
+ true,
+ )
+ await expect.poll(() => el()).toMatch('2')
+
+ await untilConsoleLogAfter(
+ () =>
+ editFile('hmr.ts', (code) =>
+ code.replace('const foo = 2', 'const foo = 3'),
+ ),
+ [
+ '>>> vite:beforeUpdate -- update',
+ 'foo was: 2',
+ '(self-accepting 1) foo is now: 3',
+ '(self-accepting 2) foo is now: 3',
+ updated('/hmr.ts'),
+ '>>> vite:afterUpdate -- update',
+ ],
+ true,
+ )
+ await expect.poll(() => el()).toMatch('3')
+ })
+
+ test('accept dep', async () => {
+ const el = () => hmr('.dep')
+ await untilConsoleLogAfter(
+ () =>
+ editFile('hmrDep.js', (code) =>
+ code.replace('const foo = 1', 'const foo = 2'),
+ ),
+ [
+ '>>> vite:beforeUpdate -- update',
+ '(dep) foo was: 1',
+ '(dep) foo from dispose: 1',
+ '(single dep) foo is now: 2',
+ '(single dep) nested foo is now: 1',
+ '(multi deps) foo is now: 2',
+ '(multi deps) nested foo is now: 1',
+ updated('/hmrDep.js', '/hmr.ts'),
+ '>>> vite:afterUpdate -- update',
+ ],
+ true,
+ )
+ await expect.poll(() => el()).toMatch('2')
+
+ await untilConsoleLogAfter(
+ () =>
+ editFile('hmrDep.js', (code) =>
+ code.replace('const foo = 2', 'const foo = 3'),
+ ),
+ [
+ '>>> vite:beforeUpdate -- update',
+ '(dep) foo was: 2',
+ '(dep) foo from dispose: 2',
+ '(single dep) foo is now: 3',
+ '(single dep) nested foo is now: 1',
+ '(multi deps) foo is now: 3',
+ '(multi deps) nested foo is now: 1',
+ updated('/hmrDep.js', '/hmr.ts'),
+ '>>> vite:afterUpdate -- update',
+ ],
+ true,
+ )
+ await expect.poll(() => el()).toMatch('3')
+ })
+
+ test('nested dep propagation', async () => {
+ const el = () => hmr('.nested')
+ await untilConsoleLogAfter(
+ () =>
+ editFile('hmrNestedDep.js', (code) =>
+ code.replace('const foo = 1', 'const foo = 2'),
+ ),
+ [
+ '>>> vite:beforeUpdate -- update',
+ '(dep) foo was: 3',
+ '(dep) foo from dispose: 3',
+ '(single dep) foo is now: 3',
+ '(single dep) nested foo is now: 2',
+ '(multi deps) foo is now: 3',
+ '(multi deps) nested foo is now: 2',
+ updated('/hmrDep.js', '/hmr.ts'),
+ '>>> vite:afterUpdate -- update',
+ ],
+ true,
+ )
+ await expect.poll(() => el()).toMatch('2')
+
+ await untilConsoleLogAfter(
+ () =>
+ editFile('hmrNestedDep.js', (code) =>
+ code.replace('const foo = 2', 'const foo = 3'),
+ ),
+ [
+ '>>> vite:beforeUpdate -- update',
+ '(dep) foo was: 3',
+ '(dep) foo from dispose: 3',
+ '(single dep) foo is now: 3',
+ '(single dep) nested foo is now: 3',
+ '(multi deps) foo is now: 3',
+ '(multi deps) nested foo is now: 3',
+ updated('/hmrDep.js', '/hmr.ts'),
+ '>>> vite:afterUpdate -- update',
+ ],
+ true,
+ )
+ await expect.poll(() => el()).toMatch('3')
+ })
+
+ test('invalidate', async () => {
+ const el = () => hmr('.invalidation')
+ await untilConsoleLogAfter(
+ () =>
+ editFile('invalidation/child.js', (code) =>
+ code.replace('child', 'child updated'),
+ ),
+ [
+ '>>> vite:beforeUpdate -- update',
+ `>>> vite:invalidate -- /invalidation/child.js`,
+ '[vite] invalidate /invalidation/child.js',
+ updated('/invalidation/child.js'),
+ '>>> vite:afterUpdate -- update',
+ '>>> vite:beforeUpdate -- update',
+ '(invalidation) parent is executing',
+ updated('/invalidation/parent.js'),
+ '>>> vite:afterUpdate -- update',
+ ],
+ true,
+ )
+ await expect.poll(() => el()).toMatch('child updated')
+ })
+
+ test('soft invalidate', async () => {
+ const el = () => hmr('.soft-invalidation')
+ expect(el()).toBe(
+ 'soft-invalidation/index.js is transformed 1 times. child is bar',
+ )
+ editFile('soft-invalidation/child.js', (code) =>
+ code.replace('bar', 'updated'),
+ )
+ await expect
+ .poll(() => el())
+ .toBe(
+ 'soft-invalidation/index.js is transformed 1 times. child is updated',
+ )
+ })
+
+ test('invalidate in circular dep should not trigger infinite HMR', async () => {
+ const el = () => hmr('.invalidation-circular-deps')
+ await expect.poll(() => el()).toMatch('child')
+ editFile(
+ 'invalidation-circular-deps/circular-invalidate/child.js',
+ (code) => code.replace('child', 'child updated'),
+ )
+ await expect.poll(() => el()).toMatch('child updated')
+ })
+
+ test('invalidate in circular dep should be hot updated if possible', async () => {
+ const el = () => hmr('.invalidation-circular-deps-handled')
+ await expect.poll(() => el()).toMatch('child')
+ editFile(
+ 'invalidation-circular-deps/invalidate-handled-in-circle/child.js',
+ (code) => code.replace('child', 'child updated'),
+ )
+ await expect.poll(() => el()).toMatch('child updated')
+ })
+
+ test('plugin hmr handler + custom event', async () => {
+ const el = () => hmr('.custom')
+ editFile('customFile.js', (code) => code.replace('custom', 'edited'))
+ await expect.poll(() => el()).toMatch('edited')
+ })
+
+ test('plugin hmr remove custom events', async () => {
+ const el = () => hmr('.toRemove')
+ editFile('customFile.js', (code) => code.replace('custom', 'edited'))
+ await expect.poll(() => el()).toMatch('edited')
+ editFile('customFile.js', (code) => code.replace('edited', 'custom'))
+ await expect.poll(() => el()).toMatch('edited')
+ })
+
+ test('plugin client-server communication', async () => {
+ const el = () => hmr('.custom-communication')
+ await expect.poll(() => el()).toMatch('3')
+ })
+
+ test('queries are correctly resolved', async () => {
+ const query1 = () => hmr('query1')
+ const query2 = () => hmr('query2')
+
+ expect(query1()).toBe('query1')
+ expect(query2()).toBe('query2')
+
+ editFile('queries/multi-query.js', (code) => code + '//comment')
+ await expect.poll(() => query1()).toBe('//commentquery1')
+ await expect.poll(() => query2()).toBe('//commentquery2')
+ })
+ })
+
+ describe('self accept with different entry point formats', () => {
+ test.each(['./unresolved.ts', './unresolved', '/unresolved'])(
+ 'accepts if entry point is relative to root %s',
+ async (entrypoint) => {
+ await setupModuleRunner(entrypoint, {}, '/unresolved.ts')
+
+ const originalUnresolvedFile = readFile('unresolved.ts')
+ onTestFinished(() => {
+ const filepath = resolve(testDir, 'unresolved.ts')
+ fs.writeFileSync(filepath, originalUnresolvedFile, 'utf-8')
+ })
+
+ const el = () => hmr('.app')
+ await untilConsoleLogAfter(
+ () =>
+ editFile('unresolved.ts', (code) =>
+ code.replace('const foo = 1', 'const foo = 2'),
+ ),
+ [
+ 'foo was: 1',
+ '(self-accepting 1) foo is now: 2',
+ '(self-accepting 2) foo is now: 2',
+ updated(entrypoint),
+ ],
+ true,
+ )
+ await expect.poll(() => el()).toMatch('2')
+
+ await untilConsoleLogAfter(
+ () =>
+ editFile('unresolved.ts', (code) =>
+ code.replace('const foo = 2', 'const foo = 3'),
+ ),
+ [
+ 'foo was: 2',
+ '(self-accepting 1) foo is now: 3',
+ '(self-accepting 2) foo is now: 3',
+ updated(entrypoint),
+ ],
+ true,
+ )
+ await expect.poll(() => el()).toMatch('3')
+ },
+ )
+ })
+
+ describe('acceptExports', () => {
+ const HOT_UPDATED = /hot updated/
+ const CONNECTED = /connected/
+ const PROGRAM_RELOAD = /program reload/
+
+ const baseDir = 'accept-exports'
+
+ describe('when all used exports are accepted', () => {
+ const testDir = baseDir + '/main-accepted'
+
+ const fileName = 'target.ts'
+ const file = `${testDir}/${fileName}`
+ const url = `/${file}`
+
+ let dep = 'dep0'
+
+ beforeAll(async () => {
+ await untilConsoleLogAfter(
+ () => setupModuleRunner(`/${testDir}/index`),
+ [CONNECTED, />>>>>>/],
+ (logs) => {
+ expect(logs).toContain(`<<<<<< A0 B0 D0 ; ${dep}`)
+ expect(logs).toContain('>>>>>> A0 D0')
+ },
+ )
+ })
+
+ test('the callback is called with the new version the module', async () => {
+ const callbackFile = `${testDir}/callback.ts`
+ const callbackUrl = `/${callbackFile}`
+
+ await untilConsoleLogAfter(
+ () => {
+ editFile(callbackFile, (code) =>
+ code
+ .replace("x = 'X'", "x = 'Y'")
+ .replace('reloaded >>>', 'reloaded (2) >>>'),
+ )
+ },
+ HOT_UPDATED,
+ (logs) => {
+ expect(logs).toEqual([
+ 'reloaded >>> Y',
+ `[vite] hot updated: ${callbackUrl}`,
+ ])
+ },
+ )
+
+ await untilConsoleLogAfter(
+ () => {
+ editFile(callbackFile, (code) => code.replace("x = 'Y'", "x = 'Z'"))
+ },
+ HOT_UPDATED,
+ (logs) => {
+ expect(logs).toEqual([
+ 'reloaded (2) >>> Z',
+ `[vite] hot updated: ${callbackUrl}`,
+ ])
+ },
+ )
+ })
+
+ test('stops HMR bubble on dependency change', async () => {
+ const depFileName = 'dep.ts'
+ const depFile = `${testDir}/${depFileName}`
+
+ await untilConsoleLogAfter(
+ () => {
+ editFile(depFile, (code) => code.replace('dep0', (dep = 'dep1')))
+ },
+ HOT_UPDATED,
+ (logs) => {
+ expect(logs).toEqual([
+ `<<<<<< A0 B0 D0 ; ${dep}`,
+ `[vite] hot updated: ${url}`,
+ ])
+ },
+ )
+ })
+
+ test('accepts itself and refreshes on change', async () => {
+ await untilConsoleLogAfter(
+ () => {
+ editFile(file, (code) => code.replace(/(\b[A-Z])0/g, '$11'))
+ },
+ HOT_UPDATED,
+ (logs) => {
+ expect(logs).toEqual([
+ `<<<<<< A1 B1 D1 ; ${dep}`,
+ `[vite] hot updated: ${url}`,
+ ])
+ },
+ )
+ })
+
+ test('accepts itself and refreshes on 2nd change', async () => {
+ await untilConsoleLogAfter(
+ () => {
+ editFile(file, (code) =>
+ code
+ .replace(/(\b[A-Z])1/g, '$12')
+ .replace(
+ "acceptExports(['a', 'default']",
+ "acceptExports(['b', 'default']",
+ ),
+ )
+ },
+ HOT_UPDATED,
+ (logs) => {
+ expect(logs).toEqual([
+ `<<<<<< A2 B2 D2 ; ${dep}`,
+ `[vite] hot updated: ${url}`,
+ ])
+ },
+ )
+ })
+
+ test('does not accept itself anymore after acceptedExports change', async () => {
+ await untilConsoleLogAfter(
+ async () => {
+ editFile(file, (code) => code.replace(/(\b[A-Z])2/g, '$13'))
+ },
+ [PROGRAM_RELOAD, />>>>>>/],
+ (logs) => {
+ expect(logs).toContain(`<<<<<< A3 B3 D3 ; ${dep}`)
+ expect(logs).toContain('>>>>>> A3 D3')
+ },
+ )
+ })
+ })
+
+ describe('when some used exports are not accepted', () => {
+ const testDir = baseDir + '/main-non-accepted'
+
+ const namedFileName = 'named.ts'
+ const namedFile = `${testDir}/${namedFileName}`
+ const defaultFileName = 'default.ts'
+ const defaultFile = `${testDir}/${defaultFileName}`
+ const depFileName = 'dep.ts'
+ const depFile = `${testDir}/${depFileName}`
+
+ const a = 'A0'
+ let dep = 'dep0'
+
+ beforeAll(async () => {
+ await untilConsoleLogAfter(
+ () => setupModuleRunner(`/${testDir}/index`),
+ [CONNECTED, />>>>>>/],
+ (logs) => {
+ expect(logs).toContain(`<<< named: ${a} ; ${dep}`)
+ expect(logs).toContain(`<<< default: def0`)
+ expect(logs).toContain(`>>>>>> ${a} def0`)
+ },
+ )
+ })
+
+ test('does not stop the HMR bubble on change to dep', async () => {
+ await untilConsoleLogAfter(
+ async () => {
+ editFile(depFile, (code) => code.replace('dep0', (dep = 'dep1')))
+ },
+ [PROGRAM_RELOAD, />>>>>>/],
+ (logs) => {
+ expect(logs).toContain(`<<< named: ${a} ; ${dep}`)
+ },
+ )
+ })
+
+ describe('does not stop the HMR bubble on change to self', () => {
+ test('with named exports', async () => {
+ await untilConsoleLogAfter(
+ async () => {
+ editFile(namedFile, (code) => code.replace(a, 'A1'))
+ },
+ [PROGRAM_RELOAD, />>>>>>/],
+ (logs) => {
+ expect(logs).toContain(`<<< named: A1 ; ${dep}`)
+ },
+ )
+ })
+
+ test('with default export', async () => {
+ await untilConsoleLogAfter(
+ async () => {
+ editFile(defaultFile, (code) => code.replace('def0', 'def1'))
+ },
+ [PROGRAM_RELOAD, />>>>>>/],
+ (logs) => {
+ expect(logs).toContain(`<<< default: def1`)
+ },
+ )
+ })
+ })
+
+ describe("doesn't reload if files not in the entrypoint importers chain is changed", async () => {
+ const testFile = 'non-tested/index.js'
+
+ beforeAll(async () => {
+ clientLogs.length = 0
+ // so it's in the module graph
+ const ssrEnvironment = server.environments.ssr
+ await ssrEnvironment.transformRequest(testFile)
+ await ssrEnvironment.transformRequest('non-tested/dep.js')
+ })
+
+ test('does not full reload', async () => {
+ editFile(
+ testFile,
+ (code) => code + '\n\nexport const query5 = "query5"',
+ )
+ const start = Date.now()
+ // for 2 seconds check that there is no log about the file being reloaded
+ while (Date.now() - start < 2000) {
+ if (
+ clientLogs.some(
+ (log) =>
+ log.match(PROGRAM_RELOAD) ||
+ log.includes('non-tested/index.js'),
+ )
+ ) {
+ throw new Error('File was reloaded')
+ }
+ await new Promise((r) => setTimeout(r, 100))
+ }
+ }, 5_000)
+
+ test('does not update', async () => {
+ editFile('non-tested/dep.js', (code) => code + '//comment')
+ const start = Date.now()
+ // for 2 seconds check that there is no log about the file being reloaded
+ while (Date.now() - start < 2000) {
+ if (
+ clientLogs.some(
+ (log) =>
+ log.match(PROGRAM_RELOAD) ||
+ log.includes('non-tested/dep.js'),
+ )
+ ) {
+ throw new Error('File was updated')
+ }
+ await new Promise((r) => setTimeout(r, 100))
+ }
+ }, 5_000)
+ })
+ })
+
+ test('accepts itself when imported for side effects only (no bindings imported)', async () => {
+ const testDir = baseDir + '/side-effects'
+ const file = 'side-effects.ts'
+
+ await untilConsoleLogAfter(
+ () => setupModuleRunner(`/${testDir}/index`),
+ [CONNECTED, />>>/],
+ (logs) => {
+ expect(logs).toContain('>>> side FX')
+ },
+ )
+
+ await untilConsoleLogAfter(
+ () => {
+ editFile(`${testDir}/${file}`, (code) =>
+ code.replace('>>> side FX', '>>> side FX !!'),
+ )
+ },
+ HOT_UPDATED,
+ (logs) => {
+ expect(logs).toEqual([
+ '>>> side FX !!',
+ updated(`/${testDir}/${file}`),
+ ])
+ },
+ )
+ })
+
+ describe('acceptExports([])', () => {
+ const testDir = baseDir + '/unused-exports'
+
+ test('accepts itself if no exports are imported', async () => {
+ const fileName = 'unused.ts'
+ const file = `${testDir}/${fileName}`
+ const url = '/' + file
+
+ await untilConsoleLogAfter(
+ () => setupModuleRunner(`/${testDir}/index`),
+ [CONNECTED, '-- unused --'],
+ (logs) => {
+ expect(logs).toContain('-- unused --')
+ },
+ )
+
+ await untilConsoleLogAfter(
+ () => {
+ editFile(file, (code) =>
+ code.replace('-- unused --', '-> unused <-'),
+ )
+ },
+ HOT_UPDATED,
+ (logs) => {
+ expect(logs).toEqual(['-> unused <-', updated(url)])
+ },
+ )
+ })
+
+ test("doesn't accept itself if any of its exports is imported", async () => {
+ const fileName = 'used.ts'
+ const file = `${testDir}/${fileName}`
+
+ await untilConsoleLogAfter(
+ () => setupModuleRunner(`/${testDir}/index`),
+ [CONNECTED, '-- used --', 'used:foo0'],
+ (logs) => {
+ expect(logs).toContain('-- used --')
+ expect(logs).toContain('used:foo0')
+ },
+ )
+
+ await untilConsoleLogAfter(
+ async () => {
+ editFile(file, (code) =>
+ code.replace('foo0', 'foo1').replace('-- used --', '-> used <-'),
+ )
+ },
+ [PROGRAM_RELOAD, /used:foo/],
+ (logs) => {
+ expect(logs).toContain('-> used <-')
+ expect(logs).toContain('used:foo1')
+ },
+ )
+ })
+ })
+
+ describe('indiscriminate imports: import *', () => {
+ const testStarExports = (testDirName: string) => {
+ const testDir = `${baseDir}/${testDirName}`
+
+ test('accepts itself if all its exports are accepted', async () => {
+ const fileName = 'deps-all-accepted.ts'
+ const file = `${testDir}/${fileName}`
+ const url = '/' + file
+
+ await untilConsoleLogAfter(
+ () => setupModuleRunner(`/${testDir}/index`),
+ [CONNECTED, '>>> ready <<<'],
+ (logs) => {
+ expect(logs).toContain('loaded:all:a0b0c0default0')
+ expect(logs).toContain('all >>>>>> a0, b0, c0')
+ },
+ )
+
+ await untilConsoleLogAfter(
+ () => {
+ editFile(file, (code) => code.replace(/([abc])0/g, '$11'))
+ },
+ HOT_UPDATED,
+ (logs) => {
+ expect(logs).toEqual(['all >>>>>> a1, b1, c1', updated(url)])
+ },
+ )
+
+ await untilConsoleLogAfter(
+ () => {
+ editFile(file, (code) => code.replace(/([abc])1/g, '$12'))
+ },
+ HOT_UPDATED,
+ (logs) => {
+ expect(logs).toEqual(['all >>>>>> a2, b2, c2', updated(url)])
+ },
+ )
+ })
+
+ test("doesn't accept itself if one export is not accepted", async () => {
+ const fileName = 'deps-some-accepted.ts'
+ const file = `${testDir}/${fileName}`
+
+ await untilConsoleLogAfter(
+ () => setupModuleRunner(`/${testDir}/index`),
+ [CONNECTED, '>>> ready <<<'],
+ (logs) => {
+ expect(logs).toContain('loaded:some:a0b0c0default0')
+ expect(logs).toContain('some >>>>>> a0, b0, c0')
+ },
+ )
+
+ await untilConsoleLogAfter(
+ async () => {
+ editFile(file, (code) => code.replace(/([abc])0/g, '$11'))
+ },
+ [PROGRAM_RELOAD, '>>> ready <<<'],
+ (logs) => {
+ expect(logs).toContain('loaded:some:a1b1c1default0')
+ expect(logs).toContain('some >>>>>> a1, b1, c1')
+ },
+ )
+ })
+ }
+
+ describe('import * from ...', () => testStarExports('star-imports'))
+
+ describe('dynamic import(...)', () => testStarExports('dynamic-imports'))
+ })
+ })
+
+ test('handle virtual module updates', async () => {
+ await setupModuleRunner('/hmr.ts')
+ const el = () => hmr('.virtual')
+ expect(el()).toBe('[success]0')
+ editFile('importedVirtual.js', (code) => code.replace('[success]', '[wow]'))
+ await expect.poll(el).toBe('[wow]0')
+ })
+
+ test('invalidate virtual module', async () => {
+ await setupModuleRunner('/hmr.ts')
+ const el = () => hmr('.virtual')
+ expect(el()).toBe('[wow]0')
+ globalThis.__HMR__['virtual:increment']()
+ await expect.poll(el).toBe('[wow]1')
+ })
+
+ test('should hmr when file is deleted and restored', async () => {
+ await setupModuleRunner('/hmr.ts')
+
+ const parentFile = 'file-delete-restore/parent.js'
+ const childFile = 'file-delete-restore/child.js'
+
+ await expect.poll(() => hmr('.file-delete-restore')).toMatch('parent:child')
+
+ editFile(childFile, (code) =>
+ code.replace("value = 'child'", "value = 'child1'"),
+ )
+ await expect
+ .poll(() => hmr('.file-delete-restore'))
+ .toMatch('parent:child1')
+
+ // delete the file
+ editFile(parentFile, (code) =>
+ code.replace(
+ "export { value as childValue } from './child'",
+ "export const childValue = 'not-child'",
+ ),
+ )
+ const originalChildFileCode = readFile(childFile)
+ removeFile(childFile)
+ await expect
+ .poll(() => hmr('.file-delete-restore'))
+ .toMatch('parent:not-child')
+
+ // restore the file
+ addFile(childFile, originalChildFileCode)
+ editFile(parentFile, (code) =>
+ code.replace(
+ "export const childValue = 'not-child'",
+ "export { value as childValue } from './child'",
+ ),
+ )
+ await expect.poll(() => hmr('.file-delete-restore')).toMatch('parent:child')
+ })
+
+ test('delete file should not break hmr', async () => {
+ await setupModuleRunner('/hmr.ts', undefined, undefined, {
+ '.intermediate-file-delete-increment': '1',
+ })
+
+ await expect
+ .poll(() => hmr('.intermediate-file-delete-display'))
+ .toMatch('count is 1')
+
+ // add state
+ globalThis.__HMR__['.delete-intermediate-file']()
+ await expect
+ .poll(() => hmr('.intermediate-file-delete-display'))
+ .toMatch('count is 2')
+
+ // update import, hmr works
+ editFile('intermediate-file-delete/index.js', (code) =>
+ code.replace("from './re-export.js'", "from './display.js'"),
+ )
+ editFile('intermediate-file-delete/display.js', (code) =>
+ code.replace('count is ${count}', 'count is ${count}!'),
+ )
+ await expect
+ .poll(() => hmr('.intermediate-file-delete-display'))
+ .toMatch('count is 2!')
+
+ // remove unused file
+ removeFile('intermediate-file-delete/re-export.js')
+ __HMR__['.intermediate-file-delete-increment'] = '1' // reset state
+ await expect
+ .poll(() => hmr('.intermediate-file-delete-display'))
+ .toMatch('count is 1!')
+
+ // re-add state
+ globalThis.__HMR__['.delete-intermediate-file']()
+ await expect
+ .poll(() => hmr('.intermediate-file-delete-display'))
+ .toMatch('count is 2!')
+
+ // hmr works after file deletion
+ editFile('intermediate-file-delete/display.js', (code) =>
+ code.replace('count is ${count}!', 'count is ${count}'),
+ )
+ await expect
+ .poll(() => hmr('.intermediate-file-delete-display'))
+ .toMatch('count is 2')
+ })
+
+ test('deleted file should trigger dispose and prune callbacks', async () => {
+ await setupModuleRunner('/hmr.ts')
+
+ const parentFile = 'file-delete-restore/parent.js'
+ const childFile = 'file-delete-restore/child.js'
+ const originalChildFileCode = readFile(childFile)
+
+ await untilConsoleLogAfter(
+ () => {
+ // delete the file
+ editFile(parentFile, (code) =>
+ code.replace(
+ "export { value as childValue } from './child'",
+ "export const childValue = 'not-child'",
+ ),
+ )
+ removeFile(childFile)
+ },
+ [
+ 'file-delete-restore/child.js is disposed',
+ 'file-delete-restore/child.js is pruned',
+ ],
+ false,
+ )
+
+ await expect
+ .poll(() => hmr('.file-delete-restore'))
+ .toMatch('parent:not-child')
+
+ // restore the file
+ addFile(childFile, originalChildFileCode)
+ editFile(parentFile, (code) =>
+ code.replace(
+ "export const childValue = 'not-child'",
+ "export { value as childValue } from './child'",
+ ),
+ )
+ await expect.poll(() => hmr('.file-delete-restore')).toMatch('parent:child')
+ })
+
+ test('import.meta.hot?.accept', async () => {
+ await setupModuleRunner('/hmr.ts')
+ await untilConsoleLogAfter(
+ () =>
+ editFile('optional-chaining/child.js', (code) =>
+ code.replace('const foo = 1', 'const foo = 2'),
+ ),
+ '(optional-chaining) child update',
+ )
+ await expect.poll(() => hmr('.optional-chaining')?.toString()).toMatch('2')
+ })
+
+ test('hmr works for self-accepted module within circular imported files', async () => {
+ await setupModuleRunner('/self-accept-within-circular/index')
+ const el = () => hmr('.self-accept-within-circular')
+ expect(el()).toBe('c')
+ editFile('self-accept-within-circular/c.js', (code) =>
+ code.replace(`export const c = 'c'`, `export const c = 'cc'`),
+ )
+ // it throws a same error as browser case,
+ // but it doesn't auto reload and it calls `hot.accept(nextExports)` with `nextExports = undefined`
+
+ // test reloading manually for now
+ server.moduleGraph.invalidateAll() // TODO: why is `runner.clearCache()` not enough?
+ await runner.import('/self-accept-within-circular/index')
+ await expect.poll(() => el()).toBe('cc')
+ })
+
+ test('hmr should not reload if no accepted within circular imported files', async () => {
+ await setupModuleRunner('/circular/index')
+ const el = () => hmr('.circular')
+ expect(el()).toBe(
+ // tests in the browser check that there is an error, but vite runtime just returns undefined in those cases
+ 'mod-a -> mod-b -> mod-c -> undefined (expected no error)',
+ )
+ editFile('circular/mod-b.js', (code) =>
+ code.replace(`mod-b ->`, `mod-b (edited) ->`),
+ )
+ await expect
+ .poll(() => el())
+ .toBe('mod-a -> mod-b (edited) -> mod-c -> undefined (expected no error)')
+ })
+
+ test('not inlined assets HMR', async () => {
+ await setupModuleRunner('/hmr.ts')
+ const el = () => hmr('#logo-no-inline')
+ await untilConsoleLogAfter(
+ () =>
+ editFile('logo-no-inline.svg', (code) =>
+ code.replace('height="30px"', 'height="40px"'),
+ ),
+ /Logo-no-inline updated/,
+ )
+ await vi.waitUntil(() => el().includes('logo-no-inline.svg?t='))
+ })
+
+ test('inlined assets HMR', async () => {
+ await setupModuleRunner('/hmr.ts')
+ const el = () => hmr('#logo')
+ const initialLogoUrl = el()
+ expect(initialLogoUrl).toMatch(/^data:image\/svg\+xml/)
+ await untilConsoleLogAfter(
+ () =>
+ editFile('logo.svg', (code) =>
+ code.replace('height="30px"', 'height="40px"'),
+ ),
+ /Logo updated/,
+ )
+ // Should be updated with new data url
+ const updatedLogoUrl = el()
+ expect(updatedLogoUrl).toMatch(/^data:image\/svg\+xml/)
+ expect(updatedLogoUrl).not.toEqual(initialLogoUrl)
+ })
+} else {
+ test('this file only includes test for serve', () => {
+ expect(true).toBe(true)
+ })
+}
+
+type UntilBrowserLogAfterCallback = (logs: string[]) => PromiseLike | void
+
+export async function untilConsoleLogAfter(
+ operation: () => any,
+ target: string | RegExp | Array,
+ expectOrder?: boolean,
+ callback?: UntilBrowserLogAfterCallback,
+): Promise
+export async function untilConsoleLogAfter(
+ operation: () => any,
+ target: string | RegExp | Array