/
vite.config.js
67 lines (62 loc) · 1.81 KB
/
vite.config.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
import fs from 'node:fs/promises'
import url from 'node:url'
import path from 'node:path'
import crypto from 'node:crypto'
import { defineConfig } from 'vite'
const __dirname = path.dirname(url.fileURLToPath(import.meta.url))
const noncePlaceholder = '#$NONCE$#'
const createNonce = () => crypto.randomBytes(16).toString('base64')
/**
* @param {import('node:http').ServerResponse} res
* @param {string} nonce
*/
const setNonceHeader = (res, nonce) => {
res.setHeader(
'Content-Security-Policy',
`default-src 'nonce-${nonce}'; connect-src 'self'`,
)
}
/**
* @param {string} file
* @param {(input: string, originalUrl: string) => Promise<string>} transform
* @returns {import('vite').Connect.NextHandleFunction}
*/
const createMiddleware = (file, transform) => async (req, res) => {
const nonce = createNonce()
setNonceHeader(res, nonce)
const content = await fs.readFile(path.join(__dirname, file), 'utf-8')
const transformedContent = await transform(content, req.originalUrl)
res.setHeader('Content-Type', 'text/html')
res.end(transformedContent.replaceAll(noncePlaceholder, nonce))
}
export default defineConfig({
plugins: [
{
name: 'nonce-inject',
config() {
return {
appType: 'custom',
html: {
cspNonce: noncePlaceholder,
},
}
},
configureServer({ transformIndexHtml, middlewares }) {
return () => {
middlewares.use(
createMiddleware('./index.html', (input, originalUrl) =>
transformIndexHtml(originalUrl, input),
),
)
}
},
configurePreviewServer({ middlewares }) {
return () => {
middlewares.use(
createMiddleware('./dist/index.html', async (input) => input),
)
}
},
},
],
})