vite-plugin-singlefile-compression 1.0.1
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/LICENSE.txt +8 -0
- package/README-zh-CN.md +93 -0
- package/README.md +93 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +126 -0
- package/dist/template-assets.js +1 -0
- package/dist/template.js +1 -0
- package/package.json +53 -0
package/LICENSE.txt
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
Copyright © 2024 bddjr
|
|
3
|
+
|
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
5
|
+
|
|
6
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
7
|
+
|
|
8
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README-zh-CN.md
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# vite plugin singlefile compression
|
|
2
|
+
|
|
3
|
+
该插件使用 gzip 将所有 JavaScript、CSS、图片等资源压缩后,嵌入到 `dist/index.html` ,方便作为单个 HTML 文件分享。
|
|
4
|
+
|
|
5
|
+
接收方可以直接使用浏览器打开,无需手动解压文件。
|
|
6
|
+
|
|
7
|
+
改编自 [vite-plugin-singlefile](https://www.npmjs.com/package/vite-plugin-singlefile)
|
|
8
|
+
|
|
9
|
+
### README Language
|
|
10
|
+
|
|
11
|
+
> [English](README.md)
|
|
12
|
+
> 简体中文
|
|
13
|
+
|
|
14
|
+
## 安装
|
|
15
|
+
|
|
16
|
+
使用 `npm` 安装
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
npm i vite-plugin-singlefile-compression
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
然后修改 [vite.config.ts](https://github.com/bddjr/vite-plugin-singlefile-compression/blob/main/test/vite.config.ts#L14)
|
|
23
|
+
|
|
24
|
+
```ts
|
|
25
|
+
// 导入 singleFileCompression
|
|
26
|
+
import singleFileCompression from 'vite-plugin-singlefile-compression'
|
|
27
|
+
|
|
28
|
+
// https://vite.dev/config/
|
|
29
|
+
export default defineConfig({
|
|
30
|
+
plugins: [
|
|
31
|
+
vue(),
|
|
32
|
+
vueDevTools(),
|
|
33
|
+
// 添加 singleFileCompression
|
|
34
|
+
singleFileCompression(),
|
|
35
|
+
],
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
然后修改 [src/router/index.ts](https://github.com/bddjr/vite-plugin-singlefile-compression/blob/main/test/src/router/index.ts#L5) ,将 `createWebHistory` 改为 `createWebHashHistory`
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
const router = createRouter({
|
|
42
|
+
history: createWebHashHistory(),
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## 效果
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
vite v5.4.11 building for production...
|
|
49
|
+
✓ 45 modules transformed.
|
|
50
|
+
rendering chunks (1)...
|
|
51
|
+
|
|
52
|
+
vite-plugin-singlefile-compression building...
|
|
53
|
+
|
|
54
|
+
file:///D:/bddjr/Desktop/code/js/vite-plugin-singlefile-compression/test/dist/index.html
|
|
55
|
+
97.2 KiB -> 50.91 KiB
|
|
56
|
+
|
|
57
|
+
Finish.
|
|
58
|
+
|
|
59
|
+
dist/index.html 52.13 kB
|
|
60
|
+
✓ built in 778ms
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
```html
|
|
64
|
+
<!DOCTYPE html>
|
|
65
|
+
<html>
|
|
66
|
+
<head>
|
|
67
|
+
<meta charset="UTF-8">
|
|
68
|
+
<link rel="icon" href="data:logo-_cUAdIX-.svg">
|
|
69
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
70
|
+
<title>Vite App</title>
|
|
71
|
+
<script type="module">fetch("data:application/gzip;base64,H4sI******AQA=").then(r=>r.blob()).then(b=>new Response(b.stream().pipeThrough(new DecompressionStream("gzip")),{headers:{"Content-Type":"text/javascript"}}).blob()).then(b=>import(b=URL.createObjectURL(b)).finally(()=>URL.revokeObjectURL(b)))</script>
|
|
72
|
+
</head>
|
|
73
|
+
<body>
|
|
74
|
+
<div id="app"></div>
|
|
75
|
+
</body>
|
|
76
|
+
</html>
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Clone
|
|
80
|
+
|
|
81
|
+
```
|
|
82
|
+
git clone https://github.com/bddjr/vite-plugin-singlefile-compression
|
|
83
|
+
cd vite-plugin-singlefile-compression
|
|
84
|
+
npm i
|
|
85
|
+
cd test
|
|
86
|
+
npm i
|
|
87
|
+
cd ..
|
|
88
|
+
npm run build
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## License
|
|
92
|
+
|
|
93
|
+
[MIT](LICENSE.txt)
|
package/README.md
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# vite plugin singlefile compression
|
|
2
|
+
|
|
3
|
+
This plugin compresses all JavaScript, CSS, images, etc. resources using gzip and embeds them into `dist/index.html`, making it convenient to share as a single HTML file.
|
|
4
|
+
|
|
5
|
+
The recipient can open it directly in the browser without manually unzipping the file.
|
|
6
|
+
|
|
7
|
+
Adapted from [vite-plugin-singlefile](https://www.npmjs.com/package/vite-plugin-singlefile)
|
|
8
|
+
|
|
9
|
+
### README Language
|
|
10
|
+
|
|
11
|
+
> English
|
|
12
|
+
> [简体中文](README-zh-CN.md)
|
|
13
|
+
|
|
14
|
+
## Install
|
|
15
|
+
|
|
16
|
+
Using `npm` to install
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
npm i vite-plugin-singlefile-compression
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Then modify [vite.config.ts](https://github.com/bddjr/vite-plugin-singlefile-compression/blob/main/test/vite.config.ts#L14)
|
|
23
|
+
|
|
24
|
+
```ts
|
|
25
|
+
// Import singleFileCompression
|
|
26
|
+
import singleFileCompression from 'vite-plugin-singlefile-compression'
|
|
27
|
+
|
|
28
|
+
// https://vite.dev/config/
|
|
29
|
+
export default defineConfig({
|
|
30
|
+
plugins: [
|
|
31
|
+
vue(),
|
|
32
|
+
vueDevTools(),
|
|
33
|
+
// Add singleFileCompression
|
|
34
|
+
singleFileCompression(),
|
|
35
|
+
],
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Then modify [src/router/index.ts](https://github.com/bddjr/vite-plugin-singlefile-compression/blob/main/test/src/router/index.ts#L5) , change `createWebHistory` to `createWebHashHistory`
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
const router = createRouter({
|
|
42
|
+
history: createWebHashHistory(),
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Effect
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
vite v5.4.11 building for production...
|
|
49
|
+
✓ 45 modules transformed.
|
|
50
|
+
rendering chunks (1)...
|
|
51
|
+
|
|
52
|
+
vite-plugin-singlefile-compression building...
|
|
53
|
+
|
|
54
|
+
file:///D:/bddjr/Desktop/code/js/vite-plugin-singlefile-compression/test/dist/index.html
|
|
55
|
+
97.2 KiB -> 50.91 KiB
|
|
56
|
+
|
|
57
|
+
Finish.
|
|
58
|
+
|
|
59
|
+
dist/index.html 52.13 kB
|
|
60
|
+
✓ built in 778ms
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
```html
|
|
64
|
+
<!DOCTYPE html>
|
|
65
|
+
<html>
|
|
66
|
+
<head>
|
|
67
|
+
<meta charset="UTF-8">
|
|
68
|
+
<link rel="icon" href="data:logo-_cUAdIX-.svg">
|
|
69
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
70
|
+
<title>Vite App</title>
|
|
71
|
+
<script type="module">fetch("data:application/gzip;base64,H4sI******AQA=").then(r=>r.blob()).then(b=>new Response(b.stream().pipeThrough(new DecompressionStream("gzip")),{headers:{"Content-Type":"text/javascript"}}).blob()).then(b=>import(b=URL.createObjectURL(b)).finally(()=>URL.revokeObjectURL(b)))</script>
|
|
72
|
+
</head>
|
|
73
|
+
<body>
|
|
74
|
+
<div id="app"></div>
|
|
75
|
+
</body>
|
|
76
|
+
</html>
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Clone
|
|
80
|
+
|
|
81
|
+
```
|
|
82
|
+
git clone https://github.com/bddjr/vite-plugin-singlefile-compression
|
|
83
|
+
cd vite-plugin-singlefile-compression
|
|
84
|
+
npm i
|
|
85
|
+
cd test
|
|
86
|
+
npm i
|
|
87
|
+
cd ..
|
|
88
|
+
npm run build
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## License
|
|
92
|
+
|
|
93
|
+
[MIT](LICENSE.txt)
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import mime from 'mime';
|
|
2
|
+
import pc from "picocolors";
|
|
3
|
+
import svgToTinyDataUri from "mini-svg-data-uri";
|
|
4
|
+
import zlib from 'zlib';
|
|
5
|
+
import path from 'path';
|
|
6
|
+
import fs from 'fs';
|
|
7
|
+
export function singleFileCompression() {
|
|
8
|
+
return {
|
|
9
|
+
name: "singleFileGzip",
|
|
10
|
+
enforce: "post",
|
|
11
|
+
config: setConfig,
|
|
12
|
+
generateBundle,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
export default singleFileCompression;
|
|
16
|
+
const template = fs.readFileSync(path.join(import.meta.dirname, "template.js")).toString();
|
|
17
|
+
const templateAssets = fs.readFileSync(path.join(import.meta.dirname, "template-assets.js")).toString();
|
|
18
|
+
const fileProtocolDistPath = (p => p.startsWith('/')
|
|
19
|
+
? `file://${p}/`
|
|
20
|
+
: `file:///${p.replaceAll('\\', '/')}/`)(path.resolve("dist"));
|
|
21
|
+
function setConfig(config) {
|
|
22
|
+
config.base = './';
|
|
23
|
+
if (!config.build)
|
|
24
|
+
config.build = {};
|
|
25
|
+
config.build.assetsInlineLimit = Infinity;
|
|
26
|
+
config.build.chunkSizeWarningLimit = Infinity;
|
|
27
|
+
config.build.cssCodeSplit = false;
|
|
28
|
+
config.build.assetsDir = 'assets';
|
|
29
|
+
config.build.modulePreload = { polyfill: false };
|
|
30
|
+
if (!config.build.rollupOptions)
|
|
31
|
+
config.build.rollupOptions = {};
|
|
32
|
+
config.build.rollupOptions.output = { inlineDynamicImports: true };
|
|
33
|
+
}
|
|
34
|
+
function gzipToBase64(buf) {
|
|
35
|
+
return zlib.gzipSync(buf, {
|
|
36
|
+
level: zlib.constants.Z_BEST_COMPRESSION,
|
|
37
|
+
}).toString('base64');
|
|
38
|
+
}
|
|
39
|
+
function KiB(size) {
|
|
40
|
+
return `${Math.ceil(size / 10.24) / 100} KiB`;
|
|
41
|
+
}
|
|
42
|
+
function generateBundle(_, bundle) {
|
|
43
|
+
console.log(pc.cyan('\n\nvite-plugin-singlefile-compression ') + pc.green('building...'));
|
|
44
|
+
const globalDel = new Set();
|
|
45
|
+
for (const htmlFileName of Object.keys(bundle)) {
|
|
46
|
+
// key format:
|
|
47
|
+
// index.html
|
|
48
|
+
// assets/index-ZZZZZZZZ.js
|
|
49
|
+
// skip other file
|
|
50
|
+
if (!htmlFileName.endsWith('.html'))
|
|
51
|
+
continue;
|
|
52
|
+
// init
|
|
53
|
+
const htmlChunk = bundle[htmlFileName];
|
|
54
|
+
let newHtml = htmlChunk.source;
|
|
55
|
+
let oldSize = newHtml.length;
|
|
56
|
+
const thisDel = new Set();
|
|
57
|
+
// Fix async import
|
|
58
|
+
const newJSCode = ["self.__VITE_PRELOAD__=void 0"];
|
|
59
|
+
// get css tag
|
|
60
|
+
newHtml = newHtml.replace(/\s*<link rel="stylesheet"[^>]* href="\.\/(assets\/[^"]+)"[^>]*>/, (match, name) => {
|
|
61
|
+
thisDel.add(name);
|
|
62
|
+
const css = bundle[name];
|
|
63
|
+
const cssSource = css.source;
|
|
64
|
+
if (cssSource) {
|
|
65
|
+
oldSize += cssSource.length;
|
|
66
|
+
// add script for load css
|
|
67
|
+
newJSCode.push('document.head.appendChild(document.createElement("style")).innerHTML='
|
|
68
|
+
+ JSON.stringify(cssSource.replace(/\s+$/, '')));
|
|
69
|
+
}
|
|
70
|
+
// delete tag
|
|
71
|
+
return '';
|
|
72
|
+
});
|
|
73
|
+
// get html assets
|
|
74
|
+
const assets = {};
|
|
75
|
+
newHtml = newHtml.replace(/(?<=[\s"])(src|href)="\.\/assets\/([^"]+)"/g, (match, attrName, name) => {
|
|
76
|
+
if (name.endsWith('.js'))
|
|
77
|
+
return match;
|
|
78
|
+
if (!Object.hasOwn(assets, name)) {
|
|
79
|
+
const bundleName = "assets/" + name;
|
|
80
|
+
const a = bundle[bundleName];
|
|
81
|
+
if (!a)
|
|
82
|
+
return match;
|
|
83
|
+
thisDel.add(bundleName);
|
|
84
|
+
const b = Buffer.from(a.source);
|
|
85
|
+
oldSize += b.length;
|
|
86
|
+
assets[name] =
|
|
87
|
+
name.endsWith('.svg')
|
|
88
|
+
? svgToTinyDataUri(b.toString())
|
|
89
|
+
: `data:${mime.getType(a.fileName)};base64,${b.toString('base64')}`;
|
|
90
|
+
}
|
|
91
|
+
return `${attrName}="data:${name}"`;
|
|
92
|
+
});
|
|
93
|
+
// add script for load html assets
|
|
94
|
+
const assetsJSON = JSON.stringify(assets);
|
|
95
|
+
if (assetsJSON != '{}')
|
|
96
|
+
newJSCode.push(templateAssets.replace('{"":""}', assetsJSON));
|
|
97
|
+
let ok = false;
|
|
98
|
+
newHtml = newHtml.replace(/<script type="module"[^>]* src="\.\/(assets\/[^"]+)"[^>]*><\/script>/, (match, name) => {
|
|
99
|
+
ok = true;
|
|
100
|
+
thisDel.add(name);
|
|
101
|
+
const js = bundle[name];
|
|
102
|
+
oldSize += js.code.length;
|
|
103
|
+
newJSCode.push(js.code.replace(/;?\n?$/, ''));
|
|
104
|
+
// gzip
|
|
105
|
+
return '<script type="module">'
|
|
106
|
+
+ template.replace('{<script>}', gzipToBase64(newJSCode.join(';')))
|
|
107
|
+
+ '</script>';
|
|
108
|
+
});
|
|
109
|
+
if (!ok)
|
|
110
|
+
continue;
|
|
111
|
+
// finish
|
|
112
|
+
htmlChunk.source = newHtml;
|
|
113
|
+
console.log("\n"
|
|
114
|
+
+ " " + pc.underline(pc.cyan(fileProtocolDistPath) + pc.greenBright(htmlFileName)) + '\n'
|
|
115
|
+
+ " " + pc.gray(KiB(oldSize) + " -> ") + pc.cyanBright(KiB(newHtml.length)) + '\n');
|
|
116
|
+
// delete assets
|
|
117
|
+
for (const name of thisDel) {
|
|
118
|
+
globalDel.add(name);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
// delete inlined assets
|
|
122
|
+
for (const name of globalDel) {
|
|
123
|
+
delete bundle[name];
|
|
124
|
+
}
|
|
125
|
+
console.log(pc.green('Finish.\n'));
|
|
126
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{const a={"":""};for(const n in a){for(const e of document.querySelectorAll(`[src="data:${n}"]`)){e.src=a[n]}for(const e of document.querySelectorAll(`[href="data:${n}"]`)){e.href=a[n]}}}
|
package/dist/template.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
fetch("data:application/gzip;base64,{<script>}").then(r=>r.blob()).then(b=>new Response(b.stream().pipeThrough(new DecompressionStream("gzip")),{headers:{"Content-Type":"text/javascript"}}).blob()).then(b=>import(b=URL.createObjectURL(b)).finally(()=>URL.revokeObjectURL(b)))
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "vite-plugin-singlefile-compression",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"main": "dist/index.js",
|
|
5
|
+
"typings": "dist/index.d.ts",
|
|
6
|
+
"files": [
|
|
7
|
+
"dist",
|
|
8
|
+
"package.json",
|
|
9
|
+
"package-lock.json",
|
|
10
|
+
"LICENSE.txt",
|
|
11
|
+
"README.md",
|
|
12
|
+
"README-zh-CN.md"
|
|
13
|
+
],
|
|
14
|
+
"type": "module",
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "rimraf dist && tsc && node build.js && cd test && npm run build",
|
|
17
|
+
"prepublishOnly": "npm run build && npm config set registry https://registry.npmjs.org"
|
|
18
|
+
},
|
|
19
|
+
"author": "bddjr",
|
|
20
|
+
"license": "MIT",
|
|
21
|
+
"description": "",
|
|
22
|
+
"repository": {
|
|
23
|
+
"type": "git",
|
|
24
|
+
"url": "https://github.com/bddjr/vite-plugin-singlefile-compression"
|
|
25
|
+
},
|
|
26
|
+
"keywords": [
|
|
27
|
+
"vite",
|
|
28
|
+
"SFA",
|
|
29
|
+
"single-file",
|
|
30
|
+
"singlefile",
|
|
31
|
+
"single",
|
|
32
|
+
"compression",
|
|
33
|
+
"compress",
|
|
34
|
+
"gzip",
|
|
35
|
+
"inline",
|
|
36
|
+
"frontend",
|
|
37
|
+
"js",
|
|
38
|
+
"javascript",
|
|
39
|
+
"css"
|
|
40
|
+
],
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"esbuild": "^0.24.0",
|
|
43
|
+
"mime": "^4.0.4",
|
|
44
|
+
"mini-svg-data-uri": "^1.4.4",
|
|
45
|
+
"picocolors": "^1.1.1",
|
|
46
|
+
"rimraf": "^6.0.1",
|
|
47
|
+
"typescript": "^5.7.2",
|
|
48
|
+
"vite": "^5.4.11"
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"@types/node": "^22.9.3"
|
|
52
|
+
}
|
|
53
|
+
}
|