fix(vite): extract styles for shared chunks (#25455)

This commit is contained in:
Daniel Roe 2024-01-28 21:25:42 +00:00 committed by GitHub
parent 80b1c7077f
commit c446602529
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 60 additions and 21 deletions

View File

@ -8,7 +8,7 @@ import type { Component } from '@nuxt/schema'
import MagicString from 'magic-string'
import { findStaticImports } from 'mlly'
import { isCSS } from '../utils'
import { isCSS, isVue } from '../utils'
interface SSRStylePluginOptions {
srcDir: string
@ -107,25 +107,31 @@ export function ssrStylesPlugin (options: SSRStylePluginOptions): Plugin {
})
},
renderChunk (_code, chunk) {
if (!chunk.facadeModuleId) { return null }
const isEntry = chunk.facadeModuleId === options.entry
if (isEntry) {
options.clientCSSMap[chunk.facadeModuleId!] ||= new Set()
}
for (const moduleId of [chunk.facadeModuleId, ...chunk.moduleIds].filter(Boolean) as string[]) {
// 'Teleport' CSS chunks that made it into the bundle on the client side
// to be inlined on server rendering
if (options.mode === 'client') {
options.clientCSSMap[chunk.facadeModuleId] ||= new Set()
for (const id of chunk.moduleIds) {
if (isCSS(id)) {
options.clientCSSMap[chunk.facadeModuleId].add(id)
options.clientCSSMap[moduleId] ||= new Set()
if (isCSS(moduleId)) {
// Vue files can (also) be their own entrypoints as they are tracked separately
if (isVue(moduleId)) {
options.clientCSSMap[moduleId].add(moduleId)
}
// This is required to track CSS in entry chunk
if (isEntry) {
options.clientCSSMap[chunk.facadeModuleId!].add(moduleId)
}
}
return
continue
}
const id = relativeToSrcDir(chunk.facadeModuleId)
for (const file in chunk.modules) {
const relativePath = relativeToSrcDir(file)
const relativePath = relativeToSrcDir(moduleId)
if (relativePath in cssMap) {
cssMap[relativePath].inBundle = cssMap[relativePath].inBundle ?? !!id
cssMap[relativePath].inBundle = cssMap[relativePath].inBundle ?? ((isVue(moduleId) && relativeToSrcDir(moduleId)) || isEntry)
}
}

View File

@ -1,5 +1,7 @@
import { hash } from 'ohash'
export { isVue } from '../../../nuxt/src/core/utils/plugins'
export function uniq<T> (arr: T[]): T[] {
return Array.from(new Set(arr))
}

View File

@ -112,7 +112,7 @@ describe('pages', () => {
// should apply attributes to client-only components
expect(html).toContain('<div style="color:red;" class="client-only"></div>')
// should render server-only components
expect(html.replace(/ data-island-uid="[^"]*"/, '')).toContain('<div class="server-only" style="background-color:gray;"> server-only component </div>')
expect(html.replace(/ data-island-uid="[^"]*"/, '')).toContain('<div class="server-only" style="background-color:gray;"> server-only component <div> server-only component child (non-server-only) </div></div>')
// should register global components automatically
expect(html).toContain('global component registered automatically')
expect(html).toContain('global component via suffix')
@ -1382,6 +1382,8 @@ describe.skipIf(isDev() || isWebpack)('inlining component styles', () => {
'{--assets:"assets"}', // <script>
'{--postcss:"postcss"}', // <style lang=postcss>
'{--scoped:"scoped"}', // <style lang=css>
'{--shared-component:"shared-component"}', // styles in a chunk shared between pages
'{--server-only-child:"server-only-child"}', // child of a server-only component
'{--server-only:"server-only"}' // server-only component not in client build
// TODO: ideally both client/server components would have inlined css when used
// '{--client-only:"client-only"}', // client-only component not in server build
@ -1392,7 +1394,7 @@ describe.skipIf(isDev() || isWebpack)('inlining component styles', () => {
it('should inline styles', async () => {
const html = await $fetch('/styles')
for (const style of inlinedCSS) {
expect(html).toContain(style)
expect.soft(html).toContain(style)
}
})
@ -1403,7 +1405,7 @@ describe.skipIf(isDev() || isWebpack)('inlining component styles', () => {
]
const html = await $fetch('/route-rules/spa')
for (const style of globalCSS) {
expect(html).toContain(style)
expect.soft(html).toContain(style)
}
})
@ -1950,6 +1952,11 @@ describe('component islands', () => {
expect(result.head).toMatchInlineSnapshot(`
{
"link": [
{
"href": "/_nuxt/components/SharedComponent.vue?vue&type=style&index=0&scoped=3ee84738&lang.css",
"key": "island-link",
"rel": "stylesheet",
},
{
"href": "/_nuxt/components/islands/PureComponent.vue?vue&type=style&index=0&scoped=c0c0cf89&lang.css",
"key": "island-link",

View File

@ -5,6 +5,7 @@ prerenderRoutes(['/some/url/from/server-only/component'])
<template>
<div>
server-only component
<ServerOnlyComponentChild />
</div>
</template>

View File

@ -0,0 +1,11 @@
<template>
<div>
server-only component child (non-server-only)
</div>
</template>
<style>
:root {
--server-only-child: 'server-only-child';
}
</style>

View File

@ -0,0 +1,9 @@
<template>
<span class="shared-component" />
</template>
<style scoped>
.shared-component {
--shared-component: 'shared-component';
}
</style>

View File

@ -3,6 +3,7 @@
<ClientOnlyScript />
<FunctionalComponent />
<ServerOnlyComponent />
<SharedComponent />
</div>
</template>

View File

@ -21,5 +21,7 @@ useLegacyVueUseHead()
<template>
<div>
<h1>VueUse head polyfill test</h1>
<!-- This component is only here to make it a shared chunk for test in `styles.vue` -->
<SharedComponent />
</div>
</template>