Skip to content

Commit d1f8e39

Browse files
committed
feat: Support webpack 4 splitChunk option
BREAKING CHANGE: Drop support for Webpack version < 4
1 parent 4a29433 commit d1f8e39

16 files changed

+2709
-899
lines changed

.editorconfig

+4
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ insert_final_newline = true
1313
[*.json]
1414
insert_final_newline = ignore
1515

16+
# Makefiles always use tabs for indentation
17+
[*.sh]
18+
indent_size = 4
19+
1620
# Makefiles always use tabs for indentation
1721
[Makefile]
1822
indent_style = tab

.eslintrc

+15-6
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,19 @@
11
{
2-
"parser": "@typescript-eslint/parser",
3-
"extends": [
4-
"plugin:@typescript-eslint/recommended",
5-
],
2+
"parser": "@typescript-eslint/parser",
3+
"extends": ["plugin:@typescript-eslint/recommended"],
64
"rules": {
75
"@typescript-eslint/indent": "off",
8-
"@typescript-eslint/no-var-requires": "off"
9-
}
6+
"@typescript-eslint/no-var-requires": "off",
7+
// disable the rule for all files
8+
"@typescript-eslint/explicit-function-return-type": "off"
9+
},
10+
"overrides": [
11+
{
12+
// enable the rule specifically for TypeScript files
13+
"files": ["*.ts", "*.tsx"],
14+
"rules": {
15+
"@typescript-eslint/explicit-function-return-type": ["error"]
16+
}
17+
}
18+
]
1019
}

.travis.yml

-30
Original file line numberDiff line numberDiff line change
@@ -3,36 +3,6 @@ stages:
33

44
jobs:
55
include:
6-
- name: Tests Webpack 1
7-
stage: test
8-
language: node_js
9-
node_js:
10-
- "12"
11-
- "11"
12-
before_install:
13-
- yarn global add webpack@^1.0.0
14-
- export NODE_PATH=$(yarn global dir)/node_modules
15-
script: yarn run travis
16-
- name: Tests Webpack 2
17-
stage: test
18-
language: node_js
19-
node_js:
20-
- "12"
21-
- "11"
22-
before_install:
23-
- yarn global add webpack@^2.0.0
24-
- export NODE_PATH=$(yarn global dir)/node_modules
25-
script: yarn run travis
26-
- name: Tests Webpack 3
27-
stage: test
28-
language: node_js
29-
node_js:
30-
- "12"
31-
- "11"
32-
before_install:
33-
- yarn global add webpack@^3.0.0
34-
- export NODE_PATH=$(yarn global dir)/node_modules
35-
script: yarn run travis
366
- name: Tests Webpack 4
377
stage: test
388
language: node_js

lib/index.d.ts

-33
This file was deleted.

lib/index.js

+140-106
Original file line numberDiff line numberDiff line change
@@ -1,133 +1,167 @@
1-
var path = require('path');
2-
var fs = require('fs');
3-
var stripAnsi = require('strip-ansi');
4-
var mkdirp = require('mkdirp');
5-
var extend = require('deep-extend');
6-
7-
// @ts-ignore
8-
var assets = {};
9-
var DEFAULT_OUTPUT_FILENAME = 'webpack-stats.json';
10-
var DEFAULT_LOG_TIME = false;
11-
12-
function Plugin(options) {
13-
// @ts-ignore
14-
this.contents = {};
15-
// @ts-ignore
16-
this.options = options || {};
17-
// @ts-ignore
18-
this.options.filename = this.options.filename || DEFAULT_OUTPUT_FILENAME;
19-
// @ts-ignore
20-
if (this.options.logTime === undefined) {
21-
// @ts-ignore
22-
this.options.logTime = DEFAULT_LOG_TIME;
23-
}
24-
}
1+
// @ts-check
2+
/** @typedef {import("lodash.defaults")} defaults */
3+
/** @typedef {import("lodash.assign")} assign */
4+
/** @typedef {import("lodash.get")} get */
5+
/** @typedef {import("webpack").Compiler} Compiler */
6+
/** @typedef {import("webpack").Stats} Stats */
7+
/** @typedef {import("webpack").compilation.Compilation} Compilation */
8+
/** @typedef {import("webpack").compilation.ContextModuleFactory} ContextModuleFactory */
9+
/** @typedef {import("webpack").ChunkData} ChunkData */
10+
/** @typedef {import("../typings").Contents} Contents */
11+
/** @typedef {import("../typings").Options} Options */
2512

26-
Plugin.prototype.apply = function(compiler) {
27-
var self = this;
28-
const ouputDirChunk = path.resolve(compiler.options.output.path || process.cwd());
29-
const ouputDirTracker = path.resolve(self.options.path || compiler.options.output.path || process.cwd());
13+
const path = require('path');
14+
const fs = require('fs');
15+
const crypto = require('crypto');
3016

31-
// @ts-ignore
32-
const _compilation = function(compilation, callback) {
33-
const failedModule = function(fail) {
34-
var output = {
35-
status: 'error',
36-
error: fail.error.name || 'unknown-error',
37-
};
38-
if (fail.error.module !== undefined) {
39-
output.file = fail.error.module.userRequest;
40-
}
41-
if (fail.error.error !== undefined) {
42-
// @ts-ignore
43-
output.message = stripAnsi(fail.error.error.codeFrame);
44-
} else {
45-
output.message = '';
46-
}
47-
self.writeOutput(compiler, output);
17+
const defaults = require('lodash.defaults');
18+
const assign = require('lodash.assign');
19+
const get = require('lodash.get');
20+
const stripAnsi = require('strip-ansi');
21+
22+
class BundleTrackerPlugin {
23+
/**
24+
* Track assets file location per bundle
25+
* @param {Options} options
26+
*/
27+
constructor(options) {
28+
/** @type {Options} */
29+
this.options = options;
30+
/** @type {Contents} */
31+
this.contents = {
32+
status: 'initialization',
33+
chunks: {},
4834
};
35+
this.name = 'BundleTrackerPlugin';
36+
37+
this.outputChunkDir = '';
38+
this.outputTrackerFile = '';
39+
this.ouputTrackerDir = '';
40+
}
41+
/**
42+
* Setup parameter from compiler data
43+
* @param {Compiler} compiler
44+
* @returns this
45+
*/
46+
_setParamsFromCompiler(compiler) {
47+
this.options = defaults({}, this.options, {
48+
path: get(compiler.options, 'output.path', process.cwd()),
49+
publicPath: get(compiler.options, 'output.publicPath', ''),
50+
filename: 'webpack-stats.json',
51+
logTime: false,
52+
relativePath: false,
53+
integrity: false,
54+
// https://www.w3.org/TR/SRI/#cryptographic-hash-functions
55+
integrityHashes: ['sha256', 'sha384', 'sha512'],
56+
});
57+
58+
// Set output directories
59+
this.outputChunkDir = path.resolve(get(compiler.options, 'output.path', process.cwd()));
60+
this.outputTrackerFile = path.resolve(path.join(this.options.path, this.options.filename));
61+
this.ouputTrackerDir = path.dirname(this.outputTrackerFile);
62+
63+
return this;
64+
}
65+
/**
66+
* Write bundle tracker stats file
67+
*
68+
* @param {Compiler} compiler
69+
* @param {Contents} contents
70+
*/
71+
_writeOutput(compiler, contents) {
72+
assign(this.contents, contents);
4973

50-
if (compilation.hooks) {
51-
const plugin = { name: 'BundleTrackerPlugin' };
52-
compilation.hooks.failedModule.tap(plugin, failedModule);
53-
} else {
54-
compilation.plugin('failed-module', failedModule);
74+
if (this.options.publicPath) {
75+
this.contents.publicPath = this.options.publicPath;
5576
}
56-
};
5777

58-
// @ts-ignore
59-
const compile = function(factory, callback) {
60-
self.writeOutput(compiler, { status: 'compiling' });
61-
};
78+
fs.mkdirSync(this.ouputTrackerDir, { recursive: true, mode: 0o755 });
79+
fs.writeFileSync(this.outputTrackerFile, JSON.stringify(this.contents, null, this.options.indent));
80+
}
81+
/**
82+
* Compute hash for a content
83+
* @param {string} content
84+
*/
85+
_computeIntegrity(content) {
86+
return this.options.integrityHashes
87+
.map(algorithm => {
88+
const hash = crypto
89+
.createHash(algorithm)
90+
.update(content, 'utf8')
91+
.digest('base64');
6292

63-
const done = function(stats) {
64-
if (stats.compilation.errors.length > 0) {
65-
var error = stats.compilation.errors[0];
66-
self.writeOutput(compiler, {
93+
return `${algorithm}-${hash}`;
94+
})
95+
.join(' ');
96+
}
97+
/**
98+
* Handle compile hook
99+
* @param {Compiler} compiler
100+
*/
101+
_handleCompile(compiler) {
102+
this._writeOutput(compiler, { status: 'compile' });
103+
}
104+
/**
105+
* Handle compile hook
106+
* @param {Compiler} compiler
107+
* @param {Stats} stats
108+
*/
109+
_handleDone(compiler, stats) {
110+
if (stats.hasErrors()) {
111+
const error = stats.compilation.errors[0];
112+
this._writeOutput(compiler, {
67113
status: 'error',
68-
error: error['name'] || 'unknown-error',
69-
// @ts-ignore
114+
error: get(error, 'name', 'unknown-error'),
70115
message: stripAnsi(error['message']),
71116
});
117+
72118
return;
73119
}
74120

75-
var chunks = {};
76-
stats.compilation.chunks.map(function(chunk) {
77-
var files = chunk.files.map(function(file) {
78-
var F = { name: file };
79-
var publicPath = self.options.publicPath || compiler.options.output.publicPath;
80-
if (publicPath) {
81-
F.publicPath = publicPath + file;
121+
const output = { status: 'done', chunks: {} };
122+
stats.compilation.chunkGroups.map(chunkGroup => {
123+
if (!chunkGroup.isInitial()) return;
124+
125+
output.chunks[chunkGroup.name] = chunkGroup.getFiles().map(filename => {
126+
const fileInfo = { name: filename };
127+
const file = stats.compilation.assets[filename];
128+
129+
if (this.options.integrity === true) {
130+
fileInfo.integrity = this._computeIntegrity(file.source());
131+
}
132+
133+
if (this.options.publicPath) {
134+
fileInfo.publicPath = this.options.publicPath + filename;
82135
}
83-
if (self.options.relativePath === true) {
84-
const absoluteFilePath = path.join(ouputDirChunk, file);
85-
F.path = path.relative(ouputDirTracker, absoluteFilePath);
136+
137+
if (this.options.relativePath === true) {
138+
const absoluteFilePath = path.join(this.outputChunkDir, filename);
139+
fileInfo.path = path.relative(this.ouputTrackerDir, absoluteFilePath);
86140
} else {
87-
F.path = path.join(ouputDirChunk, file);
141+
fileInfo.path = path.join(this.outputChunkDir, filename);
88142
}
89143

90-
return F;
144+
return fileInfo;
91145
});
92-
chunks[chunk.name] = files;
93146
});
94-
var output = {
95-
status: 'done',
96-
chunks: chunks,
97-
};
98147

99-
if (self.options.logTime === true) {
148+
if (this.options.logTime === true) {
100149
output.startTime = stats.startTime;
101150
output.endTime = stats.endTime;
102151
}
103152

104-
self.writeOutput(compiler, output);
105-
};
106-
107-
if (compiler.hooks) {
108-
const plugin = { name: 'BundleTrackerPlugin' };
109-
compiler.hooks.compilation.tap(plugin, _compilation);
110-
compiler.hooks.compile.tap(plugin, compile);
111-
compiler.hooks.done.tap(plugin, done);
112-
} else {
113-
compiler.plugin('compilation', _compilation);
114-
compiler.plugin('compile', compile);
115-
compiler.plugin('done', done);
116-
}
117-
};
118-
119-
Plugin.prototype.writeOutput = function(compiler, contents) {
120-
var outputDir = this.options.path || '.';
121-
var outputFilename = path.join(outputDir, this.options.filename || DEFAULT_OUTPUT_FILENAME);
122-
var publicPath = this.options.publicPath || compiler.options.output.publicPath;
123-
if (publicPath) {
124-
contents.publicPath = publicPath;
153+
this._writeOutput(compiler, output);
125154
}
126-
mkdirp.sync(path.dirname(outputFilename));
155+
/**
156+
* Method called by webpack to apply plugin hook
157+
* @param {Compiler} compiler
158+
*/
159+
apply(compiler) {
160+
this._setParamsFromCompiler(compiler);
127161

128-
this.contents = extend(this.contents, contents);
129-
// @ts-ignore
130-
fs.writeFileSync(outputFilename, JSON.stringify(this.contents, null, this.options.indent));
131-
};
162+
compiler.hooks.compile.tap(this.name, this._handleCompile.bind(this, compiler));
163+
compiler.hooks.done.tap(this.name, this._handleDone.bind(this, compiler));
164+
}
165+
}
132166

133-
module.exports = Plugin;
167+
module.exports = BundleTrackerPlugin;

0 commit comments

Comments
 (0)