vite-plugin-singlefile-compression 1.2.5 → 1.3.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/LICENSE.txt CHANGED
@@ -1,5 +1,5 @@
1
1
  The MIT License (MIT)
2
- Copyright © 2024-2025 bddjr
2
+ Copyright © 2024-present bddjr ; Adapted from https://www.npmjs.com/package/vite-plugin-singlefile
3
3
 
4
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
5
 
package/README.md CHANGED
@@ -25,24 +25,6 @@ export default defineConfig({
25
25
  // Add singleFileCompression
26
26
  singleFileCompression(),
27
27
  ],
28
-
29
- // Not required options:
30
- esbuild: {
31
- // Remove license comments, make file smaller.
32
- legalComments: "none"
33
- },
34
- build: {
35
- terserOptions: {
36
- format: {
37
- // Remove license comments, make file smaller.
38
- comments: false
39
- }
40
- },
41
- // Not use old syntax, make file smaller.
42
- target: 'esnext',
43
- // Disable reporting compressed chunk sizes, slightly improve build speed.
44
- reportCompressedSize: false
45
- },
46
28
  ```
47
29
 
48
30
  Then modify [src/router/index.ts](test/src/router/index.ts#L5)
@@ -55,47 +37,7 @@ const router = createRouter({
55
37
 
56
38
  ## Options
57
39
 
58
- ```ts
59
- export interface Options {
60
- /**
61
- * https://github.com/terser/html-minifier-terser?tab=readme-ov-file#options-quick-reference
62
- * @default defaultHtmlMinifierTerserOptions
63
- */
64
- htmlMinifierTerser?: htmlMinifierOptions | boolean;
65
-
66
- /**
67
- * Try inline html used assets, if inlined or not used in JS.
68
- * @default true
69
- */
70
- tryInlineHtmlAssets?: boolean;
71
-
72
- /**
73
- * Remove inlined asset files.
74
- * @default true
75
- */
76
- removeInlinedAssetFiles?: boolean;
77
-
78
- /**
79
- * Try inline html icon, if icon is in public dir.
80
- * @default true
81
- */
82
- tryInlineHtmlPublicIcon?: boolean;
83
-
84
- /**
85
- * Remove inlined html icon files.
86
- * @default true
87
- */
88
- removeInlinedPublicIconFiles?: boolean;
89
-
90
- /**
91
- * Use Base128 to encode gzipped script.
92
- * If false, use Base64.
93
- * https://www.npmjs.com/package/base128-ascii
94
- * @default true
95
- */
96
- useBase128?: boolean;
97
- }
98
- ```
40
+ See [src/options.ts](src/options.ts)
99
41
 
100
42
  ## Effect
101
43
 
@@ -106,15 +48,15 @@ vite v6.0.7 building for production...
106
48
  ✓ 45 modules transformed.
107
49
  rendering chunks (1)...
108
50
 
109
- vite-plugin-singlefile-compression 1.2.5 building...
51
+ vite-plugin-singlefile-compression 1.3.0 building...
110
52
 
111
53
  file:///D:/bddjr/Desktop/code/js/vite-plugin-singlefile-compression/test/dist/index.html
112
- 101.02 KiB -> 46.52 KiB
54
+ 101.56 KiB -> 46.76 KiB
113
55
 
114
56
  Finish.
115
57
 
116
- dist/index.html 47.64 kB
117
- ✓ built in 716ms
58
+ dist/index.html 47.88 kB
59
+ ✓ built in 677ms
118
60
  ```
119
61
 
120
62
  ## Clone
@@ -131,6 +73,7 @@ npm run build
131
73
 
132
74
  ## License
133
75
 
134
- [MIT](LICENSE.txt)
76
+ Using [MIT License](LICENSE.txt).
77
+ [src/template](src/template) using [Unlicense](src/template/LICENSE.txt).
135
78
 
136
79
  Adapted from [vite-plugin-singlefile](https://www.npmjs.com/package/vite-plugin-singlefile).
package/dist/KiB.d.ts ADDED
@@ -0,0 +1 @@
1
+ export declare function KiB(size: number): string;
package/dist/KiB.js ADDED
@@ -0,0 +1,3 @@
1
+ export function KiB(size) {
2
+ return `${Math.ceil(size / 10.24) / 100} KiB`;
3
+ }
@@ -0,0 +1,3 @@
1
+ import zlib from 'zlib';
2
+ export type compressFormat = "deflate-raw" | "deflate" | "gzip";
3
+ export declare function compress(format: compressFormat, buf: zlib.InputType, useBase128: boolean): string;
@@ -0,0 +1,24 @@
1
+ import base128 from "base128-ascii";
2
+ import zlib from 'zlib';
3
+ export function compress(format, buf, useBase128) {
4
+ const options = {
5
+ level: zlib.constants.Z_BEST_COMPRESSION,
6
+ };
7
+ let outBuf;
8
+ switch (format) {
9
+ case "deflate":
10
+ outBuf = zlib.deflateSync(buf, options);
11
+ break;
12
+ case "deflate-raw":
13
+ outBuf = zlib.deflateRawSync(buf, options);
14
+ break;
15
+ case "gzip":
16
+ outBuf = zlib.gzipSync(buf, options);
17
+ break;
18
+ default:
19
+ throw `unknown compress format ${format}`;
20
+ }
21
+ return useBase128
22
+ ? base128.encode(Uint8Array.from(outBuf)).toJSTemplateLiterals()
23
+ : outBuf.toString('base64');
24
+ }
@@ -0,0 +1 @@
1
+ export declare function bufferToDataURL(name: string, b: Buffer): string;
@@ -0,0 +1,7 @@
1
+ import svgToTinyDataUri from "mini-svg-data-uri";
2
+ import mime from 'mime';
3
+ export function bufferToDataURL(name, b) {
4
+ return name.endsWith('.svg')
5
+ ? svgToTinyDataUri(b.toString())
6
+ : `data:${mime.getType(name)};base64,${b.toString('base64')}`;
7
+ }
@@ -0,0 +1,4 @@
1
+ export declare const template: {
2
+ base(script: string, format: string, useBase128: boolean): string;
3
+ assets(assetsJSON: string): string;
4
+ };
@@ -0,0 +1,22 @@
1
+ import path from 'path';
2
+ import fs from 'fs';
3
+ const files = {
4
+ base64: fs.readFileSync(path.join(import.meta.dirname, "template/base64.js")).toString(),
5
+ base128: fs.readFileSync(path.join(import.meta.dirname, "template/base128.js")).toString(),
6
+ assets: fs.readFileSync(path.join(import.meta.dirname, "template/assets.js")).toString().split('{"":""}', 2),
7
+ };
8
+ export const template = {
9
+ base(script, format, useBase128) {
10
+ if (useBase128) {
11
+ return files.base128
12
+ .replace("<format>", format)
13
+ .split('"<script>"', 2).join(script);
14
+ }
15
+ return files.base64
16
+ .replace("<format>", format)
17
+ .split("<script>", 2).join(script);
18
+ },
19
+ assets(assetsJSON) {
20
+ return files.assets.join(assetsJSON);
21
+ },
22
+ };
@@ -0,0 +1 @@
1
+ export declare const version: string;
@@ -0,0 +1,3 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ export const { version } = JSON.parse(fs.readFileSync(path.join(import.meta.dirname, "../package.json")).toString());
package/dist/index.d.ts CHANGED
@@ -1,39 +1,4 @@
1
1
  import { PluginOption } from "vite";
2
- import { Options as htmlMinifierOptions } from 'html-minifier-terser';
3
- export interface Options {
4
- /**
5
- * https://github.com/terser/html-minifier-terser?tab=readme-ov-file#options-quick-reference
6
- * @default defaultHtmlMinifierTerserOptions
7
- */
8
- htmlMinifierTerser?: htmlMinifierOptions | boolean;
9
- /**
10
- * Try inline html used assets, if inlined or not used in JS.
11
- * @default true
12
- */
13
- tryInlineHtmlAssets?: boolean;
14
- /**
15
- * Remove inlined asset files.
16
- * @default true
17
- */
18
- removeInlinedAssetFiles?: boolean;
19
- /**
20
- * Try inline html icon, if icon is in public dir.
21
- * @default true
22
- */
23
- tryInlineHtmlPublicIcon?: boolean;
24
- /**
25
- * Remove inlined html icon files.
26
- * @default true
27
- */
28
- removeInlinedPublicIconFiles?: boolean;
29
- /**
30
- * Use Base128 to encode gzipped script.
31
- * If false, use Base64.
32
- * https://www.npmjs.com/package/base128-ascii
33
- * @default true
34
- */
35
- useBase128?: boolean;
36
- }
37
- export declare const defaultHtmlMinifierTerserOptions: htmlMinifierOptions;
2
+ import { Options } from "./options.js";
38
3
  export declare function singleFileCompression(opt?: Options): PluginOption;
39
4
  export default singleFileCompression;
package/dist/index.js CHANGED
@@ -1,90 +1,54 @@
1
- import mime from 'mime';
2
1
  import pc from "picocolors";
3
- import svgToTinyDataUri from "mini-svg-data-uri";
4
2
  import { minify as htmlMinify } from 'html-minifier-terser';
5
- import base128 from "base128-ascii";
6
- import zlib from 'zlib';
7
3
  import path from 'path';
8
4
  import fs from 'fs';
9
5
  import { pathToFileURL } from "url";
10
- export const defaultHtmlMinifierTerserOptions = {
11
- removeAttributeQuotes: true,
12
- removeComments: true,
13
- collapseWhitespace: true,
14
- removeOptionalTags: true,
15
- removeRedundantAttributes: true,
16
- minifyJS: false,
17
- };
6
+ import { version } from './getVersion.js';
7
+ import { template } from './getTemplate.js';
8
+ import { compress } from "./compress.js";
9
+ import { bufferToDataURL } from "./dataurl.js";
10
+ import { KiB } from "./KiB.js";
11
+ import { getInnerOptions } from "./options.js";
18
12
  export function singleFileCompression(opt) {
19
- opt ||= {};
20
- const innerOpt = {
21
- htmlMinifierTerser: opt.htmlMinifierTerser == null || opt.htmlMinifierTerser === true
22
- ? defaultHtmlMinifierTerserOptions
23
- : opt.htmlMinifierTerser,
24
- tryInlineHtmlAssets: opt.tryInlineHtmlAssets ?? true,
25
- removeInlinedAssetFiles: opt.removeInlinedAssetFiles ?? true,
26
- tryInlineHtmlPublicIcon: opt.tryInlineHtmlPublicIcon ?? true,
27
- removeInlinedPublicIconFiles: opt.removeInlinedPublicIconFiles ?? true,
28
- useBase128: opt.useBase128 ?? true,
29
- };
30
13
  let conf;
31
14
  return {
32
15
  name: "singleFileCompression",
33
16
  enforce: "post",
34
17
  config: setConfig,
35
18
  configResolved(c) { conf = c; },
36
- generateBundle: (_, bundle) => generateBundle(bundle, conf, innerOpt),
19
+ generateBundle: (_, bundle) => generateBundle(bundle, conf, getInnerOptions(opt)),
37
20
  };
38
21
  }
39
22
  export default singleFileCompression;
40
- const template = fs.readFileSync(path.join(import.meta.dirname, "template.js")).toString().split('{<script>}', 2);
41
- const templateBase128 = fs.readFileSync(path.join(import.meta.dirname, "template-base128.js")).toString().split('"<script>"', 2);
42
- const templateAssets = fs.readFileSync(path.join(import.meta.dirname, "template-assets.js")).toString().split('{"":""}', 2);
43
- const { version } = JSON.parse(fs.readFileSync(path.join(import.meta.dirname, "../package.json")).toString());
44
- function bufferToDataURL(name, b) {
45
- return name.endsWith('.svg')
46
- ? svgToTinyDataUri(b.toString())
47
- : `data:${mime.getType(name)};base64,${b.toString('base64')}`;
48
- }
49
- function gzip(buf) {
50
- return zlib.gzipSync(buf, {
51
- level: zlib.constants.Z_BEST_COMPRESSION,
52
- });
53
- }
54
- function gzipToBase64(buf) {
55
- return gzip(buf).toString('base64');
56
- }
57
- function gzipToBase128(buf) {
58
- return base128.encode(Uint8Array.from(gzip(buf))).toJSTemplateLiterals();
59
- }
60
- function KiB(size) {
61
- return `${Math.ceil(size / 10.24) / 100} KiB`;
62
- }
63
23
  function setConfig(config) {
64
24
  config.base = './';
65
25
  config.build ??= {};
66
- config.build.assetsInlineLimit = () => true;
67
- config.build.chunkSizeWarningLimit = Infinity;
68
- config.build.cssCodeSplit = false;
69
26
  config.build.assetsDir = 'assets';
70
- config.build.modulePreload = { polyfill: false };
27
+ config.build.cssCodeSplit = false;
28
+ config.build.assetsInlineLimit ??= () => true;
29
+ config.build.chunkSizeWarningLimit ??= Infinity;
30
+ config.build.modulePreload ??= { polyfill: false };
31
+ config.build.target ??= 'esnext';
32
+ config.build.reportCompressedSize ??= false;
71
33
  config.build.rollupOptions ??= {};
72
34
  config.build.rollupOptions.output ??= {};
35
+ function setRollupOutput(output) {
36
+ output.inlineDynamicImports = true;
37
+ delete output.assetFileNames;
38
+ delete output.chunkFileNames;
39
+ delete output.entryFileNames;
40
+ }
73
41
  if (Array.isArray(config.build.rollupOptions.output)) {
74
42
  for (const output of config.build.rollupOptions.output) {
75
- output.inlineDynamicImports = true;
43
+ setRollupOutput(output);
76
44
  }
77
45
  }
78
46
  else {
79
- config.build.rollupOptions.output.inlineDynamicImports = true;
47
+ setRollupOutput(config.build.rollupOptions.output);
80
48
  }
81
49
  }
82
50
  async function generateBundle(bundle, config, options) {
83
51
  console.log(pc.cyan('\n\nvite-plugin-singlefile-compression ' + version) + pc.green(' building...'));
84
- if (config.base !== './')
85
- return console.error("error: config.base has been changed!");
86
- if (config.build.assetsDir !== 'assets')
87
- return console.error("error: config.build.assetsDir has been changed!");
88
52
  const distURL = pathToFileURL(path.resolve(config.build.outDir)).href + '/';
89
53
  const globalDelete = new Set();
90
54
  const globalDoNotDelete = new Set();
@@ -109,9 +73,6 @@ async function generateBundle(bundle, config, options) {
109
73
  const thisDel = new Set();
110
74
  // Fix async import
111
75
  const newJSCode = ["self.__VITE_PRELOAD__=void 0"];
112
- newJSCode.toString = function () {
113
- return this.join(';');
114
- };
115
76
  // remove html comments
116
77
  newHtml = newHtml.replaceAll(/<!--[\d\D]*?-->/g, '');
117
78
  // get css tag
@@ -227,7 +188,7 @@ async function generateBundle(bundle, config, options) {
227
188
  // add script for load html assets
228
189
  const assetsJSON = JSON.stringify(assetsDataURL);
229
190
  if (assetsJSON != '{}')
230
- newJSCode.push(templateAssets.join(assetsJSON));
191
+ newJSCode.push(template.assets(assetsJSON));
231
192
  }
232
193
  // add script
233
194
  newJSCode.push(js.code.replace(/;?\n?$/, ''));
@@ -240,9 +201,8 @@ async function generateBundle(bundle, config, options) {
240
201
  if (options.htmlMinifierTerser)
241
202
  newHtml = await htmlMinify(newHtml, options.htmlMinifierTerser);
242
203
  // fill script
243
- newHtml = newHtml.split('self.__vitePluginSinglefileCompression=1', 2).join(options.useBase128
244
- ? templateBase128.join(gzipToBase128(newJSCode.toString()))
245
- : template.join(gzipToBase64(newJSCode.toString())));
204
+ const compressedScript = compress(options.compressFormat, newJSCode.join(';'), options.useBase128);
205
+ newHtml = newHtml.split('self.__vitePluginSinglefileCompression=1', 2).join(template.base(compressedScript, options.compressFormat, options.useBase128));
246
206
  // finish
247
207
  htmlChunk.source = newHtml;
248
208
  console.log("\n"
@@ -0,0 +1,52 @@
1
+ import { Options as htmlMinifierOptions } from 'html-minifier-terser';
2
+ import { compressFormat } from './compress.js';
3
+ export interface Options {
4
+ /**
5
+ * https://github.com/terser/html-minifier-terser?tab=readme-ov-file#options-quick-reference
6
+ * @default defaultHtmlMinifierTerserOptions
7
+ */
8
+ htmlMinifierTerser?: htmlMinifierOptions | boolean;
9
+ /**
10
+ * Try inline html used assets, if inlined or not used in JS.
11
+ * @default true
12
+ */
13
+ tryInlineHtmlAssets?: boolean;
14
+ /**
15
+ * Remove inlined asset files.
16
+ * @default true
17
+ */
18
+ removeInlinedAssetFiles?: boolean;
19
+ /**
20
+ * Try inline html icon, if icon is in public dir.
21
+ * @default true
22
+ */
23
+ tryInlineHtmlPublicIcon?: boolean;
24
+ /**
25
+ * Remove inlined html icon files.
26
+ * @default true
27
+ */
28
+ removeInlinedPublicIconFiles?: boolean;
29
+ /**
30
+ * Use Base128 to encode gzipped script.
31
+ * If false, use Base64.
32
+ * https://www.npmjs.com/package/base128-ascii
33
+ * @default true
34
+ */
35
+ useBase128?: boolean;
36
+ /**
37
+ * Compress format.
38
+ * @default "deflate-raw"
39
+ */
40
+ compressFormat?: compressFormat;
41
+ }
42
+ export declare const defaultHtmlMinifierTerserOptions: htmlMinifierOptions;
43
+ export interface innerOptions {
44
+ htmlMinifierTerser: htmlMinifierOptions | false;
45
+ tryInlineHtmlAssets: boolean;
46
+ removeInlinedAssetFiles: boolean;
47
+ tryInlineHtmlPublicIcon: boolean;
48
+ removeInlinedPublicIconFiles: boolean;
49
+ useBase128: boolean;
50
+ compressFormat: "deflate-raw" | "deflate" | "gzip";
51
+ }
52
+ export declare function getInnerOptions(opt?: Options): innerOptions;
@@ -0,0 +1,24 @@
1
+ export const defaultHtmlMinifierTerserOptions = {
2
+ removeAttributeQuotes: true,
3
+ removeComments: true,
4
+ collapseWhitespace: true,
5
+ removeOptionalTags: true,
6
+ removeRedundantAttributes: true,
7
+ minifyJS: false,
8
+ };
9
+ export function getInnerOptions(opt) {
10
+ opt ||= {};
11
+ return {
12
+ htmlMinifierTerser: opt.htmlMinifierTerser == null || opt.htmlMinifierTerser === true
13
+ ? defaultHtmlMinifierTerserOptions
14
+ : opt.htmlMinifierTerser,
15
+ tryInlineHtmlAssets: opt.tryInlineHtmlAssets ?? true,
16
+ removeInlinedAssetFiles: opt.removeInlinedAssetFiles ?? true,
17
+ tryInlineHtmlPublicIcon: opt.tryInlineHtmlPublicIcon ?? true,
18
+ removeInlinedPublicIconFiles: opt.removeInlinedPublicIconFiles ?? true,
19
+ useBase128: opt.useBase128 ?? true,
20
+ compressFormat: ["deflate-raw", "deflate", "gzip"].includes(opt.compressFormat)
21
+ ? opt.compressFormat
22
+ : "deflate-raw",
23
+ };
24
+ }
@@ -0,0 +1,24 @@
1
+ This is free and unencumbered software released into the public domain.
2
+
3
+ Anyone is free to copy, modify, publish, use, compile, sell, or
4
+ distribute this software, either in source code form or as a compiled
5
+ binary, for any purpose, commercial or non-commercial, and by any
6
+ means.
7
+
8
+ In jurisdictions that recognize copyright laws, the author or authors
9
+ of this software dedicate any and all copyright interest in the
10
+ software to the public domain. We make this dedication for the benefit
11
+ of the public at large and to the detriment of our heirs and
12
+ successors. We intend this dedication to be an overt act of
13
+ relinquishment in perpetuity of all present and future rights to this
14
+ software under copyright law.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19
+ IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
20
+ OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21
+ ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
+ OTHER DEALINGS IN THE SOFTWARE.
23
+
24
+ For more information, please refer to <https://unlicense.org/>
@@ -1 +1 @@
1
- function s(e){let l=0,o=0,n,r=()=>n=e.charCodeAt(l++),t=new Uint8Array(Math.floor(e.length/8*7));for(;l<e.length;)t[o++]=r()<<1|r()>>6,t[o++]=n<<2|r()>>5,t[o++]=n<<3|r()>>4,t[o++]=n<<4|r()>>3,t[o++]=n<<5|r()>>2,t[o++]=n<<6|r()>>1,t[o++]=n<<7|r();return t}new Response(new ReadableStream({start(e){e.enqueue(s("<script>")),e.close()}}).pipeThrough(new DecompressionStream("gzip")),{headers:{"Content-Type":"text/javascript"}}).blob().then(e=>import(e=URL.createObjectURL(e)).finally(()=>URL.revokeObjectURL(e)))
1
+ function s(e){let l=0,o=0,n,r=()=>n=e.charCodeAt(l++),t=new Uint8Array(Math.floor(e.length/8*7));for(;l<e.length;)t[o++]=r()<<1|r()>>6,t[o++]=n<<2|r()>>5,t[o++]=n<<3|r()>>4,t[o++]=n<<4|r()>>3,t[o++]=n<<5|r()>>2,t[o++]=n<<6|r()>>1,t[o++]=n<<7|r();return t}new Response(new ReadableStream({start(e){e.enqueue(s("<script>")),e.close()}}).pipeThrough(new DecompressionStream("<format>")),{headers:{"Content-Type":"text/javascript"}}).blob().then(e=>import(e=URL.createObjectURL(e)).finally(()=>URL.revokeObjectURL(e)))
@@ -0,0 +1 @@
1
+ fetch("data:;base64,<script>").then(e=>new Response(e.body.pipeThrough(new DecompressionStream("<format>")),{headers:{"Content-Type":"text/javascript"}}).blob()).then(e=>import(e=URL.createObjectURL(e)).finally(()=>URL.revokeObjectURL(e)))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vite-plugin-singlefile-compression",
3
- "version": "1.2.5",
3
+ "version": "1.3.0",
4
4
  "main": "dist/index.js",
5
5
  "typings": "dist/index.d.ts",
6
6
  "files": [
@@ -42,7 +42,7 @@
42
42
  "dependencies": {
43
43
  "@types/html-minifier-terser": "^7.0.2",
44
44
  "@types/node": "^22.9.3",
45
- "base128-ascii": "^2.0.2",
45
+ "base128-ascii": "^2.0.3",
46
46
  "esbuild": "^0.24.0",
47
47
  "html-minifier-terser": "^7.2.0",
48
48
  "mime": "^4.0.4",
package/dist/template.js DELETED
@@ -1 +0,0 @@
1
- fetch("data:application/gzip;base64,{<script>}").then(e=>new Response(e.body.pipeThrough(new DecompressionStream("gzip")),{headers:{"Content-Type":"text/javascript"}}).blob()).then(e=>import(e=URL.createObjectURL(e)).finally(()=>URL.revokeObjectURL(e)))
File without changes