Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 47 additions & 5 deletions packages/vite/src/node/plugins/html.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,11 @@ export const isAsyncScriptMap: WeakMap<
Map<string, boolean>
> = new WeakMap()

export const scriptExtraAttrsMap: WeakMap<
ResolvedConfig,
Map<string, Record<string, string | boolean>>
> = new WeakMap()

export function nodeIsElement(
node: DefaultTreeAdapterMap['node'],
): node is DefaultTreeAdapterMap['element'] {
Expand Down Expand Up @@ -222,18 +227,30 @@ export async function traverseHtml(
}
}

// Attributes that are handled specially by Vite and should not be
// forwarded to the output script tag as-is.
const viteHandledScriptAttrs = new Set([
'src',
'type',
'async',
'crossorigin',
'vite-ignore',
])

export function getScriptInfo(node: DefaultTreeAdapterMap['element']): {
src: Token.Attribute | undefined
srcSourceCodeLocation: Token.Location | undefined
isModule: boolean
isAsync: boolean
isIgnored: boolean
extraAttrs: Record<string, string | boolean>
} {
let src: Token.Attribute | undefined
let srcSourceCodeLocation: Token.Location | undefined
let isModule = false
let isAsync = false
let isIgnored = false
const extraAttrs: Record<string, string | boolean> = {}
for (const p of node.attrs) {
if (p.prefix !== undefined) continue
if (p.name === 'src') {
Expand All @@ -247,9 +264,18 @@ export function getScriptInfo(node: DefaultTreeAdapterMap['element']): {
isAsync = true
} else if (p.name === 'vite-ignore') {
isIgnored = true
} else if (!viteHandledScriptAttrs.has(p.name)) {
extraAttrs[p.name] = p.value === '' ? true : p.value
}
}
return { src, srcSourceCodeLocation, isModule, isAsync, isIgnored }
return {
src,
srcSourceCodeLocation,
isModule,
isAsync,
isIgnored,
extraAttrs,
}
}

const attrValueStartRE = /=\s*(.)/
Expand Down Expand Up @@ -365,6 +391,7 @@ export function buildHtmlPlugin(config: ResolvedConfig): Plugin {

// Same reason with `htmlInlineProxyPlugin`
isAsyncScriptMap.set(config, new Map())
scriptExtraAttrsMap.set(config, new Map())

return {
name: 'vite:build-html',
Expand Down Expand Up @@ -438,6 +465,7 @@ export function buildHtmlPlugin(config: ResolvedConfig): Plugin {
let everyScriptIsAsync = true
let someScriptsAreAsync = false
let someScriptsAreDefer = false
let mergedExtraAttrs: Record<string, string | boolean> = {}

const assetUrlsPromises: Promise<void>[] = []

Expand Down Expand Up @@ -472,8 +500,14 @@ export function buildHtmlPlugin(config: ResolvedConfig): Plugin {

// script tags
if (node.nodeName === 'script') {
const { src, srcSourceCodeLocation, isModule, isAsync, isIgnored } =
getScriptInfo(node)
const {
src,
srcSourceCodeLocation,
isModule,
isAsync,
isIgnored,
extraAttrs,
} = getScriptInfo(node)

if (isIgnored) {
removeViteIgnoreAttr(s, node.sourceCodeLocation!)
Expand Down Expand Up @@ -531,6 +565,7 @@ export function buildHtmlPlugin(config: ResolvedConfig): Plugin {
everyScriptIsAsync &&= isAsync
someScriptsAreAsync ||= isAsync
someScriptsAreDefer ||= !isAsync
mergedExtraAttrs = { ...mergedExtraAttrs, ...extraAttrs }
Comment thread
o-m12a marked this conversation as resolved.
} else if (url && !isPublicFile) {
if (!isExcludedUrl(url)) {
config.logger.warn(
Expand Down Expand Up @@ -682,6 +717,7 @@ export function buildHtmlPlugin(config: ResolvedConfig): Plugin {
})

isAsyncScriptMap.get(config)!.set(id, everyScriptIsAsync)
scriptExtraAttrsMap.get(config)!.set(id, mergedExtraAttrs)

if (someScriptsAreAsync && someScriptsAreDefer) {
config.logger.warn(
Expand Down Expand Up @@ -777,6 +813,7 @@ export function buildHtmlPlugin(config: ResolvedConfig): Plugin {
chunkOrUrl: OutputChunk | string,
toOutputPath: (filename: string) => string,
isAsync: boolean,
extraAttrs: Record<string, string | boolean> = {},
): HtmlTagDescriptor => ({
tag: 'script',
attrs: {
Expand All @@ -793,6 +830,7 @@ export function buildHtmlPlugin(config: ResolvedConfig): Plugin {
typeof chunkOrUrl === 'string'
? chunkOrUrl
: toOutputPath(chunkOrUrl.fileName),
...extraAttrs,
},
})

Expand Down Expand Up @@ -894,6 +932,8 @@ export function buildHtmlPlugin(config: ResolvedConfig): Plugin {
toOutputFilePath(filename, 'public')

const isAsync = isAsyncScriptMap.get(config)!.get(normalizedId)!
const extraAttrs =
scriptExtraAttrsMap.get(config)!.get(normalizedId) ?? {}

let result = html

Expand Down Expand Up @@ -923,11 +963,13 @@ export function buildHtmlPlugin(config: ResolvedConfig): Plugin {
let assetTags: HtmlTagDescriptor[]
if (canInlineEntry) {
assetTags = imports.map((chunk) =>
toScriptTag(chunk, toOutputAssetFilePath, isAsync),
toScriptTag(chunk, toOutputAssetFilePath, isAsync, extraAttrs),
)
} else {
const { modulePreload } = this.environment.config.build
assetTags = [toScriptTag(chunk, toOutputAssetFilePath, isAsync)]
assetTags = [
toScriptTag(chunk, toOutputAssetFilePath, isAsync, extraAttrs),
]
if (modulePreload !== false) {
const resolveDependencies =
typeof modulePreload === 'object' &&
Expand Down
12 changes: 12 additions & 0 deletions playground/html/__tests__/html.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,18 @@ describe.runIf(isBuild)('build', () => {
})
})

describe('scriptFetchPriority', () => {
beforeAll(async () => {
await page.goto(viteTestUrl + '/scriptFetchPriority.html')
})

test('script preserves fetchpriority', async () => {
expect(
await page.$('head script[type=module][fetchpriority="high"]'),
).toBeTruthy()
})
})

describe('zeroJS', () => {
// Ensure that the modulePreload polyfill is discarded in this case

Expand Down
5 changes: 5 additions & 0 deletions playground/html/scriptFetchPriority.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<link rel="stylesheet" href="/main.css" />

<h1>scriptFetchPriority.html</h1>

<script type="module" fetchpriority="high" src="/main.js"></script>
1 change: 1 addition & 0 deletions playground/html/vite.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export default defineConfig({
nested: resolve(dirname, 'nested/index.html'),
scriptAsync: resolve(dirname, 'scriptAsync.html'),
scriptMixed: resolve(dirname, 'scriptMixed.html'),
scriptFetchPriority: resolve(dirname, 'scriptFetchPriority.html'),
emptyAttr: resolve(dirname, 'emptyAttr.html'),
link: resolve(dirname, 'link.html'),
'link/target': resolve(dirname, 'index.html'),
Expand Down