Skip to content

Commit bd2a942

Browse files
authored
Merge pull request #132 from django-webpack/add-asset-emitted-hook
Change how stats file assets are build using emit hook
2 parents 8ab4370 + 31eaa92 commit bd2a942

File tree

5 files changed

+214
-67
lines changed

5 files changed

+214
-67
lines changed

.github/workflows/config.yml

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,26 +7,6 @@ on:
77
branches: [ master ]
88

99
jobs:
10-
# Oldest maintenance LTS, End-of-Life 2025-04-30
11-
test-node-18:
12-
runs-on: ubuntu-latest
13-
container:
14-
image: node:18.19
15-
env:
16-
NODE_OPTIONS: --openssl-legacy-provider
17-
steps:
18-
- name: Checkout
19-
uses: actions/checkout@v4
20-
- name: Set up Node.js
21-
run: |
22-
echo "Node version: $(node --version)"
23-
echo "NPM version: $(npm --version)"
24-
npm install
25-
npm run ci
26-
- name: Unset NODE_OPTIONS
27-
run: |
28-
unset NODE_OPTIONS
29-
3010
test:
3111
runs-on: ubuntu-latest
3212
strategy:

lib/index.js

Lines changed: 48 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,8 @@ function getAssetPath(compilation, name) {
1717
return path.join(compilation.getPath(compilation.compiler.outputPath), name.split('?')[0]);
1818
}
1919

20-
function getSource(compilation, name) {
21-
const path = getAssetPath(compilation, name);
22-
return fs.readFileSync(path, { encoding: 'utf-8' });
20+
function getSource(asset) {
21+
return asset.source.source();
2322
}
2423

2524
/**
@@ -56,6 +55,7 @@ class BundleTrackerPlugin {
5655
};
5756
this.name = 'BundleTrackerPlugin';
5857

58+
this.assets = {};
5959
this.outputChunkDir = '';
6060
this.outputTrackerFile = '';
6161
this.outputTrackerDir = '';
@@ -103,11 +103,8 @@ class BundleTrackerPlugin {
103103
}
104104
/**
105105
* Write bundle tracker stats file
106-
*
107-
* @param {Compiler} _compiler
108-
* @param {Partial<Contents>} contents
109106
*/
110-
_writeOutput(_compiler, contents) {
107+
_writeOutput(contents) {
111108
Object.assign(this.contents, contents, {
112109
assets: mergeObjectsAndSortKeys(this.contents.assets, contents.assets),
113110
chunks: mergeObjectsAndSortKeys(this.contents.chunks, contents.chunks),
@@ -140,44 +137,25 @@ class BundleTrackerPlugin {
140137
}
141138
/**
142139
* Handle compile hook
143-
* @param {Compiler} compiler
144140
*/
145-
_handleCompile(compiler) {
146-
this._writeOutput(compiler, { status: 'compile' });
141+
_handleCompile() {
142+
this._writeOutput({ status: 'compile' });
147143
}
144+
148145
/**
149-
* Handle compile hook
150-
* @param {Compiler} compiler
151-
* @param {Stats} stats
146+
* Hook to handle the assets when they are ready to be emitted
147+
* @param {Compilation} compilation
152148
*/
153-
_handleDone(compiler, stats) {
154-
if (stats.hasErrors()) {
155-
const findError = compilation => {
156-
if (compilation.errors.length > 0) {
157-
return compilation.errors[0];
158-
}
159-
return compilation.children.find(child => findError(child));
160-
};
161-
const error = findError(stats.compilation);
162-
this._writeOutput(compiler, {
163-
status: 'error',
164-
error: error?.name ?? 'unknown-error',
165-
message: stripAnsi(error['message']),
166-
});
167-
168-
return;
169-
}
170-
171-
/** @type {Contents} */
172-
const output = { status: 'done', assets: {}, chunks: {} };
173-
Object.entries(stats.compilation.assets).map(([assetName, _]) => {
149+
_handleEmit(compilation) {
150+
Object.keys(compilation.assets).forEach(assetName => {
174151
const fileInfo = {
175152
name: assetName,
176-
path: getAssetPath(stats.compilation, assetName),
153+
path: getAssetPath(compilation, assetName),
177154
};
178155

179156
if (this.options.integrity === true) {
180-
fileInfo.integrity = this._computeIntegrity(getSource(stats.compilation, assetName));
157+
const asset = compilation.getAsset(assetName);
158+
fileInfo.integrity = this._computeIntegrity(getSource(asset));
181159
}
182160

183161
if (this.options.publicPath) {
@@ -193,35 +171,59 @@ class BundleTrackerPlugin {
193171
}
194172

195173
// @ts-ignore: TS2339: Property 'assetsInfo' does not exist on type 'Compilation'.
196-
if (stats.compilation.assetsInfo) {
174+
if (compilation.assetsInfo) {
197175
// @ts-ignore: TS2339: Property 'assetsInfo' does not exist on type 'Compilation'.
198-
fileInfo.sourceFilename = stats.compilation.assetsInfo.get(assetName).sourceFilename;
176+
fileInfo.sourceFilename = compilation.assetsInfo.get(assetName).sourceFilename;
199177
}
200178

201-
output.assets[assetName] = fileInfo;
179+
this.assets[assetName] = fileInfo;
202180
});
181+
}
182+
183+
/**
184+
* Handle done hook and write output file
185+
* @param {Stats} stats
186+
*/
187+
_handleDone(stats) {
188+
if (stats.hasErrors()) {
189+
const findError = compilation => {
190+
if (compilation.errors.length > 0) {
191+
return compilation.errors[0];
192+
}
193+
return compilation.children.find(child => findError(child));
194+
};
195+
const error = findError(stats.compilation);
196+
this._writeOutput({
197+
status: 'error',
198+
error: error?.name ?? 'unknown-error',
199+
message: stripAnsi(error['message']),
200+
});
201+
return;
202+
}
203+
204+
const chunks = {};
203205
stats.compilation.chunkGroups.forEach(chunkGroup => {
204206
if (!chunkGroup.isInitial()) return;
205-
206-
output.chunks[chunkGroup.name] = chunkGroup.getFiles();
207+
chunks[chunkGroup.name] = chunkGroup.getFiles();
207208
});
208209

210+
const output = { status: 'done', chunks, assets: this.assets };
209211
if (this.options.logTime === true) {
210212
output.startTime = stats.startTime;
211213
output.endTime = stats.endTime;
212214
}
213-
214-
this._writeOutput(compiler, output);
215+
this._writeOutput(output);
215216
}
217+
216218
/**
217219
* Method called by webpack to apply plugin hook
218220
* @param {Compiler} compiler
219221
*/
220222
apply(compiler) {
221223
this._setParamsFromCompiler(compiler);
222-
223-
compiler.hooks.compile.tap(this.name, this._handleCompile.bind(this, compiler));
224-
compiler.hooks.done.tap(this.name, this._handleDone.bind(this, compiler));
224+
compiler.hooks.compile.tap(this.name, this._handleCompile.bind(this));
225+
compiler.hooks.emit.tap(this.name, this._handleEmit.bind(this));
226+
compiler.hooks.done.tap(this.name, this._handleDone.bind(this));
225227
}
226228
}
227229

package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "webpack-bundle-tracker",
3-
"version": "3.2.1",
3+
"version": "3.2.2",
44
"description": "Spits out some stats about webpack compilation process to a file",
55
"keywords": [
66
"bundle",
@@ -11,6 +11,9 @@
1111
],
1212
"homepage": "https://github.com/django-webpack/webpack-bundle-tracker",
1313
"bugs": "https://github.com/django-webpack/webpack-bundle-tracker/issues",
14+
"engines": {
15+
"node": ">=20.0.0"
16+
},
1417
"repository": {
1518
"type": "git",
1619
"url": "git+https://github.com/django-webpack/webpack-bundle-tracker.git"

tests/base.test.js

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,87 @@ describe('BundleTrackerPlugin bases tests', () => {
6464
);
6565
});
6666

67+
it('It should generate the stats file when the plugin runs twice and the output assets already exist', done => {
68+
const expectErrors = null;
69+
const expectWarnings = getWebpack4WarningMessage();
70+
71+
// 1st run
72+
testPlugin(
73+
webpack,
74+
{
75+
context: __dirname,
76+
entry: path.resolve(__dirname, 'fixtures', 'index.js'),
77+
output: {
78+
path: OUTPUT_DIR,
79+
filename: 'js/[name].js',
80+
publicPath: 'http://localhost:3000/assets/',
81+
},
82+
plugins: [
83+
new BundleTrackerPlugin({
84+
path: OUTPUT_DIR,
85+
filename: 'webpack-stats.json',
86+
}),
87+
],
88+
},
89+
{
90+
status: 'done',
91+
publicPath: 'http://localhost:3000/assets/',
92+
chunks: {
93+
main: ['js/main.js'],
94+
},
95+
assets: {
96+
'js/main.js': {
97+
name: 'js/main.js',
98+
path: OUTPUT_DIR + '/js/main.js',
99+
publicPath: 'http://localhost:3000/assets/js/main.js',
100+
},
101+
},
102+
},
103+
'webpack-stats.json',
104+
jest.fn(),
105+
expectErrors,
106+
expectWarnings,
107+
);
108+
109+
// 2nd run
110+
testPlugin(
111+
webpack,
112+
{
113+
context: __dirname,
114+
entry: path.resolve(__dirname, 'fixtures', 'index.js'),
115+
output: {
116+
path: OUTPUT_DIR,
117+
filename: 'js/[name].js',
118+
publicPath: 'http://localhost:3000/assets/',
119+
},
120+
plugins: [
121+
new BundleTrackerPlugin({
122+
path: OUTPUT_DIR,
123+
filename: 'webpack-stats.json',
124+
}),
125+
],
126+
},
127+
{
128+
status: 'done',
129+
publicPath: 'http://localhost:3000/assets/',
130+
chunks: {
131+
main: ['js/main.js'],
132+
},
133+
assets: {
134+
'js/main.js': {
135+
name: 'js/main.js',
136+
path: OUTPUT_DIR + '/js/main.js',
137+
publicPath: 'http://localhost:3000/assets/js/main.js',
138+
},
139+
},
140+
},
141+
'webpack-stats.json',
142+
done,
143+
expectErrors,
144+
expectWarnings,
145+
);
146+
});
147+
67148
it('It should add log time when option is set', done => {
68149
const expectErrors = null;
69150
const expectWarnings = getWebpack4WarningMessage();

tests/webpack5.test.js

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,87 @@ describe('BundleTrackerPlugin bases tests', () => {
6464
);
6565
});
6666

67+
it('It should generate the stats file when the plugin runs twice and the output assets already exist', done => {
68+
const expectErrors = null;
69+
const expectWarnings = getWebpack5WarningMessage();
70+
71+
// 1st run
72+
testPlugin(
73+
webpack5,
74+
{
75+
context: __dirname,
76+
entry: path.resolve(__dirname, 'fixtures', 'index.js'),
77+
output: {
78+
path: OUTPUT_DIR,
79+
filename: 'js/[name].js',
80+
publicPath: 'http://localhost:3000/assets/',
81+
},
82+
plugins: [
83+
new BundleTrackerPlugin({
84+
path: OUTPUT_DIR,
85+
filename: 'webpack-stats.json',
86+
}),
87+
],
88+
},
89+
{
90+
status: 'done',
91+
publicPath: 'http://localhost:3000/assets/',
92+
chunks: {
93+
main: ['js/main.js'],
94+
},
95+
assets: {
96+
'js/main.js': {
97+
name: 'js/main.js',
98+
path: OUTPUT_DIR + '/js/main.js',
99+
publicPath: 'http://localhost:3000/assets/js/main.js',
100+
},
101+
},
102+
},
103+
'webpack-stats.json',
104+
jest.fn(),
105+
expectErrors,
106+
expectWarnings,
107+
);
108+
109+
// 2nd run
110+
testPlugin(
111+
webpack5,
112+
{
113+
context: __dirname,
114+
entry: path.resolve(__dirname, 'fixtures', 'index.js'),
115+
output: {
116+
path: OUTPUT_DIR,
117+
filename: 'js/[name].js',
118+
publicPath: 'http://localhost:3000/assets/',
119+
},
120+
plugins: [
121+
new BundleTrackerPlugin({
122+
path: OUTPUT_DIR,
123+
filename: 'webpack-stats.json',
124+
}),
125+
],
126+
},
127+
{
128+
status: 'done',
129+
publicPath: 'http://localhost:3000/assets/',
130+
chunks: {
131+
main: ['js/main.js'],
132+
},
133+
assets: {
134+
'js/main.js': {
135+
name: 'js/main.js',
136+
path: OUTPUT_DIR + '/js/main.js',
137+
publicPath: 'http://localhost:3000/assets/js/main.js',
138+
},
139+
},
140+
},
141+
'webpack-stats.json',
142+
done,
143+
expectErrors,
144+
expectWarnings,
145+
);
146+
});
147+
67148
it('It should add log time when option is set', done => {
68149
const expectErrors = null;
69150
const expectWarnings = getWebpack5WarningMessage();

0 commit comments

Comments
 (0)