rollup-plugin-websqz 1.0.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.md +9 -0
- package/README.md +35 -0
- package/dist/bin/websqz +0 -0
- package/dist/index.d.ts +39 -0
- package/dist/index.js +203 -0
- package/dist/index.js.map +1 -0
- package/package.json +37 -0
- package/scripts/install.js +180 -0
package/LICENSE.md
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
Copyright 2026 Rootkids
|
|
4
|
+
|
|
5
|
+
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:
|
|
6
|
+
|
|
7
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
8
|
+
|
|
9
|
+
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.md
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# rollup-plugin-websqz
|
|
2
|
+
Rollup / Vite plugin for using [websqz](https://github.com/r00tkids/websqz) to compress and bundle code and assets into one HTML file. This is intented for intros in the [demoscene](https://en.wikipedia.org/wiki/Demoscene) or size restricted JS challenges.
|
|
3
|
+
|
|
4
|
+
## Usage
|
|
5
|
+
```js
|
|
6
|
+
// vite.config.js
|
|
7
|
+
import { defineConfig } from 'vite';
|
|
8
|
+
import websqz from 'rollup-plugin-websqz';
|
|
9
|
+
|
|
10
|
+
export default defineConfig({
|
|
11
|
+
plugins: [websqz()]
|
|
12
|
+
});
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
See the [example](./example) for a working example with support for `vite-plugin-glsl`.
|
|
16
|
+
|
|
17
|
+
## Example Options
|
|
18
|
+
```js
|
|
19
|
+
websqz({
|
|
20
|
+
websqzPath: null, // It's resolved to the websqz executable installed when installing the npm package, else it'll try to execute based on $PATH
|
|
21
|
+
fileHooks: [
|
|
22
|
+
{
|
|
23
|
+
filter: /\.glsl$/,
|
|
24
|
+
transform: async (ctx, id, content) => {
|
|
25
|
+
return {
|
|
26
|
+
content: Buffer.from("Hello World", "utf-8"),
|
|
27
|
+
isCompressed: false,
|
|
28
|
+
isText: true,
|
|
29
|
+
fileExt: ".glsl" // Optional
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
]
|
|
34
|
+
})
|
|
35
|
+
```
|
package/dist/bin/websqz
ADDED
|
Binary file
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { PluginContext, type Plugin } from "rollup";
|
|
2
|
+
import { FilterPattern } from "@rollup/pluginutils";
|
|
3
|
+
export type WebsqzFileTransformRes = {
|
|
4
|
+
/**
|
|
5
|
+
* The result of processing
|
|
6
|
+
*/
|
|
7
|
+
content: Buffer;
|
|
8
|
+
/**
|
|
9
|
+
* Is already compressed and should not be compressed again by websqz
|
|
10
|
+
*/
|
|
11
|
+
isCompressed: boolean;
|
|
12
|
+
/**
|
|
13
|
+
* Is it text? The plugin orders files by type for better compression ratios
|
|
14
|
+
*/
|
|
15
|
+
isText: boolean;
|
|
16
|
+
/**
|
|
17
|
+
* File extension including the dot, e.g. .txt.
|
|
18
|
+
* This is used to order files for better compression ratios.
|
|
19
|
+
* Same type of content should share same file extension.
|
|
20
|
+
*
|
|
21
|
+
* By default it is extracted from the original file path.
|
|
22
|
+
*/
|
|
23
|
+
fileExt?: string;
|
|
24
|
+
};
|
|
25
|
+
type WebSqzOptions = {
|
|
26
|
+
websqzPath?: string;
|
|
27
|
+
/**
|
|
28
|
+
* File transform hooks to process files before they are imported in code or compressed by websqz
|
|
29
|
+
*/
|
|
30
|
+
fileTransforms?: [
|
|
31
|
+
{
|
|
32
|
+
include?: FilterPattern;
|
|
33
|
+
exclude?: FilterPattern;
|
|
34
|
+
transform: (ctx: PluginContext, id: string, content: Buffer) => Promise<WebsqzFileTransformRes>;
|
|
35
|
+
}
|
|
36
|
+
];
|
|
37
|
+
};
|
|
38
|
+
export default function (options?: WebSqzOptions): Plugin;
|
|
39
|
+
export {};
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
2
|
+
exports.default = default_1;
|
|
3
|
+
const tslib_1 = require("tslib");
|
|
4
|
+
const node_querystring_1 = tslib_1.__importDefault(require("node:querystring"));
|
|
5
|
+
const promises_1 = tslib_1.__importDefault(require("node:fs/promises"));
|
|
6
|
+
const node_fs_1 = tslib_1.__importDefault(require("node:fs"));
|
|
7
|
+
const node_path_1 = tslib_1.__importDefault(require("node:path"));
|
|
8
|
+
const node_child_process_1 = tslib_1.__importDefault(require("node:child_process"));
|
|
9
|
+
const pluginutils_1 = require("@rollup/pluginutils");
|
|
10
|
+
class WebSqzExe {
|
|
11
|
+
websqzPath;
|
|
12
|
+
constructor(websqzPath) {
|
|
13
|
+
this.websqzPath = websqzPath;
|
|
14
|
+
}
|
|
15
|
+
async run(cliOptions) {
|
|
16
|
+
const args = [];
|
|
17
|
+
args.push("--js-main", cliOptions.jsMain);
|
|
18
|
+
for (const file of cliOptions.files) {
|
|
19
|
+
args.push("--files", file);
|
|
20
|
+
}
|
|
21
|
+
for (const preCompressedFile of cliOptions.preCompressedFiles) {
|
|
22
|
+
args.push("--pre-compressed-files", preCompressedFile);
|
|
23
|
+
}
|
|
24
|
+
args.push("--output-directory", cliOptions.output);
|
|
25
|
+
const spawn = await node_child_process_1.default.spawn(this.websqzPath, args, {
|
|
26
|
+
stdio: "inherit",
|
|
27
|
+
});
|
|
28
|
+
return new Promise((resolve, reject) => {
|
|
29
|
+
spawn.once("error", (err) => {
|
|
30
|
+
reject(err);
|
|
31
|
+
});
|
|
32
|
+
spawn.once("close", (code) => {
|
|
33
|
+
if (code !== 0) {
|
|
34
|
+
reject(new Error(`websqz process exited with code ${code}`));
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
resolve();
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function websqzExecutablePath(executablePath) {
|
|
44
|
+
if (!executablePath) {
|
|
45
|
+
const path = __dirname + "/bin/websqz";
|
|
46
|
+
const extension = process.platform == "win32" ? ".exe" : "";
|
|
47
|
+
if (node_fs_1.default.existsSync(path + extension)) {
|
|
48
|
+
return path + extension;
|
|
49
|
+
}
|
|
50
|
+
return "websqz" + extension;
|
|
51
|
+
}
|
|
52
|
+
return executablePath;
|
|
53
|
+
}
|
|
54
|
+
function default_1(options = {}) {
|
|
55
|
+
const isBuild = process.env.NODE_ENV === "production";
|
|
56
|
+
const websqzExePath = websqzExecutablePath(options.websqzPath);
|
|
57
|
+
const websqzExe = new WebSqzExe(websqzExePath);
|
|
58
|
+
const fileTransforms = options.fileTransforms?.map(transform => {
|
|
59
|
+
const include = transform.include ? (Array.isArray(transform.include) ? transform.include : [transform.include])
|
|
60
|
+
: undefined;
|
|
61
|
+
const exclude = transform.exclude ? (Array.isArray(transform.exclude) ? transform.exclude : [transform.exclude])
|
|
62
|
+
: undefined;
|
|
63
|
+
const filter = (0, pluginutils_1.createFilter)(include, exclude);
|
|
64
|
+
return {
|
|
65
|
+
transform: transform.transform,
|
|
66
|
+
filter,
|
|
67
|
+
};
|
|
68
|
+
});
|
|
69
|
+
const files = new Map();
|
|
70
|
+
let fileNameIdx = 0;
|
|
71
|
+
const findNextAvailableFileName = () => {
|
|
72
|
+
const startChar = 97; // a
|
|
73
|
+
const endChar = 122; // z
|
|
74
|
+
const alphabetSize = endChar - startChar;
|
|
75
|
+
let numChars = fileNameIdx === 0 ? 1 : Math.floor(Math.log(fileNameIdx) / Math.log(alphabetSize)) + 1;
|
|
76
|
+
let candidateName = "";
|
|
77
|
+
for (let i = 0; i < numChars; i++) {
|
|
78
|
+
const charCode = startChar + ((Math.floor(fileNameIdx / Math.pow(alphabetSize, i))) % alphabetSize);
|
|
79
|
+
candidateName = String.fromCharCode(charCode) + candidateName;
|
|
80
|
+
}
|
|
81
|
+
fileNameIdx++;
|
|
82
|
+
return candidateName;
|
|
83
|
+
};
|
|
84
|
+
const loadAndTransform = async function (id, hookRes) {
|
|
85
|
+
if (isBuild) {
|
|
86
|
+
const fileName = findNextAvailableFileName();
|
|
87
|
+
files.set(id, {
|
|
88
|
+
fileName,
|
|
89
|
+
content: hookRes.content,
|
|
90
|
+
isCompressed: hookRes.isCompressed,
|
|
91
|
+
fileExt: hookRes.fileExt ?? node_path_1.default.extname(id),
|
|
92
|
+
isText: hookRes.isText,
|
|
93
|
+
});
|
|
94
|
+
return {
|
|
95
|
+
code: hookRes.isText
|
|
96
|
+
? `export default new TextDecoder().decode(wsqz.files["${fileName}"]);`
|
|
97
|
+
: `export default wsqz.files["${fileName}"];`,
|
|
98
|
+
moduleSideEffects: false,
|
|
99
|
+
moduleType: 'js',
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
return {
|
|
104
|
+
code: hookRes.isText
|
|
105
|
+
? `export default ${JSON.stringify(hookRes.content.toString("utf-8"))};`
|
|
106
|
+
: `export default Uint8Array.fromBase64("${hookRes.content.toString("base64")}");`,
|
|
107
|
+
moduleSideEffects: false,
|
|
108
|
+
moduleType: 'js',
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
return {
|
|
113
|
+
name: "rollup-plugin-websqz",
|
|
114
|
+
load: {
|
|
115
|
+
order: "pre",
|
|
116
|
+
async handler(id) {
|
|
117
|
+
const qIdx = id.indexOf("?");
|
|
118
|
+
const beforeParams = id.slice(0, qIdx === -1 ? id.length : qIdx);
|
|
119
|
+
const afterParams = id.slice(id.indexOf("?") + 1);
|
|
120
|
+
const parsed = node_querystring_1.default.parse(afterParams);
|
|
121
|
+
const cleanedUpId = beforeParams;
|
|
122
|
+
let cachedFile = null;
|
|
123
|
+
const loadFromDisk = async () => {
|
|
124
|
+
if (cachedFile != null) {
|
|
125
|
+
return cachedFile;
|
|
126
|
+
}
|
|
127
|
+
cachedFile = await promises_1.default.readFile(cleanedUpId);
|
|
128
|
+
return cachedFile;
|
|
129
|
+
};
|
|
130
|
+
if (fileTransforms) {
|
|
131
|
+
for (const transform of fileTransforms) {
|
|
132
|
+
if (transform.filter(id)) {
|
|
133
|
+
let hookRes = await transform.transform(this, id, await loadFromDisk());
|
|
134
|
+
if (hookRes == null) {
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
return await loadAndTransform(id, hookRes);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
const isWebSqzTxt = parsed["websqz-txt"] != null;
|
|
142
|
+
const isWebSqzBin = parsed["websqz-bin"] != null;
|
|
143
|
+
if (isWebSqzTxt && isWebSqzBin) {
|
|
144
|
+
throw new Error(`Cannot use both websqz-txt and websqz-bin on the same import: ${id}`);
|
|
145
|
+
}
|
|
146
|
+
const isCompressed = parsed["compressed"] != null;
|
|
147
|
+
if (isWebSqzTxt || isWebSqzBin) {
|
|
148
|
+
const content = await loadFromDisk();
|
|
149
|
+
let hookRes = {
|
|
150
|
+
content,
|
|
151
|
+
isCompressed,
|
|
152
|
+
isText: isWebSqzTxt,
|
|
153
|
+
fileExt: node_path_1.default.extname(cleanedUpId),
|
|
154
|
+
};
|
|
155
|
+
return await loadAndTransform(id, hookRes);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
},
|
|
159
|
+
writeBundle: async function (outputOptions, bundle) {
|
|
160
|
+
const jsFiles = Object.entries(bundle)
|
|
161
|
+
.filter(([fileName, asset]) => fileName.endsWith('.js') && asset.type === 'chunk');
|
|
162
|
+
if (jsFiles.length === 0) {
|
|
163
|
+
throw new Error('No JavaScript file found in the bundle.');
|
|
164
|
+
}
|
|
165
|
+
if (jsFiles.length > 1) {
|
|
166
|
+
throw new Error('Multiple JavaScript files found in the bundle. Make sure to bundle into a single file.\nTry setting "build.rollupOptions.output.inlineDynamicImports" to true in Vite config.');
|
|
167
|
+
}
|
|
168
|
+
const [jsFileName, jsChunk] = jsFiles[0];
|
|
169
|
+
if (isBuild) {
|
|
170
|
+
const outDir = node_path_1.default.resolve(outputOptions.dir || "", "websqz-tmp");
|
|
171
|
+
if (await promises_1.default.stat(outDir).catch(() => false)) {
|
|
172
|
+
await promises_1.default.rmdir(outDir, { recursive: true });
|
|
173
|
+
}
|
|
174
|
+
const filesToCompress = [];
|
|
175
|
+
const preCompressedFiles = [];
|
|
176
|
+
for (const [id, file] of files) {
|
|
177
|
+
this.debug(`Copying '${id}' for websqz...`);
|
|
178
|
+
const outPath = node_path_1.default.resolve(outputOptions.dir || "", "websqz-tmp", file.fileName);
|
|
179
|
+
await promises_1.default.mkdir(node_path_1.default.dirname(outPath), { recursive: true });
|
|
180
|
+
await promises_1.default.writeFile(outPath, file.content);
|
|
181
|
+
if (file.isCompressed) {
|
|
182
|
+
preCompressedFiles.push(node_path_1.default.relative(".", outPath));
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
filesToCompress.push({ isText: file.isText, fileExt: file.fileExt, path: node_path_1.default.relative(".", outPath) });
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
// Sort by file extension for better compression ratios in websqz
|
|
189
|
+
filesToCompress.sort((a, b) => Math.sign(a.fileExt.localeCompare(b.fileExt)) + 2 * (a.isText === b.isText ? 0 : a.isText ? -1 : 1));
|
|
190
|
+
this.info(`Using websqz executable at '${websqzExe.websqzPath}'`);
|
|
191
|
+
await websqzExe.run({
|
|
192
|
+
jsMain: node_path_1.default.resolve(outputOptions.dir || "", jsFileName),
|
|
193
|
+
files: filesToCompress.map(f => f.path),
|
|
194
|
+
preCompressedFiles: preCompressedFiles,
|
|
195
|
+
output: node_path_1.default.resolve(outputOptions.dir || "", "websqz-output"),
|
|
196
|
+
});
|
|
197
|
+
const relOutPath = node_path_1.default.relative(".", node_path_1.default.resolve(outputOptions.dir || "", "websqz-output"));
|
|
198
|
+
this.info(`Websqz completed, output at '${relOutPath}'.\nRun 'python -m http.server -d ${relOutPath}' to serve the output.`);
|
|
199
|
+
}
|
|
200
|
+
},
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/index.ts"],"sourcesContent":["import { PluginContext, rollup, type OutputAsset, type OutputChunk, type OutputOptions, type Plugin } from \"rollup\";\nimport querystring from \"node:querystring\";\nimport fs from \"node:fs/promises\";\nimport fsSync from \"node:fs\";\nimport path from \"node:path\";\nimport child_process from \"node:child_process\";\nimport { createFilter, FilterPattern } from \"@rollup/pluginutils\";\n\ntype WebSqzFile = {\n fileName: string;\n content: Buffer;\n isCompressed: boolean;\n fileExt: string;\n isText: boolean;\n};\n\nexport type WebsqzFileTransformRes = {\n /**\n * The result of processing\n */\n content: Buffer;\n\n /**\n * Is already compressed and should not be compressed again by websqz\n */\n isCompressed: boolean;\n\n /**\n * Is it text? The plugin orders files by type for better compression ratios\n */\n isText: boolean;\n\n /**\n * File extension including the dot, e.g. .txt.\n * This is used to order files for better compression ratios.\n * Same type of content should share same file extension.\n * \n * By default it is extracted from the original file path.\n */\n fileExt?: string;\n};\n\ntype WebSqzOptions = {\n websqzPath?: string;\n\n /**\n * File transform hooks to process files before they are imported in code or compressed by websqz\n */\n fileTransforms?: [\n {\n include?: FilterPattern,\n exclude?: FilterPattern,\n transform: (ctx: PluginContext, id: string, content: Buffer) => Promise<WebsqzFileTransformRes>;\n }\n ]\n};\n\ntype WebSqzCliOptions = {\n jsMain: string;\n files: string[];\n preCompressedFiles: string[];\n output: string;\n}\n\nclass WebSqzExe {\n websqzPath: string;\n constructor(websqzPath: string) {\n this.websqzPath = websqzPath;\n }\n\n async run(cliOptions: WebSqzCliOptions): Promise<void> {\n const args: string[] = [];\n\n args.push(\"--js-main\", cliOptions.jsMain);\n\n for (const file of cliOptions.files) {\n args.push(\"--files\", file);\n }\n for (const preCompressedFile of cliOptions.preCompressedFiles) {\n args.push(\"--pre-compressed-files\", preCompressedFile);\n }\n\n args.push(\"--output-directory\", cliOptions.output);\n\n const spawn = await child_process.spawn(this.websqzPath, args, {\n stdio: \"inherit\",\n });\n\n return new Promise<void>((resolve, reject) => {\n spawn.once(\"error\", (err) => {\n reject(err);\n });\n\n spawn.once(\"close\", (code) => {\n if (code !== 0) {\n reject(new Error(`websqz process exited with code ${code}`));\n } else {\n resolve();\n }\n });\n });\n }\n}\n\nfunction websqzExecutablePath(executablePath: string | undefined): string {\n if (!executablePath) {\n const path = __dirname + \"/bin/websqz\";\n const extension = process.platform == \"win32\" ? \".exe\" : \"\";\n\n if (fsSync.existsSync(path + extension)) {\n return path + extension;\n }\n\n return \"websqz\" + extension;\n }\n\n return executablePath;\n}\n\nexport default function (options: WebSqzOptions = {}): Plugin {\n const isBuild = process.env.NODE_ENV === \"production\";\n const websqzExePath = websqzExecutablePath(options.websqzPath);\n const websqzExe = new WebSqzExe(websqzExePath);\n\n const fileTransforms = options.fileTransforms?.map(transform => {\n const include = transform.include ? (Array.isArray(transform.include) ? transform.include : [transform.include]) \n : undefined;\n const exclude = transform.exclude ? (Array.isArray(transform.exclude) ? transform.exclude : [transform.exclude]) \n : undefined;\n\n const filter = createFilter(include, exclude);\n return {\n transform: transform.transform,\n filter,\n }\n });\n\n const files = new Map<string, WebSqzFile>();\n let fileNameIdx = 0;\n\n const findNextAvailableFileName = () => {\n const startChar = 97; // a\n const endChar = 122; // z\n const alphabetSize= endChar - startChar;\n\n let numChars = fileNameIdx === 0 ? 1 : Math.floor(Math.log(fileNameIdx) / Math.log(alphabetSize)) + 1;\n let candidateName = \"\";\n \n for (let i = 0; i < numChars; i++) {\n const charCode = startChar + ((Math.floor(fileNameIdx / Math.pow(alphabetSize, i))) % alphabetSize);\n candidateName = String.fromCharCode(charCode) + candidateName;\n }\n\n fileNameIdx++;\n\n return candidateName;\n }\n\n const loadAndTransform = async function (id: string, hookRes: WebsqzFileTransformRes) {\n if (isBuild) {\n const fileName = findNextAvailableFileName();\n files.set(id, {\n fileName,\n content: hookRes.content,\n isCompressed: hookRes.isCompressed,\n fileExt: hookRes.fileExt ?? path.extname(id),\n isText: hookRes.isText,\n });\n\n return {\n code: hookRes.isText \n ? `export default new TextDecoder().decode(wsqz.files[\"${fileName}\"]);` \n : `export default wsqz.files[\"${fileName}\"];`,\n moduleSideEffects: false,\n moduleType: 'js',\n };\n } else {\n return {\n code: hookRes.isText \n ? `export default ${JSON.stringify(hookRes.content.toString(\"utf-8\"))};` \n : `export default Uint8Array.fromBase64(\"${hookRes.content.toString(\"base64\")}\");`,\n moduleSideEffects: false,\n moduleType: 'js',\n };\n }\n };\n\n return {\n name: \"rollup-plugin-websqz\",\n\n load: {\n order: \"pre\",\n async handler(id: string) {\n const qIdx = id.indexOf(\"?\");\n const beforeParams = id.slice(0, qIdx === -1 ? id.length : qIdx);\n const afterParams = id.slice(id.indexOf(\"?\") + 1);\n const parsed = querystring.parse(afterParams);\n const cleanedUpId = beforeParams;\n\n let cachedFile: Buffer | null = null;\n const loadFromDisk = async () => {\n if (cachedFile != null) {\n return cachedFile;\n }\n cachedFile = await fs.readFile(cleanedUpId);\n return cachedFile;\n };\n\n if (fileTransforms) {\n for (const transform of fileTransforms) {\n if (transform.filter(id)) {\n let hookRes = await transform.transform(this, id, await loadFromDisk());\n if (hookRes == null) {\n continue;\n }\n return await loadAndTransform(id, hookRes);\n }\n }\n }\n\n const isWebSqzTxt = parsed[\"websqz-txt\"] != null;\n const isWebSqzBin = parsed[\"websqz-bin\"] != null;\n\n if (isWebSqzTxt && isWebSqzBin) {\n throw new Error(\n `Cannot use both websqz-txt and websqz-bin on the same import: ${id}`,\n );\n }\n\n const isCompressed = parsed[\"compressed\"] != null;\n\n if (isWebSqzTxt || isWebSqzBin) {\n const content = await loadFromDisk();\n \n let hookRes: WebsqzFileTransformRes = {\n content,\n isCompressed,\n isText: isWebSqzTxt,\n fileExt: path.extname(cleanedUpId),\n };\n return await loadAndTransform(id, hookRes);\n }\n }\n },\n\n writeBundle: async function (outputOptions: OutputOptions, bundle: { [fileName: string]: OutputAsset | OutputChunk }) {\n const jsFiles = Object.entries(bundle)\n .filter(([fileName, asset]) => fileName.endsWith('.js') && asset.type === 'chunk');\n\n if (jsFiles.length === 0) {\n throw new Error('No JavaScript file found in the bundle.');\n }\n if (jsFiles.length > 1) {\n throw new Error('Multiple JavaScript files found in the bundle. Make sure to bundle into a single file.\\nTry setting \"build.rollupOptions.output.inlineDynamicImports\" to true in Vite config.');\n }\n\n const [jsFileName, jsChunk] = jsFiles[0];\n\n if (isBuild) {\n const outDir = path.resolve(\n outputOptions.dir || \"\",\n \"websqz-tmp\");\n if (await fs.stat(outDir).catch(() => false)) {\n await fs.rmdir(outDir, { recursive: true });\n }\n\n const filesToCompress = [];\n const preCompressedFiles = [];\n\n for (const [id, file] of files) {\n this.debug(`Copying '${id}' for websqz...`);\n const outPath = path.resolve(\n outputOptions.dir || \"\",\n \"websqz-tmp\",\n file.fileName,\n );\n\n await fs.mkdir(path.dirname(outPath), { recursive: true });\n await fs.writeFile(outPath, file.content);\n\n if (file.isCompressed) {\n preCompressedFiles.push(path.relative(\".\", outPath));\n } else {\n filesToCompress.push({ isText: file.isText, fileExt: file.fileExt, path: path.relative(\".\", outPath) });\n }\n }\n\n // Sort by file extension for better compression ratios in websqz\n filesToCompress.sort((a, b) => Math.sign(a.fileExt.localeCompare(b.fileExt)) + 2 * (a.isText === b.isText ? 0 : a.isText ? -1 : 1));\n\n this.info(`Using websqz executable at '${websqzExe.websqzPath}'`);\n await websqzExe.run({\n jsMain: path.resolve(\n outputOptions.dir || \"\",\n jsFileName,\n ),\n files: filesToCompress.map(f => f.path),\n preCompressedFiles: preCompressedFiles,\n output: path.resolve(\n outputOptions.dir || \"\",\n \"websqz-output\",\n ),\n });\n\n const relOutPath = path.relative(\".\", path.resolve(outputOptions.dir || \"\", \"websqz-output\"));\n this.info(`Websqz completed, output at '${relOutPath}'.\\nRun 'python -m http.server -d ${relOutPath}' to serve the output.`);\n }\n },\n };\n}\n"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "rollup-plugin-websqz",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"types": "./dist/index.d.ts",
|
|
7
|
+
"exports": "./dist/index.js",
|
|
8
|
+
"files": [
|
|
9
|
+
"scripts/install.js",
|
|
10
|
+
"dist",
|
|
11
|
+
"LICENSE.md"
|
|
12
|
+
],
|
|
13
|
+
"keywords": [
|
|
14
|
+
"rollup-plugin",
|
|
15
|
+
"vite-plugin",
|
|
16
|
+
"websqz",
|
|
17
|
+
"compression"
|
|
18
|
+
],
|
|
19
|
+
"author": "",
|
|
20
|
+
"license": "ISC",
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"@rollup/pluginutils": "^5.3.0"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"@rollup/plugin-typescript": "^12.3.0",
|
|
26
|
+
"@tsconfig/node22": "^22.0.5",
|
|
27
|
+
"rollup": "^4.55.3",
|
|
28
|
+
"tslib": "^2.8.1",
|
|
29
|
+
"typescript": "^5.9.3"
|
|
30
|
+
},
|
|
31
|
+
"scripts": {
|
|
32
|
+
"build": "rollup -c",
|
|
33
|
+
"build:watch": "rollup -c -w",
|
|
34
|
+
"build:dts": "tsc --emitDeclarationOnly",
|
|
35
|
+
"postinstall": "node scripts/install.js"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const https = require("https");
|
|
3
|
+
const http = require("http");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
const os = require("os");
|
|
6
|
+
|
|
7
|
+
const RELEASE_BASE_URL = "https://github.com/r00tkids/websqz/releases/download";
|
|
8
|
+
const VERSION = "0.3";
|
|
9
|
+
const TOOL_NAME = "websqz";
|
|
10
|
+
const BIN_DIR = path.resolve(__dirname, "../dist/bin");
|
|
11
|
+
|
|
12
|
+
function getRustTarget() {
|
|
13
|
+
const platform = process.platform;
|
|
14
|
+
const arch = process.arch;
|
|
15
|
+
|
|
16
|
+
let triple;
|
|
17
|
+
let ext = "";
|
|
18
|
+
|
|
19
|
+
if (platform === "win32") {
|
|
20
|
+
ext = ".exe";
|
|
21
|
+
if (arch === "x64") triple = "x86_64-pc-windows-msvc";
|
|
22
|
+
else if (arch === "arm64") triple = "aarch64-pc-windows-msvc";
|
|
23
|
+
else triple = `${arch}-pc-windows-msvc`;
|
|
24
|
+
} else if (platform === "darwin") {
|
|
25
|
+
if (arch === "x64") triple = "x86_64-apple-darwin";
|
|
26
|
+
else if (arch === "arm64") triple = "aarch64-apple-darwin";
|
|
27
|
+
else triple = `${arch}-apple-darwin`;
|
|
28
|
+
} else if (platform === "linux") {
|
|
29
|
+
if (arch === "x64") triple = "x86_64-unknown-linux-gnu";
|
|
30
|
+
else if (arch === "arm64") triple = "aarch64-unknown-linux-gnu";
|
|
31
|
+
else triple = `${arch}-unknown-linux-gnu`;
|
|
32
|
+
} else {
|
|
33
|
+
throw new Error(`Unsupported platform: ${platform}`);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return { triple, ext };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function buildAssetName(triple, ext) {
|
|
40
|
+
return `${TOOL_NAME}-${triple}${ext}`;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function getDownloadUrl(assetName) {
|
|
44
|
+
return `${RELEASE_BASE_URL}/v${VERSION}/${assetName}`;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function httpRequest(url, opts, callback) {
|
|
48
|
+
const client = url.startsWith("https://") ? https : http;
|
|
49
|
+
return client.get(url, opts, callback);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function downloadToFile(url, destPath) {
|
|
53
|
+
return new Promise((resolve, reject) => {
|
|
54
|
+
const maxRedirects = 5;
|
|
55
|
+
let redirects = 0;
|
|
56
|
+
|
|
57
|
+
function get(urlToGet) {
|
|
58
|
+
const req = httpRequest(urlToGet, {}, (res) => {
|
|
59
|
+
// Follow redirects
|
|
60
|
+
if (
|
|
61
|
+
res.statusCode >= 300 &&
|
|
62
|
+
res.statusCode < 400 &&
|
|
63
|
+
res.headers.location
|
|
64
|
+
) {
|
|
65
|
+
if (redirects++ >= maxRedirects) {
|
|
66
|
+
reject(new Error("Too many redirects"));
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
const next = new URL(res.headers.location, urlToGet).toString();
|
|
70
|
+
res.resume();
|
|
71
|
+
get(next);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (res.statusCode !== 200) {
|
|
76
|
+
reject(
|
|
77
|
+
new Error(
|
|
78
|
+
`Download failed: ${res.statusCode} ${res.statusMessage}`,
|
|
79
|
+
),
|
|
80
|
+
);
|
|
81
|
+
res.resume();
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const total = parseInt(res.headers["content-length"] || "0", 10);
|
|
86
|
+
let downloaded = 0;
|
|
87
|
+
|
|
88
|
+
const fileStream = fs.createWriteStream(destPath, { mode: 0o755 });
|
|
89
|
+
res.on("data", (chunk) => {
|
|
90
|
+
downloaded += chunk.length;
|
|
91
|
+
if (total) {
|
|
92
|
+
const pct = ((downloaded / total) * 100).toFixed(1);
|
|
93
|
+
process.stdout.write(
|
|
94
|
+
`\rDownloading ${path.basename(destPath)} ${pct}% (${(downloaded / 1024).toFixed(1)} KB)`,
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
res.pipe(fileStream);
|
|
100
|
+
|
|
101
|
+
fileStream.on("finish", () => {
|
|
102
|
+
fileStream.close(() => {
|
|
103
|
+
process.stdout.write("\n");
|
|
104
|
+
resolve();
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
fileStream.on("error", (err) => {
|
|
109
|
+
fs.unlink(destPath, () => reject(err));
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
req.on("error", (err) => reject(err));
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
get(url);
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async function ensureBinDir() {
|
|
121
|
+
await fs.promises.mkdir(BIN_DIR, { recursive: true });
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async function makeExecutable(filePath) {
|
|
125
|
+
if (process.platform === "win32") {
|
|
126
|
+
// Windows uses .exe; no chmod needed generally
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
try {
|
|
130
|
+
await fs.promises.chmod(filePath, 0o755);
|
|
131
|
+
} catch (err) {
|
|
132
|
+
// Best-effort, not fatal
|
|
133
|
+
console.warn(
|
|
134
|
+
`Warning: could not set executable permissions on ${filePath}: ${err.message}`,
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
async function main() {
|
|
140
|
+
try {
|
|
141
|
+
const { triple, ext } = getRustTarget();
|
|
142
|
+
const assetName = buildAssetName(triple, ext);
|
|
143
|
+
const url = getDownloadUrl(assetName);
|
|
144
|
+
|
|
145
|
+
console.log(`Detected platform: ${process.platform} ${process.arch}`);
|
|
146
|
+
console.log(`Downloading asset: ${assetName}`);
|
|
147
|
+
console.log(`From: ${url}`);
|
|
148
|
+
|
|
149
|
+
if (fs.existsSync(BIN_DIR + "/" + TOOL_NAME + ext)) {
|
|
150
|
+
console.log(`WebSQZ binary already exists at ${BIN_DIR}/${TOOL_NAME}${ext}, skipping download.`);
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
await ensureBinDir();
|
|
155
|
+
|
|
156
|
+
const destFileName = `${TOOL_NAME}${ext}`; // place binary in bin with consistent name
|
|
157
|
+
const destPath = path.join(BIN_DIR, destFileName);
|
|
158
|
+
|
|
159
|
+
// Remove existing file if present (optional)
|
|
160
|
+
try {
|
|
161
|
+
await fs.promises.unlink(destPath);
|
|
162
|
+
} catch (err) {
|
|
163
|
+
// ignore if not exists
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
await downloadToFile(url, destPath);
|
|
167
|
+
|
|
168
|
+
await makeExecutable(destPath);
|
|
169
|
+
|
|
170
|
+
console.log(`Downloaded and saved to: ${destPath}`);
|
|
171
|
+
console.log("Done.");
|
|
172
|
+
} catch (err) {
|
|
173
|
+
console.error(`Error: ${err.message}`);
|
|
174
|
+
process.exitCode = 1;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (require.main === module) {
|
|
179
|
+
main();
|
|
180
|
+
}
|