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
18 changes: 17 additions & 1 deletion packages/vite/src/node/plugins/html.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,17 @@ function traverseNodes(
}
}

function isInsideTemplate(node: DefaultTreeAdapterMap['node']): boolean {
// `traverseHtml` parses a full document, so the only document fragments
// are the contents of <template> elements
let parent = 'parentNode' in node ? node.parentNode : null
while (parent) {
if (parent.nodeName === '#document-fragment') return true
parent = 'parentNode' in parent ? parent.parentNode : null
}
return false
}

type ParseWarnings = Partial<Record<ErrorCodes, string>>

export async function traverseHtml(
Expand Down Expand Up @@ -691,7 +702,12 @@ export function buildHtmlPlugin(config: ResolvedConfig): Plugin {
node.nodeName === 'link' &&
isCSSRequest(url) &&
// should not be converted if following attributes are present (#6748)
!('media' in attr.attributes || 'disabled' in attr.attributes)
!(
'media' in attr.attributes || 'disabled' in attr.attributes
) &&
// a stylesheet inside <template> applies to wherever the
// template content ends up (e.g. a shadow root), not the document
!isInsideTemplate(node)
) {
// CSS references, convert to import
const importExpression = `\nimport ${JSON.stringify(url)}`
Expand Down
14 changes: 14 additions & 0 deletions playground/assets/__tests__/assets.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -959,3 +959,17 @@ test.runIf(isBuild)('assets inside <template> is rewrote', async () => {
/<img class="template" src="\/foo\/bar\/assets\/asset-[-\w]+\.png" \/>/,
)
})

test('stylesheet link inside shadow root <template> applies to the shadow root', async () => {
expect(await getColor('.template-shadow')).toBe('red')
})

test.runIf(isBuild)(
'stylesheet link inside <template> is kept in place',
async () => {
const indexHtml = readFile('./dist/foo/index.html')
expect(indexHtml).toMatch(
/<template shadowrootmode="open">\s*<link rel="stylesheet" href="data:text\/css;base64,[^"]+" \/>/,
)
},
)
3 changes: 3 additions & 0 deletions playground/assets/css/template-shadow.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.template-shadow {
color: red;
}
6 changes: 6 additions & 0 deletions playground/assets/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -510,6 +510,12 @@ <h3>assets in template</h3>
<template>
<img class="template" src="./nested/asset.png" />
</template>
<div class="template-shadow-host">
<template shadowrootmode="open">
<link rel="stylesheet" href="./css/template-shadow.css" />
<span class="template-shadow">stylesheet in shadow root template</span>
</template>
</div>

<link rel="stylesheet" href="asset/style.css" />
<div class="relative-css">link style</div>
Expand Down
Loading