@foxlight/bundle 0.1.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/README.md +59 -0
- package/dist/chunk-KFZFLOGH.js +151 -0
- package/dist/chunk-LMLCG5N6.js +130 -0
- package/dist/chunk-NAMHCMIC.js +86 -0
- package/dist/index.d.ts +40 -0
- package/dist/index.js +20 -0
- package/dist/plugins/vite-plugin.d.ts +35 -0
- package/dist/plugins/vite-plugin.js +7 -0
- package/dist/plugins/webpack-plugin.d.ts +74 -0
- package/dist/plugins/webpack-plugin.js +7 -0
- package/package.json +62 -0
package/README.md
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# @foxlight/bundle
|
|
2
|
+
|
|
3
|
+
Bundle size analysis for [Foxlight](https://github.com/josegabrielcruz/foxlight) — the open-source front-end intelligence platform.
|
|
4
|
+
|
|
5
|
+
## What's Inside
|
|
6
|
+
|
|
7
|
+
- **Size Tracker** — computes raw and gzip sizes, per-component bundle breakdown (self, exclusive, total), chunk tracking
|
|
8
|
+
- **Vite Plugin** — Rollup-based dependency resolution with BFS traversal, JSON report output, console summary
|
|
9
|
+
- **Webpack Plugin** — hooks into Webpack's emit phase for per-module size extraction and reporting
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install @foxlight/bundle
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Vite Plugin
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
// vite.config.ts
|
|
21
|
+
import { defineConfig } from 'vite';
|
|
22
|
+
import { foxlightVitePlugin } from '@foxlight/bundle/vite';
|
|
23
|
+
|
|
24
|
+
export default defineConfig({
|
|
25
|
+
plugins: [
|
|
26
|
+
foxlightVitePlugin({
|
|
27
|
+
outputPath: '.foxlight/bundle-report.json',
|
|
28
|
+
}),
|
|
29
|
+
],
|
|
30
|
+
});
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Webpack Plugin
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
// webpack.config.js
|
|
37
|
+
import { FoxlightWebpackPlugin } from '@foxlight/bundle/webpack';
|
|
38
|
+
|
|
39
|
+
export default {
|
|
40
|
+
plugins: [
|
|
41
|
+
new FoxlightWebpackPlugin({
|
|
42
|
+
outputPath: '.foxlight/bundle-report.json',
|
|
43
|
+
}),
|
|
44
|
+
],
|
|
45
|
+
};
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Programmatic API
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
import { computeSize, computeComponentBundleInfo, formatBytes } from '@foxlight/bundle';
|
|
52
|
+
|
|
53
|
+
const size = computeSize(sourceCode);
|
|
54
|
+
console.log(formatBytes(size.gzip)); // "12.4 KB"
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## License
|
|
58
|
+
|
|
59
|
+
MIT
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import {
|
|
2
|
+
computeComponentBundleInfo,
|
|
3
|
+
computeSize,
|
|
4
|
+
formatBytes
|
|
5
|
+
} from "./chunk-NAMHCMIC.js";
|
|
6
|
+
|
|
7
|
+
// src/plugins/vite-plugin.ts
|
|
8
|
+
import { writeFile, mkdir } from "fs/promises";
|
|
9
|
+
import { join } from "path";
|
|
10
|
+
function foxlightBundle(options = {}) {
|
|
11
|
+
const { outputPath = ".foxlight/bundle-report.json", printSummary = true } = options;
|
|
12
|
+
let rootDir;
|
|
13
|
+
return {
|
|
14
|
+
name: "foxlight-bundle",
|
|
15
|
+
enforce: "post",
|
|
16
|
+
configResolved(config) {
|
|
17
|
+
rootDir = config.root;
|
|
18
|
+
},
|
|
19
|
+
async generateBundle(_outputOptions, bundle) {
|
|
20
|
+
const modules = /* @__PURE__ */ new Map();
|
|
21
|
+
const chunkSizes = [];
|
|
22
|
+
for (const [fileName, chunk] of Object.entries(bundle)) {
|
|
23
|
+
if (chunk.type !== "chunk") continue;
|
|
24
|
+
const chunkSize = computeSize(chunk.code);
|
|
25
|
+
chunkSizes.push({ name: fileName, size: chunkSize });
|
|
26
|
+
if (chunk.modules) {
|
|
27
|
+
for (const [moduleId, moduleInfo] of Object.entries(
|
|
28
|
+
chunk.modules
|
|
29
|
+
)) {
|
|
30
|
+
const existing = modules.get(moduleId);
|
|
31
|
+
if (existing) {
|
|
32
|
+
existing.chunks.push(fileName);
|
|
33
|
+
} else {
|
|
34
|
+
modules.set(moduleId, {
|
|
35
|
+
id: moduleId,
|
|
36
|
+
code: moduleInfo.code ?? "",
|
|
37
|
+
chunks: [fileName]
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
let componentInfo;
|
|
44
|
+
if (options.componentModules) {
|
|
45
|
+
const moduleDeps = /* @__PURE__ */ new Map();
|
|
46
|
+
for (const [_fileName, chunk] of Object.entries(bundle)) {
|
|
47
|
+
if (chunk.type !== "chunk") continue;
|
|
48
|
+
if (chunk.modules) {
|
|
49
|
+
for (const moduleId of Object.keys(
|
|
50
|
+
chunk.modules
|
|
51
|
+
)) {
|
|
52
|
+
if (!moduleDeps.has(moduleId)) {
|
|
53
|
+
moduleDeps.set(moduleId, []);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
if (chunk.imports) {
|
|
58
|
+
for (const imp of chunk.imports) {
|
|
59
|
+
const importedChunk = bundle[imp];
|
|
60
|
+
if (importedChunk && importedChunk.type === "chunk" && importedChunk.modules) {
|
|
61
|
+
for (const moduleId of Object.keys(
|
|
62
|
+
chunk.modules
|
|
63
|
+
)) {
|
|
64
|
+
const deps = moduleDeps.get(moduleId) ?? [];
|
|
65
|
+
for (const depId of Object.keys(
|
|
66
|
+
importedChunk.modules
|
|
67
|
+
)) {
|
|
68
|
+
if (!deps.includes(depId)) {
|
|
69
|
+
deps.push(depId);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
moduleDeps.set(moduleId, deps);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
const dependencyResolver = (moduleId) => {
|
|
79
|
+
const visited = /* @__PURE__ */ new Set();
|
|
80
|
+
const queue = [moduleId];
|
|
81
|
+
const result = [];
|
|
82
|
+
while (queue.length > 0) {
|
|
83
|
+
const current = queue.shift();
|
|
84
|
+
if (visited.has(current)) continue;
|
|
85
|
+
visited.add(current);
|
|
86
|
+
const deps = moduleDeps.get(current) ?? [];
|
|
87
|
+
for (const dep of deps) {
|
|
88
|
+
if (!visited.has(dep)) {
|
|
89
|
+
result.push(dep);
|
|
90
|
+
queue.push(dep);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return result;
|
|
95
|
+
};
|
|
96
|
+
componentInfo = computeComponentBundleInfo(
|
|
97
|
+
options.componentModules,
|
|
98
|
+
modules,
|
|
99
|
+
dependencyResolver
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
const report = {
|
|
103
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
104
|
+
rootDir,
|
|
105
|
+
chunks: chunkSizes.map((c) => ({
|
|
106
|
+
name: c.name,
|
|
107
|
+
rawSize: c.size.raw,
|
|
108
|
+
gzipSize: c.size.gzip
|
|
109
|
+
})),
|
|
110
|
+
modules: Array.from(modules.values()).map((m) => ({
|
|
111
|
+
id: m.id,
|
|
112
|
+
rawSize: Buffer.byteLength(m.code, "utf-8"),
|
|
113
|
+
chunks: m.chunks
|
|
114
|
+
})),
|
|
115
|
+
components: componentInfo,
|
|
116
|
+
totalModules: modules.size,
|
|
117
|
+
totalChunks: chunkSizes.length
|
|
118
|
+
};
|
|
119
|
+
const reportPath = join(rootDir, outputPath);
|
|
120
|
+
const reportDir = reportPath.substring(0, reportPath.lastIndexOf("/"));
|
|
121
|
+
await mkdir(reportDir, { recursive: true });
|
|
122
|
+
await writeFile(reportPath, JSON.stringify(report, null, 2));
|
|
123
|
+
if (printSummary) {
|
|
124
|
+
const totalRaw = chunkSizes.reduce((sum, c) => sum + c.size.raw, 0);
|
|
125
|
+
const totalGzip = chunkSizes.reduce((sum, c) => sum + c.size.gzip, 0);
|
|
126
|
+
console.log("\n\u{1F4E6} Foxlight Bundle Report");
|
|
127
|
+
console.log("\u2500".repeat(50));
|
|
128
|
+
console.log(` Chunks: ${chunkSizes.length}`);
|
|
129
|
+
console.log(` Modules: ${modules.size}`);
|
|
130
|
+
console.log(` Total (raw): ${formatBytes(totalRaw)}`);
|
|
131
|
+
console.log(` Total (gzip): ${formatBytes(totalGzip)}`);
|
|
132
|
+
console.log(` Report: ${outputPath}`);
|
|
133
|
+
console.log("\u2500".repeat(50));
|
|
134
|
+
if (componentInfo && componentInfo.length > 0) {
|
|
135
|
+
console.log("\n Components by size (gzip):");
|
|
136
|
+
const sorted = [...componentInfo].sort((a, b) => b.selfSize.gzip - a.selfSize.gzip);
|
|
137
|
+
for (const comp of sorted.slice(0, 10)) {
|
|
138
|
+
console.log(
|
|
139
|
+
` ${formatBytes(comp.selfSize.gzip).padStart(10)} ${comp.componentId}`
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
console.log("");
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export {
|
|
150
|
+
foxlightBundle
|
|
151
|
+
};
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import {
|
|
2
|
+
computeComponentBundleInfo,
|
|
3
|
+
computeSize,
|
|
4
|
+
formatBytes
|
|
5
|
+
} from "./chunk-NAMHCMIC.js";
|
|
6
|
+
|
|
7
|
+
// src/plugins/webpack-plugin.ts
|
|
8
|
+
import { writeFile, mkdir } from "fs/promises";
|
|
9
|
+
import { join } from "path";
|
|
10
|
+
var FoxlightWebpackPlugin = class {
|
|
11
|
+
options;
|
|
12
|
+
constructor(options = {}) {
|
|
13
|
+
this.options = options;
|
|
14
|
+
}
|
|
15
|
+
apply(compiler) {
|
|
16
|
+
const pluginName = "FoxlightWebpackPlugin";
|
|
17
|
+
const { outputPath = ".foxlight/bundle-report.json", printSummary = true } = this.options;
|
|
18
|
+
compiler.hooks.emit.tapAsync(pluginName, async (compilation, callback) => {
|
|
19
|
+
try {
|
|
20
|
+
const rootDir = compiler.options.context ?? process.cwd();
|
|
21
|
+
const modules = /* @__PURE__ */ new Map();
|
|
22
|
+
const chunkSizes = [];
|
|
23
|
+
for (const [fileName, asset] of Object.entries(compilation.assets)) {
|
|
24
|
+
if (!fileName.endsWith(".js") && !fileName.endsWith(".mjs")) continue;
|
|
25
|
+
const source = asset.source();
|
|
26
|
+
const code = typeof source === "string" ? source : source.toString("utf-8");
|
|
27
|
+
const size = computeSize(code);
|
|
28
|
+
chunkSizes.push({ name: fileName, size });
|
|
29
|
+
}
|
|
30
|
+
for (const mod of compilation.modules) {
|
|
31
|
+
const id = mod.resource ?? mod.identifier();
|
|
32
|
+
if (!id || id.includes("node_modules")) continue;
|
|
33
|
+
const code = mod._source?._value ?? "";
|
|
34
|
+
if (!code) continue;
|
|
35
|
+
const chunks = [];
|
|
36
|
+
for (const chunk of mod.chunks) {
|
|
37
|
+
const chunkName = chunk.name ?? String(chunk.id);
|
|
38
|
+
chunks.push(chunkName);
|
|
39
|
+
}
|
|
40
|
+
modules.set(id, { id, code, chunks });
|
|
41
|
+
}
|
|
42
|
+
const moduleDeps = /* @__PURE__ */ new Map();
|
|
43
|
+
for (const mod of compilation.modules) {
|
|
44
|
+
const id = mod.resource ?? mod.identifier();
|
|
45
|
+
if (!id) continue;
|
|
46
|
+
if (!moduleDeps.has(id)) {
|
|
47
|
+
moduleDeps.set(id, []);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
let componentInfo;
|
|
51
|
+
if (this.options.componentModules) {
|
|
52
|
+
const dependencyResolver = (moduleId) => {
|
|
53
|
+
const visited = /* @__PURE__ */ new Set();
|
|
54
|
+
const queue = [moduleId];
|
|
55
|
+
const result = [];
|
|
56
|
+
while (queue.length > 0) {
|
|
57
|
+
const current = queue.shift();
|
|
58
|
+
if (visited.has(current)) continue;
|
|
59
|
+
visited.add(current);
|
|
60
|
+
const deps = moduleDeps.get(current) ?? [];
|
|
61
|
+
for (const dep of deps) {
|
|
62
|
+
if (!visited.has(dep)) {
|
|
63
|
+
result.push(dep);
|
|
64
|
+
queue.push(dep);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return result;
|
|
69
|
+
};
|
|
70
|
+
componentInfo = computeComponentBundleInfo(
|
|
71
|
+
this.options.componentModules,
|
|
72
|
+
modules,
|
|
73
|
+
dependencyResolver
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
const report = {
|
|
77
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
78
|
+
rootDir,
|
|
79
|
+
chunks: chunkSizes.map((c) => ({
|
|
80
|
+
name: c.name,
|
|
81
|
+
rawSize: c.size.raw,
|
|
82
|
+
gzipSize: c.size.gzip
|
|
83
|
+
})),
|
|
84
|
+
modules: Array.from(modules.values()).map((m) => ({
|
|
85
|
+
id: m.id,
|
|
86
|
+
rawSize: Buffer.byteLength(m.code, "utf-8"),
|
|
87
|
+
chunks: m.chunks
|
|
88
|
+
})),
|
|
89
|
+
components: componentInfo,
|
|
90
|
+
totalModules: modules.size,
|
|
91
|
+
totalChunks: chunkSizes.length
|
|
92
|
+
};
|
|
93
|
+
const reportPath = join(rootDir, outputPath);
|
|
94
|
+
const reportDir = reportPath.substring(0, reportPath.lastIndexOf("/"));
|
|
95
|
+
await mkdir(reportDir, { recursive: true });
|
|
96
|
+
await writeFile(reportPath, JSON.stringify(report, null, 2));
|
|
97
|
+
if (printSummary) {
|
|
98
|
+
const totalRaw = chunkSizes.reduce((sum, c) => sum + c.size.raw, 0);
|
|
99
|
+
const totalGzip = chunkSizes.reduce((sum, c) => sum + c.size.gzip, 0);
|
|
100
|
+
console.log("\n\u{1F4E6} Foxlight Bundle Report");
|
|
101
|
+
console.log("\u2500".repeat(50));
|
|
102
|
+
console.log(` Chunks: ${chunkSizes.length}`);
|
|
103
|
+
console.log(` Modules: ${modules.size}`);
|
|
104
|
+
console.log(` Total (raw): ${formatBytes(totalRaw)}`);
|
|
105
|
+
console.log(` Total (gzip): ${formatBytes(totalGzip)}`);
|
|
106
|
+
console.log(` Report: ${outputPath}`);
|
|
107
|
+
console.log("\u2500".repeat(50));
|
|
108
|
+
if (componentInfo && componentInfo.length > 0) {
|
|
109
|
+
console.log("\n Components by size (gzip):");
|
|
110
|
+
const sorted = [...componentInfo].sort((a, b) => b.selfSize.gzip - a.selfSize.gzip);
|
|
111
|
+
for (const comp of sorted.slice(0, 10)) {
|
|
112
|
+
console.log(
|
|
113
|
+
` ${formatBytes(comp.selfSize.gzip).padStart(10)} ${comp.componentId}`
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
console.log("");
|
|
118
|
+
}
|
|
119
|
+
callback();
|
|
120
|
+
} catch (error) {
|
|
121
|
+
console.error("[foxlight] Error generating bundle report:", error);
|
|
122
|
+
callback();
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
export {
|
|
129
|
+
FoxlightWebpackPlugin
|
|
130
|
+
};
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
// src/size-tracker.ts
|
|
2
|
+
import { gzipSync } from "zlib";
|
|
3
|
+
function computeSize(code) {
|
|
4
|
+
const raw = Buffer.byteLength(code, "utf-8");
|
|
5
|
+
const gzip = gzipSync(Buffer.from(code, "utf-8")).byteLength;
|
|
6
|
+
return { raw, gzip };
|
|
7
|
+
}
|
|
8
|
+
function computeComponentBundleInfo(componentModules, allModules, dependencyResolver) {
|
|
9
|
+
const results = [];
|
|
10
|
+
const allComponentIds = Array.from(componentModules.keys());
|
|
11
|
+
for (const [componentId, moduleIds] of componentModules) {
|
|
12
|
+
const selfSize = aggregateSize(moduleIds, allModules);
|
|
13
|
+
const allDeps = /* @__PURE__ */ new Set();
|
|
14
|
+
for (const modId of moduleIds) {
|
|
15
|
+
allDeps.add(modId);
|
|
16
|
+
for (const dep of dependencyResolver(modId)) {
|
|
17
|
+
allDeps.add(dep);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
const totalSize = aggregateSize(Array.from(allDeps), allModules);
|
|
21
|
+
const otherDeps = /* @__PURE__ */ new Set();
|
|
22
|
+
for (const otherId of allComponentIds) {
|
|
23
|
+
if (otherId === componentId) continue;
|
|
24
|
+
const otherModules = componentModules.get(otherId) ?? [];
|
|
25
|
+
for (const modId of otherModules) {
|
|
26
|
+
otherDeps.add(modId);
|
|
27
|
+
for (const dep of dependencyResolver(modId)) {
|
|
28
|
+
otherDeps.add(dep);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
const exclusiveModules = Array.from(allDeps).filter((d) => !otherDeps.has(d));
|
|
33
|
+
const exclusiveSize = aggregateSize(exclusiveModules, allModules);
|
|
34
|
+
const chunks = /* @__PURE__ */ new Set();
|
|
35
|
+
for (const modId of moduleIds) {
|
|
36
|
+
const mod = allModules.get(modId);
|
|
37
|
+
if (mod) {
|
|
38
|
+
for (const chunk of mod.chunks) {
|
|
39
|
+
chunks.add(chunk);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
results.push({
|
|
44
|
+
componentId,
|
|
45
|
+
selfSize,
|
|
46
|
+
exclusiveSize,
|
|
47
|
+
totalSize,
|
|
48
|
+
chunks: Array.from(chunks)
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
return results;
|
|
52
|
+
}
|
|
53
|
+
function aggregateSize(moduleIds, allModules) {
|
|
54
|
+
let totalRaw = 0;
|
|
55
|
+
let totalGzip = 0;
|
|
56
|
+
for (const id of moduleIds) {
|
|
57
|
+
const mod = allModules.get(id);
|
|
58
|
+
if (mod) {
|
|
59
|
+
const size = computeSize(mod.code);
|
|
60
|
+
totalRaw += size.raw;
|
|
61
|
+
totalGzip += size.gzip;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return { raw: totalRaw, gzip: totalGzip };
|
|
65
|
+
}
|
|
66
|
+
function formatBytes(bytes) {
|
|
67
|
+
if (bytes === 0) return "0 B";
|
|
68
|
+
const units = ["B", "KB", "MB", "GB"];
|
|
69
|
+
const i = Math.floor(Math.log(Math.abs(bytes)) / Math.log(1024));
|
|
70
|
+
const value = bytes / Math.pow(1024, i);
|
|
71
|
+
const sign = bytes < 0 ? "-" : "";
|
|
72
|
+
return `${sign}${Math.abs(value).toFixed(value >= 100 ? 0 : value >= 10 ? 1 : 2)} ${units[i]}`;
|
|
73
|
+
}
|
|
74
|
+
function formatDelta(before, after) {
|
|
75
|
+
const delta = after.gzip - before.gzip;
|
|
76
|
+
const sign = delta > 0 ? "+" : "";
|
|
77
|
+
const percent = before.gzip > 0 ? ` (${sign}${(delta / before.gzip * 100).toFixed(1)}%)` : "";
|
|
78
|
+
return `${sign}${formatBytes(delta)}${percent}`;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export {
|
|
82
|
+
computeSize,
|
|
83
|
+
computeComponentBundleInfo,
|
|
84
|
+
formatBytes,
|
|
85
|
+
formatDelta
|
|
86
|
+
};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { ComponentId, ComponentBundleInfo, SizeInfo } from '@foxlight/core';
|
|
2
|
+
export { FoxlightVitePluginOptions, foxlightBundle } from './plugins/vite-plugin.js';
|
|
3
|
+
export { FoxlightWebpackPlugin, FoxlightWebpackPluginOptions } from './plugins/webpack-plugin.js';
|
|
4
|
+
import 'vite';
|
|
5
|
+
|
|
6
|
+
/** A module in the build output. */
|
|
7
|
+
interface ModuleEntry {
|
|
8
|
+
/** Unique module identifier (usually the file path) */
|
|
9
|
+
id: string;
|
|
10
|
+
/** Raw source code or bundled code for this module */
|
|
11
|
+
code: string;
|
|
12
|
+
/** Which chunk(s) this module ended up in */
|
|
13
|
+
chunks: string[];
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Compute size info for a piece of code.
|
|
17
|
+
*/
|
|
18
|
+
declare function computeSize(code: string): SizeInfo;
|
|
19
|
+
/**
|
|
20
|
+
* Compute per-component bundle info.
|
|
21
|
+
*
|
|
22
|
+
* Takes a mapping of componentId → moduleIds and the full list of
|
|
23
|
+
* modules from the build output, then calculates self, exclusive,
|
|
24
|
+
* and total sizes.
|
|
25
|
+
*
|
|
26
|
+
* @param componentModules - Map of componentId to its direct module IDs
|
|
27
|
+
* @param allModules - All modules from the build output
|
|
28
|
+
* @param dependencyResolver - Function that returns transitive dependencies for a module
|
|
29
|
+
*/
|
|
30
|
+
declare function computeComponentBundleInfo(componentModules: Map<ComponentId, string[]>, allModules: Map<string, ModuleEntry>, dependencyResolver: (moduleId: string) => string[]): ComponentBundleInfo[];
|
|
31
|
+
/**
|
|
32
|
+
* Format bytes into a human-readable string.
|
|
33
|
+
*/
|
|
34
|
+
declare function formatBytes(bytes: number): string;
|
|
35
|
+
/**
|
|
36
|
+
* Format a size delta with a +/- prefix and color indicator.
|
|
37
|
+
*/
|
|
38
|
+
declare function formatDelta(before: SizeInfo, after: SizeInfo): string;
|
|
39
|
+
|
|
40
|
+
export { type ModuleEntry, computeComponentBundleInfo, computeSize, formatBytes, formatDelta };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import {
|
|
2
|
+
foxlightBundle
|
|
3
|
+
} from "./chunk-KFZFLOGH.js";
|
|
4
|
+
import {
|
|
5
|
+
FoxlightWebpackPlugin
|
|
6
|
+
} from "./chunk-LMLCG5N6.js";
|
|
7
|
+
import {
|
|
8
|
+
computeComponentBundleInfo,
|
|
9
|
+
computeSize,
|
|
10
|
+
formatBytes,
|
|
11
|
+
formatDelta
|
|
12
|
+
} from "./chunk-NAMHCMIC.js";
|
|
13
|
+
export {
|
|
14
|
+
FoxlightWebpackPlugin,
|
|
15
|
+
computeComponentBundleInfo,
|
|
16
|
+
computeSize,
|
|
17
|
+
formatBytes,
|
|
18
|
+
formatDelta,
|
|
19
|
+
foxlightBundle
|
|
20
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { Plugin } from 'vite';
|
|
2
|
+
|
|
3
|
+
interface FoxlightVitePluginOptions {
|
|
4
|
+
/**
|
|
5
|
+
* Path to the output report file.
|
|
6
|
+
* @default ".foxlight/bundle-report.json"
|
|
7
|
+
*/
|
|
8
|
+
outputPath?: string;
|
|
9
|
+
/**
|
|
10
|
+
* Map of componentId → module file paths.
|
|
11
|
+
* If not provided, the plugin will report per-module sizes
|
|
12
|
+
* without component mapping.
|
|
13
|
+
*/
|
|
14
|
+
componentModules?: Map<string, string[]>;
|
|
15
|
+
/**
|
|
16
|
+
* Whether to print a summary to the console.
|
|
17
|
+
* @default true
|
|
18
|
+
*/
|
|
19
|
+
printSummary?: boolean;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Vite plugin for Foxlight bundle analysis.
|
|
23
|
+
*
|
|
24
|
+
* Usage in vite.config.ts:
|
|
25
|
+
* ```ts
|
|
26
|
+
* import { foxlightBundle } from "@foxlight/bundle/vite";
|
|
27
|
+
*
|
|
28
|
+
* export default defineConfig({
|
|
29
|
+
* plugins: [foxlightBundle()],
|
|
30
|
+
* });
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
declare function foxlightBundle(options?: FoxlightVitePluginOptions): Plugin;
|
|
34
|
+
|
|
35
|
+
export { type FoxlightVitePluginOptions, foxlightBundle };
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
interface FoxlightWebpackPluginOptions {
|
|
2
|
+
/**
|
|
3
|
+
* Path to the output report file.
|
|
4
|
+
* @default ".foxlight/bundle-report.json"
|
|
5
|
+
*/
|
|
6
|
+
outputPath?: string;
|
|
7
|
+
/**
|
|
8
|
+
* Map of componentId → module file paths.
|
|
9
|
+
* If not provided, the plugin will report per-module sizes
|
|
10
|
+
* without component mapping.
|
|
11
|
+
*/
|
|
12
|
+
componentModules?: Map<string, string[]>;
|
|
13
|
+
/**
|
|
14
|
+
* Whether to print a summary to the console.
|
|
15
|
+
* @default true
|
|
16
|
+
*/
|
|
17
|
+
printSummary?: boolean;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Webpack-compatible compiler and compilation types.
|
|
21
|
+
* Kept minimal to avoid requiring webpack as a dependency.
|
|
22
|
+
*/
|
|
23
|
+
interface WebpackCompiler {
|
|
24
|
+
hooks: {
|
|
25
|
+
emit: {
|
|
26
|
+
tapAsync(name: string, callback: (compilation: WebpackCompilation, cb: () => void) => void): void;
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
options: {
|
|
30
|
+
context?: string;
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
interface WebpackCompilation {
|
|
34
|
+
assets: Record<string, WebpackAsset>;
|
|
35
|
+
chunks: Iterable<WebpackChunk>;
|
|
36
|
+
modules: Iterable<WebpackModule>;
|
|
37
|
+
}
|
|
38
|
+
interface WebpackAsset {
|
|
39
|
+
source(): string | Buffer;
|
|
40
|
+
size(): number;
|
|
41
|
+
}
|
|
42
|
+
interface WebpackChunk {
|
|
43
|
+
name: string | null;
|
|
44
|
+
files: Set<string>;
|
|
45
|
+
id: string | number | null;
|
|
46
|
+
}
|
|
47
|
+
interface WebpackModule {
|
|
48
|
+
identifier(): string;
|
|
49
|
+
resource?: string;
|
|
50
|
+
size(): number;
|
|
51
|
+
_source?: {
|
|
52
|
+
_value?: string;
|
|
53
|
+
};
|
|
54
|
+
chunks: Iterable<WebpackChunk>;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Webpack plugin for Foxlight bundle analysis.
|
|
58
|
+
*
|
|
59
|
+
* Usage in webpack.config.js:
|
|
60
|
+
* ```js
|
|
61
|
+
* const { FoxlightWebpackPlugin } = require("@foxlight/bundle/webpack");
|
|
62
|
+
*
|
|
63
|
+
* module.exports = {
|
|
64
|
+
* plugins: [new FoxlightWebpackPlugin()],
|
|
65
|
+
* };
|
|
66
|
+
* ```
|
|
67
|
+
*/
|
|
68
|
+
declare class FoxlightWebpackPlugin {
|
|
69
|
+
private options;
|
|
70
|
+
constructor(options?: FoxlightWebpackPluginOptions);
|
|
71
|
+
apply(compiler: WebpackCompiler): void;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export { FoxlightWebpackPlugin, type FoxlightWebpackPluginOptions };
|
package/package.json
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@foxlight/bundle",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Bundle size analysis for Foxlight — Vite and Webpack plugins for per-component size tracking.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./vite": {
|
|
14
|
+
"types": "./dist/plugins/vite-plugin.d.ts",
|
|
15
|
+
"import": "./dist/plugins/vite-plugin.js"
|
|
16
|
+
},
|
|
17
|
+
"./webpack": {
|
|
18
|
+
"types": "./dist/plugins/webpack-plugin.d.ts",
|
|
19
|
+
"import": "./dist/plugins/webpack-plugin.js"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"scripts": {
|
|
23
|
+
"build": "tsup",
|
|
24
|
+
"dev": "tsup --watch"
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"@foxlight/core": "*"
|
|
28
|
+
},
|
|
29
|
+
"peerDependencies": {
|
|
30
|
+
"vite": "^5.0.0 || ^6.0.0",
|
|
31
|
+
"webpack": "^5.0.0"
|
|
32
|
+
},
|
|
33
|
+
"peerDependenciesMeta": {
|
|
34
|
+
"vite": {
|
|
35
|
+
"optional": true
|
|
36
|
+
},
|
|
37
|
+
"webpack": {
|
|
38
|
+
"optional": true
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
"files": [
|
|
42
|
+
"dist"
|
|
43
|
+
],
|
|
44
|
+
"license": "MIT",
|
|
45
|
+
"author": "Jose Cruz",
|
|
46
|
+
"repository": {
|
|
47
|
+
"type": "git",
|
|
48
|
+
"url": "https://github.com/josegabrielcruz/foxlight.git",
|
|
49
|
+
"directory": "packages/bundle"
|
|
50
|
+
},
|
|
51
|
+
"homepage": "https://github.com/josegabrielcruz/foxlight/tree/master/packages/bundle#readme",
|
|
52
|
+
"bugs": {
|
|
53
|
+
"url": "https://github.com/josegabrielcruz/foxlight/issues"
|
|
54
|
+
},
|
|
55
|
+
"keywords": [
|
|
56
|
+
"foxlight",
|
|
57
|
+
"bundle-size",
|
|
58
|
+
"vite-plugin",
|
|
59
|
+
"webpack-plugin",
|
|
60
|
+
"performance"
|
|
61
|
+
]
|
|
62
|
+
}
|