- Click outside or fix the code to dismiss.
- You can also disable this overlay by setting
-
server.hmr.overlay
to
false
in
vite.config.js.
+
+
+
+
+
+
+
+ Click outside or fix the code to dismiss.
+ You can also disable this overlay by setting
+ server.hmr.overlay
to false
in vite.config.js.
+
`
diff --git a/packages/vite/src/node/__tests__/plugins/dynamicImportVar/__snapshots__/parse.test.ts.snap b/packages/vite/src/node/__tests__/plugins/dynamicImportVar/__snapshots__/parse.test.ts.snap
index b5f649d6bc336b..a90bf10d119042 100644
--- a/packages/vite/src/node/__tests__/plugins/dynamicImportVar/__snapshots__/parse.test.ts.snap
+++ b/packages/vite/src/node/__tests__/plugins/dynamicImportVar/__snapshots__/parse.test.ts.snap
@@ -1,9 +1,11 @@
// Vitest Snapshot v1
-exports[`parse positives > ? in url 1`] = `"__variableDynamicImportRuntimeHelper((import.meta.glob(\\"./mo?ds/*.js\\", {\\"as\\":\\"raw\\",\\"import\\":\\"*\\"})), \`./mo?ds/\${base ?? foo}.js\`)"`;
+exports[`parse positives > ? in url 1`] = `"__variableDynamicImportRuntimeHelper((import.meta.glob(\\"./mo?ds/*.js\\", {\\"as\\":\\"url\\",\\"import\\":\\"*\\"})), \`./mo?ds/\${base ?? foo}.js\`)"`;
exports[`parse positives > ? in variables 1`] = `"__variableDynamicImportRuntimeHelper((import.meta.glob(\\"./mods/*.js\\", {\\"as\\":\\"raw\\",\\"import\\":\\"*\\"})), \`./mods/\${base ?? foo}.js\`)"`;
+exports[`parse positives > ? in worker 1`] = `"__variableDynamicImportRuntimeHelper((import.meta.glob(\\"./mo?ds/*.js\\", {\\"as\\":\\"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 > basic 1`] = `"__variableDynamicImportRuntimeHelper((import.meta.glob(\\"./mods/*.js\\")), \`./mods/\${base}.js\`)"`;
@@ -14,4 +16,4 @@ exports[`parse positives > with multi ../ and itself 1`] = `"__variableDynamicIm
exports[`parse positives > with query raw 1`] = `"__variableDynamicImportRuntimeHelper((import.meta.glob(\\"./mods/*.js\\", {\\"as\\":\\"raw\\",\\"import\\":\\"*\\"})), \`./mods/\${base}.js\`)"`;
-exports[`parse positives > with query url 1`] = `"__variableDynamicImportRuntimeHelper((import.meta.glob(\\"./mods/*.js\\")), \`./mods/\${base}.js\`)"`;
+exports[`parse positives > with query url 1`] = `"__variableDynamicImportRuntimeHelper((import.meta.glob(\\"./mods/*.js\\", {\\"as\\":\\"url\\",\\"import\\":\\"*\\"})), \`./mods/\${base}.js\`)"`;
diff --git a/packages/vite/src/node/__tests__/plugins/dynamicImportVar/parse.test.ts b/packages/vite/src/node/__tests__/plugins/dynamicImportVar/parse.test.ts
index 422dcb59ca5b37..cfd816d00fe8b5 100644
--- a/packages/vite/src/node/__tests__/plugins/dynamicImportVar/parse.test.ts
+++ b/packages/vite/src/node/__tests__/plugins/dynamicImportVar/parse.test.ts
@@ -39,7 +39,11 @@ describe('parse positives', () => {
})
it('? in url', async () => {
- expect(await run('`./mo?ds/${base ?? foo}.js?raw`')).toMatchSnapshot()
+ expect(await run('`./mo?ds/${base ?? foo}.js?url`')).toMatchSnapshot()
+ })
+
+ it('? in worker', async () => {
+ expect(await run('`./mo?ds/${base ?? foo}.js?worker`')).toMatchSnapshot()
})
it('with ../ and itself', async () => {
diff --git a/packages/vite/src/node/build.ts b/packages/vite/src/node/build.ts
index a994182750bbc7..41b5d0524aad04 100644
--- a/packages/vite/src/node/build.ts
+++ b/packages/vite/src/node/build.ts
@@ -42,7 +42,6 @@ import {
getDepsCacheDir,
initDepsOptimizer
} from './optimizer'
-import { assetImportMetaUrlPlugin } from './plugins/assetImportMetaUrl'
import { loadFallbackPlugin } from './plugins/loadFallback'
import type { PackageData } from './packages'
import { watchPackageDataPlugin } from './packages'
@@ -310,7 +309,6 @@ export function resolveBuildPlugins(config: ResolvedConfig): {
watchPackageDataPlugin(config),
...(usePluginCommonjs ? [commonjsPlugin(options.commonjsOptions)] : []),
dataURIPlugin(),
- assetImportMetaUrlPlugin(config),
...(options.rollupOptions.plugins
? (options.rollupOptions.plugins.filter(Boolean) as Plugin[])
: [])
@@ -373,7 +371,7 @@ async function doBuild(
const resolve = (p: string) => path.resolve(config.root, p)
const input = libOptions
- ? resolve(libOptions.entry)
+ ? options.rollupOptions?.input || resolve(libOptions.entry)
: typeof options.ssr === 'string'
? resolve(options.ssr)
: options.rollupOptions?.input || resolve('index.html')
diff --git a/packages/vite/src/node/cli.ts b/packages/vite/src/node/cli.ts
index 86ae76bba12215..c94e9427db87d3 100644
--- a/packages/vite/src/node/cli.ts
+++ b/packages/vite/src/node/cli.ts
@@ -103,7 +103,7 @@ cli
const viteStartTime = global.__vite_start_time ?? false
const startupDurationString = viteStartTime
? colors.dim(
- `ready in ${colors.white(
+ `ready in ${colors.reset(
colors.bold(Math.ceil(performance.now() - viteStartTime))
)} ms`
)
diff --git a/packages/vite/src/node/config.ts b/packages/vite/src/node/config.ts
index 051df99e6bb8f5..a760a06fb86601 100644
--- a/packages/vite/src/node/config.ts
+++ b/packages/vite/src/node/config.ts
@@ -43,6 +43,8 @@ import {
CLIENT_ENTRY,
DEFAULT_ASSETS_RE,
DEFAULT_CONFIG_FILES,
+ DEFAULT_EXTENSIONS,
+ DEFAULT_MAIN_FIELDS,
ENV_ENTRY
} from './constants'
import type { InternalResolveOptions, ResolveOptions } from './plugins/resolve'
@@ -321,7 +323,7 @@ export type ResolvedConfig = Readonly<
mainConfig: ResolvedConfig | null
isProduction: boolean
env: Record
- resolve: ResolveOptions & {
+ resolve: Required & {
alias: Alias[]
}
plugins: readonly Plugin[]
@@ -442,16 +444,7 @@ export async function resolveConfig(
// run config hooks
const userPlugins = [...prePlugins, ...normalPlugins, ...postPlugins]
- for (const p of getSortedPluginsByHook('config', userPlugins)) {
- const hook = p.config
- const handler = hook && 'handler' in hook ? hook.handler : hook
- if (handler) {
- const res = await handler(config, configEnv)
- if (res) {
- config = mergeConfig(config, res)
- }
- }
- }
+ config = await runConfigHook(config, userPlugins, configEnv)
if (process.env.VITE_TEST_WITHOUT_PLUGIN_COMMONJS) {
config = mergeConfig(config, {
@@ -483,7 +476,12 @@ export async function resolveConfig(
)
const resolveOptions: ResolvedConfig['resolve'] = {
- ...config.resolve,
+ mainFields: config.resolve?.mainFields ?? DEFAULT_MAIN_FIELDS,
+ browserField: config.resolve?.browserField ?? true,
+ conditions: config.resolve?.conditions ?? [],
+ extensions: config.resolve?.extensions ?? DEFAULT_EXTENSIONS,
+ dedupe: config.resolve?.dedupe ?? [],
+ preserveSymlinks: config.resolve?.preserveSymlinks ?? false,
alias: resolvedAlias
}
@@ -590,8 +588,8 @@ export async function resolveConfig(
const server = resolveServerOptions(resolvedRoot, config.server, logger)
const ssr = resolveSSROptions(
config.ssr,
- config.legacy?.buildSsrCjsExternalHeuristics,
- config.resolve?.preserveSymlinks
+ resolveOptions.preserveSymlinks,
+ config.legacy?.buildSsrCjsExternalHeuristics
)
const middlewareMode = config?.server?.middlewareMode
@@ -611,16 +609,7 @@ export async function resolveConfig(
...workerNormalPlugins,
...workerPostPlugins
]
- for (const p of getSortedPluginsByHook('config', workerUserPlugins)) {
- const hook = p.config
- const handler = hook && 'handler' in hook ? hook.handler : hook
- if (handler) {
- const res = await handler(workerConfig, configEnv)
- if (res) {
- workerConfig = mergeConfig(workerConfig, res)
- }
- }
- }
+ workerConfig = await runConfigHook(workerConfig, workerUserPlugins, configEnv)
const resolvedWorkerOptions: ResolveWorkerOptions = {
format: workerConfig.worker?.format || 'iife',
plugins: [],
@@ -667,7 +656,7 @@ export async function resolveConfig(
disabled: 'build',
...optimizeDeps,
esbuildOptions: {
- preserveSymlinks: config.resolve?.preserveSymlinks,
+ preserveSymlinks: resolveOptions.preserveSymlinks,
...optimizeDeps.esbuildOptions
}
},
@@ -1089,6 +1078,27 @@ async function loadConfigFromBundledFile(
}
}
+async function runConfigHook(
+ config: InlineConfig,
+ plugins: Plugin[],
+ configEnv: ConfigEnv
+): Promise {
+ let conf = config
+
+ for (const p of getSortedPluginsByHook('config', plugins)) {
+ const hook = p.config
+ const handler = hook && 'handler' in hook ? hook.handler : hook
+ if (handler) {
+ const res = await handler(conf, configEnv)
+ if (res) {
+ conf = mergeConfig(conf, res)
+ }
+ }
+ }
+
+ return conf
+}
+
export function getDepOptimizationConfig(
config: ResolvedConfig,
ssr: boolean
diff --git a/packages/vite/src/node/http.ts b/packages/vite/src/node/http.ts
index 2da2f30d3afb0f..c77f5ec664ba3c 100644
--- a/packages/vite/src/node/http.ts
+++ b/packages/vite/src/node/http.ts
@@ -121,8 +121,7 @@ export async function resolveHttpServer(
}
export async function resolveHttpsConfig(
- https: boolean | HttpsServerOptions | undefined,
- cacheDir: string
+ https: boolean | HttpsServerOptions | undefined
): Promise {
if (!https) return undefined
diff --git a/packages/vite/src/node/logger.ts b/packages/vite/src/node/logger.ts
index 326fa0e6dfd778..a4750a8b2a9dbc 100644
--- a/packages/vite/src/node/logger.ts
+++ b/packages/vite/src/node/logger.ts
@@ -153,7 +153,7 @@ export function printServerUrls(
info(` ${colors.green('➜')} ${colors.bold('Network')}: ${colorUrl(url)}`)
}
if (urls.network.length === 0 && optionsHost === undefined) {
- const note = `use ${colors.white(colors.bold('--host'))} to expose`
+ const note = `use ${colors.reset(colors.bold('--host'))} to expose`
info(
colors.dim(` ${colors.green('➜')} ${colors.bold('Network')}: ${note}`)
)
diff --git a/packages/vite/src/node/optimizer/index.ts b/packages/vite/src/node/optimizer/index.ts
index 891d2dd709833c..eb9b51aa472e59 100644
--- a/packages/vite/src/node/optimizer/index.ts
+++ b/packages/vite/src/node/optimizer/index.ts
@@ -66,6 +66,8 @@ export interface DepsOptimizer {
resetRegisteredIds: () => void
ensureFirstRun: () => void
+ close: () => Promise
+
options: DepOptimizationOptions
}
@@ -911,12 +913,22 @@ function esbuildOutputFromId(
id: string,
cacheDirOutputPath: string
): any {
+ const cwd = process.cwd()
const flatId = flattenId(id) + '.js'
- return outputs[
- normalizePath(
- path.relative(process.cwd(), path.join(cacheDirOutputPath, flatId))
- )
- ]
+ const normalizedOutputPath = normalizePath(
+ path.relative(cwd, path.join(cacheDirOutputPath, flatId))
+ )
+ const output = outputs[normalizedOutputPath]
+ if (output) {
+ return output
+ }
+ // If the root dir was symlinked, esbuild could return output keys as `../cwd/`
+ // Normalize keys to support this case too
+ for (const [key, value] of Object.entries(outputs)) {
+ if (normalizePath(path.relative(cwd, key)) === normalizedOutputPath) {
+ return value
+ }
+ }
}
export async function extractExportsData(
diff --git a/packages/vite/src/node/optimizer/optimizer.ts b/packages/vite/src/node/optimizer/optimizer.ts
index 9bf03aa88005b0..37061cdaf0c6f5 100644
--- a/packages/vite/src/node/optimizer/optimizer.ts
+++ b/packages/vite/src/node/optimizer/optimizer.ts
@@ -103,6 +103,8 @@ async function createDepsOptimizer(
let handle: NodeJS.Timeout | undefined
+ let closed = false
+
let metadata =
cachedMetadata || initDepsOptimizerMetadata(config, ssr, sessionTimestamp)
@@ -118,6 +120,7 @@ async function createDepsOptimizer(
delayDepsOptimizerUntil,
resetRegisteredIds,
ensureFirstRun,
+ close,
options: getDepOptimizationConfig(config, ssr)
}
@@ -159,6 +162,13 @@ async function createDepsOptimizer(
let postScanOptimizationResult: Promise | undefined
+ let optimizingNewDeps: Promise | undefined
+ async function close() {
+ closed = true
+ await postScanOptimizationResult
+ await optimizingNewDeps
+ }
+
if (!cachedMetadata) {
// Enter processing state until crawl of static imports ends
currentlyProcessing = true
@@ -232,7 +242,7 @@ async function createDepsOptimizer(
}
}
- async function startNextDiscoveredBatch() {
+ function startNextDiscoveredBatch() {
newDepsDiscovered = false
// Add the current depOptimizationProcessing to the queue, these
@@ -288,7 +298,7 @@ async function createDepsOptimizer(
// Ensure that a rerun will not be issued for current discovered deps
if (handle) clearTimeout(handle)
- if (Object.keys(metadata.discovered).length === 0) {
+ if (closed || Object.keys(metadata.discovered).length === 0) {
currentlyProcessing = false
return
}
@@ -296,7 +306,16 @@ async function createDepsOptimizer(
currentlyProcessing = true
try {
- const processingResult = preRunResult ?? (await optimizeNewDeps())
+ const processingResult =
+ preRunResult ?? (await (optimizingNewDeps = optimizeNewDeps()))
+ optimizingNewDeps = undefined
+
+ if (closed) {
+ currentlyProcessing = false
+ processingResult.cancel()
+ resolveEnqueuedProcessingPromises()
+ return
+ }
const newData = processingResult.metadata
@@ -665,7 +684,7 @@ async function createDepsOptimizer(
function ensureFirstRun() {
if (!firstRunEnsured && !firstRunCalled && registeredIds.length === 0) {
setTimeout(() => {
- if (registeredIds.length === 0) {
+ if (!closed && registeredIds.length === 0) {
onCrawlEnd()
}
}, runOptimizerIfIdleAfterMs)
@@ -699,7 +718,7 @@ async function createDepsOptimizer(
waitingOn = next.id
const afterLoad = () => {
waitingOn = undefined
- if (!workersSources.has(next.id)) {
+ if (!closed && !workersSources.has(next.id)) {
if (registeredIds.length > 0) {
runOptimizerWhenIdle()
} else {
@@ -745,6 +764,8 @@ async function createDevSsrDepsOptimizer(
delayDepsOptimizerUntil: (id: string, done: () => Promise) => {},
resetRegisteredIds: () => {},
ensureFirstRun: () => {},
+
+ close: async () => {},
options: config.ssr.optimizeDeps
}
devSsrDepsOptimizerMap.set(config, depsOptimizer)
diff --git a/packages/vite/src/node/plugins/asset.ts b/packages/vite/src/node/plugins/asset.ts
index 94c34a40f8d22e..db2f9d7a7cb613 100644
--- a/packages/vite/src/node/plugins/asset.ts
+++ b/packages/vite/src/node/plugins/asset.ts
@@ -5,6 +5,7 @@ import { Buffer } from 'node:buffer'
import * as mrmime from 'mrmime'
import type {
NormalizedOutputOptions,
+ OutputAsset,
OutputOptions,
PluginContext,
PreRenderedAsset,
@@ -20,6 +21,11 @@ import { FS_PREFIX } from '../constants'
export const assetUrlRE = /__VITE_ASSET__([a-z\d]{8})__(?:\$_(.*?)__)?/g
+export const duplicateAssets = new WeakMap<
+ ResolvedConfig,
+ Map
+>()
+
const rawRE = /(\?|&)raw(?:&|$)/
const urlRE = /(\?|&)url(?:&|$)/
@@ -129,6 +135,7 @@ export function assetPlugin(config: ResolvedConfig): Plugin {
buildStart() {
assetCache.set(config, new Map())
emittedHashMap.set(config, new Set())
+ duplicateAssets.set(config, new Map())
},
resolveId(id) {
@@ -470,8 +477,9 @@ async function fileToBuiltUrl(
map.set(contentHash, fileName)
}
const emittedSet = emittedHashMap.get(config)!
+ const duplicates = duplicateAssets.get(config)!
+ const name = normalizePath(path.relative(config.root, file))
if (!emittedSet.has(contentHash)) {
- const name = normalizePath(path.relative(config.root, file))
pluginContext.emitFile({
name,
fileName,
@@ -479,6 +487,14 @@ async function fileToBuiltUrl(
source: content
})
emittedSet.add(contentHash)
+ } else {
+ duplicates.set(name, {
+ name,
+ fileName: map.get(contentHash)!,
+ type: 'asset',
+ source: content,
+ isAsset: true
+ })
}
url = `__VITE_ASSET__${contentHash}__${postfix ? `$_${postfix}__` : ``}` // TODO_BASE
diff --git a/packages/vite/src/node/plugins/assetImportMetaUrl.ts b/packages/vite/src/node/plugins/assetImportMetaUrl.ts
index 4b52a225a68458..de946614620866 100644
--- a/packages/vite/src/node/plugins/assetImportMetaUrl.ts
+++ b/packages/vite/src/node/plugins/assetImportMetaUrl.ts
@@ -3,7 +3,13 @@ import MagicString from 'magic-string'
import { stripLiteral } from 'strip-literal'
import type { Plugin } from '../plugin'
import type { ResolvedConfig } from '../config'
-import { transformStableResult } from '../utils'
+import type { ResolveFn } from '../'
+import {
+ isParentDirectory,
+ normalizePath,
+ slash,
+ transformStableResult
+} from '../utils'
import { fileToUrl } from './asset'
import { preloadHelperId } from './importAnalysisBuild'
@@ -18,6 +24,9 @@ import { preloadHelperId } from './importAnalysisBuild'
* ```
*/
export function assetImportMetaUrlPlugin(config: ResolvedConfig): Plugin {
+ const normalizedPublicDir = normalizePath(config.publicDir)
+ let assetResolver: ResolveFn
+
return {
name: 'vite:asset-import-meta-url',
async transform(code, id, options) {
@@ -63,16 +72,45 @@ export function assetImportMetaUrlPlugin(config: ResolvedConfig): Plugin {
}
const url = rawUrl.slice(1, -1)
- const file = path.resolve(path.dirname(id), url)
- // Get final asset URL. Catch error if the file does not exist,
- // in which we can resort to the initial URL and let it resolve in runtime
- const builtUrl = await fileToUrl(file, config, this).catch(() => {
+ let file: string | undefined
+ if (url.startsWith('.')) {
+ file = slash(path.resolve(path.dirname(id), url))
+ } else {
+ assetResolver ??= config.createResolver({
+ extensions: [],
+ mainFields: [],
+ tryIndex: false,
+ preferRelative: true
+ })
+ file = await assetResolver(url, id)
+ file ??= url.startsWith('/')
+ ? slash(path.join(config.publicDir, url))
+ : slash(path.resolve(path.dirname(id), url))
+ }
+
+ // Get final asset URL. If the file does not exist,
+ // we fall back to the initial URL and let it resolve in runtime
+ let builtUrl: string | undefined
+ if (file) {
+ try {
+ if (isParentDirectory(normalizedPublicDir, file)) {
+ const publicPath =
+ '/' + path.posix.relative(normalizedPublicDir, file)
+ builtUrl = await fileToUrl(publicPath, config, this)
+ } else {
+ builtUrl = await fileToUrl(file, config, this)
+ }
+ } catch {
+ // do nothing, we'll log a warning after this
+ }
+ }
+ if (!builtUrl) {
const rawExp = code.slice(index, index + exp.length)
config.logger.warnOnce(
`\n${rawExp} doesn't exist at build time, it will remain unchanged to be resolved at runtime`
)
- return url
- })
+ builtUrl = url
+ }
s.overwrite(
index,
index + exp.length,
diff --git a/packages/vite/src/node/plugins/css.ts b/packages/vite/src/node/plugins/css.ts
index 6737e5be1c26b6..bcc5359d17ca93 100644
--- a/packages/vite/src/node/plugins/css.ts
+++ b/packages/vite/src/node/plugins/css.ts
@@ -1441,8 +1441,8 @@ const scss: SassStylePreprocessor = async (
const importer = [internalImporter]
if (options.importer) {
Array.isArray(options.importer)
- ? importer.push(...options.importer)
- : importer.push(options.importer)
+ ? importer.unshift(...options.importer)
+ : importer.unshift(options.importer)
}
const { content: data, map: additionalMap } = await getSource(
diff --git a/packages/vite/src/node/plugins/dynamicImportVars.ts b/packages/vite/src/node/plugins/dynamicImportVars.ts
index 93250f74618316..0f32f5ff581920 100644
--- a/packages/vite/src/node/plugins/dynamicImportVars.ts
+++ b/packages/vite/src/node/plugins/dynamicImportVars.ts
@@ -4,6 +4,7 @@ import { init, parse as parseImports } from 'es-module-lexer'
import type { ImportSpecifier } from 'es-module-lexer'
import { parse as parseJS } from 'acorn'
import { dynamicImportToGlob } from '@rollup/plugin-dynamic-import-vars'
+import type { KnownAsTypeMap } from 'types/importGlob'
import type { Plugin } from '../plugin'
import type { ResolvedConfig } from '../config'
import {
@@ -19,7 +20,7 @@ import { toAbsoluteGlob } from './importMetaGlob'
export const dynamicImportHelperId = '/@vite/dynamic-import-helper'
interface DynamicImportRequest {
- as?: 'raw'
+ as?: keyof KnownAsTypeMap
}
interface DynamicImportPattern {
@@ -65,6 +66,14 @@ function parseDynamicImportPattern(
globParams = { as: 'raw' }
}
+ if (rawQuery?.url !== undefined) {
+ globParams = { as: 'url' }
+ }
+
+ if (rawQuery?.worker !== undefined) {
+ globParams = { as: 'worker' }
+ }
+
return {
globParams,
userPattern,
diff --git a/packages/vite/src/node/plugins/esbuild.ts b/packages/vite/src/node/plugins/esbuild.ts
index f1fbe8e607342e..43c319f2713761 100644
--- a/packages/vite/src/node/plugins/esbuild.ts
+++ b/packages/vite/src/node/plugins/esbuild.ts
@@ -179,6 +179,7 @@ export function esbuildPlugin(options: ESBuildOptions = {}): Plugin {
// Remove optimization options for dev as we only need to transpile them,
// and for build as the final optimization is in `buildEsbuildPlugin`
const transformOptions: TransformOptions = {
+ target: 'esnext',
...options,
minify: false,
minifyIdentifiers: false,
diff --git a/packages/vite/src/node/plugins/html.ts b/packages/vite/src/node/plugins/html.ts
index a3df68ca539602..f09c2a6fcd89b0 100644
--- a/packages/vite/src/node/plugins/html.ts
+++ b/packages/vite/src/node/plugins/html.ts
@@ -196,7 +196,7 @@ export function getScriptInfo(node: DefaultTreeAdapterMap['element']): {
return { src, sourceCodeLocation, isModule, isAsync }
}
-const attrValueStartRE = /=[\s\t\n\r]*(["']|.)/
+const attrValueStartRE = /=[\s\t\n\r]*(.)/
export function overwriteAttrValue(
s: MagicString,
@@ -214,7 +214,7 @@ export function overwriteAttrValue(
`[vite:html] internal error, failed to overwrite attribute value`
)
}
- const wrapOffset = valueStart[1] ? 1 : 0
+ const wrapOffset = valueStart[1] === '"' || valueStart[1] === "'" ? 1 : 0
const valueOffset = valueStart.index! + valueStart[0].length - 1
s.overwrite(
sourceCodeLocation.startOffset + valueOffset + wrapOffset,
diff --git a/packages/vite/src/node/plugins/importAnalysis.ts b/packages/vite/src/node/plugins/importAnalysis.ts
index f1356d5c7f879e..86bad0b75ac001 100644
--- a/packages/vite/src/node/plugins/importAnalysis.ts
+++ b/packages/vite/src/node/plugins/importAnalysis.ts
@@ -15,9 +15,7 @@ import {
CLIENT_DIR,
CLIENT_PUBLIC_PATH,
DEP_VERSION_RE,
- FS_PREFIX,
- NULL_BYTE_PLACEHOLDER,
- VALID_ID_PREFIX
+ FS_PREFIX
} from '../constants'
import {
debugHmr,
@@ -42,7 +40,8 @@ import {
stripBomTag,
timeFrom,
transformStableResult,
- unwrapId
+ unwrapId,
+ wrapId
} from '../utils'
import type { ResolvedConfig } from '../config'
import type { Plugin } from '../plugin'
@@ -87,7 +86,7 @@ function markExplicitImport(url: string) {
return url
}
-async function extractImportedBindings(
+function extractImportedBindings(
id: string,
source: string,
importSpec: ImportSpecifier,
@@ -330,8 +329,7 @@ export function importAnalysisPlugin(config: ResolvedConfig): Plugin {
// prefix it to make it valid. We will strip this before feeding it
// back into the transform pipeline
if (!url.startsWith('.') && !url.startsWith('/')) {
- url =
- VALID_ID_PREFIX + resolved.id.replace('\0', NULL_BYTE_PLACEHOLDER)
+ url = wrapId(resolved.id)
}
// make the URL browser-valid if not SSR
@@ -361,7 +359,7 @@ export function importAnalysisPlugin(config: ResolvedConfig): Plugin {
try {
// delay setting `isSelfAccepting` until the file is actually used (#7870)
const depModule = await moduleGraph.ensureEntryFromUrl(
- url,
+ unwrapId(url),
ssr,
canSkipImportAnalysis(url)
)
@@ -536,9 +534,9 @@ export function importAnalysisPlugin(config: ResolvedConfig): Plugin {
}
// record for HMR import chain analysis
- // make sure to normalize away base
- const urlWithoutBase = url.replace(base, '/')
- importedUrls.add(urlWithoutBase)
+ // make sure to unwrap and normalize away base
+ const hmrUrl = unwrapId(url.replace(base, '/'))
+ importedUrls.add(hmrUrl)
if (enablePartialAccept && importedBindings) {
extractImportedBindings(
@@ -551,7 +549,7 @@ export function importAnalysisPlugin(config: ResolvedConfig): Plugin {
if (!isDynamicImport) {
// for pre-transforming
- staticImportedUrls.add({ url: urlWithoutBase, id: resolvedId })
+ staticImportedUrls.add({ url: hmrUrl, id: resolvedId })
}
} else if (!importer.startsWith(clientDir)) {
if (!importer.includes('node_modules')) {
@@ -712,10 +710,7 @@ export function importAnalysisPlugin(config: ResolvedConfig): Plugin {
// by the deps optimizer
if (config.server.preTransformRequests && staticImportedUrls.size) {
staticImportedUrls.forEach(({ url, id }) => {
- url = unwrapId(removeImportQuery(url)).replace(
- NULL_BYTE_PLACEHOLDER,
- '\0'
- )
+ url = removeImportQuery(url)
transformRequest(url, server, { ssr }).catch((e) => {
if (e?.code === ERR_OUTDATED_OPTIMIZED_DEP) {
// This are expected errors
diff --git a/packages/vite/src/node/plugins/importAnalysisBuild.ts b/packages/vite/src/node/plugins/importAnalysisBuild.ts
index d78e4c3be77ba7..3c8d8013263195 100644
--- a/packages/vite/src/node/plugins/importAnalysisBuild.ts
+++ b/packages/vite/src/node/plugins/importAnalysisBuild.ts
@@ -65,6 +65,8 @@ function preload(
return baseModule()
}
+ const links = document.getElementsByTagName('link')
+
return Promise.all(
deps.map((dep) => {
// @ts-ignore
@@ -75,10 +77,24 @@ function preload(
seen[dep] = true
const isCss = dep.endsWith('.css')
const cssSelector = isCss ? '[rel="stylesheet"]' : ''
- // @ts-ignore check if the file is already preloaded by SSR markup
- if (document.querySelector(`link[href="${dep}"]${cssSelector}`)) {
+ const isBaseRelative = !!importerUrl
+
+ // check if the file is already preloaded by SSR markup
+ if (isBaseRelative) {
+ // When isBaseRelative is true then we have `importerUrl` and `dep` is
+ // already converted to an absolute URL by the `assetsURL` function
+ for (let i = links.length - 1; i >= 0; i--) {
+ const link = links[i]
+ // The `links[i].href` is an absolute URL thanks to browser doing the work
+ // for us. See https://html.spec.whatwg.org/multipage/common-dom-interfaces.html#reflecting-content-attributes-in-idl-attributes:idl-domstring-5
+ if (link.href === dep && (!isCss || link.rel === 'stylesheet')) {
+ return
+ }
+ }
+ } else if (document.querySelector(`link[href="${dep}"]${cssSelector}`)) {
return
}
+
// @ts-ignore
const link = document.createElement('link')
// @ts-ignore
@@ -418,10 +434,12 @@ export function buildImportAnalysisPlugin(config: ResolvedConfig): Plugin {
const chunk = bundle[filename] as OutputChunk | undefined
if (chunk) {
deps.add(chunk.fileName)
+ chunk.imports.forEach(addDeps)
+ // Ensure that the css imported by current chunk is loaded after the dependencies.
+ // So the style of current chunk won't be overwritten unexpectedly.
chunk.viteMetadata.importedCss.forEach((file) => {
deps.add(file)
})
- chunk.imports.forEach(addDeps)
} else {
const removedPureCssFiles =
removedPureCssFilesCache.get(config)!
diff --git a/packages/vite/src/node/plugins/index.ts b/packages/vite/src/node/plugins/index.ts
index 75c8297bf26919..f5db400a7b7c7d 100644
--- a/packages/vite/src/node/plugins/index.ts
+++ b/packages/vite/src/node/plugins/index.ts
@@ -20,6 +20,7 @@ import { preAliasPlugin } from './preAlias'
import { definePlugin } from './define'
import { ssrRequireHookPlugin } from './ssrRequireHook'
import { workerImportMetaUrlPlugin } from './workerImportMetaUrl'
+import { assetImportMetaUrlPlugin } from './assetImportMetaUrl'
import { ensureWatchPlugin } from './ensureWatch'
import { metadataPlugin } from './metadata'
import { dynamicImportVarsPlugin } from './dynamicImportVars'
@@ -88,6 +89,7 @@ export async function resolvePlugins(
isBuild && config.build.ssr ? ssrRequireHookPlugin(config) : null,
isBuild && buildHtmlPlugin(config),
workerImportMetaUrlPlugin(config),
+ assetImportMetaUrlPlugin(config),
...buildPlugins.pre,
dynamicImportVarsPlugin(config),
importGlobPlugin(config),
diff --git a/packages/vite/src/node/plugins/manifest.ts b/packages/vite/src/node/plugins/manifest.ts
index e342102946904a..a584daadc940e0 100644
--- a/packages/vite/src/node/plugins/manifest.ts
+++ b/packages/vite/src/node/plugins/manifest.ts
@@ -4,6 +4,7 @@ import type { ResolvedConfig } from '..'
import type { Plugin } from '../plugin'
import { normalizePath } from '../utils'
import { cssEntryFilesCache } from './css'
+import { duplicateAssets } from './asset'
export type Manifest = Record
@@ -122,6 +123,11 @@ export function manifestPlugin(config: ResolvedConfig): Plugin {
}
}
+ duplicateAssets.get(config)!.forEach((asset) => {
+ const chunk = createAsset(asset)
+ manifest[asset.name!] = chunk
+ })
+
outputCount++
const output = config.build.rollupOptions?.output
const outputLength = Array.isArray(output) ? output.length : 1
diff --git a/packages/vite/src/node/plugins/resolve.ts b/packages/vite/src/node/plugins/resolve.ts
index 371609fdf23c83..b44bdb337b2525 100644
--- a/packages/vite/src/node/plugins/resolve.ts
+++ b/packages/vite/src/node/plugins/resolve.ts
@@ -29,6 +29,7 @@ import {
isFileReadable,
isNonDriveRelativeAbsolutePath,
isObject,
+ isOptimizable,
isPossibleTsOutput,
isTsRequest,
isWindows,
@@ -62,13 +63,18 @@ const debug = createDebugger('vite:resolve-details', {
export interface ResolveOptions {
mainFields?: string[]
+ /**
+ * @deprecated In future, `mainFields` should be used instead.
+ * @default true
+ */
+ browserField?: boolean
conditions?: string[]
extensions?: string[]
dedupe?: string[]
preserveSymlinks?: boolean
}
-export interface InternalResolveOptions extends ResolveOptions {
+export interface InternalResolveOptions extends Required {
root: string
isBuild: boolean
isProduction: boolean
@@ -84,7 +90,6 @@ export interface InternalResolveOptions extends ResolveOptions {
tryPrefix?: string
skipPackageJson?: boolean
preferRelative?: boolean
- preserveSymlinks?: boolean
isRequire?: boolean
// #3040
// when the importer is a ts module,
@@ -175,10 +180,13 @@ export function resolvePlugin(resolveOptions: InternalResolveOptions): Plugin {
// as if they would have been imported through a bare import
// Use the original id to do the check as the resolved id may be the real
// file path after symlinks resolution
- const isNodeModule = !!normalizePath(id).match(nodeModulesInPathRE)
+ const isNodeModule =
+ nodeModulesInPathRE.test(normalizePath(id)) ||
+ nodeModulesInPathRE.test(normalizePath(resolved))
+
if (isNodeModule && !resolved.match(DEP_VERSION_RE)) {
const versionHash = depsOptimizer.metadata.browserHash
- if (versionHash && OPTIMIZABLE_ENTRY_RE.test(resolved)) {
+ if (versionHash && isOptimizable(resolved, depsOptimizer.options)) {
resolved = injectQuery(resolved, `v=${versionHash}`)
}
}
@@ -234,6 +242,7 @@ export function resolvePlugin(resolveOptions: InternalResolveOptions): Plugin {
if (
targetWeb &&
+ options.browserField &&
(res = tryResolveBrowserMapping(fsPath, importer, options, true))
) {
return res
@@ -304,6 +313,7 @@ export function resolvePlugin(resolveOptions: InternalResolveOptions): Plugin {
if (
targetWeb &&
+ options.browserField &&
(res = tryResolveBrowserMapping(
id,
importer,
@@ -447,7 +457,7 @@ function tryFsResolve(
return res
}
- for (const ext of options.extensions || DEFAULT_EXTENSIONS) {
+ for (const ext of options.extensions) {
if (
postfix &&
(res = tryResolveFile(
@@ -717,7 +727,11 @@ export function tryNodeResolve(
let resolvedId = id
if (isDeepImport) {
if (!pkg?.data.exports && path.extname(id) !== resolvedExt) {
- resolvedId += resolvedExt
+ resolvedId = resolved.id.slice(resolved.id.indexOf(id))
+ isDebug &&
+ debug(
+ `[processResult] ${colors.cyan(id)} -> ${colors.dim(resolvedId)}`
+ )
}
}
return { ...resolved, id: resolvedId, external: true }
@@ -747,7 +761,9 @@ export function tryNodeResolve(
}
// if we reach here, it's a valid dep import that hasn't been optimized.
- const isJsType = OPTIMIZABLE_ENTRY_RE.test(resolved)
+ const isJsType = depsOptimizer
+ ? isOptimizable(resolved, depsOptimizer.options)
+ : OPTIMIZABLE_ENTRY_RE.test(resolved)
let exclude = depsOptimizer?.options.exclude
let include = depsOptimizer?.options.exclude
@@ -882,7 +898,11 @@ export function resolvePackageEntry(
// This is because .mjs files can technically import .cjs files which would
// make them invalid for pure ESM environments - so if other module/browser
// fields are present, prioritize those instead.
- if (targetWeb && (!entryPoint || entryPoint.endsWith('.mjs'))) {
+ if (
+ targetWeb &&
+ options.browserField &&
+ (!entryPoint || entryPoint.endsWith('.mjs'))
+ ) {
// check browser field
// https://github.com/defunctzombie/package-browser-field-spec
const browserEntry =
@@ -893,6 +913,7 @@ export function resolvePackageEntry(
// check if the package also has a "module" field.
if (
!options.isRequire &&
+ options.mainFields.includes('module') &&
typeof data.module === 'string' &&
data.module !== browserEntry
) {
@@ -923,7 +944,8 @@ export function resolvePackageEntry(
}
if (!entryPoint || entryPoint.endsWith('.mjs')) {
- for (const field of options.mainFields || DEFAULT_MAIN_FIELDS) {
+ for (const field of options.mainFields) {
+ if (field === 'browser') continue // already checked above
if (typeof data[field] === 'string') {
entryPoint = data[field]
break
@@ -941,8 +963,8 @@ export function resolvePackageEntry(
for (let entry of entryPoints) {
// make sure we don't get scripts when looking for sass
if (
- options.mainFields?.[0] === 'sass' &&
- !options.extensions?.includes(path.extname(entry))
+ options.mainFields[0] === 'sass' &&
+ !options.extensions.includes(path.extname(entry))
) {
entry = ''
options.skipPackageJson = true
@@ -950,7 +972,7 @@ export function resolvePackageEntry(
// resolve object browser field in package.json
const { browser: browserField } = data
- if (targetWeb && isObject(browserField)) {
+ if (targetWeb && options.browserField && isObject(browserField)) {
entry = mapWithBrowserField(entry, browserField) || entry
}
@@ -991,13 +1013,13 @@ function resolveExports(
if (!options.isRequire) {
conditions.push('module')
}
- if (options.conditions) {
+ if (options.conditions.length > 0) {
conditions.push(...options.conditions)
}
return _resolveExports(pkg, key, {
- browser: targetWeb,
- require: options.isRequire,
+ browser: targetWeb && !conditions.includes('node'),
+ require: options.isRequire && !conditions.includes('import'),
conditions
})
}
@@ -1043,7 +1065,7 @@ function resolveDeepImport(
`${path.join(dir, 'package.json')}.`
)
}
- } else if (targetWeb && isObject(browserField)) {
+ } else if (targetWeb && options.browserField && isObject(browserField)) {
// resolve without postfix (see #7098)
const { file, postfix } = splitFileAndPostfix(relativeId)
const mapped = mapWithBrowserField(file, browserField)
diff --git a/packages/vite/src/node/plugins/ssrRequireHook.ts b/packages/vite/src/node/plugins/ssrRequireHook.ts
index d1173b211ff836..e02f9b48e6b6c7 100644
--- a/packages/vite/src/node/plugins/ssrRequireHook.ts
+++ b/packages/vite/src/node/plugins/ssrRequireHook.ts
@@ -13,7 +13,7 @@ export function ssrRequireHookPlugin(config: ResolvedConfig): Plugin | null {
if (
config.command !== 'build' ||
!config.build.ssr ||
- !config.resolve.dedupe?.length ||
+ !config.resolve.dedupe.length ||
config.ssr?.noExternal === true ||
config.ssr?.format !== 'cjs' ||
isBuildOutputEsm(config)
diff --git a/packages/vite/src/node/plugins/workerImportMetaUrl.ts b/packages/vite/src/node/plugins/workerImportMetaUrl.ts
index 5fbd7d4883382f..8867924cb093c0 100644
--- a/packages/vite/src/node/plugins/workerImportMetaUrl.ts
+++ b/packages/vite/src/node/plugins/workerImportMetaUrl.ts
@@ -8,11 +8,12 @@ import type { Plugin } from '../plugin'
import {
cleanUrl,
injectQuery,
- normalizePath,
parseRequest,
+ slash,
transformStableResult
} from '../utils'
import { getDepsOptimizer } from '../optimizer'
+import type { ResolveFn } from '..'
import type { WorkerType } from './worker'
import { WORKER_FILE_ID, workerFileToUrl } from './worker'
import { fileToUrl } from './asset'
@@ -38,7 +39,11 @@ function getWorkerType(raw: string, clean: string, i: number): WorkerType {
}
// need to find in comment code
- const workerOptString = raw.substring(commaIndex + 1, endIndex)
+ let workerOptString = raw.substring(commaIndex + 1, endIndex).trim()
+ // strip trailing comma for parsing
+ if (workerOptString.endsWith(',')) {
+ workerOptString = workerOptString.slice(0, -1)
+ }
const hasViteIgnore = ignoreFlagRE.test(workerOptString)
if (hasViteIgnore) {
@@ -46,8 +51,8 @@ function getWorkerType(raw: string, clean: string, i: number): WorkerType {
}
// need to find in no comment code
- const cleanWorkerOptString = clean.substring(commaIndex + 1, endIndex)
- if (!cleanWorkerOptString.trim().length) {
+ const cleanWorkerOptString = clean.substring(commaIndex + 1, endIndex).trim()
+ if (!cleanWorkerOptString.length) {
return 'classic'
}
@@ -71,6 +76,7 @@ function getWorkerType(raw: string, clean: string, i: number): WorkerType {
export function workerImportMetaUrlPlugin(config: ResolvedConfig): Plugin {
const isBuild = config.command === 'build'
+ let workerResolver: ResolveFn
return {
name: 'vite:worker-import-meta-url',
@@ -112,22 +118,37 @@ export function workerImportMetaUrlPlugin(config: ResolvedConfig): Plugin {
cleanString,
index + allExp.length
)
- const file = normalizePath(
- path.resolve(path.dirname(id), rawUrl.slice(1, -1))
- )
+ const url = rawUrl.slice(1, -1)
+ let file: string | undefined
+ if (url.startsWith('.')) {
+ file = path.resolve(path.dirname(id), url)
+ } else {
+ workerResolver ??= config.createResolver({
+ extensions: [],
+ tryIndex: false,
+ preferRelative: true
+ })
+ file = await workerResolver(url, id)
+ file ??= url.startsWith('/')
+ ? slash(path.join(config.publicDir, url))
+ : slash(path.resolve(path.dirname(id), url))
+ }
- let url: string
+ let builtUrl: string
if (isBuild) {
getDepsOptimizer(config, ssr)?.registerWorkersSource(id)
- url = await workerFileToUrl(config, file, query)
+ builtUrl = await workerFileToUrl(config, file, query)
} else {
- url = await fileToUrl(cleanUrl(file), config, this)
- url = injectQuery(url, WORKER_FILE_ID)
- url = injectQuery(url, `type=${workerType}`)
+ builtUrl = await fileToUrl(cleanUrl(file), config, this)
+ builtUrl = injectQuery(builtUrl, WORKER_FILE_ID)
+ builtUrl = injectQuery(builtUrl, `type=${workerType}`)
}
- s.overwrite(urlIndex, urlIndex + exp.length, JSON.stringify(url), {
- contentOnly: true
- })
+ s.overwrite(
+ urlIndex,
+ urlIndex + exp.length,
+ `new URL(${JSON.stringify(builtUrl)}, self.location)`,
+ { contentOnly: true }
+ )
}
if (s) {
diff --git a/packages/vite/src/node/preview.ts b/packages/vite/src/node/preview.ts
index eb8abb47b7699f..d17fc0845326dc 100644
--- a/packages/vite/src/node/preview.ts
+++ b/packages/vite/src/node/preview.ts
@@ -82,7 +82,7 @@ export async function preview(
const httpServer = await resolveHttpServer(
config.preview,
app,
- await resolveHttpsConfig(config.preview?.https, config.cacheDir)
+ await resolveHttpsConfig(config.preview?.https)
)
setClientErrorHandler(httpServer, config.logger)
@@ -111,12 +111,20 @@ export async function preview(
// static assets
const distDir = path.resolve(config.root, config.build.outDir)
+ const headers = config.preview.headers
app.use(
previewBase,
sirv(distDir, {
etag: true,
dev: true,
- single: config.appType === 'spa'
+ single: config.appType === 'spa',
+ setHeaders(res) {
+ if (headers) {
+ for (const name in headers) {
+ res.setHeader(name, headers[name]!)
+ }
+ }
+ }
})
)
diff --git a/packages/vite/src/node/server/index.ts b/packages/vite/src/node/server/index.ts
index 6e62fe344d9ba1..57a6a01e1fea12 100644
--- a/packages/vite/src/node/server/index.ts
+++ b/packages/vite/src/node/server/index.ts
@@ -11,6 +11,8 @@ import type { FSWatcher, WatchOptions } from 'dep-types/chokidar'
import type { Connect } from 'dep-types/connect'
import launchEditorMiddleware from 'launch-editor-middleware'
import type { SourceMap } from 'rollup'
+import picomatch from 'picomatch'
+import type { Matcher } from 'picomatch'
import type { CommonServerOptions } from '../http'
import {
httpServerStart,
@@ -143,7 +145,7 @@ export interface FileSystemServeOptions {
* Restrict accessing files that matches the patterns.
*
* This will have higher priority than `allow`.
- * Glob patterns are supported.
+ * picomatch patterns are supported.
*
* @default ['.env', '.env.*', '*.crt', '*.pem']
*/
@@ -283,6 +285,10 @@ export interface ViteDevServer {
abort: () => void
}
>
+ /**
+ * @internal
+ */
+ _fsDenyGlob: Matcher
}
export interface ResolvedServerUrls {
@@ -295,10 +301,7 @@ export async function createServer(
): Promise {
const config = await resolveConfig(inlineConfig, 'serve', 'development')
const { root, server: serverConfig } = config
- const httpsOptions = await resolveHttpsConfig(
- config.server.https,
- config.cacheDir
- )
+ const httpsOptions = await resolveHttpsConfig(config.server.https)
const { middlewareMode } = serverConfig
const resolvedWatchOptions = resolveChokidarOptions({
@@ -395,6 +398,8 @@ export async function createServer(
watcher.close(),
ws.close(),
container.close(),
+ getDepsOptimizer(server.config)?.close(),
+ getDepsOptimizer(server.config, true)?.close(),
closeHttpServer()
])
server.resolvedUrls = null
@@ -429,7 +434,8 @@ export async function createServer(
_restartPromise: null,
_importGlobMap: new Map(),
_forceOptimizeOnRestart: false,
- _pendingRequests: new Map()
+ _pendingRequests: new Map(),
+ _fsDenyGlob: picomatch(config.server.fs.deny, { matchBase: true })
}
server.transformIndexHtml = createDevHtmlTransformFn(server)
diff --git a/packages/vite/src/node/server/middlewares/indexHtml.ts b/packages/vite/src/node/server/middlewares/indexHtml.ts
index a9de3793574fd0..199468727c17c2 100644
--- a/packages/vite/src/node/server/middlewares/indexHtml.ts
+++ b/packages/vite/src/node/server/middlewares/indexHtml.ts
@@ -19,19 +19,15 @@ import {
} from '../../plugins/html'
import type { ResolvedConfig, ViteDevServer } from '../..'
import { send } from '../send'
-import {
- CLIENT_PUBLIC_PATH,
- FS_PREFIX,
- NULL_BYTE_PLACEHOLDER,
- VALID_ID_PREFIX
-} from '../../constants'
+import { CLIENT_PUBLIC_PATH, FS_PREFIX } from '../../constants'
import {
cleanUrl,
ensureWatchedFile,
fsPathFromId,
injectQuery,
normalizePath,
- processSrcSetSync
+ processSrcSetSync,
+ wrapId
} from '../../utils'
import type { ModuleGraph } from '../moduleGraph'
@@ -144,7 +140,7 @@ const devHtmlHook: IndexHtmlTransformHook = async (
// and ids are properly handled
const validPath = `${htmlPath}${trailingSlash ? 'index.html' : ''}`
proxyModulePath = `\0${validPath}`
- proxyModuleUrl = `${VALID_ID_PREFIX}${NULL_BYTE_PLACEHOLDER}${validPath}`
+ proxyModuleUrl = wrapId(proxyModulePath)
}
const s = new MagicString(html)
diff --git a/packages/vite/src/node/server/middlewares/proxy.ts b/packages/vite/src/node/server/middlewares/proxy.ts
index 2958d70163fec6..dc9ff4753006aa 100644
--- a/packages/vite/src/node/server/middlewares/proxy.ts
+++ b/packages/vite/src/node/server/middlewares/proxy.ts
@@ -88,7 +88,9 @@ export function proxyMiddleware(
if (doesProxyContextMatchUrl(context, url)) {
const [proxy, opts] = proxies[context]
if (
- (opts.ws || opts.target?.toString().startsWith('ws:')) &&
+ (opts.ws ||
+ opts.target?.toString().startsWith('ws:') ||
+ opts.target?.toString().startsWith('wss:')) &&
req.headers['sec-websocket-protocol'] !== HMR_HEADER
) {
if (opts.rewrite) {
diff --git a/packages/vite/src/node/server/middlewares/static.ts b/packages/vite/src/node/server/middlewares/static.ts
index ca466b8dbffb6c..c877022a63cde7 100644
--- a/packages/vite/src/node/server/middlewares/static.ts
+++ b/packages/vite/src/node/server/middlewares/static.ts
@@ -3,7 +3,6 @@ import type { OutgoingHttpHeaders, ServerResponse } from 'node:http'
import type { Options } from 'sirv'
import sirv from 'sirv'
import type { Connect } from 'dep-types/connect'
-import micromatch from 'micromatch'
import type { ViteDevServer } from '../..'
import { FS_PREFIX } from '../../constants'
import {
@@ -18,8 +17,6 @@ import {
slash
} from '../../utils'
-const { isMatch } = micromatch
-
const sirvOptions = (headers?: OutgoingHttpHeaders): Options => {
return {
dev: true,
@@ -158,8 +155,6 @@ export function serveRawFsMiddleware(
}
}
-const _matchOptions = { matchBase: true }
-
export function isFileServingAllowed(
url: string,
server: ViteDevServer
@@ -168,8 +163,7 @@ export function isFileServingAllowed(
const file = fsPathFromUrl(url)
- if (server.config.server.fs.deny.some((i) => isMatch(file, i, _matchOptions)))
- return false
+ if (server._fsDenyGlob(file)) return false
if (server.moduleGraph.safeModulesPath.has(file)) return true
diff --git a/packages/vite/src/node/ssr/index.ts b/packages/vite/src/node/ssr/index.ts
index 7d2e4724f98b44..d23e78b18cae5f 100644
--- a/packages/vite/src/node/ssr/index.ts
+++ b/packages/vite/src/node/ssr/index.ts
@@ -41,8 +41,8 @@ export interface ResolvedSSROptions extends SSROptions {
export function resolveSSROptions(
ssr: SSROptions | undefined,
- buildSsrCjsExternalHeuristics?: boolean,
- preserveSymlinks?: boolean
+ preserveSymlinks: boolean,
+ buildSsrCjsExternalHeuristics?: boolean
): ResolvedSSROptions {
ssr ??= {}
const optimizeDeps = ssr.optimizeDeps ?? {}
diff --git a/packages/vite/src/node/ssr/ssrExternal.ts b/packages/vite/src/node/ssr/ssrExternal.ts
index d73d17c1d7c3c8..7c48c84db48743 100644
--- a/packages/vite/src/node/ssr/ssrExternal.ts
+++ b/packages/vite/src/node/ssr/ssrExternal.ts
@@ -1,7 +1,7 @@
import fs from 'node:fs'
import path from 'node:path'
import { createRequire } from 'node:module'
-import type { InternalResolveOptions } from '../plugins/resolve'
+import type { InternalResolveOptions, ResolveOptions } from '../plugins/resolve'
import { tryNodeResolve } from '../plugins/resolve'
import {
bareImportRE,
@@ -53,7 +53,7 @@ export function cjsSsrResolveExternals(
cjsSsrCollectExternals(
config.root,
- config.resolve.preserveSymlinks,
+ config.resolve,
ssrExternals,
seen,
config.logger
@@ -116,8 +116,8 @@ export function createIsConfiguredAsSsrExternal(
createFilter(undefined, noExternal, { resolve: false })
const resolveOptions: InternalResolveOptions = {
+ ...config.resolve,
root,
- preserveSymlinks: config.resolve.preserveSymlinks,
isProduction: false,
isBuild: true
}
@@ -211,7 +211,7 @@ function createIsSsrExternal(
// is used reverting to the Vite 2.9 SSR externalization heuristics
function cjsSsrCollectExternals(
root: string,
- preserveSymlinks: boolean | undefined,
+ resolveOptions: Required,
ssrExternals: Set,
seen: Set,
logger: Logger
@@ -227,9 +227,9 @@ function cjsSsrCollectExternals(
...rootPkg.dependencies
}
- const resolveOptions: InternalResolveOptions = {
+ const internalResolveOptions: InternalResolveOptions = {
+ ...resolveOptions,
root,
- preserveSymlinks,
isProduction: false,
isBuild: true
}
@@ -247,7 +247,7 @@ function cjsSsrCollectExternals(
esmEntry = tryNodeResolve(
id,
undefined,
- resolveOptions,
+ internalResolveOptions,
true, // we set `targetWeb` to `true` to get the ESM entry
undefined,
true
@@ -314,13 +314,7 @@ function cjsSsrCollectExternals(
}
for (const depRoot of depsToTrace) {
- cjsSsrCollectExternals(
- depRoot,
- preserveSymlinks,
- ssrExternals,
- seen,
- logger
- )
+ cjsSsrCollectExternals(depRoot, resolveOptions, ssrExternals, seen, logger)
}
}
diff --git a/packages/vite/src/node/ssr/ssrModuleLoader.ts b/packages/vite/src/node/ssr/ssrModuleLoader.ts
index 8f61125c134d8c..13b58541c64c1b 100644
--- a/packages/vite/src/node/ssr/ssrModuleLoader.ts
+++ b/packages/vite/src/node/ssr/ssrModuleLoader.ts
@@ -12,7 +12,6 @@ import { transformRequest } from '../server/transformRequest'
import type { InternalResolveOptions } from '../plugins/resolve'
import { tryNodeResolve } from '../plugins/resolve'
import { hookNodeResolve } from '../plugins/ssrRequireHook'
-import { NULL_BYTE_PLACEHOLDER } from '../constants'
import {
ssrDynamicImportKey,
ssrExportAllKey,
@@ -38,7 +37,7 @@ export async function ssrLoadModule(
urlStack: string[] = [],
fixStacktrace?: boolean
): Promise {
- url = unwrapId(url).replace(NULL_BYTE_PLACEHOLDER, '\0')
+ url = unwrapId(url)
// when we instantiate multiple dependency modules in parallel, they may
// point to shared modules. We need to avoid duplicate instantiation attempts
@@ -119,13 +118,15 @@ async function instantiateModule(
// CommonJS modules are preferred. We want to avoid ESM->ESM imports
// whenever possible, because `hookNodeResolve` can't intercept them.
const resolveOptions: InternalResolveOptions = {
- dedupe,
+ mainFields: ['main'],
+ browserField: true,
+ conditions: [],
extensions: ['.js', '.cjs', '.json'],
+ dedupe,
+ preserveSymlinks,
isBuild: true,
isProduction,
isRequire: true,
- mainFields: ['main'],
- preserveSymlinks,
root
}
@@ -138,7 +139,7 @@ async function instantiateModule(
return nodeImport(dep, mod.file!, resolveOptions)
}
// convert to rollup URL because `pendingImports`, `moduleGraph.urlToModuleMap` requires that
- dep = unwrapId(dep).replace(NULL_BYTE_PLACEHOLDER, '\0')
+ dep = unwrapId(dep)
if (!isCircular(dep) && !pendingImports.get(dep)?.some(isCircular)) {
pendingDeps.push(dep)
if (pendingDeps.length === 1) {
diff --git a/packages/vite/src/node/utils.ts b/packages/vite/src/node/utils.ts
index 8243dbcb831b63..2c6a8c790c607c 100644
--- a/packages/vite/src/node/utils.ts
+++ b/packages/vite/src/node/utils.ts
@@ -25,6 +25,7 @@ import {
DEFAULT_EXTENSIONS,
ENV_PUBLIC_PATH,
FS_PREFIX,
+ NULL_BYTE_PLACEHOLDER,
OPTIMIZABLE_ENTRY_RE,
VALID_ID_PREFIX,
loopbackHosts,
@@ -53,10 +54,24 @@ export function slash(p: string): string {
return p.replace(/\\/g, '/')
}
-// Strip valid id prefix. This is prepended to resolved Ids that are
-// not valid browser import specifiers by the importAnalysis plugin.
+/**
+ * Prepend `/@id/` and replace null byte so the id is URL-safe.
+ * This is prepended to resolved ids that are not valid browser
+ * import specifiers by the importAnalysis plugin.
+ */
+export function wrapId(id: string): string {
+ return id.startsWith(VALID_ID_PREFIX)
+ ? id
+ : VALID_ID_PREFIX + id.replace('\0', NULL_BYTE_PLACEHOLDER)
+}
+
+/**
+ * Undo {@link wrapId}'s `/@id/` and null byte replacements.
+ */
export function unwrapId(id: string): string {
- return id.startsWith(VALID_ID_PREFIX) ? id.slice(VALID_ID_PREFIX.length) : id
+ return id.startsWith(VALID_ID_PREFIX)
+ ? id.slice(VALID_ID_PREFIX.length).replace(NULL_BYTE_PLACEHOLDER, '\0')
+ : id
}
export const flattenId = (id: string): string =>
diff --git a/playground/alias/package.json b/playground/alias/package.json
index 4b49ca8d346ebf..56f95bdabb61f4 100644
--- a/playground/alias/package.json
+++ b/playground/alias/package.json
@@ -10,8 +10,8 @@
},
"dependencies": {
"aliased-module": "file:./dir/module",
- "vue": "^3.2.37",
- "@vue/shared": "^3.2.37"
+ "vue": "^3.2.39",
+ "@vue/shared": "^3.2.39"
},
"devDependencies": {
"resolve-linked": "workspace:*"
diff --git a/playground/assets/__tests__/assets.spec.ts b/playground/assets/__tests__/assets.spec.ts
index ceb259102d4031..768fadf7f35ce5 100644
--- a/playground/assets/__tests__/assets.spec.ts
+++ b/playground/assets/__tests__/assets.spec.ts
@@ -272,7 +272,7 @@ describe.runIf(isBuild)('encodeURI', () => {
test('img src with encodeURI', async () => {
const img = await page.$('.encodeURI')
expect(
- await (await img.getAttribute('src')).startsWith('data:image/png;base64')
+ (await img.getAttribute('src')).startsWith('data:image/png;base64')
).toBe(true)
})
})
@@ -281,6 +281,16 @@ test('new URL(..., import.meta.url)', async () => {
expect(await page.textContent('.import-meta-url')).toMatch(assetMatch)
})
+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(`${dynamic}`, import.meta.url)', async () => {
expect(await page.textContent('.dynamic-import-meta-url-1')).toMatch(
isBuild ? 'data:image/png;base64' : '/foo/nested/icon.png'
diff --git a/playground/assets/index.html b/playground/assets/index.html
index 42fa5498f28b8c..f897d61a355ed0 100644
--- a/playground/assets/index.html
+++ b/playground/assets/index.html
@@ -182,6 +182,14 @@ new URL('...', import.meta.url)
+new URL('@/...', import.meta.url)
+
+
+
+new URL('/...', import.meta.url)
+
+
+
new URL('...', import.meta.url,) (with comma)
@@ -354,6 +362,16 @@ style in svg
text('.import-meta-url', metaUrl)
document.querySelector('.import-meta-url-img').src = metaUrl
+ const metaUrlDep = new URL('@/asset.png', import.meta.url)
+ text('.import-meta-url-dep', metaUrlDep)
+ document.querySelector('.import-meta-url-dep-img').src = metaUrlDep
+
+ // testing URLs for public assets served at the public base path
+ // equivalent to `new URL(`${import.meta.env.BASE_URL}/icon.png`, self.location)
+ const metaUrlBasePath = new URL('/icon.png', import.meta.url)
+ text('.import-meta-url-base-path', metaUrlBasePath)
+ document.querySelector('.import-meta-url-base-path-img').src = metaUrlBasePath
+
// prettier-ignore
const metaUrlWithComma = new URL('./nested/asset.png', import.meta.url,)
text('.import-meta-url-comma', metaUrlWithComma)
diff --git a/playground/backend-integration/package.json b/playground/backend-integration/package.json
index 299ffe9d9920dd..581da6594ee3d3 100644
--- a/playground/backend-integration/package.json
+++ b/playground/backend-integration/package.json
@@ -9,8 +9,8 @@
"preview": "vite preview"
},
"devDependencies": {
- "sass": "^1.54.5",
+ "sass": "^1.55.0",
"tailwindcss": "^3.1.8",
- "fast-glob": "^3.2.11"
+ "fast-glob": "^3.2.12"
}
}
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..56757fc293dbba
--- /dev/null
+++ b/playground/css-dynamic-import/__tests__/css-dynamic-import.spec.ts
@@ -0,0 +1,121 @@
+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',
+ 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()
+ await new Promise((r) => setTimeout(r, 500))
+
+ try {
+ await page.goto(server.resolvedUrls.local[0])
+ await fn()
+ } finally {
+ 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')
+ expect(await getLinks()).toEqual([
+ {
+ pathname: expect.stringMatching(/^\/assets\/index\..+\.css$/),
+ rel: 'stylesheet',
+ as: ''
+ },
+ {
+ pathname: expect.stringMatching(/^\/assets\/dynamic\..+\.css$/),
+ rel: 'preload',
+ as: 'style'
+ },
+ {
+ pathname: expect.stringMatching(/^\/assets\/dynamic\..+\.js$/),
+ rel: 'modulepreload',
+ as: 'script'
+ },
+ {
+ pathname: expect.stringMatching(/^\/assets\/dynamic\..+\.css$/),
+ rel: 'stylesheet',
+ as: ''
+ },
+ {
+ pathname: expect.stringMatching(/^\/assets\/static\..+\.js$/),
+ rel: 'modulepreload',
+ as: 'script'
+ },
+ {
+ pathname: expect.stringMatching(/^\/assets\/index\..+\.js$/),
+ rel: 'modulepreload',
+ as: 'script'
+ }
+ ])
+ })
+ }
+ )
+
+ 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..ae33c33a5db107
--- /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/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-sourcemap/package.json b/playground/css-sourcemap/package.json
index cdc1d168f2d448..d61c74d04eab04 100644
--- a/playground/css-sourcemap/package.json
+++ b/playground/css-sourcemap/package.json
@@ -10,8 +10,8 @@
},
"devDependencies": {
"less": "^4.1.3",
- "magic-string": "^0.26.2",
- "sass": "^1.54.5",
+ "magic-string": "^0.26.4",
+ "sass": "^1.55.0",
"stylus": "^0.59.0"
}
}
diff --git a/playground/css/__tests__/css.spec.ts b/playground/css/__tests__/css.spec.ts
index f46e6d0bfdbabc..689e9c925f644a 100644
--- a/playground/css/__tests__/css.spec.ts
+++ b/playground/css/__tests__/css.spec.ts
@@ -9,7 +9,8 @@ import {
page,
removeFile,
serverLogs,
- untilUpdated
+ untilUpdated,
+ withRetry
} from '~utils'
// note: tests should retrieve the element at the beginning of test and reuse it
@@ -455,3 +456,17 @@ test.runIf(isBuild)('warning can be suppressed by esbuild.logOverride', () => {
expect(log).not.toMatch('unsupported-css-property')
})
})
+
+// NOTE: the match inline snapshot should generate by build mode
+test('async css order', async () => {
+ await withRetry(async () => {
+ expect(await getColor('.async-green')).toMatchInlineSnapshot('"green"')
+ expect(await getColor('.async-blue')).toMatchInlineSnapshot('"blue"')
+ }, true)
+})
+
+test('async css order with css modules', async () => {
+ await withRetry(async () => {
+ expect(await getColor('.modules-pink')).toMatchInlineSnapshot('"pink"')
+ }, true)
+})
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/main.js b/playground/css/main.js
index 39ccd916467faf..4e1ed37754968f 100644
--- a/playground/css/main.js
+++ b/playground/css/main.js
@@ -109,3 +109,5 @@ document
.classList.add(aliasModule.aliasedModule)
import './unsupported.css'
+
+import './async/index'
diff --git a/playground/css/package.json b/playground/css/package.json
index 87e768f5938630..a0d2bb41a00edf 100644
--- a/playground/css/package.json
+++ b/playground/css/package.json
@@ -14,10 +14,10 @@
"devDependencies": {
"css-dep": "link:./css-dep",
"css-js-dep": "file:./css-js-dep",
- "fast-glob": "^3.2.11",
+ "fast-glob": "^3.2.12",
"less": "^4.1.3",
"postcss-nested": "^5.0.6",
- "sass": "^1.54.5",
+ "sass": "^1.55.0",
"stylus": "^0.59.0"
}
}
diff --git a/playground/css/sass.scss b/playground/css/sass.scss
index 3c7095418e01e6..1db47622b016ad 100644
--- a/playground/css/sass.scss
+++ b/playground/css/sass.scss
@@ -3,6 +3,7 @@
@import 'css-dep'; // package w/ sass entry points
@import 'virtual-dep'; // virtual file added through importer
@import '@/pkg-dep'; // package w/out sass field
+@import '@/weapp.wxss'; // wxss file
.sass {
/* injected via vite.config.js */
diff --git a/playground/css/vite.config.js b/playground/css/vite.config.js
index d8333b07fb4d63..221c2a75187f35 100644
--- a/playground/css/vite.config.js
+++ b/playground/css/vite.config.js
@@ -45,9 +45,14 @@ module.exports = {
preprocessorOptions: {
scss: {
additionalData: `$injectedColor: orange;`,
- importer(url) {
- if (url === 'virtual-dep') return { contents: '' }
- }
+ importer: [
+ function (url) {
+ return url === 'virtual-dep' ? { contents: '' } : null
+ },
+ function (url) {
+ return url.endsWith('.wxss') ? { contents: '' } : null
+ }
+ ]
},
styl: {
additionalData: `$injectedColor ?= orange`,
diff --git a/playground/css/weapp.wxss b/playground/css/weapp.wxss
new file mode 100644
index 00000000000000..e69de29bb2d1d6
diff --git a/playground/dynamic-import/__tests__/dynamic-import.spec.ts b/playground/dynamic-import/__tests__/dynamic-import.spec.ts
index cec996b46a669f..8b599462470d18 100644
--- a/playground/dynamic-import/__tests__/dynamic-import.spec.ts
+++ b/playground/dynamic-import/__tests__/dynamic-import.spec.ts
@@ -1,5 +1,5 @@
import { expect, test } from 'vitest'
-import { getColor, page, serverLogs, untilUpdated } from '~utils'
+import { getColor, isBuild, page, serverLogs, untilUpdated } from '~utils'
test('should load literal dynamic import', async () => {
await page.click('.baz')
@@ -93,6 +93,22 @@ test('should load dynamic import with vars raw', async () => {
)
})
+test('should load dynamic import with vars url', async () => {
+ await untilUpdated(
+ () => page.textContent('.dynamic-import-with-vars-url'),
+ isBuild ? 'data:application/javascript' : '/alias/url.js',
+ true
+ )
+})
+
+test('should load dynamic import with vars worker', async () => {
+ await untilUpdated(
+ () => page.textContent('.dynamic-import-with-vars-worker'),
+ 'load worker',
+ true
+ )
+})
+
test('should load dynamic import with css in package', async () => {
await page.click('.pkg-css')
await untilUpdated(() => getColor('.pkg-css'), 'blue', true)
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..6206a6536b8064
--- /dev/null
+++ b/playground/dynamic-import/alias/worker.js
@@ -0,0 +1,5 @@
+self.onmessage = (event) => {
+ self.postMessage({
+ msg: 'load worker'
+ })
+}
diff --git a/playground/dynamic-import/index.html b/playground/dynamic-import/index.html
index 8eb5e60098a15c..d13e842804eaa7 100644
--- a/playground/dynamic-import/index.html
+++ b/playground/dynamic-import/index.html
@@ -19,6 +19,12 @@
dynamic-import-with-vars-raw
todo
+dynamic-import-with-vars-url
+todo
+
+dynamic-import-with-vars-worker
+todo
+
diff --git a/playground/dynamic-import/nested/index.js b/playground/dynamic-import/nested/index.js
index baad37bc0d9228..dc4992abc85021 100644
--- a/playground/dynamic-import/nested/index.js
+++ b/playground/dynamic-import/nested/index.js
@@ -97,6 +97,21 @@ 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())
diff --git a/playground/env/package.json b/playground/env/package.json
index 271cbf0a7d20c0..091afe4bd08a88 100644
--- a/playground/env/package.json
+++ b/playground/env/package.json
@@ -3,12 +3,9 @@
"private": true,
"version": "0.0.0",
"scripts": {
- "dev": "cross-env VITE_INLINE=inline-serve vite",
- "build": "cross-env VITE_INLINE=inline-build vite build",
+ "dev": "VITE_INLINE=inline-serve vite",
+ "build": "VITE_INLINE=inline-build vite build",
"debug": "node --inspect-brk ../../packages/vite/bin/vite",
"preview": "vite preview"
- },
- "devDependencies": {
- "cross-env": "^7.0.3"
}
}
diff --git a/playground/extensions/package.json b/playground/extensions/package.json
index 380aaa142fd0c4..ab6e8a04d8f746 100644
--- a/playground/extensions/package.json
+++ b/playground/extensions/package.json
@@ -9,6 +9,6 @@
"preview": "vite preview"
},
"dependencies": {
- "vue": "^3.2.37"
+ "vue": "^3.2.39"
}
}
diff --git a/playground/external/dep-that-imports-vue/package.json b/playground/external/dep-that-imports-vue/package.json
index 0fcd4b186a979b..ae2c4e1312514f 100644
--- a/playground/external/dep-that-imports-vue/package.json
+++ b/playground/external/dep-that-imports-vue/package.json
@@ -3,6 +3,6 @@
"private": true,
"version": "0.0.0",
"dependencies": {
- "vue": "^3.2.37"
+ "vue": "^3.2.39"
}
}
diff --git a/playground/external/dep-that-requires-vue/package.json b/playground/external/dep-that-requires-vue/package.json
index a71847a02bb969..92e8b85ceda1c6 100644
--- a/playground/external/dep-that-requires-vue/package.json
+++ b/playground/external/dep-that-requires-vue/package.json
@@ -3,6 +3,6 @@
"private": true,
"version": "0.0.0",
"dependencies": {
- "vue": "^3.2.37"
+ "vue": "^3.2.39"
}
}
diff --git a/playground/external/package.json b/playground/external/package.json
index 94fdc429ce2b0b..b50c2a299bfbe0 100644
--- a/playground/external/package.json
+++ b/playground/external/package.json
@@ -14,6 +14,6 @@
},
"devDependencies": {
"vite": "workspace:*",
- "vue": "^3.2.37"
+ "vue": "^3.2.39"
}
}
diff --git a/playground/fs-serve/__tests__/fs-serve.spec.ts b/playground/fs-serve/__tests__/fs-serve.spec.ts
index 8fcb3f61125158..90d35d21b04773 100644
--- a/playground/fs-serve/__tests__/fs-serve.spec.ts
+++ b/playground/fs-serve/__tests__/fs-serve.spec.ts
@@ -1,3 +1,4 @@
+import fetch from 'node-fetch'
import { beforeAll, describe, expect, test } from 'vitest'
import testJSON from '../safe.json'
import { isServe, page, viteTestUrl } from '~utils'
@@ -97,3 +98,11 @@ describe.runIf(isServe)('main', () => {
expect(await page.textContent('.unsafe-dotenv')).toBe('404')
})
})
+
+describe('fetch', () => {
+ // Note: this should pass in build too, but the test setup doesn't use Vite preview
+ test.runIf(isServe)('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/package.json b/playground/fs-serve/package.json
index c50be06a8cb286..6a5d18e2303c53 100644
--- a/playground/fs-serve/package.json
+++ b/playground/fs-serve/package.json
@@ -6,6 +6,6 @@
"dev": "vite root",
"build": "vite build root",
"debug": "node --inspect-brk ../../packages/vite/bin/vite",
- "preview": "vite preview"
+ "preview": "vite preview root"
}
}
diff --git a/playground/fs-serve/root/vite.config.js b/playground/fs-serve/root/vite.config.js
index 5712ad5acb3438..12d07754cbf10d 100644
--- a/playground/fs-serve/root/vite.config.js
+++ b/playground/fs-serve/root/vite.config.js
@@ -18,6 +18,14 @@ module.exports = {
},
hmr: {
overlay: false
+ },
+ headers: {
+ 'x-served-by': 'vite'
+ }
+ },
+ preview: {
+ headers: {
+ 'x-served-by': 'vite'
}
},
define: {
diff --git a/playground/hmr/__tests__/hmr.spec.ts b/playground/hmr/__tests__/hmr.spec.ts
index 3858719b772a37..ef8def29a389a5 100644
--- a/playground/hmr/__tests__/hmr.spec.ts
+++ b/playground/hmr/__tests__/hmr.spec.ts
@@ -627,4 +627,15 @@ if (!isBuild) {
btn = await page.$('button')
expect(await btn.textContent()).toBe('Compteur 0')
})
+
+ test('handle virtual module updates', async () => {
+ await page.goto(viteTestUrl)
+ const el = await page.$('.virtual')
+ expect(await el.textContent()).toBe('[success]')
+ editFile('importedVirtual.js', (code) => code.replace('[success]', '[wow]'))
+ await untilUpdated(async () => {
+ const el = await page.$('.virtual')
+ return await el.textContent()
+ }, '[wow]')
+ })
}
diff --git a/playground/hmr/hmr.ts b/playground/hmr/hmr.ts
index 97330f05f29f64..dc3c22eac9d56e 100644
--- a/playground/hmr/hmr.ts
+++ b/playground/hmr/hmr.ts
@@ -1,3 +1,5 @@
+// @ts-ignore
+import { virtual } from 'virtual:file'
import { foo as depFoo, nestedFoo } from './hmrDep'
import './importing-updated'
@@ -5,6 +7,7 @@ export const foo = 1
text('.app', foo)
text('.dep', depFoo)
text('.nested', nestedFoo)
+text('.virtual', virtual)
if (import.meta.hot) {
import.meta.hot.accept(({ foo }) => {
diff --git a/playground/hmr/importedVirtual.js b/playground/hmr/importedVirtual.js
new file mode 100644
index 00000000000000..8b0b417bc3113d
--- /dev/null
+++ b/playground/hmr/importedVirtual.js
@@ -0,0 +1 @@
+export const virtual = '[success]'
diff --git a/playground/hmr/index.html b/playground/hmr/index.html
index aafeaea5b565d4..28f08014036ade 100644
--- a/playground/hmr/index.html
+++ b/playground/hmr/index.html
@@ -19,6 +19,7 @@
+
diff --git a/playground/hmr/vite.config.ts b/playground/hmr/vite.config.ts
index 2ee03c28228ade..d68c0ed84e7135 100644
--- a/playground/hmr/vite.config.ts
+++ b/playground/hmr/vite.config.ts
@@ -19,6 +19,19 @@ export default defineConfig({
client.send('custom:remote-add-result', { result: a + b })
})
}
+ },
+ {
+ name: 'virtual-file',
+ resolveId(id) {
+ if (id === 'virtual:file') {
+ return '\0virtual:file'
+ }
+ },
+ load(id) {
+ if (id === '\0virtual:file') {
+ return 'import { virtual } from "/importedVirtual.js"; export { virtual };'
+ }
+ }
}
]
})
diff --git a/playground/html/__tests__/html.spec.ts b/playground/html/__tests__/html.spec.ts
index 388496bb49900f..c4563a5f632b81 100644
--- a/playground/html/__tests__/html.spec.ts
+++ b/playground/html/__tests__/html.spec.ts
@@ -256,3 +256,14 @@ test('importmap', () => {
'An import map is added after module script load was triggered.'
)
})
+
+describe('Valid HTML', () => {
+ test('valid HTML is parsed', async () => {
+ await page.goto(viteTestUrl + '/valid.html')
+ expect(await page.textContent('#no-quotes-on-attr')).toBe(
+ 'No quotes on Attr working'
+ )
+
+ expect(await getColor('#duplicated-attrs')).toBe('green')
+ })
+})
diff --git a/playground/html/valid.html b/playground/html/valid.html
index 9ff48bbeafba6b..a2bd28f802184e 100644
--- a/playground/html/valid.html
+++ b/playground/html/valid.html
@@ -5,3 +5,6 @@
+
+No quotes on Attr
+
\ No newline at end of file
diff --git a/playground/html/valid.js b/playground/html/valid.js
new file mode 100644
index 00000000000000..f7f62b132fbc94
--- /dev/null
+++ b/playground/html/valid.js
@@ -0,0 +1,3 @@
+document.getElementById(
+ `no-quotes-on-attr`
+).innerHTML = `No quotes on Attr working`
diff --git a/playground/json/package.json b/playground/json/package.json
index ef60b42ff96971..c329f7c4669df3 100644
--- a/playground/json/package.json
+++ b/playground/json/package.json
@@ -8,13 +8,12 @@
"debug": "node --inspect-brk ../../packages/vite/bin/vite",
"preview": "vite preview",
"dev:ssr": "node server",
- "serve:ssr": "cross-env NODE_ENV=production node server",
+ "serve:ssr": "NODE_ENV=production node server",
"debug:ssr": "node --inspect-brk server"
},
"devDependencies": {
- "cross-env": "^7.0.3",
"express": "^4.18.1",
"json-module": "file:./json-module",
- "vue": "^3.2.37"
+ "vue": "^3.2.39"
}
}
diff --git a/playground/multiple-entrypoints/package.json b/playground/multiple-entrypoints/package.json
index 88896854311469..05a184d7fb2c9f 100644
--- a/playground/multiple-entrypoints/package.json
+++ b/playground/multiple-entrypoints/package.json
@@ -9,7 +9,7 @@
"preview": "vite preview"
},
"devDependencies": {
- "fast-glob": "^3.2.11",
- "sass": "^1.54.5"
+ "fast-glob": "^3.2.12",
+ "sass": "^1.55.0"
}
}
diff --git a/playground/object-hooks/package.json b/playground/object-hooks/package.json
index 380aaa142fd0c4..ab6e8a04d8f746 100644
--- a/playground/object-hooks/package.json
+++ b/playground/object-hooks/package.json
@@ -9,6 +9,6 @@
"preview": "vite preview"
},
"dependencies": {
- "vue": "^3.2.37"
+ "vue": "^3.2.39"
}
}
diff --git a/playground/optimize-deps/package.json b/playground/optimize-deps/package.json
index 2ece5488da5425..4e0df0ab00e553 100644
--- a/playground/optimize-deps/package.json
+++ b/playground/optimize-deps/package.json
@@ -28,12 +28,12 @@
"added-in-entries": "file:./added-in-entries",
"lodash-es": "^4.17.21",
"nested-exclude": "file:./nested-exclude",
- "phoenix": "^1.6.11",
+ "phoenix": "^1.6.12",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"resolve-linked": "workspace:0.0.0",
"url": "^0.11.0",
- "vue": "^3.2.37",
+ "vue": "^3.2.39",
"vuex": "^4.0.2",
"lodash": "^4.17.21",
"lodash.clonedeep": "^4.5.0"
diff --git a/playground/optimize-deps/vite.config.js b/playground/optimize-deps/vite.config.js
index 84805e21560a72..d8e575271f2572 100644
--- a/playground/optimize-deps/vite.config.js
+++ b/playground/optimize-deps/vite.config.js
@@ -62,6 +62,12 @@ module.exports = {
res.statusCode = 200
res.end('pong')
})
+ },
+ configurePreviewServer({ middlewares }) {
+ middlewares.use('/ping', (_, res) => {
+ res.statusCode = 200
+ res.end('pong')
+ })
}
},
{
diff --git a/playground/preload/package.json b/playground/preload/package.json
index 07fad09b3b6f3f..154dd22889756e 100644
--- a/playground/preload/package.json
+++ b/playground/preload/package.json
@@ -9,7 +9,7 @@
"preview": "vite preview"
},
"dependencies": {
- "vue": "^3.2.37",
+ "vue": "^3.2.39",
"vue-router": "^4.1.5"
},
"devDependencies": {
diff --git a/playground/react-emotion/package.json b/playground/react-emotion/package.json
index bca5d25bc6a3e9..842f7c6402d84e 100644
--- a/playground/react-emotion/package.json
+++ b/playground/react-emotion/package.json
@@ -9,7 +9,7 @@
"preview": "vite preview"
},
"dependencies": {
- "@emotion/react": "^11.10.0",
+ "@emotion/react": "^11.10.4",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-switch": "^7.0.0"
diff --git a/playground/react-sourcemap/package.json b/playground/react-sourcemap/package.json
index 91aa3331b877b4..70398d97257914 100644
--- a/playground/react-sourcemap/package.json
+++ b/playground/react-sourcemap/package.json
@@ -4,9 +4,9 @@
"version": "0.0.0",
"scripts": {
"dev": "vite",
- "dev:classic": "cross-env USE_CLASSIC=1 vite",
+ "dev:classic": "USE_CLASSIC=1 vite",
"build": "vite build",
- "build:classic": "cross-env USE_CLASSIC=1 vite build",
+ "build:classic": "USE_CLASSIC=1 vite build",
"debug": "node --inspect-brk ../../packages/vite/bin/vite",
"preview": "vite preview"
},
@@ -15,7 +15,6 @@
"react-dom": "^18.2.0"
},
"devDependencies": {
- "@vitejs/plugin-react": "workspace:*",
- "cross-env": "^7.0.3"
+ "@vitejs/plugin-react": "workspace:*"
}
}
diff --git a/playground/resolve/package.json b/playground/resolve/package.json
index 1b8e76e353a7ff..760fba0c02f5a0 100644
--- a/playground/resolve/package.json
+++ b/playground/resolve/package.json
@@ -9,7 +9,7 @@
"preview": "vite preview"
},
"dependencies": {
- "@babel/runtime": "^7.18.9",
+ "@babel/runtime": "^7.19.0",
"es5-ext": "0.10.62",
"normalize.css": "^8.0.1",
"require-pkg-with-module-field": "link:./require-pkg-with-module-field",
diff --git a/playground/ssr-deps/package.json b/playground/ssr-deps/package.json
index ac82dadbcead63..785806c3bab816 100644
--- a/playground/ssr-deps/package.json
+++ b/playground/ssr-deps/package.json
@@ -5,7 +5,7 @@
"type": "module",
"scripts": {
"dev": "node server",
- "serve": "cross-env NODE_ENV=production node server",
+ "serve": "NODE_ENV=production node server",
"debug": "node --inspect-brk server"
},
"dependencies": {
@@ -32,7 +32,6 @@
"pkg-exports": "file:./pkg-exports"
},
"devDependencies": {
- "cross-env": "^7.0.3",
"express": "^4.18.1"
}
}
diff --git a/playground/ssr-html/__tests__/ssr-html.spec.ts b/playground/ssr-html/__tests__/ssr-html.spec.ts
index f29b0ac33cf5d8..94e64bdb5d8b8b 100644
--- a/playground/ssr-html/__tests__/ssr-html.spec.ts
+++ b/playground/ssr-html/__tests__/ssr-html.spec.ts
@@ -1,7 +1,7 @@
import fetch from 'node-fetch'
import { describe, expect, test } from 'vitest'
import { port } from './serve'
-import { page } from '~utils'
+import { editFile, isServe, page, untilUpdated } from '~utils'
const url = `http://localhost:${port}`
@@ -39,3 +39,19 @@ describe('injected inline scripts', () => {
}
})
})
+
+describe.runIf(isServe)('hmr', () => {
+ test('handle virtual module updates', async () => {
+ await page.goto(url)
+ const el = await page.$('.virtual')
+ expect(await el.textContent()).toBe('[success]')
+ editFile('src/importedVirtual.js', (code) =>
+ code.replace('[success]', '[wow]')
+ )
+ await page.waitForNavigation()
+ await untilUpdated(async () => {
+ const el = await page.$('.virtual')
+ return await el.textContent()
+ }, '[wow]')
+ })
+})
diff --git a/playground/ssr-html/index.html b/playground/ssr-html/index.html
index 995c828caae1a8..cca83257565a95 100644
--- a/playground/ssr-html/index.html
+++ b/playground/ssr-html/index.html
@@ -12,5 +12,6 @@
SSR Dynamic HTML
+