-
-
Notifications
You must be signed in to change notification settings - Fork 0
/
vite.config.ts
119 lines (108 loc) · 3.99 KB
/
vite.config.ts
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
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
import * as cheerio from "cheerio";
import crypto from "node:crypto";
import fs from "node:fs/promises";
import path from "node:path";
import { svelte } from "@sveltejs/vite-plugin-svelte";
import { optimize } from "svgo";
import { defineConfig, Plugin } from "vite";
import { viteSingleFile } from "vite-plugin-singlefile";
const CLOUDFLARE_HEADERS_FILE_NAME = "_headers";
/**
* Generate `_headers` file with CSP directive based on hashes of script and style elements.
* See: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/Sources#hash-algorithm-base64-value
*
* Also serve these headers in the preview server.
*
* Inspired by: https://github.com/RockiRider/csp
*/
function generateHeadersPlugin(): Plugin {
const algorithm = "sha256";
const scriptSrcElemHashes: string[] = [];
const styleSrcElemHashes: string[] = [];
return {
name: "mbox-wtf-csp-plugin",
generateBundle: {
order: "post",
handler(_options, bundle, _isWrite) {
const indexHtml = bundle["index.html"];
if (indexHtml.type !== "asset" || typeof indexHtml.source !== "string") {
throw new Error("Unexpected index.html asset");
}
const $ = cheerio.load(indexHtml.source);
$("script").each((_i, el) => {
const content = $(el).text();
if (content.length > 0) {
scriptSrcElemHashes.push(`${algorithm}-${crypto.hash(algorithm, content, "base64")}`);
}
});
$("style").each((_i, el) => {
const content = $(el).text();
if (content.length > 0) {
styleSrcElemHashes.push(`${algorithm}-${crypto.hash(algorithm, content, "base64")}`);
}
});
console.log("Found script-src-elem hashes:", scriptSrcElemHashes);
console.log("Found style-src-elem hashes:", styleSrcElemHashes);
const cspDirectives = [
// sandbox seems to work in Chrome and Firefox, but not Safari
// "sandbox allow-scripts",
"default-src 'none'",
`script-src-elem ${scriptSrcElemHashes.map((hash) => `'${hash}'`).join(" ")}`,
`style-src-elem ${styleSrcElemHashes.map((hash) => `'${hash}'`).join(" ")}`,
"worker-src blob:",
];
bundle[CLOUDFLARE_HEADERS_FILE_NAME] = {
type: "asset",
fileName: CLOUDFLARE_HEADERS_FILE_NAME,
name: CLOUDFLARE_HEADERS_FILE_NAME,
originalFileName: null,
needsCodeReference: false,
source: `/*\n Content-Security-Policy: ${cspDirectives.join("; ")};`,
};
},
},
// Read _headers file and set headers for preview server
async configurePreviewServer(server) {
const headers = (
await fs.readFile(
path.join(server.config.root, server.config.build.outDir, CLOUDFLARE_HEADERS_FILE_NAME),
"utf-8",
)
).split("\n");
const firstLine = headers.shift();
if (firstLine !== "/*") {
throw new Error(`Unexpected _headers pattern: ${firstLine}`);
}
server.middlewares.use((_req, res, next) => {
for (const line of headers) {
if (!line.startsWith(" ")) {
throw new Error("Expected indented header rule");
}
const [name, value] = line.split(": ", 2);
res.setHeader(name.trim(), value);
}
next();
});
},
};
}
/**
* Embed the favicon/logo as a `data:` URI into index.html.
*/
function faviconPlugin(): Plugin {
return {
name: "mbox-wtf-favicon-plugin",
async transformIndexHtml(_html, _ctx) {
const svg = await fs.readFile(path.join(__dirname, "src", "logo.svg"), "utf-8");
const dataUri = optimize(svg, { multipass: true, datauri: "base64" }).data;
return [{ tag: "link", attrs: { rel: "icon", type: "image/svg+xml", href: dataUri } }];
},
};
}
// https://vitejs.dev/config/
export default defineConfig({
plugins: [svelte(), viteSingleFile(), generateHeadersPlugin(), faviconPlugin()],
build: {
assetsInlineLimit: 0,
},
});