vite-plugin-singlefile-compression 1.4.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +62 -54
- package/dist/getTemplate.d.ts +1 -0
- package/dist/getTemplate.js +2 -0
- package/dist/index.js +80 -62
- package/dist/template/resolve.js +1 -0
- package/package.json +11 -11
package/README.md
CHANGED
|
@@ -37,58 +37,66 @@ const router = createRouter({
|
|
|
37
37
|
|
|
38
38
|
## Options
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
Example:
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
singleFileCompression({
|
|
44
|
+
rename: 'example.html'
|
|
45
|
+
}),
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
More info see [src/options.ts](src/options.ts)
|
|
41
49
|
|
|
42
50
|
```ts
|
|
43
51
|
export interface Options {
|
|
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
|
-
|
|
52
|
+
/**
|
|
53
|
+
* Rename index.html
|
|
54
|
+
*/
|
|
55
|
+
rename?: string;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* https://github.com/terser/html-minifier-terser?tab=readme-ov-file#options-quick-reference
|
|
59
|
+
* @default defaultHtmlMinifierTerserOptions
|
|
60
|
+
*/
|
|
61
|
+
htmlMinifierTerser?: htmlMinifierOptions | boolean;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Try inline html used assets, if inlined or not used in JS.
|
|
65
|
+
* @default true
|
|
66
|
+
*/
|
|
67
|
+
tryInlineHtmlAssets?: boolean;
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Remove inlined asset files.
|
|
71
|
+
* @default true
|
|
72
|
+
*/
|
|
73
|
+
removeInlinedAssetFiles?: boolean;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Try inline html icon, if icon is in public dir.
|
|
77
|
+
* @default true
|
|
78
|
+
*/
|
|
79
|
+
tryInlineHtmlPublicIcon?: boolean;
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Remove inlined html icon files.
|
|
83
|
+
* @default true
|
|
84
|
+
*/
|
|
85
|
+
removeInlinedPublicIconFiles?: boolean;
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Use Base128 to encode gzipped script.
|
|
89
|
+
* If false, use Base64.
|
|
90
|
+
* https://www.npmjs.com/package/base128-ascii
|
|
91
|
+
* @default true
|
|
92
|
+
*/
|
|
93
|
+
useBase128?: boolean;
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Compress format.
|
|
97
|
+
* @default "deflate-raw"
|
|
98
|
+
*/
|
|
99
|
+
compressFormat?: compressFormat;
|
|
92
100
|
}
|
|
93
101
|
```
|
|
94
102
|
|
|
@@ -97,19 +105,19 @@ export interface Options {
|
|
|
97
105
|
https://bddjr.github.io/vite-plugin-singlefile-compression/
|
|
98
106
|
|
|
99
107
|
```
|
|
100
|
-
vite v6.0.
|
|
108
|
+
vite v6.0.11 building for production...
|
|
101
109
|
✓ 45 modules transformed.
|
|
102
110
|
rendering chunks (1)...
|
|
103
111
|
|
|
104
|
-
vite-plugin-singlefile-compression
|
|
112
|
+
vite-plugin-singlefile-compression 2.0.0 building...
|
|
105
113
|
|
|
106
114
|
file:///D:/bddjr/Desktop/code/js/vite-plugin-singlefile-compression/test/dist/index.html
|
|
107
|
-
101.
|
|
115
|
+
101.6 KiB -> 46.39 KiB
|
|
108
116
|
|
|
109
117
|
Finish.
|
|
110
118
|
|
|
111
|
-
dist/index.html 47.
|
|
112
|
-
✓ built in
|
|
119
|
+
dist/index.html 47.50 kB
|
|
120
|
+
✓ built in 732ms
|
|
113
121
|
```
|
|
114
122
|
|
|
115
123
|

|
package/dist/getTemplate.d.ts
CHANGED
package/dist/getTemplate.js
CHANGED
|
@@ -4,6 +4,7 @@ const files = {
|
|
|
4
4
|
base64: fs.readFileSync(fileURLToPath(import.meta.resolve("./template/base64.js"))).toString(),
|
|
5
5
|
base128: fs.readFileSync(fileURLToPath(import.meta.resolve("./template/base128.js"))).toString(),
|
|
6
6
|
assets: fs.readFileSync(fileURLToPath(import.meta.resolve("./template/assets.js"))).toString().split('{"":""}', 2),
|
|
7
|
+
resolve: fs.readFileSync(fileURLToPath(import.meta.resolve("./template/resolve.js"))).toString(),
|
|
7
8
|
};
|
|
8
9
|
export const template = {
|
|
9
10
|
base(script, format, useBase128) {
|
|
@@ -19,4 +20,5 @@ export const template = {
|
|
|
19
20
|
assets(assetsJSON) {
|
|
20
21
|
return files.assets.join(assetsJSON);
|
|
21
22
|
},
|
|
23
|
+
resolve: files.resolve,
|
|
22
24
|
};
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import pc from "picocolors";
|
|
2
2
|
import { minify as htmlMinify } from 'html-minifier-terser';
|
|
3
|
+
import { JSDOM } from 'jsdom';
|
|
3
4
|
import path from 'path';
|
|
4
5
|
import fs from 'fs';
|
|
5
6
|
import { pathToFileURL } from "url";
|
|
@@ -23,7 +24,7 @@ export default singleFileCompression;
|
|
|
23
24
|
function setConfig(config) {
|
|
24
25
|
config.base = './';
|
|
25
26
|
config.build ??= {};
|
|
26
|
-
config.build.assetsDir = 'assets'
|
|
27
|
+
// config.build.assetsDir = 'assets'
|
|
27
28
|
config.build.cssCodeSplit = false;
|
|
28
29
|
config.build.assetsInlineLimit ??= () => true;
|
|
29
30
|
config.build.chunkSizeWarningLimit ??= Infinity;
|
|
@@ -34,9 +35,9 @@ function setConfig(config) {
|
|
|
34
35
|
config.build.rollupOptions.output ??= {};
|
|
35
36
|
function setRollupOutput(output) {
|
|
36
37
|
output.inlineDynamicImports = true;
|
|
37
|
-
delete output.assetFileNames
|
|
38
|
-
delete output.chunkFileNames
|
|
39
|
-
delete output.entryFileNames
|
|
38
|
+
// delete output.assetFileNames
|
|
39
|
+
// delete output.chunkFileNames
|
|
40
|
+
// delete output.entryFileNames
|
|
40
41
|
}
|
|
41
42
|
if (Array.isArray(config.build.rollupOptions.output)) {
|
|
42
43
|
for (const output of config.build.rollupOptions.output) {
|
|
@@ -48,7 +49,7 @@ function setConfig(config) {
|
|
|
48
49
|
}
|
|
49
50
|
}
|
|
50
51
|
async function generateBundle(bundle, config, options) {
|
|
51
|
-
console.log(pc.reset('') + pc.cyan('
|
|
52
|
+
console.log(pc.reset('\n\n') + pc.cyan('vite-plugin-singlefile-compression ' + version) + pc.green(' building...'));
|
|
52
53
|
// rename
|
|
53
54
|
if (options.rename
|
|
54
55
|
&& options.rename !== "index.html"
|
|
@@ -59,6 +60,13 @@ async function generateBundle(bundle, config, options) {
|
|
|
59
60
|
delete bundle["index.html"];
|
|
60
61
|
}
|
|
61
62
|
const distURL = pathToFileURL(config.build.outDir).href + '/';
|
|
63
|
+
/** "assets/" */
|
|
64
|
+
const assetsDir = path.posix.join(config.build.assetsDir, '/');
|
|
65
|
+
/** '[href^="./assets/"]' */
|
|
66
|
+
const assetsHrefSelector = `[href^="./${assetsDir}"]`;
|
|
67
|
+
/** '[src^="./assets/"]' */
|
|
68
|
+
const assetsSrcSelector = `[src^="./${assetsDir}"]`;
|
|
69
|
+
const fakeScript = `_vitePluginSinglefileCompression(${Date.now()})`;
|
|
62
70
|
const globalDelete = new Set();
|
|
63
71
|
const globalDoNotDelete = new Set();
|
|
64
72
|
const globalRemoveDistFileNames = new Set();
|
|
@@ -69,7 +77,7 @@ async function generateBundle(bundle, config, options) {
|
|
|
69
77
|
/** format: ["index.html"] */
|
|
70
78
|
const bundleHTMLNames = [];
|
|
71
79
|
for (const name of Object.keys(bundle)) {
|
|
72
|
-
if (name.startsWith(
|
|
80
|
+
if (name.startsWith(assetsDir))
|
|
73
81
|
bundleAssetsNames.push(name);
|
|
74
82
|
else if (name.endsWith('.html'))
|
|
75
83
|
bundleHTMLNames.push(name);
|
|
@@ -77,69 +85,80 @@ async function generateBundle(bundle, config, options) {
|
|
|
77
85
|
for (const htmlFileName of bundleHTMLNames) {
|
|
78
86
|
// init
|
|
79
87
|
const htmlChunk = bundle[htmlFileName];
|
|
80
|
-
|
|
81
|
-
let oldSize =
|
|
88
|
+
const oldHTML = htmlChunk.source;
|
|
89
|
+
let oldSize = oldHTML.length;
|
|
90
|
+
const dom = new JSDOM(oldHTML);
|
|
91
|
+
const document = dom.window.document;
|
|
92
|
+
const scriptElement = document.querySelector(`script[type=module]${assetsSrcSelector}`);
|
|
93
|
+
if (!scriptElement) {
|
|
94
|
+
console.error(`Error: Can not find script tag from ${htmlFileName}`);
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
82
97
|
const thisDel = new Set();
|
|
83
98
|
// Fix async import
|
|
84
99
|
const newJSCode = ["self.__VITE_PRELOAD__=void 0"];
|
|
85
|
-
// remove html comments
|
|
86
|
-
newHtml = newHtml.replaceAll(/<!--[\d\D]*?-->/g, '');
|
|
87
100
|
// get css tag
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
101
|
+
{
|
|
102
|
+
const element = document.querySelector(`link[rel=stylesheet]${assetsHrefSelector}`);
|
|
103
|
+
if (element) {
|
|
104
|
+
const name = element.href.slice("./".length);
|
|
105
|
+
thisDel.add(name);
|
|
106
|
+
const css = bundle[name];
|
|
107
|
+
const cssSource = css.source;
|
|
108
|
+
if (cssSource) {
|
|
109
|
+
oldSize += cssSource.length;
|
|
110
|
+
// do not delete not inlined asset
|
|
111
|
+
for (const name of bundleAssetsNames) {
|
|
112
|
+
if (cssSource.includes(name.slice(assetsDir.length)))
|
|
113
|
+
globalDoNotDelete.add(name);
|
|
114
|
+
}
|
|
115
|
+
// add script for load css
|
|
116
|
+
newJSCode.push(
|
|
117
|
+
// 'document.querySelector("script[type=module]").insertAdjacentElement("afterend",document.createElement("style")).innerHTML='
|
|
118
|
+
'document.head.appendChild(document.createElement("style")).innerHTML='
|
|
119
|
+
+ JSON.stringify(cssSource.replace(/\n$/, '')));
|
|
98
120
|
}
|
|
99
|
-
//
|
|
100
|
-
|
|
101
|
-
+ JSON.stringify(cssSource.replace(/\s+$/, '')));
|
|
121
|
+
// delete tag
|
|
122
|
+
element.remove();
|
|
102
123
|
}
|
|
103
|
-
|
|
104
|
-
return '';
|
|
105
|
-
});
|
|
124
|
+
}
|
|
106
125
|
// inline html assets
|
|
107
126
|
const assetsDataURL = {};
|
|
108
127
|
if (options.tryInlineHtmlAssets) {
|
|
109
|
-
|
|
128
|
+
for (const element of document.querySelectorAll(assetsSrcSelector)) {
|
|
129
|
+
const name = element.src.slice(`./${assetsDir}`.length);
|
|
110
130
|
if (name.endsWith('.js'))
|
|
111
|
-
|
|
131
|
+
continue;
|
|
112
132
|
if (!Object.hasOwn(assetsDataURL, name)) {
|
|
113
|
-
const bundleName =
|
|
133
|
+
const bundleName = assetsDir + name;
|
|
114
134
|
const a = bundle[bundleName];
|
|
115
135
|
if (!a)
|
|
116
|
-
|
|
136
|
+
continue;
|
|
117
137
|
thisDel.add(bundleName);
|
|
118
138
|
oldSize += a.source.length;
|
|
119
139
|
if (!Object.hasOwn(globalAssetsDataURL, name))
|
|
120
140
|
globalAssetsDataURL[name] = bufferToDataURL(name, Buffer.from(a.source));
|
|
121
141
|
assetsDataURL[name] = globalAssetsDataURL[name];
|
|
122
142
|
}
|
|
123
|
-
|
|
124
|
-
}
|
|
143
|
+
element.src = `data:${name}`;
|
|
144
|
+
}
|
|
125
145
|
}
|
|
126
146
|
// inline html icon
|
|
127
147
|
if (options.tryInlineHtmlPublicIcon) {
|
|
128
148
|
let needInline = true;
|
|
129
|
-
let hasTag = false;
|
|
130
149
|
let iconName = 'favicon.ico';
|
|
131
150
|
// replace tag
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
iconName =
|
|
135
|
-
if (bundleAssetsNames.includes(
|
|
151
|
+
const element = document.querySelector('link[rel=icon][href^="./"], link[rel="shortcut icon"][href^="./"]');
|
|
152
|
+
if (element) {
|
|
153
|
+
iconName = element.href.slice("./".length);
|
|
154
|
+
if (bundleAssetsNames.includes(iconName)) {
|
|
136
155
|
needInline = false;
|
|
137
|
-
return match;
|
|
138
156
|
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
157
|
+
else {
|
|
158
|
+
element.rel = 'icon';
|
|
159
|
+
element.href = 'data:';
|
|
160
|
+
}
|
|
161
|
+
}
|
|
143
162
|
if (needInline) {
|
|
144
163
|
// inline
|
|
145
164
|
try {
|
|
@@ -163,31 +182,30 @@ async function generateBundle(bundle, config, options) {
|
|
|
163
182
|
const { dataURL, size } = globalPublicFilesCache[iconName];
|
|
164
183
|
oldSize += size;
|
|
165
184
|
newJSCode.push('document.querySelector("link[rel=icon]").href=' + JSON.stringify(dataURL));
|
|
166
|
-
if (!
|
|
185
|
+
if (!element) {
|
|
167
186
|
// add link icon tag
|
|
168
|
-
|
|
169
|
-
oldSize += l.length;
|
|
170
|
-
newHtml = newHtml.replace(/(?=<script )/, l);
|
|
187
|
+
document.head.insertAdjacentHTML('beforeend', '<link rel="icon" href="data:">');
|
|
171
188
|
}
|
|
172
189
|
}
|
|
173
190
|
catch (e) {
|
|
174
|
-
if (
|
|
191
|
+
if (element)
|
|
175
192
|
console.error(e);
|
|
176
193
|
}
|
|
177
194
|
}
|
|
178
195
|
}
|
|
179
196
|
// script
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
ok = true;
|
|
197
|
+
{
|
|
198
|
+
const name = scriptElement.src.slice("./".length);
|
|
183
199
|
thisDel.add(name);
|
|
184
200
|
const js = bundle[name];
|
|
185
201
|
oldSize += js.code.length;
|
|
186
202
|
// fix new URL
|
|
187
203
|
newJSCode.push(`import.meta.url=new URL(${JSON.stringify(name)},location).href`);
|
|
204
|
+
// fix import.meta.resolve
|
|
205
|
+
newJSCode.push(template.resolve);
|
|
188
206
|
// do not delete not inlined asset
|
|
189
207
|
for (const name of bundleAssetsNames) {
|
|
190
|
-
const assetName = name.slice(
|
|
208
|
+
const assetName = name.slice(assetsDir.length);
|
|
191
209
|
if (js.code.includes(assetName)) {
|
|
192
210
|
globalDoNotDelete.add(name);
|
|
193
211
|
delete assetsDataURL[assetName];
|
|
@@ -201,22 +219,22 @@ async function generateBundle(bundle, config, options) {
|
|
|
201
219
|
}
|
|
202
220
|
// add script
|
|
203
221
|
newJSCode.push(js.code.replace(/;?\n?$/, ''));
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
222
|
+
}
|
|
223
|
+
// fill fake script
|
|
224
|
+
scriptElement.removeAttribute('src');
|
|
225
|
+
scriptElement.removeAttribute('crossorigin');
|
|
226
|
+
scriptElement.innerHTML = fakeScript;
|
|
227
|
+
// generate html
|
|
228
|
+
htmlChunk.source = dom.serialize();
|
|
209
229
|
// minify html
|
|
210
230
|
if (options.htmlMinifierTerser)
|
|
211
|
-
|
|
231
|
+
htmlChunk.source = await htmlMinify(htmlChunk.source, options.htmlMinifierTerser);
|
|
212
232
|
// fill script
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
// finish
|
|
216
|
-
htmlChunk.source = newHtml;
|
|
233
|
+
htmlChunk.source = htmlChunk.source.split(fakeScript, 2).join(template.base(compress(options.compressFormat, newJSCode.join(';'), options.useBase128), options.compressFormat, options.useBase128));
|
|
234
|
+
// log
|
|
217
235
|
console.log("\n"
|
|
218
236
|
+ " " + pc.underline(pc.cyan(distURL) + pc.greenBright(bundle[htmlFileName].fileName)) + '\n'
|
|
219
|
-
+ " " + pc.gray(KiB(oldSize) + " -> ") + pc.cyanBright(KiB(
|
|
237
|
+
+ " " + pc.gray(KiB(oldSize) + " -> ") + pc.cyanBright(KiB(htmlChunk.source.length)) + '\n');
|
|
220
238
|
// delete assets
|
|
221
239
|
for (const name of thisDel) {
|
|
222
240
|
globalDelete.add(name);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{let t=import.meta.resolve;import.meta.resolve=e=>/^\.{0,2}\//.test(e)?new URL(e,import.meta.url).href:t(e)}
|
package/package.json
CHANGED
|
@@ -1,14 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vite-plugin-singlefile-compression",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"typings": "dist/index.d.ts",
|
|
6
6
|
"files": [
|
|
7
|
-
"dist"
|
|
8
|
-
"package.json",
|
|
9
|
-
"package-lock.json",
|
|
10
|
-
"LICENSE.txt",
|
|
11
|
-
"README.md"
|
|
7
|
+
"dist"
|
|
12
8
|
],
|
|
13
9
|
"type": "module",
|
|
14
10
|
"scripts": {
|
|
@@ -40,16 +36,20 @@
|
|
|
40
36
|
"css"
|
|
41
37
|
],
|
|
42
38
|
"dependencies": {
|
|
43
|
-
"@types/html-minifier-terser": "^7.0.2",
|
|
44
|
-
"@types/node": "^22.9.3",
|
|
45
39
|
"base128-ascii": "^2.1.0",
|
|
46
|
-
"esbuild": "^0.24.0",
|
|
47
40
|
"html-minifier-terser": "^7.2.0",
|
|
41
|
+
"jsdom": "^26.0.0",
|
|
48
42
|
"mime": "^4.0.4",
|
|
49
43
|
"mini-svg-data-uri": "^1.4.4",
|
|
50
44
|
"picocolors": "^1.1.1",
|
|
51
45
|
"rimraf": "^6.0.1",
|
|
52
|
-
"
|
|
53
|
-
"
|
|
46
|
+
"vite": "^6.0.11",
|
|
47
|
+
"@types/html-minifier-terser": "^7.0.2",
|
|
48
|
+
"@types/node": "^22.10.7",
|
|
49
|
+
"@types/jsdom": "^21.1.7"
|
|
50
|
+
},
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"esbuild": "^0.24.0",
|
|
53
|
+
"typescript": "^5.7.2"
|
|
54
54
|
}
|
|
55
55
|
}
|