vite-plugin-singlefile-compression 2.0.4 → 2.0.6

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-present bddjr ; Adapted from https://www.npmjs.com/package/vite-plugin-singlefile
2
+ Copyright © 2024 bddjr
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
@@ -49,54 +49,60 @@ More info see [src/options.ts](src/options.ts)
49
49
 
50
50
  ```ts
51
51
  export interface Options {
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;
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
+ * https://developer.mozilla.org/en-US/docs/Web/API/DecompressionStream/DecompressionStream
98
+ * @default "deflate-raw"
99
+ */
100
+ compressFormat?: compressFormat
101
+
102
+ /**
103
+ * Custom compressor.
104
+ */
105
+ compressor?: compressor
100
106
  }
101
107
  ```
102
108
 
@@ -105,19 +111,19 @@ export interface Options {
105
111
  https://bddjr.github.io/vite-plugin-singlefile-compression/
106
112
 
107
113
  ```
108
- vite v6.0.11 building for production...
109
- 45 modules transformed.
114
+ vite v7.3.1 building client environment for production...
115
+ 46 modules transformed.
110
116
  rendering chunks (1)...
111
117
 
112
- vite-plugin-singlefile-compression 2.0.1 building...
118
+ vite-plugin-singlefile-compression 2.0.6 building...
113
119
 
114
- file:///D:/bddjr/Desktop/code/js/vite-plugin-singlefile-compression/test/dist/index.html
115
- 101.6 KiB -> 46.39 KiB
120
+ file:///D:/code/js/vite-plugin-singlefile-compression/test/dist/index.html
121
+ 107.124 kB -> 48.342 kB
116
122
 
117
123
  Finish.
118
124
 
119
- dist/index.html 47.50 kB
120
- ✓ built in 732ms
125
+ dist/index.html 48.34 kB
126
+ ✓ built in 783ms
121
127
  ```
122
128
 
123
129
  ![](effect.jpg)
@@ -131,12 +137,5 @@ npm i
131
137
  cd test
132
138
  npm i
133
139
  cd ..
134
- npm run build
140
+ node --run build
135
141
  ```
136
-
137
- ## License
138
-
139
- Using [MIT License](LICENSE.txt).
140
- [src/template](src/template) using [Unlicense](src/template/LICENSE.txt).
141
-
142
- Adapted from [vite-plugin-singlefile](https://www.npmjs.com/package/vite-plugin-singlefile).
@@ -1,3 +1,12 @@
1
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;
2
+ declare const compressors: {
3
+ "deflate-raw"(buf: zlib.InputType): Buffer;
4
+ deflate(buf: zlib.InputType): Buffer;
5
+ gzip(buf: zlib.InputType): Buffer;
6
+ brotli: typeof zlib.brotliCompressSync;
7
+ zstd: typeof zlib.zstdCompressSync;
8
+ };
9
+ export type compressor = ((buf: zlib.InputType) => Buffer);
10
+ export type compressFormat = keyof typeof compressors;
11
+ export declare function compress(format: compressFormat, buf: zlib.InputType, useBase128: boolean, compressor: compressor): string;
12
+ export {};
package/dist/compress.js CHANGED
@@ -1,24 +1,33 @@
1
1
  import base128 from "base128-ascii";
2
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}`;
3
+ const zlibDefaultOptions = {
4
+ level: zlib.constants.Z_BEST_COMPRESSION,
5
+ };
6
+ const compressors = {
7
+ "deflate-raw"(buf) {
8
+ return zlib.deflateRawSync(buf, zlibDefaultOptions);
9
+ },
10
+ deflate(buf) {
11
+ return zlib.deflateSync(buf, zlibDefaultOptions);
12
+ },
13
+ gzip(buf) {
14
+ return zlib.gzipSync(buf, zlibDefaultOptions);
15
+ },
16
+ brotli: zlib.brotliCompressSync,
17
+ zstd: zlib.zstdCompressSync,
18
+ };
19
+ function switchCompressor(format) {
20
+ const f = compressors[format] || zlib[format + 'CompressSync'];
21
+ if (!f) {
22
+ if (Object.prototype.hasOwnProperty.call(compressors, format))
23
+ throw Error(`Could not get compressor: Please upgrade node.js or set your compressor function.`);
24
+ throw Error(`Could not get compressor: Unknown compress format '${format}', please set your compressor function.`);
20
25
  }
26
+ return f;
27
+ }
28
+ export function compress(format, buf, useBase128, compressor) {
29
+ const outBuf = compressor ? compressor(buf) : switchCompressor(format)(buf);
21
30
  return useBase128
22
- ? base128.encode(Uint8Array.from(outBuf)).toJSTemplateLiterals()
31
+ ? base128.encode(outBuf).toJSTemplateLiterals()
23
32
  : outBuf.toString('base64');
24
33
  }
@@ -0,0 +1 @@
1
+ export declare function cutPrefix(str: string, prefix: string): string;
@@ -0,0 +1,5 @@
1
+ export function cutPrefix(str, prefix) {
2
+ return str.startsWith(prefix)
3
+ ? str.slice(prefix.length)
4
+ : str;
5
+ }
@@ -1,5 +1,8 @@
1
+ import { compressFormat, compressor } from './compress.js';
1
2
  export declare const template: {
2
- base(script: string, format: string, useBase128: boolean): string;
3
+ base(script: string, format: compressFormat, useBase128: boolean, compressor: compressor): string;
3
4
  assets(assetsJSON: string): string;
4
- resolve: string;
5
+ css(cssSource: string): string;
6
+ icon(dataURL: string): string;
7
+ importmeta(p: string): string;
5
8
  };
@@ -1,13 +1,26 @@
1
1
  import fs from 'fs';
2
2
  import { fileURLToPath } from 'url';
3
+ import { compress } from './compress.js';
4
+ function r(name) {
5
+ return fs.readFileSync(fileURLToPath(import.meta.resolve(`./template/${name}.js`))).toString();
6
+ }
7
+ function rt(name, template) {
8
+ const s = r(name).split(template);
9
+ if (s.length !== 2)
10
+ throw Error("s.length!==2");
11
+ return s;
12
+ }
3
13
  const files = {
4
- base64: fs.readFileSync(fileURLToPath(import.meta.resolve("./template/base64.js"))).toString(),
5
- base128: fs.readFileSync(fileURLToPath(import.meta.resolve("./template/base128.js"))).toString(),
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(),
14
+ base64: r('base64'),
15
+ base128: r('base128'),
16
+ assets: rt('assets', '{"":""}'),
17
+ css: rt('css', '"<style>"'),
18
+ icon: rt('icon', '"<icon>"'),
19
+ importmeta: rt('importmeta', '"<path>"'),
8
20
  };
9
21
  export const template = {
10
- base(script, format, useBase128) {
22
+ base(script, format, useBase128, compressor) {
23
+ script = compress(format, script, useBase128, compressor);
11
24
  if (useBase128) {
12
25
  return files.base128
13
26
  .replace("<format>", format)
@@ -20,5 +33,13 @@ export const template = {
20
33
  assets(assetsJSON) {
21
34
  return files.assets.join(assetsJSON);
22
35
  },
23
- resolve: files.resolve,
36
+ css(cssSource) {
37
+ return files.css.join(JSON.stringify(cssSource));
38
+ },
39
+ icon(dataURL) {
40
+ return files.icon.join(JSON.stringify(dataURL));
41
+ },
42
+ importmeta(p) {
43
+ return files.importmeta.join(JSON.stringify(p));
44
+ },
24
45
  };
package/dist/index.js CHANGED
@@ -6,10 +6,10 @@ import fs from 'fs';
6
6
  import { pathToFileURL } from "url";
7
7
  import { version } from './getVersion.js';
8
8
  import { template } from './getTemplate.js';
9
- import { compress } from "./compress.js";
10
9
  import { bufferToDataURL } from "./dataurl.js";
11
- import { KiB } from "./KiB.js";
10
+ import { kB } from "./kB.js";
12
11
  import { getInnerOptions } from "./options.js";
12
+ import { cutPrefix } from "./cutPrefix.js";
13
13
  export function singleFileCompression(opt) {
14
14
  let conf;
15
15
  return {
@@ -22,19 +22,17 @@ export function singleFileCompression(opt) {
22
22
  }
23
23
  export default singleFileCompression;
24
24
  function setConfig(config) {
25
- config.base = './';
26
- config.build ??= {};
27
- // config.build.assetsDir = 'assets'
28
- config.build.cssCodeSplit = false;
29
- config.build.assetsInlineLimit ??= () => true;
30
- config.build.chunkSizeWarningLimit ??= Number.MAX_SAFE_INTEGER;
31
- config.build.modulePreload ??= { polyfill: false };
32
- // config.build.target ??= 'esnext'
33
- config.build.reportCompressedSize ??= false;
34
- config.build.rollupOptions ??= {};
35
- config.build.rollupOptions.output ??= {};
36
- for (const output of [config.build.rollupOptions.output].flat(1)) {
37
- output.inlineDynamicImports = true;
25
+ var _a, _b, _c, _d, _e, _f, _g;
26
+ config.base ?? (config.base = './');
27
+ config.build ?? (config.build = {});
28
+ (_a = config.build).cssCodeSplit ?? (_a.cssCodeSplit = false);
29
+ (_b = config.build).assetsInlineLimit ?? (_b.assetsInlineLimit = () => true);
30
+ (_c = config.build).chunkSizeWarningLimit ?? (_c.chunkSizeWarningLimit = Number.MAX_SAFE_INTEGER);
31
+ (_d = config.build).modulePreload ?? (_d.modulePreload = { polyfill: false });
32
+ (_e = config.build).reportCompressedSize ?? (_e.reportCompressedSize = false);
33
+ (_f = config.build).rollupOptions ?? (_f.rollupOptions = {});
34
+ for (const output of [(_g = config.build.rollupOptions).output ?? (_g.output = {})].flat(1)) {
35
+ output.inlineDynamicImports ?? (output.inlineDynamicImports = true);
38
36
  }
39
37
  }
40
38
  async function generateBundle(bundle, config, options) {
@@ -48,23 +46,19 @@ async function generateBundle(bundle, config, options) {
48
46
  bundle[options.rename].fileName = options.rename;
49
47
  delete bundle["index.html"];
50
48
  }
51
- const distURL = pathToFileURL(config.build.outDir).href + '/';
49
+ const distURL = pathToFileURL(config.build.outDir).href + '/',
52
50
  /** "assets/" */
53
- const assetsDir = path.posix.join(config.build.assetsDir, '/');
51
+ assetsDir = path.posix.join(config.build.assetsDir, '/'),
52
+ /** "./assets/" */
53
+ assetsDirWithBase = config.base + assetsDir,
54
54
  /** '[href^="./assets/"]' */
55
- const assetsHrefSelector = `[href^="./${assetsDir}"]`;
55
+ assetsHrefSelector = `[href^="${assetsDirWithBase}"]`,
56
56
  /** '[src^="./assets/"]' */
57
- const assetsSrcSelector = `[src^="./${assetsDir}"]`;
58
- const fakeScript = `_vitePluginSinglefileCompression(${Date.now()})`;
59
- const globalDelete = new Set();
60
- const globalDoNotDelete = new Set();
61
- const globalRemoveDistFileNames = new Set();
62
- const globalAssetsDataURL = {};
63
- const globalPublicFilesCache = {};
64
- /** fotmat: ["assets/index-XXXXXXXX.js"] */
65
- const bundleAssetsNames = [];
57
+ assetsSrcSelector = `[src^="${assetsDirWithBase}"]`, fakeScript = `_vitePluginSinglefileCompression(${Date.now()})`, globalDelete = new Set(), globalDoNotDelete = new Set(), globalRemoveDistFileNames = new Set(), globalAssetsDataURL = {}, globalPublicFilesCache = {},
58
+ /** format: ["assets/index-XXXXXXXX.js"] */
59
+ bundleAssetsNames = [],
66
60
  /** format: ["index.html"] */
67
- const bundleHTMLNames = [];
61
+ bundleHTMLNames = [];
68
62
  for (const name of Object.keys(bundle)) {
69
63
  if (name.startsWith(assetsDir))
70
64
  bundleAssetsNames.push(name);
@@ -72,60 +66,56 @@ async function generateBundle(bundle, config, options) {
72
66
  bundleHTMLNames.push(name);
73
67
  }
74
68
  for (const htmlFileName of bundleHTMLNames) {
69
+ console.log("\n " + pc.underline(pc.cyan(distURL) + pc.greenBright(bundle[htmlFileName].fileName)));
75
70
  // init
76
- const htmlChunk = bundle[htmlFileName];
77
- const oldHTML = htmlChunk.source;
71
+ const htmlChunk = bundle[htmlFileName], oldHTML = htmlChunk.source, dom = new JSDOM(oldHTML), document = dom.window.document, thisDel = new Set(), newJSCode = [], scriptElement = document.querySelector(`script[type=module]${assetsSrcSelector}`), scriptName = scriptElement ? cutPrefix(scriptElement.src, config.base) : '';
78
72
  let oldSize = oldHTML.length;
79
- const dom = new JSDOM(oldHTML);
80
- const document = dom.window.document;
81
- const scriptElement = document.querySelector(`script[type=module]${assetsSrcSelector}`);
82
- if (!scriptElement) {
83
- console.error(`Error: Can not find script tag from ${htmlFileName}`);
84
- continue;
73
+ // fill fake script
74
+ if (scriptElement) {
75
+ scriptElement.remove();
76
+ scriptElement.removeAttribute('src');
77
+ scriptElement.innerHTML = fakeScript;
78
+ document.body.appendChild(scriptElement);
79
+ }
80
+ else {
81
+ document.body.insertAdjacentHTML('beforeend', `<script type="module">${fakeScript}</script>`);
85
82
  }
86
- const thisDel = new Set();
87
- // Fix async import
88
- const newJSCode = ["self.__VITE_PRELOAD__=void 0"];
89
83
  // get css tag
90
- {
91
- const element = document.querySelector(`link[rel=stylesheet]${assetsHrefSelector}`);
92
- if (element) {
93
- const name = element.href.slice("./".length);
94
- thisDel.add(name);
95
- const css = bundle[name];
96
- const cssSource = css.source;
97
- if (cssSource) {
98
- oldSize += cssSource.length;
99
- // do not delete not inlined asset
100
- for (const name of bundleAssetsNames) {
101
- if (cssSource.includes(name.slice(assetsDir.length)))
102
- globalDoNotDelete.add(name);
103
- }
104
- // add script for load css
105
- newJSCode.push(
106
- // 'document.querySelector("script[type=module]").insertAdjacentElement("afterend",document.createElement("style")).innerHTML='
107
- 'document.head.appendChild(document.createElement("style")).innerHTML='
108
- + JSON.stringify(cssSource.replace(/\n$/, '')));
84
+ let allCSS = '';
85
+ for (const element of document.querySelectorAll(`link[rel=stylesheet]${assetsHrefSelector}`)) {
86
+ const name = cutPrefix(element.href, config.base);
87
+ thisDel.add(name);
88
+ const css = bundle[name];
89
+ const cssSource = css.source;
90
+ if (cssSource) {
91
+ oldSize += cssSource.length;
92
+ // do not delete not inlined asset
93
+ for (const name of bundleAssetsNames) {
94
+ if (cssSource.includes(name.slice(assetsDir.length)))
95
+ globalDoNotDelete.add(name);
109
96
  }
110
- // delete tag
111
- element.remove();
97
+ // add script for load css
98
+ allCSS += cssSource.replace(/\n$/, '');
112
99
  }
100
+ // remove tag
101
+ element.remove();
113
102
  }
103
+ newJSCode.push(template.css(allCSS));
114
104
  // inline html assets
115
105
  const assetsDataURL = {};
116
106
  if (options.tryInlineHtmlAssets) {
117
107
  for (const element of document.querySelectorAll(assetsSrcSelector)) {
118
- const name = element.src.slice(`./${assetsDir}`.length);
108
+ const name = cutPrefix(element.src, assetsDirWithBase);
119
109
  if (name.endsWith('.js'))
120
110
  continue;
121
- if (!Object.hasOwn(assetsDataURL, name)) {
111
+ if (!Object.prototype.hasOwnProperty.call(assetsDataURL, name)) {
122
112
  const bundleName = assetsDir + name;
123
113
  const a = bundle[bundleName];
124
114
  if (!a)
125
115
  continue;
126
116
  thisDel.add(bundleName);
127
117
  oldSize += a.source.length;
128
- if (!Object.hasOwn(globalAssetsDataURL, name))
118
+ if (!Object.prototype.hasOwnProperty.call(globalAssetsDataURL, name))
129
119
  globalAssetsDataURL[name] = bufferToDataURL(name, Buffer.from(
130
120
  //@ts-ignore
131
121
  a.source));
@@ -139,9 +129,9 @@ async function generateBundle(bundle, config, options) {
139
129
  let needInline = true;
140
130
  let iconName = 'favicon.ico';
141
131
  // replace tag
142
- const element = document.querySelector('link[rel=icon][href^="./"], link[rel="shortcut icon"][href^="./"]');
132
+ const element = document.querySelector(`link[rel=icon][href^="${config.base}"], link[rel="shortcut icon"][href^="${config.base}"]`);
143
133
  if (element) {
144
- iconName = element.href.slice("./".length);
134
+ iconName = cutPrefix(element.href, config.base);
145
135
  if (bundleAssetsNames.includes(iconName)) {
146
136
  needInline = false;
147
137
  }
@@ -153,7 +143,7 @@ async function generateBundle(bundle, config, options) {
153
143
  if (needInline) {
154
144
  // inline
155
145
  try {
156
- if (!Object.hasOwn(globalPublicFilesCache, iconName)) {
146
+ if (!Object.prototype.hasOwnProperty.call(globalPublicFilesCache, iconName)) {
157
147
  // dist/favicon.ico
158
148
  let Path = path.join(config.build.outDir, iconName);
159
149
  if (fs.existsSync(Path)) {
@@ -172,7 +162,7 @@ async function generateBundle(bundle, config, options) {
172
162
  }
173
163
  const { dataURL, size } = globalPublicFilesCache[iconName];
174
164
  oldSize += size;
175
- newJSCode.push('document.querySelector("link[rel=icon]").href=' + JSON.stringify(dataURL));
165
+ newJSCode.push(template.icon(dataURL));
176
166
  if (!element) {
177
167
  // add link icon tag
178
168
  document.head.insertAdjacentHTML('beforeend', '<link rel="icon" href="data:">');
@@ -184,48 +174,47 @@ async function generateBundle(bundle, config, options) {
184
174
  }
185
175
  }
186
176
  }
187
- // script
188
- {
189
- const name = scriptElement.src.slice("./".length);
190
- thisDel.add(name);
191
- const js = bundle[name];
192
- oldSize += js.code.length;
193
- // fix new URL
194
- newJSCode.push(`import.meta.url=new URL(${JSON.stringify(name)},location).href`);
195
- // fix import.meta.resolve
196
- newJSCode.push(template.resolve);
177
+ // generate html
178
+ htmlChunk.source = dom.serialize();
179
+ // minify html
180
+ if (options.htmlMinifierTerser)
181
+ htmlChunk.source = await htmlMinify(htmlChunk.source, options.htmlMinifierTerser);
182
+ // fill script
183
+ function inlineHtmlAssets() {
184
+ if (options.tryInlineHtmlAssets) {
185
+ // add script for load html assets
186
+ const assetsJSON = JSON.stringify(assetsDataURL);
187
+ if (assetsJSON !== '{}')
188
+ newJSCode.push(template.assets(assetsJSON));
189
+ }
190
+ }
191
+ if (scriptElement) {
192
+ thisDel.add(scriptName);
193
+ let { code } = bundle[scriptName];
194
+ oldSize += code.length;
195
+ code = code.replace(/;?\n?$/, '');
197
196
  // do not delete not inlined asset
198
197
  for (const name of bundleAssetsNames) {
199
198
  const assetName = name.slice(assetsDir.length);
200
- if (js.code.includes(assetName)) {
199
+ if (code.includes(assetName)) {
201
200
  globalDoNotDelete.add(name);
202
201
  delete assetsDataURL[assetName];
203
202
  }
204
203
  }
205
- if (options.tryInlineHtmlAssets) {
206
- // add script for load html assets
207
- const assetsJSON = JSON.stringify(assetsDataURL);
208
- if (assetsJSON != '{}')
209
- newJSCode.push(template.assets(assetsJSON));
210
- }
211
- // add script
212
- newJSCode.push(js.code.replace(/;?\n?$/, ''));
204
+ inlineHtmlAssets();
205
+ newJSCode.push(template.importmeta(scriptName));
206
+ // polyfill 仅在以下选项的值为 true 时需要。
207
+ // config.build.rollupOptions.output.inlineDynamicImports
208
+ if (code.includes("__VITE_PRELOAD__"))
209
+ newJSCode.push("var __VITE_PRELOAD__");
210
+ newJSCode.push(code);
213
211
  }
214
- // fill fake script
215
- scriptElement.removeAttribute('src');
216
- scriptElement.removeAttribute('crossorigin');
217
- scriptElement.innerHTML = fakeScript;
218
- // generate html
219
- htmlChunk.source = dom.serialize();
220
- // minify html
221
- if (options.htmlMinifierTerser)
222
- htmlChunk.source = await htmlMinify(htmlChunk.source, options.htmlMinifierTerser);
223
- // fill script
224
- htmlChunk.source = htmlChunk.source.split(fakeScript, 2).join(template.base(compress(options.compressFormat, newJSCode.join(';'), options.useBase128), options.compressFormat, options.useBase128));
212
+ else {
213
+ inlineHtmlAssets();
214
+ }
215
+ htmlChunk.source = htmlChunk.source.split(fakeScript, 2).join(template.base(newJSCode.join(';'), options.compressFormat, options.useBase128, options.compressor));
225
216
  // log
226
- console.log("\n"
227
- + " " + pc.underline(pc.cyan(distURL) + pc.greenBright(bundle[htmlFileName].fileName)) + '\n'
228
- + " " + pc.gray(KiB(oldSize) + " -> ") + pc.cyanBright(KiB(htmlChunk.source.length)) + '\n');
217
+ console.log(" " + pc.gray(kB(oldSize) + " -> ") + pc.cyanBright(kB(htmlChunk.source.length)) + '\n');
229
218
  // delete assets
230
219
  for (const name of thisDel) {
231
220
  globalDelete.add(name);
@@ -250,5 +239,5 @@ async function generateBundle(bundle, config, options) {
250
239
  }
251
240
  }
252
241
  }
253
- console.log(pc.green('Finish.\n'));
242
+ console.log(pc.green('Finish.') + pc.reset('\n'));
254
243
  }
package/dist/kB.d.ts ADDED
@@ -0,0 +1 @@
1
+ export declare function kB(size: number): string;
package/dist/kB.js ADDED
@@ -0,0 +1,3 @@
1
+ export function kB(size) {
2
+ return `${size / 1000} kB`;
3
+ }
package/dist/options.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Options as htmlMinifierOptions } from 'html-minifier-terser';
2
- import { compressFormat } from './compress.js';
2
+ import { compressFormat, compressor } from './compress.js';
3
3
  export interface Options {
4
4
  /**
5
5
  * Rename index.html
@@ -39,9 +39,14 @@ export interface Options {
39
39
  useBase128?: boolean;
40
40
  /**
41
41
  * Compress format.
42
+ * https://developer.mozilla.org/en-US/docs/Web/API/DecompressionStream/DecompressionStream
42
43
  * @default "deflate-raw"
43
44
  */
44
45
  compressFormat?: compressFormat;
46
+ /**
47
+ * Custom compressor.
48
+ */
49
+ compressor?: compressor;
45
50
  }
46
51
  export declare const defaultHtmlMinifierTerserOptions: htmlMinifierOptions;
47
52
  export interface innerOptions {
@@ -53,5 +58,6 @@ export interface innerOptions {
53
58
  removeInlinedPublicIconFiles: boolean;
54
59
  useBase128: boolean;
55
60
  compressFormat: compressFormat;
61
+ compressor?: compressor;
56
62
  }
57
63
  export declare function getInnerOptions(opt?: Options): innerOptions;
package/dist/options.js CHANGED
@@ -7,7 +7,7 @@ export const defaultHtmlMinifierTerserOptions = {
7
7
  minifyJS: false,
8
8
  };
9
9
  export function getInnerOptions(opt) {
10
- opt ||= {};
10
+ opt || (opt = {});
11
11
  return {
12
12
  rename: opt.rename && opt.rename.replace(/(\.(html?)?)?$/, '.html'),
13
13
  htmlMinifierTerser: opt.htmlMinifierTerser == null || opt.htmlMinifierTerser === true
@@ -18,8 +18,7 @@ export function getInnerOptions(opt) {
18
18
  tryInlineHtmlPublicIcon: opt.tryInlineHtmlPublicIcon ?? true,
19
19
  removeInlinedPublicIconFiles: opt.removeInlinedPublicIconFiles ?? true,
20
20
  useBase128: opt.useBase128 ?? true,
21
- compressFormat: ["deflate-raw", "deflate", "gzip"].includes(opt.compressFormat)
22
- ? opt.compressFormat
23
- : "deflate-raw",
21
+ compressFormat: opt.compressFormat || "deflate-raw",
22
+ compressor: typeof opt.compressor == 'function' ? opt.compressor : undefined,
24
23
  };
25
24
  }
@@ -1 +1 @@
1
- function l(e){let t=new Uint8Array(Math.floor(e.length/8*7)),a=0,c=0,r,o=()=>(r=e.charCodeAt(a++))>>7?r=0:r;for(;a<e.length;)t[c++]=o()<<1|o()>>6,t[c++]=r<<2|o()>>5,t[c++]=r<<3|o()>>4,t[c++]=r<<4|o()>>3,t[c++]=r<<5|o()>>2,t[c++]=r<<6|o()>>1,t[c++]=r<<7|o();return t}new Response(new ReadableStream({start(e){e.enqueue(l("<script>")),e.close()}}).pipeThrough(new DecompressionStream("<format>")),{headers:{"Content-Type":"text/javascript"}}).blob().then(e=>import(e=URL.createObjectURL(e)).finally(()=>URL.revokeObjectURL(e)))
1
+ var a="<script>",i=a.length,r=new Uint8Array(Math.floor(i/8*7)),c=0,o=0,e,t=()=>(e=a.charCodeAt(c++))>127?e=0:e;for(;c<i;)r[o++]=t()<<1|t()>>6,r[o++]=e<<2|t()>>5,r[o++]=e<<3|t()>>4,r[o++]=e<<4|t()>>3,r[o++]=e<<5|t()>>2,r[o++]=e<<6|t()>>1,r[o++]=e<<7|t();new Response(new Blob([r]).stream().pipeThrough(new DecompressionStream("<format>")),{headers:{"Content-Type":"text/javascript"}}).blob().then(n=>import(n=URL.createObjectURL(n)).finally(h=>URL.revokeObjectURL(n)))
@@ -1 +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)))
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(t=>URL.revokeObjectURL(e)))
@@ -0,0 +1 @@
1
+ document.head.appendChild(document.createElement("style")).innerHTML="<style>"
@@ -0,0 +1 @@
1
+ document.querySelector("link[rel=icon]").href="<icon>"
@@ -0,0 +1 @@
1
+ {import.meta.url=new URL("<path>",location).href;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,23 +1,27 @@
1
1
  {
2
2
  "name": "vite-plugin-singlefile-compression",
3
- "version": "2.0.4",
4
- "main": "dist/index.js",
5
- "typings": "dist/index.d.ts",
3
+ "version": "2.0.6",
4
+ "author": "bddjr",
5
+ "license": "MIT",
6
+ "description": "",
7
+ "main": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "browser": "null.js",
6
10
  "files": [
7
11
  "dist"
8
12
  ],
9
13
  "type": "module",
10
14
  "scripts": {
11
- "build": "rimraf dist && tsc && node build.js && cd test && npm run build",
12
- "prepublishOnly": "npm run build"
15
+ "build": "rimraf dist && tsc && node build.js && cd test && node --run build",
16
+ "prepublishOnly": "node --run build"
13
17
  },
14
- "author": "bddjr",
15
- "license": "MIT",
16
- "description": "",
17
18
  "repository": {
18
19
  "type": "git",
19
20
  "url": "git+https://github.com/bddjr/vite-plugin-singlefile-compression.git"
20
21
  },
22
+ "publishConfig": {
23
+ "registry": "https://registry.npmjs.org"
24
+ },
21
25
  "keywords": [
22
26
  "vite-plugin",
23
27
  "vite",
@@ -54,4 +58,4 @@
54
58
  "esbuild": ">=0.25.0",
55
59
  "typescript": ">=5.7.2"
56
60
  }
57
- }
61
+ }
package/dist/KiB.d.ts DELETED
@@ -1 +0,0 @@
1
- export declare function KiB(size: number): string;
package/dist/KiB.js DELETED
@@ -1,3 +0,0 @@
1
- export function KiB(size) {
2
- return `${Math.ceil(size / 10.24) / 100} KiB`;
3
- }
@@ -1,24 +0,0 @@
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 +0,0 @@
1
- {let t=import.meta.resolve;import.meta.resolve=e=>/^\.{0,2}\//.test(e)?new URL(e,import.meta.url).href:t(e)}