-
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.js
125 lines (119 loc) · 3.24 KB
/
index.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
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
120
121
122
123
124
125
import { escapeRegExp } from "lodash-es"
import { extname } from "node:path"
import { fileURLToPath } from "node:url"
import lzString from "lz-string"
import { readFile } from "node:fs/promises"
import { resolve } from "import-meta-resolve"
const { compressToBase64 } = lzString
/**
* @typedef {object} Options
* @property {readonly Compressor[]=} compressors
*/
/**
* @typedef {object} Compressor
* @property {RegExp} filter path filter
* @property {string=} namespace namespace filter
* @property {"json" | "text"} loader compression mode
* @property {boolean=} lazy whether decompression is lazy
* @property {boolean=} onEnd whether to compress on `onEnd`
*/
/**
* Creates a compression plugin.
*
* Lazy decompression returns a {@link PromiseLike}.
*
* @param {Options} options plugin options
* @returns {import("esbuild").Plugin} an esbuild plugin
*/
export default function esbuildCompress(options = {}) {
const name = "compress",
{ compressors } = options
return {
name,
setup(build) {
build.onResolve({
filter: new RegExp(`^${escapeRegExp(name)}:`, "u"),
}, ({ path }) => ({
path: fileURLToPath(resolve(
path.slice(`${name}:`.length),
import.meta.url,
)),
}))
for (const {
filter,
namespace,
loader,
lazy,
onEnd,
} of compressors ?? []) {
let callback
switch (loader) {
case "json":
callback = data => {
data = JSON.stringify(JSON.parse(data))
return {
contents:
`import{decompressFromBase64 as dc}from"${name}:lz-string"
export default JSON.parse(dc(${jsString(compressToBase64(data))}))`,
loader: "js",
}
}
break
case "text":
callback = data => ({
contents:
`import{decompressFromBase64 as dc}from"${name}:lz-string"
export default dc(${jsString(compressToBase64(data))})`,
loader: "js",
})
break
default:
throw new Error(loader)
}
if (lazy) {
const callback2 = callback
callback = (...args) => {
const ret = callback2(...args)
if (lazy) { ret.contents = makeDefaultLazy(name, ret.contents) }
return ret
}
}
if (onEnd) {
build.onEnd(({ outputFiles }) => {
for (const file of (outputFiles ?? [])
.filter(({ path }) => filter.test(path))) {
const { contents, loader } = callback(file.text)
file.contents = new TextEncoder().encode(contents)
if (extname(file.path) !== `.${loader ?? "js"}`) {
file.path += `.${loader ?? "js"}`
}
}
})
} else {
build.onLoad({ filter, namespace }, async ({ path }) =>
callback(await readFile(path, { encoding: "utf-8" })))
}
}
},
}
}
/**
* Escapes a string for embedding in JavaScript.
*
* @param {string} string a string
* @returns {string} a JavaScript string
*/
function jsString(string) {
return `\`${string.replace(/(?<char>`|\\|\$)/ug, "\\$<char>")}\``
}
/**
* Make the default export lazy.
*
* @param {string} namespace import namespace
* @param {string} code code with a default export
* @returns code with a lazy default export
*/
function makeDefaultLazy(namespace, code) {
return `import PL from"${namespace}:p-lazy"
${code.replace("export default", "export default PL.from(()=>(")}))`
}