Skip to content

Commit d9574c7

Browse files
committed
perf: Use of more compatible programmes
1 parent adb18eb commit d9574c7

File tree

8 files changed

+404
-358
lines changed

8 files changed

+404
-358
lines changed

.npmrc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
shamefully-hoist=true
22
strict-peer-dependencies=false
3-
registry=https://registry.npmjs.org/
3+
# registry=https://registry.npmjs.org/

package.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,10 @@
3030
},
3131
"dependencies": {
3232
"@nuxt/kit": "^3.12.4",
33-
"beastcss": "^2.1.3",
34-
"ohash": "^1.1.3"
33+
"cssnano": "^7.0.6",
34+
"ohash": "^1.1.3",
35+
"purgecss": "^6.0.0",
36+
"purgecss-from-html": "^6.0.0"
3537
},
3638
"devDependencies": {
3739
"@nuxt/devtools": "^1.3.9",

playground/app.vue

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
<template>
2-
<div>View Panel</div>
2+
<div class="foo">
3+
View Panel
4+
</div>
35
</template>
46

57
<script setup>
@@ -9,7 +11,13 @@ useServerHead({
911
innerHTML: 'body { color: red }',
1012
},
1113
{
12-
innerHTML: 'body { background: gray }',
14+
innerHTML: '.foo { background: gray }',
15+
},
16+
{
17+
innerHTML: '.bar { color: blue }',
18+
},
19+
{
20+
innerHTML: '.foo { color: white }',
1321
},
1422
],
1523
})

pnpm-lock.yaml

Lines changed: 235 additions & 302 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/module.ts

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { join } from 'node:path'
2-
import fs from 'node:fs/promises'
2+
import fs, { readFile } from 'node:fs/promises'
33
import { existsSync } from 'node:fs'
4-
import { addPlugin, addServerPlugin, createResolver, defineNuxtModule } from '@nuxt/kit'
4+
import { addPlugin, addServerPlugin, addTemplate, addTypeTemplate, createResolver, defineNuxtModule } from '@nuxt/kit'
55

66
function emptyDir(dir: string) {
77
if (!existsSync(dir)) {
@@ -21,7 +21,10 @@ export default defineNuxtModule<ModuleOptions>({
2121
name: 'nuxt-style-extractor',
2222
configKey: 'Extracts the style of the page as an external css when rendered on the server side | 提取服务端渲染时页面的 style 为外部 css',
2323
},
24-
defaults: {},
24+
defaults: {
25+
minify: true,
26+
removeUnused: true,
27+
},
2528
async setup(_options, nuxt) {
2629
const resolver = createResolver(import.meta.url)
2730

@@ -50,5 +53,20 @@ export default defineNuxtModule<ModuleOptions>({
5053
maxAge: 0,
5154
})
5255
}
56+
57+
addTemplate({
58+
filename: 'nuxt-style-extractor-options.mjs',
59+
60+
getContents() {
61+
return `export default ${JSON.stringify(_options)}`
62+
},
63+
})
64+
65+
addTypeTemplate({
66+
filename: 'nuxt-style-extractor-options.d.ts',
67+
getContents() {
68+
return readFile(resolver.resolve('./nuxt-style-extractor-options.d.ts'), 'utf-8')
69+
},
70+
})
5371
},
5472
})
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
declare module '#build/nuxt-style-extractor-options.mjs' {
2+
interface Options {}
3+
const _Options: {
4+
minify: boolean
5+
removeUnused: boolean
6+
}
7+
export default _Options
8+
}
Lines changed: 116 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,130 @@
11
import { hash } from 'ohash'
2-
import { defineNuxtPlugin, useRoute } from '#imports'
2+
import cssnano from 'cssnano'
3+
import { PurgeCSS } from 'purgecss'
4+
import purgehtml from 'purgecss-from-html'
5+
import { defineNuxtPlugin } from '#imports'
6+
import extractorOptions from '#build/nuxt-style-extractor-options.mjs'
7+
8+
console.log(extractorOptions)
9+
10+
interface Options {
11+
html: string
12+
css: string
13+
name: string
14+
}
15+
16+
let purgeCssCtx: PurgeCSS
17+
async function removeUnusedCss(options: Options) {
18+
const { html, css } = options
19+
if (!purgeCssCtx) {
20+
purgeCssCtx = new PurgeCSS()
21+
}
22+
const [result] = await purgeCssCtx.purge({
23+
content: [{ raw: html, extension: 'html' }],
24+
css: [{ raw: css }],
25+
extractors: [{
26+
extensions: ['html'],
27+
extractor: purgehtml,
28+
}],
29+
})
30+
return result.css || ''
31+
}
32+
33+
let cssnanoCtx: ReturnType<typeof cssnano>
34+
async function minifyCss(options: Options) {
35+
const { css, name } = options
36+
if (!cssnanoCtx) {
37+
cssnanoCtx = cssnano()
38+
}
39+
40+
const result = await cssnanoCtx.process(css, {
41+
from: name,
42+
map: false,
43+
})
44+
45+
return result.css || ''
46+
}
47+
48+
async function optimiseCss(options: Options) {
49+
if (extractorOptions.removeUnused) {
50+
options.css = await removeUnusedCss(options)
51+
}
52+
53+
if (extractorOptions.minify) {
54+
options.css = await minifyCss(options)
55+
}
56+
57+
return options.css
58+
}
359

460
export default defineNuxtPlugin({
561
name: 'inject-style-id',
662
setup(nuxt) {
7-
const route = useRoute()
8-
nuxt.ssrContext?.head.use({
9-
hooks: {
10-
'ssr:render'(ctx) {
11-
let style = ''
12-
ctx.tags = ctx.tags.filter((tag) => {
13-
if (tag.tag === 'style' && tag.innerHTML) {
14-
style += tag.innerHTML
15-
return false
63+
nuxt.hook('app:rendered', async (nuxtCtx) => {
64+
const html = nuxtCtx.renderResult?.html || ''
65+
nuxtCtx.ssrContext?.head.use({
66+
hooks: {
67+
async 'ssr:render'(ctx) {
68+
let style = ''
69+
ctx.tags = ctx.tags.filter((tag) => {
70+
if (tag.tag === 'style' && tag.innerHTML) {
71+
style += tag.innerHTML
72+
return false
73+
}
74+
return true
75+
})
76+
77+
if (!style) {
78+
return
1679
}
17-
return true
18-
})
19-
if (style) {
20-
const key = hash([route.matched, style]) + '.css'
80+
81+
const name = hash([html, style]) + '.css'
82+
83+
const oldCss = await $fetch(`/_css/${name}`)
84+
85+
if (oldCss === '') {
86+
return
87+
}
88+
89+
if (oldCss) {
90+
ctx.tags.push({
91+
tag: 'link',
92+
props: {
93+
rel: 'stylesheet',
94+
href: `/_css/${name}`,
95+
},
96+
})
97+
return
98+
}
99+
100+
const css = await optimiseCss({
101+
html,
102+
css: style,
103+
name,
104+
})
105+
106+
await $fetch('/_css', {
107+
body: {
108+
name,
109+
css,
110+
},
111+
method: 'POST',
112+
})
113+
114+
if (css === '') {
115+
return
116+
}
117+
21118
ctx.tags.push({
22-
tag: 'style',
119+
tag: 'link',
23120
props: {
24-
'data-style-extractor-key': key,
121+
rel: 'stylesheet',
122+
href: `/_css/${name}`,
25123
},
26-
innerHTML: style,
27124
})
28-
}
125+
},
29126
},
30-
},
127+
})
31128
})
32129
},
33130
})
Lines changed: 9 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,9 @@
1-
import Beastcss from 'beastcss'
2-
import { defineNitroPlugin, defineEventHandler, useStorage, getRouterParam, setHeader } from '#imports'
1+
import { defineNitroPlugin, defineEventHandler, useStorage, getRouterParam, setHeader, readBody } from '#imports'
32

43
export default defineNitroPlugin((nitroApp) => {
54
const cacheStorage = useStorage<string>('cache:_css')
65
const assetsStorage = useStorage<string>('assets:_css')
7-
const beastcss = new Beastcss({
8-
minifyCss: true,
9-
merge: true,
10-
})
116

12-
const keyReg = /data-style-extractor-key="(.*?)"/
13-
const styleReg = /<style[^>]*>([\s\S]*?)<\/style>/
147
nitroApp.router.add('/_css/:name', defineEventHandler(async (event) => {
158
const name = getRouterParam(event, 'name')
169
let css = await cacheStorage.getItem(name!)
@@ -21,26 +14,13 @@ export default defineNitroPlugin((nitroApp) => {
2114
return css
2215
}))
2316

24-
nitroApp.hooks.hook('render:response', async (res) => {
25-
const html: string = res.body
26-
const [_, key] = html.match(keyReg) || []
27-
if (!key) {
28-
return
29-
}
30-
const storage = import.meta.prerender ? assetsStorage : cacheStorage
31-
const item = await storage.getItem(key)
32-
if (item === null) {
33-
const newHtml = await beastcss.process(html).catch(() => html)
34-
const [style, css] = newHtml.match(styleReg) || ['']
17+
nitroApp.router.post('/_css', defineEventHandler(async (event) => {
18+
const body = await readBody<{ name: string, css: string }>(event)
19+
const { name, css } = body
3520

36-
await storage.setItem(key, css || '')
37-
if (css) {
38-
res.body = newHtml.replace(style, `<link href="/_css/${key}" rel="stylesheet" />`)
39-
}
40-
return
41-
}
42-
if (item) {
43-
res.body = html.replace(styleReg, `<link href="/_css/${key}" rel="stylesheet" />`)
44-
}
45-
})
21+
await Promise.all([
22+
cacheStorage.setItem(name, css),
23+
assetsStorage.setItem(name, css),
24+
])
25+
}))
4626
})

0 commit comments

Comments
 (0)