-
Notifications
You must be signed in to change notification settings - Fork 34
/
inject-assets-plugin.js
89 lines (69 loc) · 3.55 KB
/
inject-assets-plugin.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
import { join } from 'node:path'
import { readFileSync, writeFileSync } from 'node:fs'
import { createHash } from 'node:crypto'
import HtmlPlugin from 'html-webpack-plugin'
import pagesManifest from '../src/pages.js'
const __dirname = import.meta.dirname
const getPages = rawAssets => {
const pages = Object.entries(pagesManifest).map(([chunk, { path, title, data, preconnect }]) => {
const scripts = rawAssets.map(({ name }) => name).filter(name => new RegExp(`[/.]${chunk}\\.(.+)\\.js$`).test(name))
return { path, scripts, title, data, preconnect }
})
return pages
}
class InjectAssetsPlugin {
apply(compiler) {
const production = compiler.options.mode === 'production'
compiler.hooks.compilation.tap('InjectAssetsPlugin', compilation => {
HtmlPlugin.getCompilationHooks(compilation).beforeEmit.tapAsync('InjectAssetsPlugin', (data, callback) => {
const preloadAssets = readFileSync(join(__dirname, '..', 'scripts', 'preload-assets.js'), 'utf-8')
const rawAssets = compilation.getAssets()
const pages = getPages(rawAssets)
const stringifiedPages = JSON.stringify(pages, (_, value) => {
return typeof value === 'function' ? `func:${value.toString()}` : value
})
let { html } = data
html = html.replace(
'</title>',
() => `</title><script id="preload-data">const pages=${stringifiedPages}\n${preloadAssets}</script>`
)
callback(null, { ...data, html })
})
})
if (!production) return
compiler.hooks.afterEmit.tapAsync('InjectAssetsPlugin', (compilation, callback) => {
let html = readFileSync(join(__dirname, '..', 'build', 'index.html'), 'utf-8')
let worker = readFileSync(join(__dirname, '..', 'build', '_worker.js'), 'utf-8')
const rawAssets = compilation.getAssets()
const pages = getPages(rawAssets)
const assets = rawAssets
.filter(({ name }) => /^scripts\/.+\.js$/.test(name))
.map(({ name, source }) => ({
url: `/${name}`,
source: source.source(),
parentPaths: pages.filter(({ scripts }) => scripts.includes(name)).map(({ path }) => path)
}))
const initialModuleScriptsString = html.match(/<script\s+type="module"[^>]*>([\s\S]*?)(?=<\/head>)/)[0]
const initialModuleScripts = initialModuleScriptsString.split('</script>')
const initialScripts = assets
.filter(({ url }) => initialModuleScriptsString.includes(url))
.map(asset => ({ ...asset, order: initialModuleScripts.findIndex(script => script.includes(asset.url)) }))
.sort((a, b) => a.order - b.order)
const asyncScripts = assets.filter(asset => !initialScripts.includes(asset))
const documentEtag = createHash('sha256').update(html).digest('hex').slice(0, 16)
html = html
.replace(/,"scripts":\s*\[(.*?)\]/g, () => '')
.replace(/scripts\.forEach[\s\S]*?data\?\.\s*forEach/, () => 'data?.forEach')
.replace(/preloadAssets/g, () => 'preloadData')
worker = worker
.replace('INJECT_INITIAL_MODULE_SCRIPTS_STRING_HERE', () => JSON.stringify(initialModuleScriptsString))
.replace('INJECT_INITIAL_SCRIPTS_HERE', () => JSON.stringify(initialScripts))
.replace('INJECT_ASYNC_SCRIPTS_HERE', () => JSON.stringify(asyncScripts))
.replace('INJECT_HTML_HERE', () => JSON.stringify(html))
.replace('INJECT_DOCUMENT_ETAG_HERE', () => JSON.stringify(documentEtag))
writeFileSync(join(__dirname, '..', 'build', '_worker.js'), worker)
callback()
})
}
}
export default InjectAssetsPlugin