mirror of
https://github.com/vitejs/vite.git
synced 2024-11-21 14:48:41 +00:00
feat: build.modulePreload options (#9938)
This commit is contained in:
parent
66c90585e2
commit
e223f84af8
@ -17,14 +17,12 @@ The transform is performed with esbuild and the value should be a valid [esbuild
|
||||
|
||||
Note the build will fail if the code contains features that cannot be safely transpiled by esbuild. See [esbuild docs](https://esbuild.github.io/content-types/#javascript) for more details.
|
||||
|
||||
## build.polyfillModulePreload
|
||||
## build.modulePreload
|
||||
|
||||
- **Type:** `boolean`
|
||||
- **Type:** `boolean | { polyfill?: boolean, resolveDependencies?: ResolveModulePreloadDependenciesFn }`
|
||||
- **Default:** `true`
|
||||
|
||||
Whether to automatically inject [module preload polyfill](https://guybedford.com/es-module-preloading-integrity#modulepreload-polyfill).
|
||||
|
||||
If set to `true`, the polyfill is auto injected into the proxy module of each `index.html` entry. If the build is configured to use a non-html custom entry via `build.rollupOptions.input`, then it is necessary to manually import the polyfill in your custom entry:
|
||||
By default, a [module preload polyfill](https://guybedford.com/es-module-preloading-integrity#modulepreload-polyfill) is automatically injected. The polyfill is auto injected into the proxy module of each `index.html` entry. If the build is configured to use a non-HTML custom entry via `build.rollupOptions.input`, then it is necessary to manually import the polyfill in your custom entry:
|
||||
|
||||
```js
|
||||
import 'vite/modulepreload-polyfill'
|
||||
@ -32,6 +30,42 @@ import 'vite/modulepreload-polyfill'
|
||||
|
||||
Note: the polyfill does **not** apply to [Library Mode](/guide/build#library-mode). If you need to support browsers without native dynamic import, you should probably avoid using it in your library.
|
||||
|
||||
The polyfill can be disabled using `{ polyfill: false }`.
|
||||
|
||||
The list of chunks to preload for each dynamic import is computed by Vite. By default, an absolute path including the `base` will be used when loading these dependencies. If the `base` is relative (`''` or `'./'`), `import.meta.url` is used at runtime to avoid absolute paths that depend on the final deployed base.
|
||||
|
||||
There is experimental support for fine grained control over the dependencies list and their paths using the `resolveDependencies` function. It expects a function of type `ResolveModulePreloadDependenciesFn`:
|
||||
|
||||
```ts
|
||||
type ResolveModulePreloadDependenciesFn = (
|
||||
url: string,
|
||||
deps: string[],
|
||||
context: {
|
||||
importer: string
|
||||
}
|
||||
) => (string | { runtime?: string })[]
|
||||
```
|
||||
|
||||
The `resolveDependencies` function will be called for each dynamic import with a list of the chunks it depends on, and it will also be called for each chunk imported in entry HTML files. A new dependencies array can be returned with these filtered or more dependencies injected, and their paths modified. The `deps` paths are relative to the `build.outDir`. Returning a relative path to the `hostId` for `hostType === 'js'` is allowed, in which case `new URL(dep, import.meta.url)` is used to get an absolute path when injecting this module preload in the HTML head.
|
||||
|
||||
```js
|
||||
modulePreload: {
|
||||
resolveDependencies: (filename, deps, { hostId, hostType }) => {
|
||||
return deps.filter(condition)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The resolved dependency paths can be further modified using [`experimental.renderBuiltUrl`](../guide/build.md#advanced-base-options).
|
||||
|
||||
## build.polyfillModulePreload
|
||||
|
||||
- **Type:** `boolean`
|
||||
- **Default:** `true`
|
||||
- **Deprecated** use `build.modulePreload.polyfill` instead
|
||||
|
||||
Whether to automatically inject a [module preload polyfill](https://guybedford.com/es-module-preloading-integrity#modulepreload-polyfill).
|
||||
|
||||
## build.outDir
|
||||
|
||||
- **Type:** `string`
|
||||
|
@ -72,8 +72,15 @@ export interface BuildOptions {
|
||||
* whether to inject module preload polyfill.
|
||||
* Note: does not apply to library mode.
|
||||
* @default true
|
||||
* @deprecated use `modulePreload.polyfill` instead
|
||||
*/
|
||||
polyfillModulePreload?: boolean
|
||||
/**
|
||||
* Configure module preload
|
||||
* Note: does not apply to library mode.
|
||||
* @default true
|
||||
*/
|
||||
modulePreload?: boolean | ModulePreloadOptions
|
||||
/**
|
||||
* Directory relative from `root` where build output will be placed. If the
|
||||
* directory exists, it will be removed before the build.
|
||||
@ -228,16 +235,67 @@ export interface LibraryOptions {
|
||||
|
||||
export type LibraryFormats = 'es' | 'cjs' | 'umd' | 'iife'
|
||||
|
||||
export type ResolvedBuildOptions = Required<BuildOptions>
|
||||
export interface ModulePreloadOptions {
|
||||
/**
|
||||
* Whether to inject a module preload polyfill.
|
||||
* Note: does not apply to library mode.
|
||||
* @default true
|
||||
*/
|
||||
polyfill?: boolean
|
||||
/**
|
||||
* Resolve the list of dependencies to preload for a given dynamic import
|
||||
* @experimental
|
||||
*/
|
||||
resolveDependencies?: ResolveModulePreloadDependenciesFn
|
||||
}
|
||||
export interface ResolvedModulePreloadOptions {
|
||||
polyfill: boolean
|
||||
resolveDependencies?: ResolveModulePreloadDependenciesFn
|
||||
}
|
||||
|
||||
export type ResolveModulePreloadDependenciesFn = (
|
||||
filename: string,
|
||||
deps: string[],
|
||||
context: {
|
||||
hostId: string
|
||||
hostType: 'html' | 'js'
|
||||
}
|
||||
) => string[]
|
||||
|
||||
export interface ResolvedBuildOptions
|
||||
extends Required<Omit<BuildOptions, 'polyfillModulePreload'>> {
|
||||
modulePreload: false | ResolvedModulePreloadOptions
|
||||
}
|
||||
|
||||
export function resolveBuildOptions(
|
||||
raw: BuildOptions | undefined,
|
||||
isBuild: boolean,
|
||||
logger: Logger
|
||||
): ResolvedBuildOptions {
|
||||
const deprecatedPolyfillModulePreload = raw?.polyfillModulePreload
|
||||
if (raw) {
|
||||
const { polyfillModulePreload, ...rest } = raw
|
||||
raw = rest
|
||||
if (deprecatedPolyfillModulePreload !== undefined) {
|
||||
logger.warn(
|
||||
'polyfillModulePreload is deprecated. Use modulePreload.polyfill instead.'
|
||||
)
|
||||
}
|
||||
if (
|
||||
deprecatedPolyfillModulePreload === false &&
|
||||
raw.modulePreload === undefined
|
||||
) {
|
||||
raw.modulePreload = { polyfill: false }
|
||||
}
|
||||
}
|
||||
|
||||
const modulePreload = raw?.modulePreload
|
||||
const defaultModulePreload = {
|
||||
polyfill: true
|
||||
}
|
||||
|
||||
const resolved: ResolvedBuildOptions = {
|
||||
target: 'modules',
|
||||
polyfillModulePreload: true,
|
||||
outDir: 'dist',
|
||||
assetsDir: 'assets',
|
||||
assetsInlineLimit: 4096,
|
||||
@ -266,7 +324,17 @@ export function resolveBuildOptions(
|
||||
warnOnError: true,
|
||||
exclude: [/node_modules/],
|
||||
...raw?.dynamicImportVarsOptions
|
||||
}
|
||||
},
|
||||
// Resolve to false | object
|
||||
modulePreload:
|
||||
modulePreload === false
|
||||
? false
|
||||
: typeof modulePreload === 'object'
|
||||
? {
|
||||
...defaultModulePreload,
|
||||
...modulePreload
|
||||
}
|
||||
: defaultModulePreload
|
||||
}
|
||||
|
||||
// handle special build targets
|
||||
@ -903,19 +971,16 @@ export type RenderBuiltAssetUrl = (
|
||||
}
|
||||
) => string | { relative?: boolean; runtime?: string } | undefined
|
||||
|
||||
export function toOutputFilePathInString(
|
||||
export function toOutputFilePathInJS(
|
||||
filename: string,
|
||||
type: 'asset' | 'public',
|
||||
hostId: string,
|
||||
hostType: 'js' | 'css' | 'html',
|
||||
config: ResolvedConfig,
|
||||
format: InternalModuleFormat,
|
||||
toRelative: (
|
||||
filename: string,
|
||||
hostType: string
|
||||
) => string | { runtime: string } = getToImportMetaURLBasedRelativePath(
|
||||
format
|
||||
)
|
||||
) => string | { runtime: string }
|
||||
): string | { runtime: string } {
|
||||
const { renderBuiltUrl } = config.experimental
|
||||
let relative = config.base === '' || config.base === './'
|
||||
@ -943,7 +1008,7 @@ export function toOutputFilePathInString(
|
||||
return config.base + filename
|
||||
}
|
||||
|
||||
function getToImportMetaURLBasedRelativePath(
|
||||
export function createToImportMetaURLBasedRelativeRuntime(
|
||||
format: InternalModuleFormat
|
||||
): (filename: string, importer: string) => { runtime: string } {
|
||||
const toRelativePath = relativeUrlMechanisms[format]
|
||||
|
@ -62,7 +62,12 @@ import { resolveSSROptions } from './ssr'
|
||||
|
||||
const debug = createDebugger('vite:config')
|
||||
|
||||
export type { RenderBuiltAssetUrl } from './build'
|
||||
export type {
|
||||
RenderBuiltAssetUrl,
|
||||
ModulePreloadOptions,
|
||||
ResolvedModulePreloadOptions,
|
||||
ResolveModulePreloadDependenciesFn
|
||||
} from './build'
|
||||
|
||||
// NOTE: every export in this file is re-exported from ./index.ts so it will
|
||||
// be part of the public API.
|
||||
|
@ -13,7 +13,10 @@ import type {
|
||||
} from 'rollup'
|
||||
import MagicString from 'magic-string'
|
||||
import colors from 'picocolors'
|
||||
import { toOutputFilePathInString } from '../build'
|
||||
import {
|
||||
createToImportMetaURLBasedRelativeRuntime,
|
||||
toOutputFilePathInJS
|
||||
} from '../build'
|
||||
import type { Plugin } from '../plugin'
|
||||
import type { ResolvedConfig } from '../config'
|
||||
import { cleanUrl, getHash, normalizePath } from '../utils'
|
||||
@ -57,6 +60,10 @@ export function renderAssetUrlInJS(
|
||||
opts: NormalizedOutputOptions,
|
||||
code: string
|
||||
): MagicString | undefined {
|
||||
const toRelativeRuntime = createToImportMetaURLBasedRelativeRuntime(
|
||||
opts.format
|
||||
)
|
||||
|
||||
let match: RegExpExecArray | null
|
||||
let s: MagicString | undefined
|
||||
|
||||
@ -76,13 +83,13 @@ export function renderAssetUrlInJS(
|
||||
const file = getAssetFilename(hash, config) || ctx.getFileName(hash)
|
||||
chunk.viteMetadata.importedAssets.add(cleanUrl(file))
|
||||
const filename = file + postfix
|
||||
const replacement = toOutputFilePathInString(
|
||||
const replacement = toOutputFilePathInJS(
|
||||
filename,
|
||||
'asset',
|
||||
chunk.fileName,
|
||||
'js',
|
||||
config,
|
||||
opts.format
|
||||
toRelativeRuntime
|
||||
)
|
||||
const replacementString =
|
||||
typeof replacement === 'string'
|
||||
@ -100,13 +107,13 @@ export function renderAssetUrlInJS(
|
||||
s ||= new MagicString(code)
|
||||
const [full, hash] = match
|
||||
const publicUrl = publicAssetUrlMap.get(hash)!.slice(1)
|
||||
const replacement = toOutputFilePathInString(
|
||||
const replacement = toOutputFilePathInJS(
|
||||
publicUrl,
|
||||
'public',
|
||||
chunk.fileName,
|
||||
'js',
|
||||
config,
|
||||
opts.format
|
||||
toRelativeRuntime
|
||||
)
|
||||
const replacementString =
|
||||
typeof replacement === 'string'
|
||||
|
@ -581,8 +581,10 @@ export function buildHtmlPlugin(config: ResolvedConfig): Plugin {
|
||||
processedHtml.set(id, s.toString())
|
||||
|
||||
// inject module preload polyfill only when configured and needed
|
||||
const { modulePreload } = config.build
|
||||
if (
|
||||
config.build.polyfillModulePreload &&
|
||||
(modulePreload === true ||
|
||||
(typeof modulePreload === 'object' && modulePreload.polyfill)) &&
|
||||
(someScriptsAreAsync || someScriptsAreDefer)
|
||||
) {
|
||||
js = `import "${modulePreloadPolyfillId}";\n${js}`
|
||||
@ -627,14 +629,14 @@ export function buildHtmlPlugin(config: ResolvedConfig): Plugin {
|
||||
})
|
||||
|
||||
const toPreloadTag = (
|
||||
chunk: OutputChunk,
|
||||
filename: string,
|
||||
toOutputPath: (filename: string) => string
|
||||
): HtmlTagDescriptor => ({
|
||||
tag: 'link',
|
||||
attrs: {
|
||||
rel: 'modulepreload',
|
||||
crossorigin: true,
|
||||
href: toOutputPath(chunk.fileName)
|
||||
href: toOutputPath(filename)
|
||||
}
|
||||
})
|
||||
|
||||
@ -726,15 +728,28 @@ export function buildHtmlPlugin(config: ResolvedConfig): Plugin {
|
||||
// when not inlined, inject <script> for entry and modulepreload its dependencies
|
||||
// when inlined, discard entry chunk and inject <script> for everything in post-order
|
||||
const imports = getImportedChunks(chunk)
|
||||
const assetTags = canInlineEntry
|
||||
? imports.map((chunk) =>
|
||||
toScriptTag(chunk, toOutputAssetFilePath, isAsync)
|
||||
)
|
||||
: [
|
||||
toScriptTag(chunk, toOutputAssetFilePath, isAsync),
|
||||
...imports.map((i) => toPreloadTag(i, toOutputAssetFilePath))
|
||||
]
|
||||
|
||||
let assetTags: HtmlTagDescriptor[]
|
||||
if (canInlineEntry) {
|
||||
assetTags = imports.map((chunk) =>
|
||||
toScriptTag(chunk, toOutputAssetFilePath, isAsync)
|
||||
)
|
||||
} else {
|
||||
const { modulePreload } = config.build
|
||||
const resolveDependencies =
|
||||
typeof modulePreload === 'object' &&
|
||||
modulePreload.resolveDependencies
|
||||
const importsFileNames = imports.map((chunk) => chunk.fileName)
|
||||
const resolvedDeps = resolveDependencies
|
||||
? resolveDependencies(chunk.fileName, importsFileNames, {
|
||||
hostId: relativeUrlPath,
|
||||
hostType: 'html'
|
||||
})
|
||||
: importsFileNames
|
||||
assetTags = [
|
||||
toScriptTag(chunk, toOutputAssetFilePath, isAsync),
|
||||
...resolvedDeps.map((i) => toPreloadTag(i, toOutputAssetFilePath))
|
||||
]
|
||||
}
|
||||
assetTags.push(...getCssTagsForChunk(chunk, toOutputAssetFilePath))
|
||||
|
||||
result = injectToHead(result, assetTags)
|
||||
|
@ -16,6 +16,7 @@ import {
|
||||
import type { Plugin } from '../plugin'
|
||||
import { getDepOptimizationConfig } from '../config'
|
||||
import type { ResolvedConfig } from '../config'
|
||||
import { toOutputFilePathInJS } from '../build'
|
||||
import { genSourceMapUrl } from '../server/sourcemap'
|
||||
import { getDepsOptimizer, optimizedDepNeedsInterop } from '../optimizer'
|
||||
import { isCSSRequest, removedPureCssFilesCache } from './css'
|
||||
@ -40,6 +41,11 @@ const dynamicImportPrefixRE = /import\s*\(/
|
||||
const optimizedDepChunkRE = /\/chunk-[A-Z0-9]{8}\.js/
|
||||
const optimizedDepDynamicRE = /-[A-Z0-9]{8}\.js/
|
||||
|
||||
function toRelativePath(filename: string, importer: string) {
|
||||
const relPath = path.relative(path.dirname(importer), filename)
|
||||
return relPath.startsWith('.') ? relPath : `./${relPath}`
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper for preloading CSS and direct imports of async chunks in parallel to
|
||||
* the async chunk itself.
|
||||
@ -124,16 +130,49 @@ function preload(
|
||||
export function buildImportAnalysisPlugin(config: ResolvedConfig): Plugin {
|
||||
const ssr = !!config.build.ssr
|
||||
const isWorker = config.isWorker
|
||||
const insertPreload = !(ssr || !!config.build.lib || isWorker)
|
||||
const insertPreload = !(
|
||||
ssr ||
|
||||
!!config.build.lib ||
|
||||
isWorker ||
|
||||
config.build.modulePreload === false
|
||||
)
|
||||
|
||||
const relativePreloadUrls = config.base === './' || config.base === ''
|
||||
const resolveModulePreloadDependencies =
|
||||
config.build.modulePreload && config.build.modulePreload.resolveDependencies
|
||||
const renderBuiltUrl = config.experimental.renderBuiltUrl
|
||||
const customModulePreloadPaths = !!(
|
||||
resolveModulePreloadDependencies || renderBuiltUrl
|
||||
)
|
||||
const isRelativeBase = config.base === './' || config.base === ''
|
||||
const optimizeModulePreloadRelativePaths =
|
||||
isRelativeBase && !customModulePreloadPaths
|
||||
|
||||
const scriptRel = config.build.polyfillModulePreload
|
||||
? `'modulepreload'`
|
||||
: `(${detectScriptRel.toString()})()`
|
||||
const assetsURL = relativePreloadUrls
|
||||
? `function(dep,importerUrl) { return new URL(dep, importerUrl).href }`
|
||||
: `function(dep) { return ${JSON.stringify(config.base)}+dep }`
|
||||
const { modulePreload } = config.build
|
||||
const scriptRel =
|
||||
modulePreload && modulePreload.polyfill
|
||||
? `'modulepreload'`
|
||||
: `(${detectScriptRel.toString()})()`
|
||||
|
||||
// There are three different cases for the preload list format in __vitePreload
|
||||
//
|
||||
// __vitePreload(() => import(asyncChunk), [ ...deps... ])
|
||||
//
|
||||
// This is maintained to keep backwards compatibility as some users developed plugins
|
||||
// using regex over this list to workaround the fact that module preload wasn't
|
||||
// configurable.
|
||||
const assetsURL = customModulePreloadPaths
|
||||
? // If `experimental.renderBuiltUrl` or `build.modulePreload.resolveDependencies` are used
|
||||
// the dependencies are already resolved. To avoid the need for `new URL(dep, import.meta.url)`
|
||||
// a helper `__vitePreloadRelativeDep` is used to resolve from relative paths which can be minimized.
|
||||
`function(dep, importerUrl) { return dep.startsWith('.') ? new URL(dep, importerUrl).href : dep }`
|
||||
: optimizeModulePreloadRelativePaths
|
||||
? // If there isn't custom resolvers affecting the deps list, deps in the list are relative
|
||||
// to the current chunk and are resolved to absolute URL by the __vitePreload helper itself.
|
||||
// The importerUrl is passed as third parameter to __vitePreload in this case
|
||||
`function(dep, importerUrl) { return new URL(dep, importerUrl).href }`
|
||||
: // If the base isn't relative, then the deps are relative to the projects `outDir` and the base
|
||||
// is appendended inside __vitePreload too.
|
||||
`function(dep) { return ${JSON.stringify(config.base)}+dep }`
|
||||
const preloadCode = `const scriptRel = ${scriptRel};const assetsURL = ${assetsURL};const seen = {};export const ${preloadMethod} = ${preload.toString()}`
|
||||
|
||||
return {
|
||||
@ -258,7 +297,9 @@ export function buildImportAnalysisPlugin(config: ResolvedConfig): Plugin {
|
||||
str().appendRight(
|
||||
expEnd,
|
||||
`,${isModernFlag}?"${preloadMarker}":void 0${
|
||||
relativePreloadUrls ? ',import.meta.url' : ''
|
||||
optimizeModulePreloadRelativePaths || customModulePreloadPaths
|
||||
? ',import.meta.url'
|
||||
: ''
|
||||
})`
|
||||
)
|
||||
}
|
||||
@ -383,7 +424,12 @@ export function buildImportAnalysisPlugin(config: ResolvedConfig): Plugin {
|
||||
},
|
||||
|
||||
generateBundle({ format }, bundle) {
|
||||
if (format !== 'es' || ssr || isWorker) {
|
||||
if (
|
||||
format !== 'es' ||
|
||||
ssr ||
|
||||
isWorker ||
|
||||
config.build.modulePreload === false
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
@ -423,7 +469,14 @@ export function buildImportAnalysisPlugin(config: ResolvedConfig): Plugin {
|
||||
const deps: Set<string> = new Set()
|
||||
let hasRemovedPureCssChunk = false
|
||||
|
||||
let normalizedFile: string | undefined = undefined
|
||||
|
||||
if (url) {
|
||||
normalizedFile = path.posix.join(
|
||||
path.posix.dirname(chunk.fileName),
|
||||
url
|
||||
)
|
||||
|
||||
const ownerFilename = chunk.fileName
|
||||
// literal import - trace direct imports and add to deps
|
||||
const analyzed: Set<string> = new Set<string>()
|
||||
@ -458,10 +511,6 @@ export function buildImportAnalysisPlugin(config: ResolvedConfig): Plugin {
|
||||
}
|
||||
}
|
||||
}
|
||||
const normalizedFile = path.posix.join(
|
||||
path.posix.dirname(chunk.fileName),
|
||||
url
|
||||
)
|
||||
addDeps(normalizedFile)
|
||||
}
|
||||
|
||||
@ -472,25 +521,71 @@ export function buildImportAnalysisPlugin(config: ResolvedConfig): Plugin {
|
||||
}
|
||||
|
||||
if (markerStartPos > 0) {
|
||||
// the dep list includes the main chunk, so only need to reload when there are actual other deps.
|
||||
const depsArray =
|
||||
deps.size > 1 ||
|
||||
// main chunk is removed
|
||||
(hasRemovedPureCssChunk && deps.size > 0)
|
||||
? [...deps]
|
||||
: []
|
||||
|
||||
let renderedDeps: string[]
|
||||
if (normalizedFile && customModulePreloadPaths) {
|
||||
const { modulePreload } = config.build
|
||||
const resolveDependencies =
|
||||
modulePreload && modulePreload.resolveDependencies
|
||||
let resolvedDeps: string[]
|
||||
if (resolveDependencies) {
|
||||
// We can't let the user remove css deps as these aren't really preloads, they are just using
|
||||
// the same mechanism as module preloads for this chunk
|
||||
const cssDeps: string[] = []
|
||||
const otherDeps: string[] = []
|
||||
for (const dep of depsArray) {
|
||||
;(dep.endsWith('.css') ? cssDeps : otherDeps).push(dep)
|
||||
}
|
||||
resolvedDeps = [
|
||||
...resolveDependencies(normalizedFile, otherDeps, {
|
||||
hostId: file,
|
||||
hostType: 'js'
|
||||
}),
|
||||
...cssDeps
|
||||
]
|
||||
} else {
|
||||
resolvedDeps = depsArray
|
||||
}
|
||||
|
||||
renderedDeps = resolvedDeps.map((dep: string) => {
|
||||
const replacement = toOutputFilePathInJS(
|
||||
dep,
|
||||
'asset',
|
||||
chunk.fileName,
|
||||
'js',
|
||||
config,
|
||||
toRelativePath
|
||||
)
|
||||
const replacementString =
|
||||
typeof replacement === 'string'
|
||||
? JSON.stringify(replacement)
|
||||
: replacement.runtime
|
||||
|
||||
return replacementString
|
||||
})
|
||||
} else {
|
||||
renderedDeps = depsArray.map((d) =>
|
||||
// Don't include the assets dir if the default asset file names
|
||||
// are used, the path will be reconstructed by the import preload helper
|
||||
JSON.stringify(
|
||||
optimizeModulePreloadRelativePaths
|
||||
? toRelativePath(d, file)
|
||||
: d
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
s.overwrite(
|
||||
markerStartPos,
|
||||
markerStartPos + preloadMarkerWithQuote.length,
|
||||
// the dep list includes the main chunk, so only need to reload when there are
|
||||
// actual other deps. Don't include the assets dir if the default asset file names
|
||||
// are used, the path will be reconstructed by the import preload helper
|
||||
deps.size > 1 ||
|
||||
// main chunk is removed
|
||||
(hasRemovedPureCssChunk && deps.size > 0)
|
||||
? `[${[...deps]
|
||||
.map((d) =>
|
||||
JSON.stringify(
|
||||
relativePreloadUrls
|
||||
? path.relative(path.dirname(file), d)
|
||||
: d
|
||||
)
|
||||
)
|
||||
.join(',')}]`
|
||||
: `[]`,
|
||||
`[${renderedDeps.join(',')}]`,
|
||||
{ contentOnly: true }
|
||||
)
|
||||
rewroteMarkerStartPos.add(markerStartPos)
|
||||
|
@ -37,6 +37,7 @@ export async function resolvePlugins(
|
||||
const buildPlugins = isBuild
|
||||
? (await import('../build')).resolveBuildPlugins(config)
|
||||
: { pre: [], post: [] }
|
||||
const { modulePreload } = config.build
|
||||
|
||||
return [
|
||||
isWatch ? ensureWatchPlugin() : null,
|
||||
@ -44,7 +45,8 @@ export async function resolvePlugins(
|
||||
preAliasPlugin(config),
|
||||
aliasPlugin({ entries: config.resolve.alias }),
|
||||
...prePlugins,
|
||||
config.build.polyfillModulePreload
|
||||
modulePreload === true ||
|
||||
(typeof modulePreload === 'object' && modulePreload.polyfill)
|
||||
? modulePreloadPolyfillPlugin(config)
|
||||
: null,
|
||||
...(isDepsOptimizerEnabled(config, false) ||
|
||||
|
@ -6,7 +6,11 @@ import type { Plugin } from '../plugin'
|
||||
import type { ViteDevServer } from '../server'
|
||||
import { ENV_ENTRY, ENV_PUBLIC_PATH } from '../constants'
|
||||
import { cleanUrl, getHash, injectQuery, parseRequest } from '../utils'
|
||||
import { onRollupWarning, toOutputFilePathInString } from '../build'
|
||||
import {
|
||||
createToImportMetaURLBasedRelativeRuntime,
|
||||
onRollupWarning,
|
||||
toOutputFilePathInJS
|
||||
} from '../build'
|
||||
import { getDepsOptimizer } from '../optimizer'
|
||||
import { fileToUrl } from './asset'
|
||||
|
||||
@ -318,6 +322,10 @@ export function webWorkerPlugin(config: ResolvedConfig): Plugin {
|
||||
)
|
||||
}
|
||||
if (code.match(workerAssetUrlRE) || code.includes('import.meta.url')) {
|
||||
const toRelativeRuntime = createToImportMetaURLBasedRelativeRuntime(
|
||||
outputOptions.format
|
||||
)
|
||||
|
||||
let match: RegExpExecArray | null
|
||||
s = new MagicString(code)
|
||||
|
||||
@ -328,13 +336,13 @@ export function webWorkerPlugin(config: ResolvedConfig): Plugin {
|
||||
while ((match = workerAssetUrlRE.exec(code))) {
|
||||
const [full, hash] = match
|
||||
const filename = fileNameHash.get(hash)!
|
||||
const replacement = toOutputFilePathInString(
|
||||
const replacement = toOutputFilePathInJS(
|
||||
filename,
|
||||
'asset',
|
||||
chunk.fileName,
|
||||
'js',
|
||||
config,
|
||||
outputOptions.format
|
||||
toRelativeRuntime
|
||||
)
|
||||
const replacementString =
|
||||
typeof replacement === 'string'
|
||||
|
@ -0,0 +1,22 @@
|
||||
import { describe, expect, test } from 'vitest'
|
||||
import { browserLogs, isBuild, page, viteTestUrl } from '~utils'
|
||||
|
||||
test('should have no 404s', () => {
|
||||
browserLogs.forEach((msg) => {
|
||||
expect(msg).not.toMatch('404')
|
||||
})
|
||||
})
|
||||
|
||||
describe.runIf(isBuild)('build', () => {
|
||||
test('dynamic import', async () => {
|
||||
const appHtml = await page.content()
|
||||
expect(appHtml).toMatch('This is <b>home</b> page.')
|
||||
})
|
||||
|
||||
test('dynamic import with comments', async () => {
|
||||
await page.goto(viteTestUrl + '/#/hello')
|
||||
const html = await page.content()
|
||||
expect(html).not.toMatch(/link rel="modulepreload"/)
|
||||
expect(html).not.toMatch(/link rel="stylesheet"/)
|
||||
})
|
||||
})
|
@ -0,0 +1 @@
|
||||
module.exports = require('../../vite.config-preload-disabled')
|
@ -0,0 +1,27 @@
|
||||
import { describe, expect, test } from 'vitest'
|
||||
import { browserLogs, isBuild, page, viteTestUrl } from '~utils'
|
||||
|
||||
test('should have no 404s', () => {
|
||||
browserLogs.forEach((msg) => {
|
||||
expect(msg).not.toMatch('404')
|
||||
})
|
||||
})
|
||||
|
||||
describe.runIf(isBuild)('build', () => {
|
||||
test('dynamic import', async () => {
|
||||
const appHtml = await page.content()
|
||||
expect(appHtml).toMatch('This is <b>home</b> page.')
|
||||
})
|
||||
|
||||
test('dynamic import with comments', async () => {
|
||||
await page.goto(viteTestUrl + '/#/hello')
|
||||
const html = await page.content()
|
||||
expect(html).toMatch(
|
||||
/link rel="modulepreload".*?href="http.*?\/Hello\.\w{8}\.js"/
|
||||
)
|
||||
expect(html).toMatch(/link rel="modulepreload".*?href="\/preloaded.js"/)
|
||||
expect(html).toMatch(
|
||||
/link rel="stylesheet".*?href="http.*?\/Hello\.\w{8}\.css"/
|
||||
)
|
||||
})
|
||||
})
|
1
playground/preload/__tests__/resolve-deps/vite.config.js
Normal file
1
playground/preload/__tests__/resolve-deps/vite.config.js
Normal file
@ -0,0 +1 @@
|
||||
module.exports = require('../../vite.config-resolve-deps')
|
@ -6,7 +6,15 @@
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"debug": "node --inspect-brk ../../packages/vite/bin/vite",
|
||||
"preview": "vite preview"
|
||||
"preview": "vite preview",
|
||||
"dev:resolve-deps": "vite --config vite.config-resolve-deps.ts",
|
||||
"build:resolve-deps": "vite build --config vite.config-resolve-deps.ts",
|
||||
"debug:resolve-deps": "node --inspect-brk ../../packages/vite/bin/vite --config vite.config-resolve-deps.ts",
|
||||
"preview:resolve-deps": "vite preview --config vite.config-resolve-deps.ts",
|
||||
"dev:preload-disabled": "vite --config vite.config-preload-disabled.ts",
|
||||
"build:preload-disabled": "vite build --config vite.config-preload-disabled.ts",
|
||||
"debug:preload-disabled": "node --inspect-brk ../../packages/vite/bin/vite --config vite.config-preload-disabled.ts",
|
||||
"preview:preload-disabled": "vite preview --config vite.config-preload-disabled.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"vue": "^3.2.39",
|
||||
|
1
playground/preload/public/preloaded.js
Normal file
1
playground/preload/public/preloaded.js
Normal file
@ -0,0 +1 @@
|
||||
console.log('preloaded')
|
18
playground/preload/vite.config-preload-disabled.ts
Normal file
18
playground/preload/vite.config-preload-disabled.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import vuePlugin from '@vitejs/plugin-vue'
|
||||
import { defineConfig } from 'vite'
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [vuePlugin()],
|
||||
build: {
|
||||
minify: 'terser',
|
||||
terserOptions: {
|
||||
format: {
|
||||
beautify: true
|
||||
},
|
||||
compress: {
|
||||
passes: 3
|
||||
}
|
||||
},
|
||||
modulePreload: false
|
||||
}
|
||||
})
|
33
playground/preload/vite.config-resolve-deps.ts
Normal file
33
playground/preload/vite.config-resolve-deps.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import vuePlugin from '@vitejs/plugin-vue'
|
||||
import { defineConfig } from 'vite'
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [vuePlugin()],
|
||||
build: {
|
||||
minify: 'terser',
|
||||
terserOptions: {
|
||||
format: {
|
||||
beautify: true
|
||||
},
|
||||
compress: {
|
||||
passes: 3
|
||||
}
|
||||
},
|
||||
modulePreload: {
|
||||
resolveDependencies(filename, deps, { hostId, hostType }) {
|
||||
if (filename.includes('Hello')) {
|
||||
return [...deps, 'preloaded.js']
|
||||
}
|
||||
return deps
|
||||
}
|
||||
}
|
||||
},
|
||||
experimental: {
|
||||
renderBuiltUrl(filename, { hostId, hostType }) {
|
||||
if (filename.includes('preloaded')) {
|
||||
return { runtime: `""+${JSON.stringify('/' + filename)}` }
|
||||
}
|
||||
return { relative: true }
|
||||
}
|
||||
}
|
||||
})
|
Loading…
Reference in New Issue
Block a user