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 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.
@@ -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)
@@ -0,0 +1,3 @@
1
+ import { PluginOption } from "vite";
2
+ export declare function singleFileCompression(): PluginOption;
3
+ export default singleFileCompression;
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]}}}
@@ -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
+ }