vite-plugin-singlefile-compression 1.0.2 → 1.0.3

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 CHANGED
@@ -33,6 +33,20 @@ export default defineConfig({
33
33
  // Add singleFileCompression
34
34
  singleFileCompression(),
35
35
  ],
36
+ esbuild: {
37
+ // Remove license comments
38
+ legalComments: "none"
39
+ },
40
+ build: {
41
+ terserOptions: {
42
+ format: {
43
+ // Remove license comments
44
+ comments: false
45
+ }
46
+ },
47
+ target: 'esnext',
48
+ reportCompressedSize: false
49
+ },
36
50
  ```
37
51
 
38
52
  Then modify [src/router/index.ts](test/src/router/index.ts#L5) , change `createWebHistory` to `createWebHashHistory`
@@ -42,6 +56,18 @@ const router = createRouter({
42
56
  history: createWebHashHistory(),
43
57
  ```
44
58
 
59
+ ## Options
60
+
61
+ ```ts
62
+ export interface Options {
63
+ /**
64
+ * https://github.com/terser/html-minifier-terser?tab=readme-ov-file#options-quick-reference
65
+ * @default defaultHtmlMinifierTerserOptions
66
+ */
67
+ htmlMinifierTerser?: htmlMinifierOptions | true | false
68
+ }
69
+ ```
70
+
45
71
  ## Effect
46
72
 
47
73
  ```
@@ -52,28 +78,16 @@ rendering chunks (1)...
52
78
  vite-plugin-singlefile-compression building...
53
79
 
54
80
  file:///D:/bddjr/Desktop/code/js/vite-plugin-singlefile-compression/test/dist/index.html
55
- 97.2 KiB -> 50.91 KiB
81
+ 97.52 KiB -> 50.98 KiB
56
82
 
57
83
  Finish.
58
84
 
59
- dist/index.html 52.13 kB
60
- ✓ built in 778ms
85
+ dist/index.html 52.19 kB
86
+ ✓ built in 685ms
61
87
  ```
62
88
 
63
89
  ```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>
90
+ <!DOCTYPE html><meta charset=UTF-8><link rel=icon href=data:logo-_cUAdIX-.svg><meta name=viewport content="width=device-width,initial-scale=1"><title>Vite App</title><script type=module>fetch("data:application/gzip;base64,H4sI********hAEA").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><div id=app></div>
77
91
  ```
78
92
 
79
93
  ## Clone
package/dist/index.d.ts CHANGED
@@ -1,3 +1,12 @@
1
1
  import { PluginOption } from "vite";
2
- export declare function singleFileCompression(): PluginOption;
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
+ export declare const defaultHtmlMinifierTerserOptions: htmlMinifierOptions;
11
+ export declare function singleFileCompression(options?: Options): PluginOption;
3
12
  export default singleFileCompression;
package/dist/index.js CHANGED
@@ -1,28 +1,47 @@
1
1
  import mime from 'mime';
2
2
  import pc from "picocolors";
3
3
  import svgToTinyDataUri from "mini-svg-data-uri";
4
+ import { minify as htmlMinify } from 'html-minifier-terser';
4
5
  import zlib from 'zlib';
5
6
  import path from 'path';
6
7
  import fs from 'fs';
7
- export function singleFileCompression() {
8
+ import { pathToFileURL } from "url";
9
+ export const defaultHtmlMinifierTerserOptions = {
10
+ removeAttributeQuotes: true,
11
+ removeComments: true,
12
+ collapseWhitespace: true,
13
+ removeOptionalTags: true,
14
+ removeRedundantAttributes: true,
15
+ minifyJS: false,
16
+ };
17
+ export function singleFileCompression(options) {
18
+ const htmlMinifierOptions = options?.htmlMinifierTerser == null || options.htmlMinifierTerser === true
19
+ ? defaultHtmlMinifierTerserOptions
20
+ : options.htmlMinifierTerser;
8
21
  return {
9
- name: "singleFileGzip",
22
+ name: "singleFileCompression",
10
23
  enforce: "post",
11
24
  config: setConfig,
12
- generateBundle,
25
+ generateBundle: (_, bundle) => generateBundle(bundle, htmlMinifierOptions),
13
26
  };
14
27
  }
15
28
  export default singleFileCompression;
16
29
  const template = fs.readFileSync(path.join(import.meta.dirname, "template.js")).toString();
17
30
  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"));
31
+ const distURL = pathToFileURL(path.resolve("dist")) + "/";
32
+ function gzipToBase64(buf) {
33
+ return zlib.gzipSync(buf, {
34
+ level: zlib.constants.Z_BEST_COMPRESSION,
35
+ }).toString('base64');
36
+ }
37
+ function KiB(size) {
38
+ return `${Math.ceil(size / 10.24) / 100} KiB`;
39
+ }
21
40
  function setConfig(config) {
22
41
  config.base = './';
23
42
  if (!config.build)
24
43
  config.build = {};
25
- config.build.assetsInlineLimit = Infinity;
44
+ config.build.assetsInlineLimit = () => true;
26
45
  config.build.chunkSizeWarningLimit = Infinity;
27
46
  config.build.cssCodeSplit = false;
28
47
  config.build.assetsDir = 'assets';
@@ -31,17 +50,10 @@ function setConfig(config) {
31
50
  config.build.rollupOptions = {};
32
51
  config.build.rollupOptions.output = { inlineDynamicImports: true };
33
52
  }
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) {
53
+ async function generateBundle(bundle, htmlMinifierOptions) {
43
54
  console.log(pc.cyan('\n\nvite-plugin-singlefile-compression ') + pc.green('building...'));
44
55
  const globalDel = new Set();
56
+ const globalDoNotDel = new Set();
45
57
  for (const htmlFileName of Object.keys(bundle)) {
46
58
  // key format:
47
59
  // index.html
@@ -54,7 +66,7 @@ function generateBundle(_, bundle) {
54
66
  let newHtml = htmlChunk.source;
55
67
  let oldSize = newHtml.length;
56
68
  const thisDel = new Set();
57
- // Fix async import
69
+ // Fix async import, fix new URL
58
70
  const newJSCode = ["self.__VITE_PRELOAD__=void 0"];
59
71
  // get css tag
60
72
  newHtml = newHtml.replace(/\s*<link rel="stylesheet"[^>]* href="\.\/(assets\/[^"]+)"[^>]*>/, (match, name) => {
@@ -100,6 +112,14 @@ function generateBundle(_, bundle) {
100
112
  thisDel.add(name);
101
113
  const js = bundle[name];
102
114
  oldSize += js.code.length;
115
+ // fix new URL
116
+ newJSCode.push(`import.meta.url=location.origin+location.pathname.replace(/[^/]*$/,"${name}")`);
117
+ for (const name in assets) {
118
+ // name: logo-XXXXXXXX.svg
119
+ if (js.code.includes(name))
120
+ globalDoNotDel.add("assets/" + name);
121
+ }
122
+ // add script
103
123
  newJSCode.push(js.code.replace(/;?\n?$/, ''));
104
124
  // gzip
105
125
  return '<script type="module">'
@@ -108,10 +128,12 @@ function generateBundle(_, bundle) {
108
128
  });
109
129
  if (!ok)
110
130
  continue;
131
+ if (htmlMinifierOptions)
132
+ newHtml = await htmlMinify(newHtml, htmlMinifierOptions);
111
133
  // finish
112
134
  htmlChunk.source = newHtml;
113
135
  console.log("\n"
114
- + " " + pc.underline(pc.cyan(fileProtocolDistPath) + pc.greenBright(htmlFileName)) + '\n'
136
+ + " " + pc.underline(pc.cyan(distURL) + pc.greenBright(htmlFileName)) + '\n'
115
137
  + " " + pc.gray(KiB(oldSize) + " -> ") + pc.cyanBright(KiB(newHtml.length)) + '\n');
116
138
  // delete assets
117
139
  for (const name of thisDel) {
@@ -120,7 +142,8 @@ function generateBundle(_, bundle) {
120
142
  }
121
143
  // delete inlined assets
122
144
  for (const name of globalDel) {
123
- delete bundle[name];
145
+ if (!globalDoNotDel.has(name))
146
+ delete bundle[name];
124
147
  }
125
148
  console.log(pc.green('Finish.\n'));
126
149
  }
@@ -1 +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]}}}
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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vite-plugin-singlefile-compression",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "main": "dist/index.js",
5
5
  "typings": "dist/index.d.ts",
6
6
  "files": [
@@ -38,15 +38,15 @@
38
38
  "css"
39
39
  ],
40
40
  "dependencies": {
41
+ "@types/html-minifier-terser": "^7.0.2",
42
+ "@types/node": "^22.9.3",
41
43
  "esbuild": "^0.24.0",
44
+ "html-minifier-terser": "^7.2.0",
42
45
  "mime": "^4.0.4",
43
46
  "mini-svg-data-uri": "^1.4.4",
44
47
  "picocolors": "^1.1.1",
45
48
  "rimraf": "^6.0.1",
46
49
  "typescript": "^5.7.2",
47
50
  "vite": "^5.4.11"
48
- },
49
- "devDependencies": {
50
- "@types/node": "^22.9.3"
51
51
  }
52
52
  }