mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-25 15:15:19 +00:00
fix(nuxt): inline css directly in root component (#21573)
This commit is contained in:
parent
2c9ac8dd80
commit
343a46d5f9
@ -66,6 +66,8 @@ const getClientManifest: () => Promise<Manifest> = () => import('#build/dist/ser
|
|||||||
.then(r => r.default || r)
|
.then(r => r.default || r)
|
||||||
.then(r => typeof r === 'function' ? r() : r) as Promise<ClientManifest>
|
.then(r => typeof r === 'function' ? r() : r) as Promise<ClientManifest>
|
||||||
|
|
||||||
|
const getEntryId: () => Promise<string> = () => getClientManifest().then(r => Object.values(r).find(r => r.isEntry)!.src!)
|
||||||
|
|
||||||
// @ts-expect-error virtual file
|
// @ts-expect-error virtual file
|
||||||
const getStaticRenderedHead = (): Promise<NuxtMeta> => import('#head-static').then(r => r.default || r)
|
const getStaticRenderedHead = (): Promise<NuxtMeta> => import('#head-static').then(r => r.default || r)
|
||||||
|
|
||||||
@ -283,6 +285,15 @@ export default defineRenderHandler(async (event): Promise<Partial<RenderResponse
|
|||||||
// Render meta
|
// Render meta
|
||||||
const renderedMeta = await ssrContext.renderMeta?.() ?? {}
|
const renderedMeta = await ssrContext.renderMeta?.() ?? {}
|
||||||
|
|
||||||
|
if (process.env.NUXT_INLINE_STYLES && !islandContext) {
|
||||||
|
const entryId = await getEntryId()
|
||||||
|
if (ssrContext.modules) {
|
||||||
|
ssrContext.modules.add(entryId)
|
||||||
|
} else if (ssrContext._registeredComponents) {
|
||||||
|
ssrContext._registeredComponents.add(entryId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Render inline styles
|
// Render inline styles
|
||||||
const inlinedStyles = (process.env.NUXT_INLINE_STYLES || Boolean(islandContext))
|
const inlinedStyles = (process.env.NUXT_INLINE_STYLES || Boolean(islandContext))
|
||||||
? await renderInlineStyles(ssrContext.modules ?? ssrContext._registeredComponents ?? [])
|
? await renderInlineStyles(ssrContext.modules ?? ssrContext._registeredComponents ?? [])
|
||||||
|
@ -2,14 +2,13 @@ import { pathToFileURL } from 'node:url'
|
|||||||
import MagicString from 'magic-string'
|
import MagicString from 'magic-string'
|
||||||
import { parseQuery, parseURL } from 'ufo'
|
import { parseQuery, parseURL } from 'ufo'
|
||||||
import type { Plugin } from 'vite'
|
import type { Plugin } from 'vite'
|
||||||
|
import { isCSS } from '../utils'
|
||||||
|
|
||||||
export interface RuntimePathsOptions {
|
export interface RuntimePathsOptions {
|
||||||
sourcemap?: boolean
|
sourcemap?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const VITE_ASSET_RE = /__VITE_ASSET__|__VITE_PUBLIC_ASSET__/
|
const VITE_ASSET_RE = /__VITE_ASSET__|__VITE_PUBLIC_ASSET__/
|
||||||
const CSS_RE =
|
|
||||||
/\.(css|less|sass|scss|styl|stylus|pcss|postcss|sss)$/
|
|
||||||
|
|
||||||
export function runtimePathsPlugin (options: RuntimePathsOptions): Plugin {
|
export function runtimePathsPlugin (options: RuntimePathsOptions): Plugin {
|
||||||
return {
|
return {
|
||||||
@ -19,7 +18,7 @@ export function runtimePathsPlugin (options: RuntimePathsOptions): Plugin {
|
|||||||
const { pathname, search } = parseURL(decodeURIComponent(pathToFileURL(id).href))
|
const { pathname, search } = parseURL(decodeURIComponent(pathToFileURL(id).href))
|
||||||
|
|
||||||
// skip import into css files
|
// skip import into css files
|
||||||
if (CSS_RE.test(pathname)) { return }
|
if (isCSS(pathname)) { return }
|
||||||
|
|
||||||
// skip import into <style> vue files
|
// skip import into <style> vue files
|
||||||
if (pathname.endsWith('.vue')) {
|
if (pathname.endsWith('.vue')) {
|
||||||
|
@ -1,17 +1,24 @@
|
|||||||
import { pathToFileURL } from 'node:url'
|
import { pathToFileURL } from 'node:url'
|
||||||
import type { Plugin } from 'vite'
|
import type { Plugin } from 'vite'
|
||||||
import { findStaticImports } from 'mlly'
|
|
||||||
import { dirname, relative } from 'pathe'
|
import { dirname, relative } from 'pathe'
|
||||||
import { genObjectFromRawEntries } from 'knitwork'
|
import { genImport, genObjectFromRawEntries } from 'knitwork'
|
||||||
import { filename } from 'pathe/utils'
|
import { filename } from 'pathe/utils'
|
||||||
import { parseQuery, parseURL } from 'ufo'
|
import { parseQuery, parseURL } from 'ufo'
|
||||||
import type { Component } from '@nuxt/schema'
|
import type { Component } from '@nuxt/schema'
|
||||||
|
import MagicString from 'magic-string'
|
||||||
|
import { findStaticImports } from 'mlly'
|
||||||
|
|
||||||
|
import { isCSS } from '../utils'
|
||||||
|
|
||||||
interface SSRStylePluginOptions {
|
interface SSRStylePluginOptions {
|
||||||
srcDir: string
|
srcDir: string
|
||||||
chunksWithInlinedCSS: Set<string>
|
chunksWithInlinedCSS: Set<string>
|
||||||
shouldInline?: ((id?: string) => boolean) | boolean
|
shouldInline?: ((id?: string) => boolean) | boolean
|
||||||
components: Component[]
|
components: Component[]
|
||||||
|
clientCSSMap: Record<string, Set<string>>
|
||||||
|
entry: string
|
||||||
|
globalCSS: string[]
|
||||||
|
mode: 'server' | 'client'
|
||||||
}
|
}
|
||||||
|
|
||||||
const SUPPORTED_FILES_RE = /\.(vue|((c|m)?j|t)sx?)$/
|
const SUPPORTED_FILES_RE = /\.(vue|((c|m)?j|t)sx?)$/
|
||||||
@ -33,10 +40,17 @@ export function ssrStylesPlugin (options: SSRStylePluginOptions): Plugin {
|
|||||||
name: 'ssr-styles',
|
name: 'ssr-styles',
|
||||||
resolveId: {
|
resolveId: {
|
||||||
order: 'pre',
|
order: 'pre',
|
||||||
async handler (id, importer, options) {
|
async handler (id, importer, _options) {
|
||||||
if (!id.endsWith('.vue')) { return }
|
// We deliberately prevent importing `#build/css` to avoid including it in the client bundle
|
||||||
|
// in its entirety. We will instead include _just_ the styles that can't be inlined,
|
||||||
|
// in the <NuxtRoot> component below
|
||||||
|
if (options.mode === 'client' && id === '#build/css' && (options.shouldInline === true || (typeof options.shouldInline === 'function' && options.shouldInline(importer)))) {
|
||||||
|
return this.resolve('unenv/runtime/mock/empty', importer, _options)
|
||||||
|
}
|
||||||
|
|
||||||
const res = await this.resolve(id, importer, { ...options, skipSelf: true })
|
if (options.mode === 'client' || !id.endsWith('.vue')) { return }
|
||||||
|
|
||||||
|
const res = await this.resolve(id, importer, { ..._options, skipSelf: true })
|
||||||
if (res) {
|
if (res) {
|
||||||
return {
|
return {
|
||||||
...res,
|
...res,
|
||||||
@ -46,6 +60,8 @@ export function ssrStylesPlugin (options: SSRStylePluginOptions): Plugin {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
generateBundle (outputOptions) {
|
generateBundle (outputOptions) {
|
||||||
|
if (options.mode === 'client') { return }
|
||||||
|
|
||||||
const emitted: Record<string, string> = {}
|
const emitted: Record<string, string> = {}
|
||||||
for (const file in cssMap) {
|
for (const file in cssMap) {
|
||||||
const { files, inBundle } = cssMap[file]
|
const { files, inBundle } = cssMap[file]
|
||||||
@ -75,6 +91,8 @@ export function ssrStylesPlugin (options: SSRStylePluginOptions): Plugin {
|
|||||||
options.chunksWithInlinedCSS.add(key)
|
options.chunksWithInlinedCSS.add(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: remove css from vite preload arrays
|
||||||
|
|
||||||
this.emitFile({
|
this.emitFile({
|
||||||
type: 'asset',
|
type: 'asset',
|
||||||
fileName: 'styles.mjs',
|
fileName: 'styles.mjs',
|
||||||
@ -89,6 +107,19 @@ export function ssrStylesPlugin (options: SSRStylePluginOptions): Plugin {
|
|||||||
},
|
},
|
||||||
renderChunk (_code, chunk) {
|
renderChunk (_code, chunk) {
|
||||||
if (!chunk.facadeModuleId) { return null }
|
if (!chunk.facadeModuleId) { return null }
|
||||||
|
|
||||||
|
// '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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const id = relativeToSrcDir(chunk.facadeModuleId)
|
const id = relativeToSrcDir(chunk.facadeModuleId)
|
||||||
for (const file in chunk.modules) {
|
for (const file in chunk.modules) {
|
||||||
const relativePath = relativeToSrcDir(file)
|
const relativePath = relativeToSrcDir(file)
|
||||||
@ -100,10 +131,41 @@ export function ssrStylesPlugin (options: SSRStylePluginOptions): Plugin {
|
|||||||
return null
|
return null
|
||||||
},
|
},
|
||||||
async transform (code, id) {
|
async transform (code, id) {
|
||||||
const { pathname, search } = parseURL(decodeURIComponent(pathToFileURL(id).href))
|
if (options.mode === 'client') {
|
||||||
const query = parseQuery(search)
|
// We will either teleport global CSS to the 'entry' chunk on the server side
|
||||||
|
// or include it here in the client build so it is emitted in the CSS.
|
||||||
|
if (id === options.entry && (options.shouldInline === true || (typeof options.shouldInline === 'function' && options.shouldInline(id)))) {
|
||||||
|
const s = new MagicString(code)
|
||||||
|
options.clientCSSMap[id] ||= new Set()
|
||||||
|
for (const file of options.globalCSS) {
|
||||||
|
const resolved = await this.resolve(file, id)
|
||||||
|
const res = await this.resolve(file + '?inline&used', id)
|
||||||
|
if (!resolved || !res) {
|
||||||
|
if (!warnCache.has(file)) {
|
||||||
|
warnCache.add(file)
|
||||||
|
this.warn(`[nuxt] Cannot extract styles for \`${file}\`. Its styles will not be inlined when server-rendering.`)
|
||||||
|
}
|
||||||
|
s.prepend(`${genImport(file)}\n`)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
options.clientCSSMap[id].add(resolved.id)
|
||||||
|
}
|
||||||
|
if (s.hasChanged()) {
|
||||||
|
return {
|
||||||
|
code: s.toString(),
|
||||||
|
map: s.generateMap({ hires: true })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (!SUPPORTED_FILES_RE.test(pathname) || query.macro || query.nuxt_component) { return }
|
const { pathname, search } = parseURL(decodeURIComponent(pathToFileURL(id).href))
|
||||||
|
|
||||||
|
if (!(id in options.clientCSSMap) && !islands.some(c => c.filePath === pathname)) { return }
|
||||||
|
|
||||||
|
const query = parseQuery(search)
|
||||||
|
if (query.macro || query.nuxt_component) { return }
|
||||||
|
|
||||||
if (!islands.some(c => c.filePath === pathname)) {
|
if (!islands.some(c => c.filePath === pathname)) {
|
||||||
if (options.shouldInline === false || (typeof options.shouldInline === 'function' && !options.shouldInline(id))) { return }
|
if (options.shouldInline === false || (typeof options.shouldInline === 'function' && !options.shouldInline(id))) { return }
|
||||||
@ -112,7 +174,32 @@ export function ssrStylesPlugin (options: SSRStylePluginOptions): Plugin {
|
|||||||
const relativeId = relativeToSrcDir(id)
|
const relativeId = relativeToSrcDir(id)
|
||||||
cssMap[relativeId] = cssMap[relativeId] || { files: [] }
|
cssMap[relativeId] = cssMap[relativeId] || { files: [] }
|
||||||
|
|
||||||
|
const emittedIds = new Set<string>()
|
||||||
|
|
||||||
let styleCtr = 0
|
let styleCtr = 0
|
||||||
|
const ids = options.clientCSSMap[id] || []
|
||||||
|
for (const file of ids) {
|
||||||
|
const resolved = await this.resolve(file, id)
|
||||||
|
if (!resolved || !(await this.resolve(file + '?inline&used', id))) {
|
||||||
|
if (!warnCache.has(file)) {
|
||||||
|
warnCache.add(file)
|
||||||
|
this.warn(`[nuxt] Cannot extract styles for \`${file}\`. Its styles will not be inlined when server-rendering.`)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (emittedIds.has(file)) { continue }
|
||||||
|
const ref = this.emitFile({
|
||||||
|
type: 'chunk',
|
||||||
|
name: `${filename(id)}-styles-${++styleCtr}.mjs`,
|
||||||
|
id: file + '?inline&used'
|
||||||
|
})
|
||||||
|
|
||||||
|
idRefMap[relativeToSrcDir(file)] = ref
|
||||||
|
cssMap[relativeId].files.push(ref)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!SUPPORTED_FILES_RE.test(pathname)) { return }
|
||||||
|
|
||||||
for (const i of findStaticImports(code)) {
|
for (const i of findStaticImports(code)) {
|
||||||
const { type } = parseQuery(i.specifier)
|
const { type } = parseQuery(i.specifier)
|
||||||
if (type !== 'style' && !i.specifier.endsWith('.css')) { continue }
|
if (type !== 'style' && !i.specifier.endsWith('.css')) { continue }
|
||||||
@ -127,6 +214,7 @@ export function ssrStylesPlugin (options: SSRStylePluginOptions): Plugin {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (emittedIds.has(resolved.id)) { continue }
|
||||||
const ref = this.emitFile({
|
const ref = this.emitFile({
|
||||||
type: 'chunk',
|
type: 'chunk',
|
||||||
name: `${filename(id)}-styles-${++styleCtr}.mjs`,
|
name: `${filename(id)}-styles-${++styleCtr}.mjs`,
|
||||||
|
@ -8,7 +8,6 @@ import type { ViteConfig } from '@nuxt/schema'
|
|||||||
import type { ViteBuildContext } from './vite'
|
import type { ViteBuildContext } from './vite'
|
||||||
import { createViteLogger } from './utils/logger'
|
import { createViteLogger } from './utils/logger'
|
||||||
import { initViteNodeServer } from './vite-node'
|
import { initViteNodeServer } from './vite-node'
|
||||||
import { ssrStylesPlugin } from './plugins/ssr-styles'
|
|
||||||
import { pureAnnotationsPlugin } from './plugins/pure-annotations'
|
import { pureAnnotationsPlugin } from './plugins/pure-annotations'
|
||||||
import { writeManifest } from './manifest'
|
import { writeManifest } from './manifest'
|
||||||
import { transpile } from './utils/transpile'
|
import { transpile } from './utils/transpile'
|
||||||
@ -120,27 +119,6 @@ export async function buildServer (ctx: ViteBuildContext) {
|
|||||||
|
|
||||||
serverConfig.customLogger = createViteLogger(serverConfig)
|
serverConfig.customLogger = createViteLogger(serverConfig)
|
||||||
|
|
||||||
if (!ctx.nuxt.options.dev) {
|
|
||||||
const chunksWithInlinedCSS = new Set<string>()
|
|
||||||
serverConfig.plugins!.push(ssrStylesPlugin({
|
|
||||||
srcDir: ctx.nuxt.options.srcDir,
|
|
||||||
chunksWithInlinedCSS,
|
|
||||||
shouldInline: ctx.nuxt.options.experimental.inlineSSRStyles,
|
|
||||||
components: ctx.nuxt.apps.default.components
|
|
||||||
}))
|
|
||||||
|
|
||||||
// Remove CSS entries for files that will have inlined styles
|
|
||||||
ctx.nuxt.hook('build:manifest', (manifest) => {
|
|
||||||
for (const key in manifest) {
|
|
||||||
const entry = manifest[key]
|
|
||||||
const shouldRemoveCSS = chunksWithInlinedCSS.has(key)
|
|
||||||
if (shouldRemoveCSS) {
|
|
||||||
entry.css = []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
await ctx.nuxt.callHook('vite:extendConfig', serverConfig, { isClient: false, isServer: true })
|
await ctx.nuxt.callHook('vite:extendConfig', serverConfig, { isClient: false, isServer: true })
|
||||||
|
|
||||||
serverConfig.plugins!.unshift(
|
serverConfig.plugins!.unshift(
|
||||||
|
@ -16,6 +16,7 @@ import { warmupViteServer } from './utils/warmup'
|
|||||||
import { resolveCSSOptions } from './css'
|
import { resolveCSSOptions } from './css'
|
||||||
import { composableKeysPlugin } from './plugins/composable-keys'
|
import { composableKeysPlugin } from './plugins/composable-keys'
|
||||||
import { logLevelMap } from './utils/logger'
|
import { logLevelMap } from './utils/logger'
|
||||||
|
import { ssrStylesPlugin } from './plugins/ssr-styles'
|
||||||
|
|
||||||
export interface ViteBuildContext {
|
export interface ViteBuildContext {
|
||||||
nuxt: Nuxt
|
nuxt: Nuxt
|
||||||
@ -143,6 +144,35 @@ export async function bundle (nuxt: Nuxt) {
|
|||||||
|
|
||||||
await nuxt.callHook('vite:extend', ctx)
|
await nuxt.callHook('vite:extend', ctx)
|
||||||
|
|
||||||
|
if (!ctx.nuxt.options.dev) {
|
||||||
|
const chunksWithInlinedCSS = new Set<string>()
|
||||||
|
const clientCSSMap = {}
|
||||||
|
|
||||||
|
nuxt.hook('vite:extendConfig', (config, { isServer }) => {
|
||||||
|
config.plugins!.push(ssrStylesPlugin({
|
||||||
|
srcDir: ctx.nuxt.options.srcDir,
|
||||||
|
clientCSSMap,
|
||||||
|
chunksWithInlinedCSS,
|
||||||
|
shouldInline: ctx.nuxt.options.experimental.inlineSSRStyles,
|
||||||
|
components: ctx.nuxt.apps.default.components,
|
||||||
|
globalCSS: ctx.nuxt.options.css,
|
||||||
|
mode: isServer ? 'server' : 'client',
|
||||||
|
entry: ctx.entry
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
|
||||||
|
// Remove CSS entries for files that will have inlined styles
|
||||||
|
ctx.nuxt.hook('build:manifest', (manifest) => {
|
||||||
|
for (const key in manifest) {
|
||||||
|
const entry = manifest[key]
|
||||||
|
const shouldRemoveCSS = chunksWithInlinedCSS.has(key) && !entry.isEntry
|
||||||
|
if (shouldRemoveCSS && entry.css) {
|
||||||
|
entry.css = []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
nuxt.hook('vite:serverCreated', (server: vite.ViteDevServer, env) => {
|
nuxt.hook('vite:serverCreated', (server: vite.ViteDevServer, env) => {
|
||||||
// Invalidate virtual modules when templates are re-generated
|
// Invalidate virtual modules when templates are re-generated
|
||||||
ctx.nuxt.hook('app:templatesGenerated', () => {
|
ctx.nuxt.hook('app:templatesGenerated', () => {
|
||||||
|
@ -1168,17 +1168,47 @@ describe('automatically keyed composables', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe.skipIf(isDev() || isWebpack)('inlining component styles', () => {
|
describe.skipIf(isDev() || isWebpack)('inlining component styles', () => {
|
||||||
|
const inlinedCSS = [
|
||||||
|
'{--plugin:"plugin"}', // CSS imported ambiently in JS/TS
|
||||||
|
'{--global:"global";', // global css from nuxt.config
|
||||||
|
'{--assets:"assets"}', // <script>
|
||||||
|
'{--postcss:"postcss"}', // <style lang=postcss>
|
||||||
|
'{--scoped:"scoped"}' // <style lang=css>
|
||||||
|
// TODO: ideally both client/server components would have inlined css when used
|
||||||
|
// '{--client-only:"client-only"}', // client-only component not in server build
|
||||||
|
// '{--server-only:"server-only"}' // server-only component not in client build
|
||||||
|
// TODO: currently functional component not associated with ssrContext (upstream bug or perf optimization?)
|
||||||
|
// '{--functional:"functional"}', // CSS imported ambiently in a functional component
|
||||||
|
]
|
||||||
|
|
||||||
it('should inline styles', async () => {
|
it('should inline styles', async () => {
|
||||||
const html = await $fetch('/styles')
|
const html = await $fetch('/styles')
|
||||||
for (const style of [
|
for (const style of inlinedCSS) {
|
||||||
'{--assets:"assets"}', // <script>
|
|
||||||
'{--scoped:"scoped"}', // <style lang=css>
|
|
||||||
'{--postcss:"postcss"}' // <style lang=postcss>
|
|
||||||
]) {
|
|
||||||
expect(html).toContain(style)
|
expect(html).toContain(style)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should not include inlined CSS in generated CSS file', async () => {
|
||||||
|
const html: string = await $fetch('/styles')
|
||||||
|
const cssFiles = new Set([...html.matchAll(/<link [^>]*href="([^"]*\.css)">/g)].map(m => m[1]))
|
||||||
|
let css = ''
|
||||||
|
for (const file of cssFiles || []) {
|
||||||
|
css += await $fetch(file)
|
||||||
|
}
|
||||||
|
|
||||||
|
// should not include inlined CSS in generated CSS files
|
||||||
|
for (const style of inlinedCSS) {
|
||||||
|
// TODO: remove 'ambient global' CSS from generated CSS file
|
||||||
|
if (style === '{--plugin:"plugin"}') { continue }
|
||||||
|
expect.soft(css).not.toContain(style)
|
||||||
|
}
|
||||||
|
|
||||||
|
// should include unloadable CSS in generated CSS file
|
||||||
|
expect.soft(css).toContain('--virtual:red')
|
||||||
|
expect.soft(css).toContain('--functional:"functional"')
|
||||||
|
expect.soft(css).toContain('--client-only:"client-only"')
|
||||||
|
})
|
||||||
|
|
||||||
it('does not load stylesheet for page styles', async () => {
|
it('does not load stylesheet for page styles', async () => {
|
||||||
const html: string = await $fetch('/styles')
|
const html: string = await $fetch('/styles')
|
||||||
expect(html.match(/<link [^>]*href="[^"]*\.css">/g)?.filter(m => m.includes('entry'))?.map(m => m.replace(/\.[^.]*\.css/, '.css'))).toMatchInlineSnapshot(`
|
expect(html.match(/<link [^>]*href="[^"]*\.css">/g)?.filter(m => m.includes('entry'))?.map(m => m.replace(/\.[^.]*\.css/, '.css'))).toMatchInlineSnapshot(`
|
||||||
@ -1468,15 +1498,10 @@ describe('component islands', () => {
|
|||||||
link.key = link.key.replace(/-[a-zA-Z0-9]+$/, '')
|
link.key = link.key.replace(/-[a-zA-Z0-9]+$/, '')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
result.head.style = result.head.style.map(s => ({
|
|
||||||
...s,
|
|
||||||
innerHTML: (s.innerHTML || '').replace(/data-v-[a-z0-9]+/, 'data-v-xxxxx'),
|
|
||||||
key: s.key.replace(/-[a-zA-Z0-9]+$/, '')
|
|
||||||
}))
|
|
||||||
|
|
||||||
// TODO: fix rendering of styles in webpack
|
// TODO: fix rendering of styles in webpack
|
||||||
if (!isDev() && !isWebpack) {
|
if (!isDev() && !isWebpack) {
|
||||||
expect(result.head).toMatchInlineSnapshot(`
|
expect(normaliseIslandResult(result).head).toMatchInlineSnapshot(`
|
||||||
{
|
{
|
||||||
"link": [],
|
"link": [],
|
||||||
"style": [
|
"style": [
|
||||||
@ -1486,7 +1511,7 @@ describe('component islands', () => {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
`)
|
`)
|
||||||
} else if (isDev() && !isWebpack) {
|
} else if (isDev() && !isWebpack) {
|
||||||
expect(result.head).toMatchInlineSnapshot(`
|
expect(result.head).toMatchInlineSnapshot(`
|
||||||
{
|
{
|
||||||
@ -1676,3 +1701,17 @@ describe.runIf(isDev())('component testing', () => {
|
|||||||
expect(comp2).toContain('12 x 4 = 48')
|
expect(comp2).toContain('12 x 4 = 48')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
function normaliseIslandResult (result: NuxtIslandResponse) {
|
||||||
|
return {
|
||||||
|
...result,
|
||||||
|
head: {
|
||||||
|
...result.head,
|
||||||
|
style: result.head.style.map(s => ({
|
||||||
|
...s,
|
||||||
|
innerHTML: (s.innerHTML || '').replace(/data-v-[a-z0-9]+/, 'data-v-xxxxx').replace(/\.[a-zA-Z0-9]+\.svg/, '.svg'),
|
||||||
|
key: s.key.replace(/-[a-zA-Z0-9]+$/, '')
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -25,7 +25,7 @@ describe.skipIf(process.env.SKIP_BUNDLE_SIZE === 'true' || process.env.ECOSYSTEM
|
|||||||
|
|
||||||
it('default client bundle size', async () => {
|
it('default client bundle size', async () => {
|
||||||
stats.client = await analyzeSizes('**/*.js', publicDir)
|
stats.client = await analyzeSizes('**/*.js', publicDir)
|
||||||
expect(roundToKilobytes(stats.client.totalBytes)).toMatchInlineSnapshot('"96.7k"')
|
expect.soft(roundToKilobytes(stats.client.totalBytes)).toMatchInlineSnapshot('"96.7k"')
|
||||||
expect(stats.client.files.map(f => f.replace(/\..*\.js/, '.js'))).toMatchInlineSnapshot(`
|
expect(stats.client.files.map(f => f.replace(/\..*\.js/, '.js'))).toMatchInlineSnapshot(`
|
||||||
[
|
[
|
||||||
"_nuxt/entry.js",
|
"_nuxt/entry.js",
|
||||||
@ -35,10 +35,10 @@ describe.skipIf(process.env.SKIP_BUNDLE_SIZE === 'true' || process.env.ECOSYSTEM
|
|||||||
|
|
||||||
it('default server bundle size', async () => {
|
it('default server bundle size', async () => {
|
||||||
stats.server = await analyzeSizes(['**/*.mjs', '!node_modules'], serverDir)
|
stats.server = await analyzeSizes(['**/*.mjs', '!node_modules'], serverDir)
|
||||||
expect(roundToKilobytes(stats.server.totalBytes)).toMatchInlineSnapshot('"61.0k"')
|
expect.soft(roundToKilobytes(stats.server.totalBytes)).toMatchInlineSnapshot('"61.3k"')
|
||||||
|
|
||||||
const modules = await analyzeSizes('node_modules/**/*', serverDir)
|
const modules = await analyzeSizes('node_modules/**/*', serverDir)
|
||||||
expect(roundToKilobytes(modules.totalBytes)).toMatchInlineSnapshot('"2295k"')
|
expect.soft(roundToKilobytes(modules.totalBytes)).toMatchInlineSnapshot('"2295k"')
|
||||||
|
|
||||||
const packages = modules.files
|
const packages = modules.files
|
||||||
.filter(m => m.endsWith('package.json'))
|
.filter(m => m.endsWith('package.json'))
|
||||||
|
@ -3,3 +3,9 @@
|
|||||||
server-only component
|
server-only component
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
:root {
|
||||||
|
--server-only: 'server-only';
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
1
test/fixtures/basic/pages/styles.vue
vendored
1
test/fixtures/basic/pages/styles.vue
vendored
@ -2,6 +2,7 @@
|
|||||||
<div>
|
<div>
|
||||||
<ClientOnlyScript />
|
<ClientOnlyScript />
|
||||||
<FunctionalComponent />
|
<FunctionalComponent />
|
||||||
|
<ServerOnlyComponent />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user