chushihua

This commit is contained in:
li
2026-01-26 23:20:48 +08:00
commit 7b5aab0206
22521 changed files with 3300090 additions and 0 deletions
+249
View File
@@ -0,0 +1,249 @@
# Changelog
> **Tags:**
> - [Breaking Change]
> - [New Feature]
> - [Improvement]
> - [Bug Fix]
> - [Internal]
> - [Documentation]
_Note: Gaps between patch versions are faulty, broken or test releases._
## UNRELEASED
<!-- Add changelog entries for new changes under this section -->
## 2.13.1
* **Improvement**
* Pretty-format the generated stats.json ([#180](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/180)) [@edmorley](https://github.com/edmorley))
* **Bug Fix**
* Properly parse Webpack 4 async chunk with `Array.concat` optimization ([#184](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/184), fixes [#183](https://github.com/webpack-contrib/webpack-bundle-analyzer/issues/183))
* **Internal**
* Refactor bundle parsing logic ([#184](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/184))
## 2.13.0
* **Improvement**
* Loosen bundle parsing logic ([#181](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/181)). Now analyzer will still show parsed sizes even if:
* It can't parse some bundle chunks. Those chunks just won't have content in the report. Fixes issues like [#160](https://github.com/webpack-contrib/webpack-bundle-analyzer/issues/160).
* Some bundle chunks are missing (it couldn't find files to parse). Those chunks just won't be visible in the report for parsed/gzipped sizes.
## 2.12.0
* **New Feature**
* Add option that allows to exclude assets from the report ([#178](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/178))
## 2.11.3
* **Bug Fix**
* Filter out modules that weren't found during bundles parsing ([#177](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/177))
## 2.11.2
* **Bug Fix**
* Properly process stat files that contain modules inside of `chunks` array ([#175](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/175))
* Fix parsing of async chunks that push to `this.webpackJsonp` array ([#176](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/176))
## 2.11.1
* **Improvement**
* Add support for parsing Webpack 4's chunked modules ([#159](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/159), [@jdelStrother](https://github.com/jdelStrother))
## 2.11.0
* **Improvement**
* Show contents of concatenated module (requires Webpack 4) ([#158](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/158), closes [#157](https://github.com/webpack-contrib/webpack-bundle-analyzer/issues/157))
## 2.10.1
* **Improvement**
* Support webpack 4 without deprecation warnings. @ai in [#156](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/156), fixes [#154](https://github.com/webpack-contrib/webpack-bundle-analyzer/issues/154)
## 2.10.0
* **Bug Fix**
* Fix "out of memory" crash when dealing with huge stats objects ([#129](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/129), [@ryan953](https://github.com/ryan953))
* **Internal**
* Update dependencies ([#146](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/146))
* Update gulp to v4 and simplify gulpfile ([#146](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/146), [#149](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/149))
* Simplify ESLint configs ([#148](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/148))
## 2.9.2
* **Bug Fix**
* Add a listener for the 'error' event on the WebSocket server client (#140)
* **Internal**
* Clean up .travis.yml (#140)
* Update ws to version 4.0.0 (#140)
## 2.9.1
* **Bug Fix**
* Bump `ws` dependency to fix DoS vulnerability (closes [#130](https://github.com/webpack-contrib/webpack-bundle-analyzer/issues/130))
## 2.9.0
* **New Feature**
* Show chunk sizes in sidebar (closes #91)
* **Bug Fix**
* Properly parse webpack bundles that use arrow functions as module wrappers (#108, @regiontog)
## 2.8.3
* **Bug Fix**
* Correctly advertise port when using a random one (#89, @yannickcr)
* Add proper support for `multi` entries (fixes #92, #87)
* Support parsing of ESNext features (fixes #94)
## 2.8.2
* **Improvement**
* Greatly improved accuracy of gzip sizes
* **Bug Fix**
* Generate report file in the bundle output directory when used with Webpack Dev Server (fixes #75)
## 2.8.1
* **Improvement**
* Improve warning message when analyzer client couldn't connect to WebSocket server
## 2.8.0
* **Improvement**
* Analyzer now supports `webpack --watch` and Webpack Dev Server!
It will automatically update modules treemap according to changes in the sources via WebSockets!
* **Internal**
* Use `babel-preset-env` and two different Babel configs to compile node and browser code
* Update deps
## 2.7.0
* **New Feature**
* Add control to sidebar that allows to choose shown chunks (closes #71 and partially addresses #38)
## 2.6.0
* **New Feature**
* Add `defaultSizes` option (closes #52)
## 2.5.0
* **New Feature**
* Added `--host` CLI option (@difelice)
## 2.4.1
* **Improvement**
* Support `NamedChunksPlugin` (@valscion)
## 2.4.0
* **Bug Fix**
* Fix `TypeError: currentFolder.addModule is not a function`
* **Internal**
* Update deps
## 2.3.1
* **Improvement**
* Improve compatibility with Webpack 2 (@valscion)
## 2.3.0
* **Improvement**
* Add `analyzerHost` option (@freaz)
* **Internal**
* Update deps
## 2.2.3
* **Bug Fix**
* Support bundles that uses `Array.concat` expression in modules definition (@valscion)
## 2.2.1
* **Bug Fix**
* Fix regression in analyzing stats files with non-empty `children` property (@gbakernet)
## 2.2.0
* **Improvement**
* Improve treemap sharpness on hi-res displays (fixes #33)
* Add support for stats files with all the information under `children` property (fixes #10)
* **Internal**
* Update deps
## 2.1.1
* **Improvement**
* Add support for `output.jsonpFunction` webpack config option (fixes #16)
## 2.1.0
* **New Feature**
* Add `logLevel` option (closes #19)
## 2.0.1
* **Bug Fix**
* Support query in bundle filenames (fixes #22)
* **Internal**
* Minimize CSS for report UI
## 2.0.0
* **New Feature**
* Analyzer now also shows gzipped sizes (closes #6)
* Added switcher that allows to choose what sizes will be used to generate tree map.
Just move your mouse to the left corner of the browser and settings sidebar will appear.
* **Bug Fix**
* Properly show sizes for some asset modules (e.g. CSS files loaded with `css-loader`)
* **Internal**
* Completely rewritten analyzer UI. Now uses Preact and Webpack 2.
## 1.5.4
* **Bug Fix**
* Fix bug when Webpack build is being controlled by some wrapper like `grunt-webpack` (see #21)
## 1.5.3
* **Bug Fix**
* Workaround `Express` bug that caused wrong `ejs` version to be used as view engine (fixes #17)
## 1.5.2
* **Bug Fix**
* Support array module descriptors that can be generated if `DedupePlugin` is used (fixes #4)
## 1.5.1
* **Internal**
* Plug analyzer to Webpack compiler `done` event instead of `emit`. Should fix #15.
## 1.5.0
* **New Feature**
* Add `statsOptions` option for `BundleAnalyzerPlugin`
## 1.4.2
* **Bug Fix**
* Fix "Unable to find bundle asset" error when bundle name starts with `/` (fixes #3)
## 1.4.1
* **Bug Fix**
* Add partial support for `DedupePlugin` (see #4 for more info)
## 1.4.0
* **New Feature**
* Add "static report" mode (closes #2)
## 1.3.0
* **Improvement**
* Add `startAnalyzer` option for `BundleAnalyzerPlugin` (fixes #1)
* **Internal**
* Make module much slimmer - remove/replace bloated dependencies
## 1.2.5
* Initial public release
+20
View File
@@ -0,0 +1,20 @@
Copyright JS Foundation and other contributors
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:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
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.
+205
View File
@@ -0,0 +1,205 @@
[![npm][npm]][npm-url]
[![node][node]][node-url]
[![deps][deps]][deps-url]
[![tests][tests]][tests-url]
[![downloads][downloads]][downloads-url]
<div align="center">
<a href="https://github.com/webpack/webpack">
<img width="200" height="200"
src="https://webpack.js.org/assets/icon-square-big.svg">
</a>
<h1>Webpack Bundle Analyzer</h1>
<p>Visualize size of webpack output files with an interactive zoomable treemap.</p>
</div>
<h2 align="center">Install</h2>
```bash
npm install --save-dev webpack-bundle-analyzer
```
<h2 align="center">Usage (as a plugin)</h2>
```js
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = {
plugins: [
new BundleAnalyzerPlugin()
]
}
```
It will create an interactive treemap visualization of the contents of all your bundles.
![webpack bundle analyzer zoomable treemap](https://cloud.githubusercontent.com/assets/302213/20628702/93f72404-b338-11e6-92d4-9a365550a701.gif)
This module will help you:
1. Realize what's *really* inside your bundle
2. Find out what modules make up the most of its size
3. Find modules that got there by mistake
4. Optimize it!
And the best thing is it supports minified bundles! It parses them to get real size of bundled modules.
And it also shows their gzipped sizes!
<h2 align="center">Options (for plugin)</h2>
```js
new BundleAnalyzerPlugin(options?: object)
```
|Name|Type|Description|
|:--:|:--:|:----------|
|**`analyzerMode`**|One of: `server`, `static`, `disabled`|Default: `server`. In `server` mode analyzer will start HTTP server to show bundle report. In `static` mode single HTML file with bundle report will be generated. In `disabled` mode you can use this plugin to just generate Webpack Stats JSON file by setting `generateStatsFile` to `true`. |
|**`analyzerHost`**|`{String}`|Default: `127.0.0.1`. Host that will be used in `server` mode to start HTTP server.|
|**`analyzerPort`**|`{Number}`|Default: `8888`. Port that will be used in `server` mode to start HTTP server.|
|**`reportFilename`**|`{String}`|Default: `report.html`. Path to bundle report file that will be generated in `static` mode. Relative to bundle output directory (which is `output.path` in webpack config).|
|**`defaultSizes`**|One of: `stat`, `parsed`, `gzip`|Default: `parsed`. Module sizes to show in report by default. [Size definitions](#size-definitions) section describes what these values mean.|
|**`openAnalyzer`**|`{Boolean}`|Default: `true`. Automatically open report in default browser.|
|**`generateStatsFile`**|`{Boolean}`|Default: `false`. If `true`, webpack stats JSON file will be generated in bundle output directory|
|**`statsFilename`**|`{String}`|Default: `stats.json`. Name of webpack stats JSON file that will be generated if `generateStatsFile` is `true`. Relative to bundle output directory.|
|**`statsOptions`**|`null` or `{Object}`|Default: `null`. Options for `stats.toJson()` method. For example you can exclude sources of your modules from stats file with `source: false` option. [See more options here](https://github.com/webpack/webpack/blob/webpack-1/lib/Stats.js#L21). |
|**`excludeAssets`**|`{null\|pattern\|pattern[]}` where `pattern` equals to `{String\|RegExp\|function}`|Default: `null`. Patterns that will be used to match against asset names to exclude them from the report. If pattern is a string it will be converted to RegExp via `new RegExp(str)`. If pattern is a function it should have the following signature `(assetName: string) => boolean` and should return `true` to *exclude* matching asset. If multiple patterns are provided asset should match at least one of them to be excluded. |
|**`logLevel`**|One of: `info`, `warn`, `error`, `silent`|Default: `info`. Used to control how much details the plugin outputs.|
<h2 align="center">Usage (as a CLI utility)</h2>
You can analyze an existing bundle if you have a webpack stats JSON file.
You can generate it using `BundleAnalyzerPlugin` with `generateStatsFile` option set to `true` or with this simple
command:
```bash
webpack --profile --json > stats.json
```
If you're on Windows and using PowerShell, you can generate the stats file with this command to [avoid BOM issues](https://github.com/webpack-contrib/webpack-bundle-analyzer/issues/47):
```
webpack --profile --json | Out-file 'stats.json' -Encoding OEM
```
Then you can run the CLI tool.
```
webpack-bundle-analyzer bundle/output/path/stats.json
```
<h2 align="center">Options (for CLI)</h2>
```bash
webpack-bundle-analyzer <bundleStatsFile> [bundleDir] [options]
```
Arguments are documented below:
### `bundleStatsFile`
Path to webpack stats JSON file
### `bundleDir`
Directory containing all generated bundles.
### `options`
```
-V, --version output the version number
-m, --mode <mode> Analyzer mode. Should be `server` or `static`.
In `server` mode analyzer will start HTTP server to show bundle report.
In `static` mode single HTML file with bundle report will be generated. (default: server)
-h, --host <host> Host that will be used in `server` mode to start HTTP server. (default: 127.0.0.1)
-p, --port <n> Port that will be used in `server` mode to start HTTP server. (default: 8888)
-r, --report <file> Path to bundle report file that will be generated in `static` mode. (default: report.html)
-s, --default-sizes <type> Module sizes to show in treemap by default.
Possible values: stat, parsed, gzip (default: parsed)
-O, --no-open Don't open report in default browser automatically.
-e, --exclude <regexp> Assets that should be excluded from the report.
Can be specified multiple times.
-l, --log-level <level> Log level.
Possible values: debug, info, warn, error, silent (default: info)
-h, --help output usage information
```
<h2 align="center" id="size-definitions">Size definitions</h2>
webpack-bundle-analyzer reports three values for sizes. `defaultSizes` can be used to control which of these is shown by default. The different reported sizes are:
### `stat`
This is the "input" size of your files, before any transformations like
minification.
It is called "stat size" because it's obtained from Webpack's
[stats object](https://webpack.js.org/configuration/stats/).
### `parsed`
This is the "output" size of your files. If you're using a Webpack plugin such
as Uglify, then this value will reflect the minified size of your code.
### `gzip`
This is the size of running the parsed bundles/modules through gzip compression.
<h2 align="center">Troubleshooting</h2>
### I can't see all the dependencies in a chunk
This is a known caveat when `webpack.optimize.ModuleConcatenationPlugin` is used with `Webpack 3` or `webpack-bundle-analyzer < 2.11.0`.
The way `ModuleConcatenationPlugin` works is that it merges multiple modules into a single one, and so that resulting module doesn't have edges anymore.
`Webpack 3` didn't provide any information about concatenated modules, but `Webpack 4` started including it into a `stats` files and `webpack-bundle-analyzer 2.11.0` learned to show it.
If for some reason you can't update to the latest versions try analyzing your bundle without `ModuleConcatenationPlugin`. See [issue #115](https://github.com/webpack-contrib/webpack-bundle-analyzer/issues/115) for more discussion.
If you are using `Angular CLI < 6.0.0` (which is based on `Webpack 3` and uses `ModuleConcatenationPlugin`) you can build your project using the following command:
```
ng build --stats-json --build-optimizer=false --vendor-chunk=true
```
The resulting stats.json can be found at `dist/stats.json`
*Please be aware, that this is not a reasonable build for production use, as it increases the build output size considerably.*
<h2 align="center">Maintainers</h2>
<table>
<tbody>
<tr>
<td align="center">
<img width="150" height="150"
src="https://avatars3.githubusercontent.com/u/302213?v=4&s=150">
</br>
<a href="https://github.com/th0r">Yuriy Grunin</a>
</td>
<td align="center">
<img width="150" height="150"
src="https://avatars3.githubusercontent.com/u/482561?v=4&s=150">
</br>
<a href="https://github.com/valscion">Vesa Laakso</a>
</td>
</tr>
<tbody>
</table>
[npm]: https://img.shields.io/npm/v/webpack-bundle-analyzer.svg
[npm-url]: https://npmjs.com/package/webpack-bundle-analyzer
[node]: https://img.shields.io/node/v/webpack-bundle-analyzer.svg
[node-url]: https://nodejs.org
[deps]: https://david-dm.org/webpack-contrib/webpack-bundle-analyzer.svg
[deps-url]: https://david-dm.org/webpack-contrib/webpack-bundle-analyzer
[tests]: http://img.shields.io/travis/webpack-contrib/webpack-bundle-analyzer.svg
[tests-url]: https://travis-ci.org/webpack-contrib/webpack-bundle-analyzer
[downloads]: https://img.shields.io/npm/dt/webpack-bundle-analyzer.svg
[downloads-url]: https://npmjs.com/package/webpack-bundle-analyzer
<h2 align="center">Contributing</h2>
Check out [CONTRIBUTING.md](./CONTRIBUTING.md) for instructions on contributing :tada:
+168
View File
@@ -0,0 +1,168 @@
'use strict';
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
var bfj = require('bfj-node4');
var path = require('path');
var mkdir = require('mkdirp');
var _require = require('chalk'),
bold = _require.bold;
var Logger = require('./Logger');
var viewer = require('./viewer');
var BundleAnalyzerPlugin = function () {
function BundleAnalyzerPlugin(opts) {
_classCallCheck(this, BundleAnalyzerPlugin);
this.opts = Object.assign({
analyzerMode: 'server',
analyzerHost: '127.0.0.1',
analyzerPort: 8888,
reportFilename: 'report.html',
defaultSizes: 'parsed',
openAnalyzer: true,
generateStatsFile: false,
statsFilename: 'stats.json',
statsOptions: null,
excludeAssets: null,
logLevel: 'info',
// deprecated
startAnalyzer: true
}, opts);
this.server = null;
this.logger = new Logger(this.opts.logLevel);
}
_createClass(BundleAnalyzerPlugin, [{
key: 'apply',
value: function apply(compiler) {
var _this = this;
this.compiler = compiler;
var done = function done(stats) {
stats = stats.toJson(_this.opts.statsOptions);
var actions = [];
if (_this.opts.generateStatsFile) {
actions.push(function () {
return _this.generateStatsFile(stats);
});
}
// Handling deprecated `startAnalyzer` flag
if (_this.opts.analyzerMode === 'server' && !_this.opts.startAnalyzer) {
_this.opts.analyzerMode = 'disabled';
}
if (_this.opts.analyzerMode === 'server') {
actions.push(function () {
return _this.startAnalyzerServer(stats);
});
} else if (_this.opts.analyzerMode === 'static') {
actions.push(function () {
return _this.generateStaticReport(stats);
});
}
if (actions.length) {
// Making analyzer logs to be after all webpack logs in the console
setImmediate(function () {
actions.forEach(function (action) {
return action();
});
});
}
};
if (compiler.hooks) {
compiler.hooks.done.tap('webpack-bundle-analyzer', done);
} else {
compiler.plugin('done', done);
}
}
}, {
key: 'generateStatsFile',
value: function () {
var _ref = _asyncToGenerator(function* (stats) {
var statsFilepath = path.resolve(this.compiler.outputPath, this.opts.statsFilename);
mkdir.sync(path.dirname(statsFilepath));
try {
yield bfj.write(statsFilepath, stats, {
space: 2,
promises: 'ignore',
buffers: 'ignore',
maps: 'ignore',
iterables: 'ignore',
circular: 'ignore'
});
this.logger.info(`${bold('Webpack Bundle Analyzer')} saved stats file to ${bold(statsFilepath)}`);
} catch (error) {
this.logger.error(`${bold('Webpack Bundle Analyzer')} error saving stats file to ${bold(statsFilepath)}: ${error}`);
}
});
function generateStatsFile(_x) {
return _ref.apply(this, arguments);
}
return generateStatsFile;
}()
}, {
key: 'startAnalyzerServer',
value: function () {
var _ref2 = _asyncToGenerator(function* (stats) {
if (this.server) {
(yield this.server).updateChartData(stats);
} else {
this.server = viewer.startServer(stats, {
openBrowser: this.opts.openAnalyzer,
host: this.opts.analyzerHost,
port: this.opts.analyzerPort,
bundleDir: this.getBundleDirFromCompiler(),
logger: this.logger,
defaultSizes: this.opts.defaultSizes,
excludeAssets: this.opts.excludeAssets
});
}
});
function startAnalyzerServer(_x2) {
return _ref2.apply(this, arguments);
}
return startAnalyzerServer;
}()
}, {
key: 'generateStaticReport',
value: function generateStaticReport(stats) {
viewer.generateReport(stats, {
openBrowser: this.opts.openAnalyzer,
reportFilename: path.resolve(this.compiler.outputPath, this.opts.reportFilename),
bundleDir: this.getBundleDirFromCompiler(),
logger: this.logger,
defaultSizes: this.opts.defaultSizes,
excludeAssets: this.opts.excludeAssets
});
}
}, {
key: 'getBundleDirFromCompiler',
value: function getBundleDirFromCompiler() {
return this.compiler.outputFileSystem.constructor.name === 'MemoryFileSystem' ? null : this.compiler.outputPath;
}
}]);
return BundleAnalyzerPlugin;
}();
module.exports = BundleAnalyzerPlugin;
+94
View File
@@ -0,0 +1,94 @@
'use strict';
var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }();
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
var LEVELS = ['debug', 'info', 'warn', 'error', 'silent'];
var LEVEL_TO_CONSOLE_METHOD = new Map([['debug', 'log'], ['info', 'log'], ['warn', 'log']]);
var Logger = function () {
function Logger() {
var level = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : Logger.defaultLevel;
_classCallCheck(this, Logger);
this.activeLevels = new Set();
this.setLogLevel(level);
}
_createClass(Logger, [{
key: 'setLogLevel',
value: function setLogLevel(level) {
var levelIndex = LEVELS.indexOf(level);
if (levelIndex === -1) throw new Error(`Invalid log level "${level}". Use one of these: ${LEVELS.join(', ')}`);
this.activeLevels.clear();
var _iteratorNormalCompletion = true;
var _didIteratorError = false;
var _iteratorError = undefined;
try {
for (var _iterator = LEVELS.entries()[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
var _ref = _step.value;
var _ref2 = _slicedToArray(_ref, 2);
var i = _ref2[0];
var _level = _ref2[1];
if (i >= levelIndex) this.activeLevels.add(_level);
}
} catch (err) {
_didIteratorError = true;
_iteratorError = err;
} finally {
try {
if (!_iteratorNormalCompletion && _iterator.return) {
_iterator.return();
}
} finally {
if (_didIteratorError) {
throw _iteratorError;
}
}
}
}
}, {
key: '_log',
value: function _log(level) {
var _console;
for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
args[_key - 1] = arguments[_key];
}
(_console = console)[LEVEL_TO_CONSOLE_METHOD.get(level) || level].apply(_console, args);
}
}]);
return Logger;
}();
Logger.levels = LEVELS;
Logger.defaultLevel = 'info';
;
LEVELS.forEach(function (level) {
if (level === 'silent') return;
Logger.prototype[level] = function () {
for (var _len2 = arguments.length, args = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
args[_key2] = arguments[_key2];
}
if (this.activeLevels.has(level)) this._log.apply(this, [level].concat(args));
};
});
module.exports = Logger;
+159
View File
@@ -0,0 +1,159 @@
'use strict';
var fs = require('fs');
var path = require('path');
var _ = require('lodash');
var gzipSize = require('gzip-size');
var Logger = require('./Logger');
var Folder = require('./tree/Folder').default;
var _require = require('./parseUtils'),
parseBundle = _require.parseBundle;
var _require2 = require('./utils'),
createAssetsFilter = _require2.createAssetsFilter;
var FILENAME_QUERY_REGEXP = /\?.*$/;
module.exports = {
getViewerData,
readStatsFromFile
};
function getViewerData(bundleStats, bundleDir, opts) {
var _ref = opts || {},
_ref$logger = _ref.logger,
logger = _ref$logger === undefined ? new Logger() : _ref$logger,
_ref$excludeAssets = _ref.excludeAssets,
excludeAssets = _ref$excludeAssets === undefined ? null : _ref$excludeAssets;
var isAssetIncluded = createAssetsFilter(excludeAssets);
// Sometimes all the information is located in `children` array (e.g. problem in #10)
if (_.isEmpty(bundleStats.assets) && !_.isEmpty(bundleStats.children)) {
bundleStats = bundleStats.children[0];
}
// Picking only `*.js` assets from bundle that has non-empty `chunks` array
bundleStats.assets = _.filter(bundleStats.assets, function (asset) {
// Removing query part from filename (yes, somebody uses it for some reason and Webpack supports it)
// See #22
asset.name = asset.name.replace(FILENAME_QUERY_REGEXP, '');
return _.endsWith(asset.name, '.js') && !_.isEmpty(asset.chunks) && isAssetIncluded(asset.name);
});
// Trying to parse bundle assets and get real module sizes if `bundleDir` is provided
var bundlesSources = null;
var parsedModules = null;
if (bundleDir) {
bundlesSources = {};
parsedModules = {};
var _iteratorNormalCompletion = true;
var _didIteratorError = false;
var _iteratorError = undefined;
try {
for (var _iterator = bundleStats.assets[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
var statAsset = _step.value;
var assetFile = path.join(bundleDir, statAsset.name);
var bundleInfo = void 0;
try {
bundleInfo = parseBundle(assetFile);
} catch (err) {
var msg = err.code === 'ENOENT' ? 'no such file' : err.message;
logger.warn(`Error parsing bundle asset "${assetFile}": ${msg}`);
continue;
}
bundlesSources[statAsset.name] = bundleInfo.src;
_.assign(parsedModules, bundleInfo.modules);
}
} catch (err) {
_didIteratorError = true;
_iteratorError = err;
} finally {
try {
if (!_iteratorNormalCompletion && _iterator.return) {
_iterator.return();
}
} finally {
if (_didIteratorError) {
throw _iteratorError;
}
}
}
if (_.isEmpty(bundlesSources)) {
bundlesSources = null;
parsedModules = null;
logger.warn('\nNo bundles were parsed. Analyzer will show only original module sizes from stats file.\n');
}
}
var modules = getBundleModules(bundleStats);
var assets = _.transform(bundleStats.assets, function (result, statAsset) {
var asset = result[statAsset.name] = _.pick(statAsset, 'size');
if (bundlesSources && _.has(bundlesSources, statAsset.name)) {
asset.parsedSize = bundlesSources[statAsset.name].length;
asset.gzipSize = gzipSize.sync(bundlesSources[statAsset.name]);
}
// Picking modules from current bundle script
asset.modules = _(modules).filter(function (statModule) {
return assetHasModule(statAsset, statModule);
}).each(function (statModule) {
if (parsedModules) {
statModule.parsedSrc = parsedModules[statModule.id];
}
});
asset.tree = createModulesTree(asset.modules);
}, {});
return _.transform(assets, function (result, asset, filename) {
result.push({
label: filename,
// Not using `asset.size` here provided by Webpack because it can be very confusing when `UglifyJsPlugin` is used.
// In this case all module sizes from stats file will represent unminified module sizes, but `asset.size` will
// be the size of minified bundle.
// Using `asset.size` only if current asset doesn't contain any modules (resulting size equals 0)
statSize: asset.tree.size || asset.size,
parsedSize: asset.parsedSize,
gzipSize: asset.gzipSize,
groups: _.invokeMap(asset.tree.children, 'toChartData')
});
}, []);
}
function readStatsFromFile(filename) {
return JSON.parse(fs.readFileSync(filename, 'utf8'));
}
function getBundleModules(bundleStats) {
return _(bundleStats.chunks).map('modules').concat(bundleStats.modules).compact().flatten().uniqBy('id').value();
}
function assetHasModule(statAsset, statModule) {
// Checking if this module is the part of asset chunks
return _.some(statModule.chunks, function (moduleChunk) {
return _.includes(statAsset.chunks, moduleChunk);
});
}
function createModulesTree(modules) {
var root = new Folder('.');
_.each(modules, function (module) {
return root.addModule(module);
});
return root;
}
+103
View File
@@ -0,0 +1,103 @@
#! /usr/bin/env node
'use strict';
var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }();
function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }
var _require = require('path'),
resolve = _require.resolve,
dirname = _require.dirname;
var _ = require('lodash');
var commander = require('commander');
var _require2 = require('chalk'),
magenta = _require2.magenta;
var analyzer = require('../analyzer');
var viewer = require('../viewer');
var Logger = require('../Logger');
var SIZES = new Set(['stat', 'parsed', 'gzip']);
var program = commander.version(require('../../package.json').version).usage(`<bundleStatsFile> [bundleDir] [options]
Arguments:
bundleStatsFile Path to Webpack Stats JSON file.
bundleDir Directory containing all generated bundles.
You should provided it if you want analyzer to show you the real parsed module sizes.
By default a directory of stats file is used.`).option('-m, --mode <mode>', 'Analyzer mode. Should be `server` or `static`.' + br('In `server` mode analyzer will start HTTP server to show bundle report.') + br('In `static` mode single HTML file with bundle report will be generated.'), 'server').option('-h, --host <host>', 'Host that will be used in `server` mode to start HTTP server.', '127.0.0.1').option('-p, --port <n>', 'Port that will be used in `server` mode to start HTTP server.', Number, 8888).option('-r, --report <file>', 'Path to bundle report file that will be generated in `static` mode.', 'report.html').option('-s, --default-sizes <type>', 'Module sizes to show in treemap by default.' + br(`Possible values: ${[].concat(_toConsumableArray(SIZES)).join(', ')}`), 'parsed').option('-O, --no-open', "Don't open report in default browser automatically.").option('-e, --exclude <regexp>', 'Assets that should be excluded from the report.' + br('Can be specified multiple times.'), array()).option('-l, --log-level <level>', 'Log level.' + br(`Possible values: ${[].concat(_toConsumableArray(Logger.levels)).join(', ')}`), Logger.defaultLevel).parse(process.argv);
var mode = program.mode,
host = program.host,
port = program.port,
reportFilename = program.report,
defaultSizes = program.defaultSizes,
logLevel = program.logLevel,
openBrowser = program.open,
excludeAssets = program.exclude,
_program$args = _slicedToArray(program.args, 2),
bundleStatsFile = _program$args[0],
bundleDir = _program$args[1];
var logger = new Logger(logLevel);
if (!bundleStatsFile) showHelp('Provide path to Webpack Stats file as first argument');
if (mode !== 'server' && mode !== 'static') showHelp('Invalid mode. Should be either `server` or `static`.');
if (mode === 'server' && !host) showHelp('Invalid host name');
if (mode === 'server' && isNaN(port)) showHelp('Invalid port number');
if (!SIZES.has(defaultSizes)) showHelp(`Invalid default sizes option. Possible values are: ${[].concat(_toConsumableArray(SIZES)).join(', ')}`);
bundleStatsFile = resolve(bundleStatsFile);
if (!bundleDir) bundleDir = dirname(bundleStatsFile);
var bundleStats = void 0;
try {
bundleStats = analyzer.readStatsFromFile(bundleStatsFile);
} catch (err) {
logger.error(`Could't read webpack bundle stats from "${bundleStatsFile}":\n${err}`);
logger.debug(err.stack);
process.exit(1);
}
if (mode === 'server') {
viewer.startServer(bundleStats, {
openBrowser,
port,
host,
defaultSizes,
bundleDir,
excludeAssets,
logger: new Logger(logLevel)
});
} else {
viewer.generateReport(bundleStats, {
openBrowser,
reportFilename: resolve(reportFilename),
defaultSizes,
bundleDir,
excludeAssets,
logger: new Logger(logLevel)
});
}
function showHelp(error) {
if (error) console.log(`\n ${magenta(error)}`);
program.outputHelp();
process.exit(1);
}
function br(str) {
return `\n${_.repeat(' ', 28)}${str}`;
}
function array() {
var arr = [];
return function (val) {
arr.push(val);
return arr;
};
}
+9
View File
@@ -0,0 +1,9 @@
'use strict';
var _require = require('./viewer'),
start = _require.start;
module.exports = {
start,
BundleAnalyzerPlugin: require('./BundleAnalyzerPlugin')
};
+198
View File
@@ -0,0 +1,198 @@
'use strict';
var fs = require('fs');
var _ = require('lodash');
var acorn = require('acorn');
var walk = require('acorn/dist/walk');
module.exports = {
parseBundle
};
function parseBundle(bundlePath) {
var content = fs.readFileSync(bundlePath, 'utf8');
var ast = acorn.parse(content, {
sourceType: 'script',
// I believe in a bright future of ECMAScript!
// Actually, it's set to `2050` to support the latest ECMAScript version that currently exists.
// Seems like `acorn` supports such weird option value.
ecmaVersion: 2050
});
var walkState = {
locations: null
};
walk.recursive(ast, walkState, {
CallExpression(node, state, c) {
if (state.locations) return;
var args = node.arguments;
// Main chunk with webpack loader.
// Modules are stored in first argument:
// (function (...) {...})(<modules>)
if (node.callee.type === 'FunctionExpression' && !node.callee.id && args.length === 1 && isSimpleModulesList(args[0])) {
state.locations = getModulesLocations(args[0]);
return;
}
// Async Webpack < v4 chunk without webpack loader.
// webpackJsonp([<chunks>], <modules>, ...)
// As function name may be changed with `output.jsonpFunction` option we can't rely on it's default name.
if (node.callee.type === 'Identifier' && mayBeAsyncChunkArguments(args) && isModulesList(args[1])) {
state.locations = getModulesLocations(args[1]);
return;
}
// Async Webpack v4 chunk without webpack loader.
// (window.webpackJsonp=window.webpackJsonp||[]).push([[<chunks>], <modules>, ...]);
// As function name may be changed with `output.jsonpFunction` option we can't rely on it's default name.
if (isAsyncChunkPushExpression(node)) {
state.locations = getModulesLocations(args[0].elements[1]);
return;
}
// Walking into arguments because some of plugins (e.g. `DedupePlugin`) or some Webpack
// features (e.g. `umd` library output) can wrap modules list into additional IIFE.
_.each(args, function (arg) {
return c(arg, state);
});
}
});
var modules = void 0;
if (walkState.locations) {
modules = _.mapValues(walkState.locations, function (loc) {
return content.slice(loc.start, loc.end);
});
} else {
modules = {};
}
return {
src: content,
modules
};
}
function isModulesList(node) {
return isSimpleModulesList(node) ||
// Modules are contained in expression `Array([minimum ID]).concat([<module>, <module>, ...])`
isOptimizedModulesArray(node);
}
function isSimpleModulesList(node) {
return (
// Modules are contained in hash. Keys are module ids.
isModulesHash(node) ||
// Modules are contained in array. Indexes are module ids.
isModulesArray(node)
);
}
function isModulesHash(node) {
return node.type === 'ObjectExpression' && _(node.properties).map('value').every(isModuleWrapper);
}
function isModulesArray(node) {
return node.type === 'ArrayExpression' && _.every(node.elements, function (elem) {
return (
// Some of array items may be skipped because there is no module with such id
!elem || isModuleWrapper(elem)
);
});
}
function isOptimizedModulesArray(node) {
// Checking whether modules are contained in `Array(<minimum ID>).concat(...modules)` array:
// https://github.com/webpack/webpack/blob/v1.14.0/lib/Template.js#L91
// The `<minimum ID>` + array indexes are module ids
return node.type === 'CallExpression' && node.callee.type === 'MemberExpression' &&
// Make sure the object called is `Array(<some number>)`
node.callee.object.type === 'CallExpression' && node.callee.object.callee.type === 'Identifier' && node.callee.object.callee.name === 'Array' && node.callee.object.arguments.length === 1 && isNumericId(node.callee.object.arguments[0]) &&
// Make sure the property X called for `Array(<some number>).X` is `concat`
node.callee.property.type === 'Identifier' && node.callee.property.name === 'concat' &&
// Make sure exactly one array is passed in to `concat`
node.arguments.length === 1 && isModulesArray(node.arguments[0]);
}
function isModuleWrapper(node) {
return (
// It's an anonymous function expression that wraps module
(node.type === 'FunctionExpression' || node.type === 'ArrowFunctionExpression') && !node.id ||
// If `DedupePlugin` is used it can be an ID of duplicated module...
isModuleId(node) ||
// or an array of shape [<module_id>, ...args]
node.type === 'ArrayExpression' && node.elements.length > 1 && isModuleId(node.elements[0])
);
}
function isModuleId(node) {
return node.type === 'Literal' && (isNumericId(node) || typeof node.value === 'string');
}
function isNumericId(node) {
return node.type === 'Literal' && Number.isInteger(node.value) && node.value >= 0;
}
function isChunkIds(node) {
// Array of numeric or string ids. Chunk IDs are strings when NamedChunksPlugin is used
return node.type === 'ArrayExpression' && _.every(node.elements, isModuleId);
}
function isAsyncChunkPushExpression(node) {
var callee = node.callee,
args = node.arguments;
return callee.type === 'MemberExpression' && callee.property.name === 'push' && callee.object.type === 'AssignmentExpression' && callee.object.left.object && (callee.object.left.object.name === 'window' ||
// Webpack 4 uses `this` instead of `window`
callee.object.left.object.type === 'ThisExpression') && args.length === 1 && args[0].type === 'ArrayExpression' && mayBeAsyncChunkArguments(args[0].elements) && isModulesList(args[0].elements[1]);
}
function mayBeAsyncChunkArguments(args) {
return args.length >= 2 && isChunkIds(args[0]);
}
function getModulesLocations(node) {
if (node.type === 'ObjectExpression') {
// Modules hash
var modulesNodes = node.properties;
return _.transform(modulesNodes, function (result, moduleNode) {
var moduleId = moduleNode.key.name || moduleNode.key.value;
result[moduleId] = getModuleLocation(moduleNode.value);
}, {});
}
var isOptimizedArray = node.type === 'CallExpression';
if (node.type === 'ArrayExpression' || isOptimizedArray) {
// Modules array or optimized array
var minId = isOptimizedArray ?
// Get the [minId] value from the Array() call first argument literal value
node.callee.object.arguments[0].value :
// `0` for simple array
0;
var _modulesNodes = isOptimizedArray ?
// The modules reside in the `concat()` function call arguments
node.arguments[0].elements : node.elements;
return _.transform(_modulesNodes, function (result, moduleNode, i) {
if (!moduleNode) return;
result[i + minId] = getModuleLocation(moduleNode);
}, {});
}
return {};
}
function getModuleLocation(node) {
return {
start: node.start,
end: node.end
};
}
+138
View File
@@ -0,0 +1,138 @@
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
var _lodash = require('lodash');
var _lodash2 = _interopRequireDefault(_lodash);
var _Node2 = require('./Node');
var _Node3 = _interopRequireDefault(_Node2);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
var BaseFolder = function (_Node) {
_inherits(BaseFolder, _Node);
function BaseFolder(name, parent) {
_classCallCheck(this, BaseFolder);
var _this = _possibleConstructorReturn(this, (BaseFolder.__proto__ || Object.getPrototypeOf(BaseFolder)).call(this, name, parent));
_this.children = Object.create(null);
return _this;
}
_createClass(BaseFolder, [{
key: 'getChild',
value: function getChild(name) {
return this.children[name];
}
}, {
key: 'addChildModule',
value: function addChildModule(module) {
var name = module.name;
var currentChild = this.children[name];
// For some reason we already have this node in children and it's a folder.
if (currentChild && currentChild instanceof BaseFolder) return;
if (currentChild) {
// We already have this node in children and it's a module.
// Merging it's data.
currentChild.mergeData(module.data);
} else {
// Pushing new module
module.parent = this;
this.children[name] = module;
}
delete this._size;
delete this._src;
}
}, {
key: 'addChildFolder',
value: function addChildFolder(folder) {
folder.parent = this;
this.children[folder.name] = folder;
delete this._size;
delete this._src;
return folder;
}
}, {
key: 'walk',
value: function walk(walker) {
var state = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
var deep = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true;
var stopped = false;
_lodash2.default.each(this.children, function (child) {
if (deep && child.walk) {
state = child.walk(walker, state, stop);
} else {
state = walker(child, state, stop);
}
if (stopped) return false;
});
return state;
function stop(finalState) {
stopped = true;
return finalState;
}
}
}, {
key: 'toChartData',
value: function toChartData() {
return {
label: this.name,
path: this.path,
statSize: this.size,
groups: _lodash2.default.invokeMap(this.children, 'toChartData')
};
}
}, {
key: 'src',
get: function get() {
if (!_lodash2.default.has(this, '_src')) {
this._src = this.walk(function (node, src) {
return src += node.src || '';
}, '', false);
}
return this._src;
}
}, {
key: 'size',
get: function get() {
if (!_lodash2.default.has(this, '_size')) {
this._size = this.walk(function (node, size) {
return size + node.size;
}, 0, false);
}
return this._size;
}
}]);
return BaseFolder;
}(_Node3.default);
exports.default = BaseFolder;
;
+122
View File
@@ -0,0 +1,122 @@
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
var _get = function get(object, property, receiver) { if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { return get(parent, property, receiver); } } else if ("value" in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } };
var _lodash = require('lodash');
var _lodash2 = _interopRequireDefault(_lodash);
var _Module2 = require('./Module');
var _Module3 = _interopRequireDefault(_Module2);
var _ContentModule = require('./ContentModule');
var _ContentModule2 = _interopRequireDefault(_ContentModule);
var _ContentFolder = require('./ContentFolder');
var _ContentFolder2 = _interopRequireDefault(_ContentFolder);
var _utils = require('./utils');
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
var ConcatenatedModule = function (_Module) {
_inherits(ConcatenatedModule, _Module);
function ConcatenatedModule(name, data, parent) {
_classCallCheck(this, ConcatenatedModule);
var _this = _possibleConstructorReturn(this, (ConcatenatedModule.__proto__ || Object.getPrototypeOf(ConcatenatedModule)).call(this, name, data, parent));
_this.name += ' (concatenated)';
_this.children = Object.create(null);
_this.fillContentModules();
return _this;
}
_createClass(ConcatenatedModule, [{
key: 'fillContentModules',
value: function fillContentModules() {
var _this2 = this;
_lodash2.default.each(this.data.modules, function (moduleData) {
return _this2.addContentModule(moduleData);
});
}
}, {
key: 'addContentModule',
value: function addContentModule(moduleData) {
var _this3 = this;
var pathParts = (0, _utils.getModulePathParts)(moduleData);
if (!pathParts) {
return;
}
var _ref = [pathParts.slice(0, -1), _lodash2.default.last(pathParts)],
folders = _ref[0],
fileName = _ref[1];
var currentFolder = this;
_lodash2.default.each(folders, function (folderName) {
var childFolder = currentFolder.getChild(folderName);
if (!childFolder) {
childFolder = currentFolder.addChildFolder(new _ContentFolder2.default(folderName, _this3));
}
currentFolder = childFolder;
});
var module = new _ContentModule2.default(fileName, moduleData, this);
currentFolder.addChildModule(module);
}
}, {
key: 'getChild',
value: function getChild(name) {
return this.children[name];
}
}, {
key: 'addChildModule',
value: function addChildModule(module) {
module.parent = this;
this.children[module.name] = module;
}
}, {
key: 'addChildFolder',
value: function addChildFolder(folder) {
folder.parent = this;
this.children[folder.name] = folder;
return folder;
}
}, {
key: 'toChartData',
value: function toChartData() {
return Object.assign({}, _get(ConcatenatedModule.prototype.__proto__ || Object.getPrototypeOf(ConcatenatedModule.prototype), 'toChartData', this).call(this), {
concatenated: true,
groups: _lodash2.default.invokeMap(this.children, 'toChartData')
});
}
}]);
return ConcatenatedModule;
}(_Module3.default);
exports.default = ConcatenatedModule;
;
+69
View File
@@ -0,0 +1,69 @@
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
var _get = function get(object, property, receiver) { if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { return get(parent, property, receiver); } } else if ("value" in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } };
var _BaseFolder2 = require('./BaseFolder');
var _BaseFolder3 = _interopRequireDefault(_BaseFolder2);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
var ContentFolder = function (_BaseFolder) {
_inherits(ContentFolder, _BaseFolder);
function ContentFolder(name, ownerModule, parent) {
_classCallCheck(this, ContentFolder);
var _this = _possibleConstructorReturn(this, (ContentFolder.__proto__ || Object.getPrototypeOf(ContentFolder)).call(this, name, parent));
_this.ownerModule = ownerModule;
return _this;
}
_createClass(ContentFolder, [{
key: 'getSize',
value: function getSize(sizeType) {
var ownerModuleSize = this.ownerModule[sizeType];
if (ownerModuleSize !== undefined) {
return Math.floor(this.size / this.ownerModule.size * ownerModuleSize);
}
}
}, {
key: 'toChartData',
value: function toChartData() {
return Object.assign({}, _get(ContentFolder.prototype.__proto__ || Object.getPrototypeOf(ContentFolder.prototype), 'toChartData', this).call(this), {
parsedSize: this.parsedSize,
gzipSize: this.gzipSize,
inaccurateSizes: true
});
}
}, {
key: 'parsedSize',
get: function get() {
return this.getSize('parsedSize');
}
}, {
key: 'gzipSize',
get: function get() {
return this.getSize('gzipSize');
}
}]);
return ContentFolder;
}(_BaseFolder3.default);
exports.default = ContentFolder;
;
+67
View File
@@ -0,0 +1,67 @@
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
var _get = function get(object, property, receiver) { if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { return get(parent, property, receiver); } } else if ("value" in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } };
var _Module2 = require('./Module');
var _Module3 = _interopRequireDefault(_Module2);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
var ContentModule = function (_Module) {
_inherits(ContentModule, _Module);
function ContentModule(name, data, ownerModule, parent) {
_classCallCheck(this, ContentModule);
var _this = _possibleConstructorReturn(this, (ContentModule.__proto__ || Object.getPrototypeOf(ContentModule)).call(this, name, data, parent));
_this.ownerModule = ownerModule;
return _this;
}
_createClass(ContentModule, [{
key: 'getSize',
value: function getSize(sizeType) {
var ownerModuleSize = this.ownerModule[sizeType];
if (ownerModuleSize !== undefined) {
return Math.floor(this.size / this.ownerModule.size * ownerModuleSize);
}
}
}, {
key: 'toChartData',
value: function toChartData() {
return Object.assign({}, _get(ContentModule.prototype.__proto__ || Object.getPrototypeOf(ContentModule.prototype), 'toChartData', this).call(this), {
inaccurateSizes: true
});
}
}, {
key: 'parsedSize',
get: function get() {
return this.getSize('parsedSize');
}
}, {
key: 'gzipSize',
get: function get() {
return this.getSize('gzipSize');
}
}]);
return ContentModule;
}(_Module3.default);
exports.default = ContentModule;
;
+114
View File
@@ -0,0 +1,114 @@
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
var _get = function get(object, property, receiver) { if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { return get(parent, property, receiver); } } else if ("value" in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } };
var _lodash = require('lodash');
var _lodash2 = _interopRequireDefault(_lodash);
var _gzipSize = require('gzip-size');
var _gzipSize2 = _interopRequireDefault(_gzipSize);
var _Module = require('./Module');
var _Module2 = _interopRequireDefault(_Module);
var _BaseFolder2 = require('./BaseFolder');
var _BaseFolder3 = _interopRequireDefault(_BaseFolder2);
var _ConcatenatedModule = require('./ConcatenatedModule');
var _ConcatenatedModule2 = _interopRequireDefault(_ConcatenatedModule);
var _utils = require('./utils');
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
var Folder = function (_BaseFolder) {
_inherits(Folder, _BaseFolder);
function Folder() {
_classCallCheck(this, Folder);
return _possibleConstructorReturn(this, (Folder.__proto__ || Object.getPrototypeOf(Folder)).apply(this, arguments));
}
_createClass(Folder, [{
key: 'addModule',
value: function addModule(moduleData) {
var pathParts = (0, _utils.getModulePathParts)(moduleData);
if (!pathParts) {
return;
}
var _ref = [pathParts.slice(0, -1), _lodash2.default.last(pathParts)],
folders = _ref[0],
fileName = _ref[1];
var currentFolder = this;
_lodash2.default.each(folders, function (folderName) {
var childNode = currentFolder.getChild(folderName);
if (
// Folder is not created yet
!childNode ||
// In some situations (invalid usage of dynamic `require()`) webpack generates a module with empty require
// context, but it's moduleId points to a directory in filesystem.
// In this case we replace this `File` node with `Folder`.
// See `test/stats/with-invalid-dynamic-require.json` as an example.
!(childNode instanceof Folder)) {
childNode = currentFolder.addChildFolder(new Folder(folderName));
}
currentFolder = childNode;
});
var ModuleConstructor = moduleData.modules ? _ConcatenatedModule2.default : _Module2.default;
var module = new ModuleConstructor(fileName, moduleData, this);
currentFolder.addChildModule(module);
}
}, {
key: 'toChartData',
value: function toChartData() {
return Object.assign({}, _get(Folder.prototype.__proto__ || Object.getPrototypeOf(Folder.prototype), 'toChartData', this).call(this), {
parsedSize: this.parsedSize,
gzipSize: this.gzipSize
});
}
}, {
key: 'parsedSize',
get: function get() {
return this.src ? this.src.length : 0;
}
}, {
key: 'gzipSize',
get: function get() {
if (!_lodash2.default.has(this, '_gzipSize')) {
this._gzipSize = this.src ? _gzipSize2.default.sync(this.src) : 0;
}
return this._gzipSize;
}
}]);
return Folder;
}(_BaseFolder3.default);
exports.default = Folder;
;
+101
View File
@@ -0,0 +1,101 @@
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
var _lodash = require('lodash');
var _lodash2 = _interopRequireDefault(_lodash);
var _gzipSize = require('gzip-size');
var _gzipSize2 = _interopRequireDefault(_gzipSize);
var _Node2 = require('./Node');
var _Node3 = _interopRequireDefault(_Node2);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
var Module = function (_Node) {
_inherits(Module, _Node);
function Module(name, data, parent) {
_classCallCheck(this, Module);
var _this = _possibleConstructorReturn(this, (Module.__proto__ || Object.getPrototypeOf(Module)).call(this, name, parent));
_this.data = data;
return _this;
}
_createClass(Module, [{
key: 'mergeData',
value: function mergeData(data) {
if (data.size) {
this.size += data.size;
}
if (data.parsedSrc) {
this.src = (this.src || '') + data.parsedSrc;
}
}
}, {
key: 'toChartData',
value: function toChartData() {
return {
id: this.data.id,
label: this.name,
path: this.path,
statSize: this.size,
parsedSize: this.parsedSize,
gzipSize: this.gzipSize
};
}
}, {
key: 'src',
get: function get() {
return this.data.parsedSrc;
},
set: function set(value) {
this.data.parsedSrc = value;
delete this._gzipSize;
}
}, {
key: 'size',
get: function get() {
return this.data.size;
},
set: function set(value) {
this.data.size = value;
}
}, {
key: 'parsedSize',
get: function get() {
return this.src ? this.src.length : undefined;
}
}, {
key: 'gzipSize',
get: function get() {
if (!_lodash2.default.has(this, '_gzipSize')) {
this._gzipSize = this.src ? _gzipSize2.default.sync(this.src) : undefined;
}
return this._gzipSize;
}
}]);
return Module;
}(_Node3.default);
exports.default = Module;
;
+38
View File
@@ -0,0 +1,38 @@
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
var Node = function () {
function Node(name, parent) {
_classCallCheck(this, Node);
this.name = name;
this.parent = parent;
}
_createClass(Node, [{
key: 'path',
get: function get() {
var path = [];
var node = this;
while (node) {
path.push(node.name);
node = node.parent;
}
return path.reverse().join('/');
}
}]);
return Node;
}();
exports.default = Node;
;
+34
View File
@@ -0,0 +1,34 @@
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.getModulePathParts = getModulePathParts;
var _lodash = require('lodash');
var _lodash2 = _interopRequireDefault(_lodash);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var MULTI_MODULE_REGEXP = /^multi /;
function getModulePathParts(moduleData) {
if (MULTI_MODULE_REGEXP.test(moduleData.identifier)) {
return [moduleData.identifier];
}
var parsedPath = _lodash2.default
// Removing loaders from module path: they're joined by `!` and the last part is a raw module path
.last(moduleData.name.split('!'))
// Splitting module path into parts
.split('/')
// Removing first `.`
.slice(1)
// Replacing `~` with `node_modules`
.map(function (part) {
return part === '~' ? 'node_modules' : part;
});
return parsedPath.length ? parsedPath : null;
}
+40
View File
@@ -0,0 +1,40 @@
'use strict';
var _require = require('util'),
inspect = _require.inspect;
var _ = require('lodash');
exports.createAssetsFilter = createAssetsFilter;
function createAssetsFilter(excludePatterns) {
var excludeFunctions = _(excludePatterns).castArray().compact().map(function (pattern) {
if (typeof pattern === 'string') {
pattern = new RegExp(pattern);
}
if (_.isRegExp(pattern)) {
return function (asset) {
return pattern.test(asset);
};
}
if (!_.isFunction(pattern)) {
throw new TypeError(`Pattern should be either string, RegExp or a function, but "${inspect(pattern, { depth: 0 })}" got.`);
}
return pattern;
}).value();
if (excludeFunctions.length) {
return function (asset) {
return _.every(excludeFunctions, function (fn) {
return fn(asset) !== true;
});
};
} else {
return function () {
return true;
};
}
}
+197
View File
@@ -0,0 +1,197 @@
'use strict';
var startServer = function () {
var _ref = _asyncToGenerator(function* (bundleStats, opts) {
var _ref2 = opts || {},
_ref2$port = _ref2.port,
port = _ref2$port === undefined ? 8888 : _ref2$port,
_ref2$host = _ref2.host,
host = _ref2$host === undefined ? '127.0.0.1' : _ref2$host,
_ref2$openBrowser = _ref2.openBrowser,
openBrowser = _ref2$openBrowser === undefined ? true : _ref2$openBrowser,
_ref2$bundleDir = _ref2.bundleDir,
bundleDir = _ref2$bundleDir === undefined ? null : _ref2$bundleDir,
_ref2$logger = _ref2.logger,
logger = _ref2$logger === undefined ? new Logger() : _ref2$logger,
_ref2$defaultSizes = _ref2.defaultSizes,
defaultSizes = _ref2$defaultSizes === undefined ? 'parsed' : _ref2$defaultSizes,
_ref2$excludeAssets = _ref2.excludeAssets,
excludeAssets = _ref2$excludeAssets === undefined ? null : _ref2$excludeAssets;
var analyzerOpts = { logger, excludeAssets };
var chartData = getChartData(analyzerOpts, bundleStats, bundleDir);
if (!chartData) return;
var app = express();
// Explicitly using our `ejs` dependency to render templates
// Fixes #17
app.engine('ejs', require('ejs').renderFile);
app.set('view engine', 'ejs');
app.set('views', `${projectRoot}/views`);
app.use(express.static(`${projectRoot}/public`));
app.use('/', function (req, res) {
res.render('viewer', {
mode: 'server',
get chartData() {
return JSON.stringify(chartData);
},
defaultSizes: JSON.stringify(defaultSizes)
});
});
var server = http.createServer(app);
yield new Promise(function (resolve) {
server.listen(port, host, function () {
resolve();
var url = `http://${host}:${server.address().port}`;
logger.info(`${bold('Webpack Bundle Analyzer')} is started at ${bold(url)}\n` + `Use ${bold('Ctrl+C')} to close it`);
if (openBrowser) {
opener(url);
}
});
});
var wss = new WebSocket.Server({ server });
wss.on('connection', function (ws) {
ws.on('error', function (err) {
// Ignore network errors like `ECONNRESET`, `EPIPE`, etc.
if (err.errno) return;
logger.info(err.message);
});
});
return {
ws: wss,
http: server,
updateChartData
};
function updateChartData(bundleStats) {
var newChartData = getChartData(analyzerOpts, bundleStats, bundleDir);
if (!newChartData) return;
chartData = newChartData;
wss.clients.forEach(function (client) {
if (client.readyState === WebSocket.OPEN) {
client.send(JSON.stringify({
event: 'chartDataUpdated',
data: newChartData
}));
}
});
}
});
return function startServer(_x, _x2) {
return _ref.apply(this, arguments);
};
}();
function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; }
var path = require('path');
var fs = require('fs');
var http = require('http');
var WebSocket = require('ws');
var _ = require('lodash');
var express = require('express');
var ejs = require('ejs');
var opener = require('opener');
var mkdir = require('mkdirp');
var _require = require('chalk'),
bold = _require.bold;
var Logger = require('./Logger');
var analyzer = require('./analyzer');
var projectRoot = path.resolve(__dirname, '..');
module.exports = {
startServer,
generateReport,
// deprecated
start: startServer
};
function generateReport(bundleStats, opts) {
var _ref3 = opts || {},
_ref3$openBrowser = _ref3.openBrowser,
openBrowser = _ref3$openBrowser === undefined ? true : _ref3$openBrowser,
_ref3$reportFilename = _ref3.reportFilename,
reportFilename = _ref3$reportFilename === undefined ? 'report.html' : _ref3$reportFilename,
_ref3$bundleDir = _ref3.bundleDir,
bundleDir = _ref3$bundleDir === undefined ? null : _ref3$bundleDir,
_ref3$logger = _ref3.logger,
logger = _ref3$logger === undefined ? new Logger() : _ref3$logger,
_ref3$defaultSizes = _ref3.defaultSizes,
defaultSizes = _ref3$defaultSizes === undefined ? 'parsed' : _ref3$defaultSizes,
_ref3$excludeAssets = _ref3.excludeAssets,
excludeAssets = _ref3$excludeAssets === undefined ? null : _ref3$excludeAssets;
var chartData = getChartData({ logger, excludeAssets }, bundleStats, bundleDir);
if (!chartData) return;
ejs.renderFile(`${projectRoot}/views/viewer.ejs`, {
mode: 'static',
chartData: JSON.stringify(chartData),
assetContent: getAssetContent,
defaultSizes: JSON.stringify(defaultSizes)
}, function (err, reportHtml) {
if (err) return logger.error(err);
var reportFilepath = path.resolve(bundleDir || process.cwd(), reportFilename);
mkdir.sync(path.dirname(reportFilepath));
fs.writeFileSync(reportFilepath, reportHtml);
logger.info(`${bold('Webpack Bundle Analyzer')} saved report to ${bold(reportFilepath)}`);
if (openBrowser) {
opener(`file://${reportFilepath}`);
}
});
}
function getAssetContent(filename) {
return fs.readFileSync(`${projectRoot}/public/${filename}`, 'utf8');
}
function getChartData(analyzerOpts) {
var chartData = void 0;
var logger = analyzerOpts.logger;
try {
for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
args[_key - 1] = arguments[_key];
}
chartData = analyzer.getViewerData.apply(analyzer, args.concat([analyzerOpts]));
} catch (err) {
logger.error(`Could't analyze webpack bundle:\n${err}`);
logger.debug(err.stack);
chartData = null;
}
if (_.isPlainObject(chartData) && _.isEmpty(chartData)) {
logger.error("Could't find any javascript bundles in provided stats file");
chartData = null;
}
return chartData;
}
@@ -0,0 +1,419 @@
2.20.3 / 2019-10-11
==================
* Support Node.js 0.10 (Revert #1059)
* Ran "npm unpublish commander@2.20.2". There is no 2.20.2.
2.20.1 / 2019-09-29
==================
* Improve executable subcommand tracking
* Update dev dependencies
2.20.0 / 2019-04-02
==================
* fix: resolve symbolic links completely when hunting for subcommands (#935)
* Update index.d.ts (#930)
* Update Readme.md (#924)
* Remove --save option as it isn't required anymore (#918)
* Add link to the license file (#900)
* Added example of receiving args from options (#858)
* Added missing semicolon (#882)
* Add extension to .eslintrc (#876)
2.19.0 / 2018-10-02
==================
* Removed newline after Options and Commands headers (#864)
* Bugfix - Error output (#862)
* Fix to change default value to string (#856)
2.18.0 / 2018-09-07
==================
* Standardize help output (#853)
* chmod 644 travis.yml (#851)
* add support for execute typescript subcommand via ts-node (#849)
2.17.1 / 2018-08-07
==================
* Fix bug in command emit (#844)
2.17.0 / 2018-08-03
==================
* fixed newline output after help information (#833)
* Fix to emit the action even without command (#778)
* npm update (#823)
2.16.0 / 2018-06-29
==================
* Remove Makefile and `test/run` (#821)
* Make 'npm test' run on Windows (#820)
* Add badge to display install size (#807)
* chore: cache node_modules (#814)
* chore: remove Node.js 4 (EOL), add Node.js 10 (#813)
* fixed typo in readme (#812)
* Fix types (#804)
* Update eslint to resolve vulnerabilities in lodash (#799)
* updated readme with custom event listeners. (#791)
* fix tests (#794)
2.15.0 / 2018-03-07
==================
* Update downloads badge to point to graph of downloads over time instead of duplicating link to npm
* Arguments description
2.14.1 / 2018-02-07
==================
* Fix typing of help function
2.14.0 / 2018-02-05
==================
* only register the option:version event once
* Fixes issue #727: Passing empty string for option on command is set to undefined
* enable eqeqeq rule
* resolves #754 add linter configuration to project
* resolves #560 respect custom name for version option
* document how to override the version flag
* document using options per command
2.13.0 / 2018-01-09
==================
* Do not print default for --no-
* remove trailing spaces in command help
* Update CI's Node.js to LTS and latest version
* typedefs: Command and Option types added to commander namespace
2.12.2 / 2017-11-28
==================
* fix: typings are not shipped
2.12.1 / 2017-11-23
==================
* Move @types/node to dev dependency
2.12.0 / 2017-11-22
==================
* add attributeName() method to Option objects
* Documentation updated for options with --no prefix
* typings: `outputHelp` takes a string as the first parameter
* typings: use overloads
* feat(typings): update to match js api
* Print default value in option help
* Fix translation error
* Fail when using same command and alias (#491)
* feat(typings): add help callback
* fix bug when description is add after command with options (#662)
* Format js code
* Rename History.md to CHANGELOG.md (#668)
* feat(typings): add typings to support TypeScript (#646)
* use current node
2.11.0 / 2017-07-03
==================
* Fix help section order and padding (#652)
* feature: support for signals to subcommands (#632)
* Fixed #37, --help should not display first (#447)
* Fix translation errors. (#570)
* Add package-lock.json
* Remove engines
* Upgrade package version
* Prefix events to prevent conflicts between commands and options (#494)
* Removing dependency on graceful-readlink
* Support setting name in #name function and make it chainable
* Add .vscode directory to .gitignore (Visual Studio Code metadata)
* Updated link to ruby commander in readme files
2.10.0 / 2017-06-19
==================
* Update .travis.yml. drop support for older node.js versions.
* Fix require arguments in README.md
* On SemVer you do not start from 0.0.1
* Add missing semi colon in readme
* Add save param to npm install
* node v6 travis test
* Update Readme_zh-CN.md
* Allow literal '--' to be passed-through as an argument
* Test subcommand alias help
* link build badge to master branch
* Support the alias of Git style sub-command
* added keyword commander for better search result on npm
* Fix Sub-Subcommands
* test node.js stable
* Fixes TypeError when a command has an option called `--description`
* Update README.md to make it beginner friendly and elaborate on the difference between angled and square brackets.
* Add chinese Readme file
2.9.0 / 2015-10-13
==================
* Add option `isDefault` to set default subcommand #415 @Qix-
* Add callback to allow filtering or post-processing of help text #434 @djulien
* Fix `undefined` text in help information close #414 #416 @zhiyelee
2.8.1 / 2015-04-22
==================
* Back out `support multiline description` Close #396 #397
2.8.0 / 2015-04-07
==================
* Add `process.execArg` support, execution args like `--harmony` will be passed to sub-commands #387 @DigitalIO @zhiyelee
* Fix bug in Git-style sub-commands #372 @zhiyelee
* Allow commands to be hidden from help #383 @tonylukasavage
* When git-style sub-commands are in use, yet none are called, display help #382 @claylo
* Add ability to specify arguments syntax for top-level command #258 @rrthomas
* Support multiline descriptions #208 @zxqfox
2.7.1 / 2015-03-11
==================
* Revert #347 (fix collisions when option and first arg have same name) which causes a bug in #367.
2.7.0 / 2015-03-09
==================
* Fix git-style bug when installed globally. Close #335 #349 @zhiyelee
* Fix collisions when option and first arg have same name. Close #346 #347 @tonylukasavage
* Add support for camelCase on `opts()`. Close #353 @nkzawa
* Add node.js 0.12 and io.js to travis.yml
* Allow RegEx options. #337 @palanik
* Fixes exit code when sub-command failing. Close #260 #332 @pirelenito
* git-style `bin` files in $PATH make sense. Close #196 #327 @zhiyelee
2.6.0 / 2014-12-30
==================
* added `Command#allowUnknownOption` method. Close #138 #318 @doozr @zhiyelee
* Add application description to the help msg. Close #112 @dalssoft
2.5.1 / 2014-12-15
==================
* fixed two bugs incurred by variadic arguments. Close #291 @Quentin01 #302 @zhiyelee
2.5.0 / 2014-10-24
==================
* add support for variadic arguments. Closes #277 @whitlockjc
2.4.0 / 2014-10-17
==================
* fixed a bug on executing the coercion function of subcommands option. Closes #270
* added `Command.prototype.name` to retrieve command name. Closes #264 #266 @tonylukasavage
* added `Command.prototype.opts` to retrieve all the options as a simple object of key-value pairs. Closes #262 @tonylukasavage
* fixed a bug on subcommand name. Closes #248 @jonathandelgado
* fixed function normalize doesnt honor option terminator. Closes #216 @abbr
2.3.0 / 2014-07-16
==================
* add command alias'. Closes PR #210
* fix: Typos. Closes #99
* fix: Unused fs module. Closes #217
2.2.0 / 2014-03-29
==================
* add passing of previous option value
* fix: support subcommands on windows. Closes #142
* Now the defaultValue passed as the second argument of the coercion function.
2.1.0 / 2013-11-21
==================
* add: allow cflag style option params, unit test, fixes #174
2.0.0 / 2013-07-18
==================
* remove input methods (.prompt, .confirm, etc)
1.3.2 / 2013-07-18
==================
* add support for sub-commands to co-exist with the original command
1.3.1 / 2013-07-18
==================
* add quick .runningCommand hack so you can opt-out of other logic when running a sub command
1.3.0 / 2013-07-09
==================
* add EACCES error handling
* fix sub-command --help
1.2.0 / 2013-06-13
==================
* allow "-" hyphen as an option argument
* support for RegExp coercion
1.1.1 / 2012-11-20
==================
* add more sub-command padding
* fix .usage() when args are present. Closes #106
1.1.0 / 2012-11-16
==================
* add git-style executable subcommand support. Closes #94
1.0.5 / 2012-10-09
==================
* fix `--name` clobbering. Closes #92
* fix examples/help. Closes #89
1.0.4 / 2012-09-03
==================
* add `outputHelp()` method.
1.0.3 / 2012-08-30
==================
* remove invalid .version() defaulting
1.0.2 / 2012-08-24
==================
* add `--foo=bar` support [arv]
* fix password on node 0.8.8. Make backward compatible with 0.6 [focusaurus]
1.0.1 / 2012-08-03
==================
* fix issue #56
* fix tty.setRawMode(mode) was moved to tty.ReadStream#setRawMode() (i.e. process.stdin.setRawMode())
1.0.0 / 2012-07-05
==================
* add support for optional option descriptions
* add defaulting of `.version()` to package.json's version
0.6.1 / 2012-06-01
==================
* Added: append (yes or no) on confirmation
* Added: allow node.js v0.7.x
0.6.0 / 2012-04-10
==================
* Added `.prompt(obj, callback)` support. Closes #49
* Added default support to .choose(). Closes #41
* Fixed the choice example
0.5.1 / 2011-12-20
==================
* Fixed `password()` for recent nodes. Closes #36
0.5.0 / 2011-12-04
==================
* Added sub-command option support [itay]
0.4.3 / 2011-12-04
==================
* Fixed custom help ordering. Closes #32
0.4.2 / 2011-11-24
==================
* Added travis support
* Fixed: line-buffered input automatically trimmed. Closes #31
0.4.1 / 2011-11-18
==================
* Removed listening for "close" on --help
0.4.0 / 2011-11-15
==================
* Added support for `--`. Closes #24
0.3.3 / 2011-11-14
==================
* Fixed: wait for close event when writing help info [Jerry Hamlet]
0.3.2 / 2011-11-01
==================
* Fixed long flag definitions with values [felixge]
0.3.1 / 2011-10-31
==================
* Changed `--version` short flag to `-V` from `-v`
* Changed `.version()` so it's configurable [felixge]
0.3.0 / 2011-10-31
==================
* Added support for long flags only. Closes #18
0.2.1 / 2011-10-24
==================
* "node": ">= 0.4.x < 0.7.0". Closes #20
0.2.0 / 2011-09-26
==================
* Allow for defaults that are not just boolean. Default peassignment only occurs for --no-*, optional, and required arguments. [Jim Isaacs]
0.1.0 / 2011-08-24
==================
* Added support for custom `--help` output
0.0.5 / 2011-08-18
==================
* Changed: when the user enters nothing prompt for password again
* Fixed issue with passwords beginning with numbers [NuckChorris]
0.0.4 / 2011-08-15
==================
* Fixed `Commander#args`
0.0.3 / 2011-08-15
==================
* Added default option value support
0.0.2 / 2011-08-15
==================
* Added mask support to `Command#password(str[, mask], fn)`
* Added `Command#password(str, fn)`
0.0.1 / 2010-01-03
==================
* Initial release
+22
View File
@@ -0,0 +1,22 @@
(The MIT License)
Copyright (c) 2011 TJ Holowaychuk <tj@vision-media.ca>
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:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
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.
+428
View File
@@ -0,0 +1,428 @@
# Commander.js
[![Build Status](https://api.travis-ci.org/tj/commander.js.svg?branch=master)](http://travis-ci.org/tj/commander.js)
[![NPM Version](http://img.shields.io/npm/v/commander.svg?style=flat)](https://www.npmjs.org/package/commander)
[![NPM Downloads](https://img.shields.io/npm/dm/commander.svg?style=flat)](https://npmcharts.com/compare/commander?minimal=true)
[![Install Size](https://packagephobia.now.sh/badge?p=commander)](https://packagephobia.now.sh/result?p=commander)
[![Join the chat at https://gitter.im/tj/commander.js](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/tj/commander.js?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
The complete solution for [node.js](http://nodejs.org) command-line interfaces, inspired by Ruby's [commander](https://github.com/commander-rb/commander).
[API documentation](http://tj.github.com/commander.js/)
## Installation
$ npm install commander
## Option parsing
Options with commander are defined with the `.option()` method, also serving as documentation for the options. The example below parses args and options from `process.argv`, leaving remaining args as the `program.args` array which were not consumed by options.
```js
#!/usr/bin/env node
/**
* Module dependencies.
*/
var program = require('commander');
program
.version('0.1.0')
.option('-p, --peppers', 'Add peppers')
.option('-P, --pineapple', 'Add pineapple')
.option('-b, --bbq-sauce', 'Add bbq sauce')
.option('-c, --cheese [type]', 'Add the specified type of cheese [marble]', 'marble')
.parse(process.argv);
console.log('you ordered a pizza with:');
if (program.peppers) console.log(' - peppers');
if (program.pineapple) console.log(' - pineapple');
if (program.bbqSauce) console.log(' - bbq');
console.log(' - %s cheese', program.cheese);
```
Short flags may be passed as a single arg, for example `-abc` is equivalent to `-a -b -c`. Multi-word options such as "--template-engine" are camel-cased, becoming `program.templateEngine` etc.
Note that multi-word options starting with `--no` prefix negate the boolean value of the following word. For example, `--no-sauce` sets the value of `program.sauce` to false.
```js
#!/usr/bin/env node
/**
* Module dependencies.
*/
var program = require('commander');
program
.option('--no-sauce', 'Remove sauce')
.parse(process.argv);
console.log('you ordered a pizza');
if (program.sauce) console.log(' with sauce');
else console.log(' without sauce');
```
To get string arguments from options you will need to use angle brackets <> for required inputs or square brackets [] for optional inputs.
e.g. ```.option('-m --myarg [myVar]', 'my super cool description')```
Then to access the input if it was passed in.
e.g. ```var myInput = program.myarg```
**NOTE**: If you pass a argument without using brackets the example above will return true and not the value passed in.
## Version option
Calling the `version` implicitly adds the `-V` and `--version` options to the command.
When either of these options is present, the command prints the version number and exits.
$ ./examples/pizza -V
0.0.1
If you want your program to respond to the `-v` option instead of the `-V` option, simply pass custom flags to the `version` method using the same syntax as the `option` method.
```js
program
.version('0.0.1', '-v, --version')
```
The version flags can be named anything, but the long option is required.
## Command-specific options
You can attach options to a command.
```js
#!/usr/bin/env node
var program = require('commander');
program
.command('rm <dir>')
.option('-r, --recursive', 'Remove recursively')
.action(function (dir, cmd) {
console.log('remove ' + dir + (cmd.recursive ? ' recursively' : ''))
})
program.parse(process.argv)
```
A command's options are validated when the command is used. Any unknown options will be reported as an error. However, if an action-based command does not define an action, then the options are not validated.
## Coercion
```js
function range(val) {
return val.split('..').map(Number);
}
function list(val) {
return val.split(',');
}
function collect(val, memo) {
memo.push(val);
return memo;
}
function increaseVerbosity(v, total) {
return total + 1;
}
program
.version('0.1.0')
.usage('[options] <file ...>')
.option('-i, --integer <n>', 'An integer argument', parseInt)
.option('-f, --float <n>', 'A float argument', parseFloat)
.option('-r, --range <a>..<b>', 'A range', range)
.option('-l, --list <items>', 'A list', list)
.option('-o, --optional [value]', 'An optional value')
.option('-c, --collect [value]', 'A repeatable value', collect, [])
.option('-v, --verbose', 'A value that can be increased', increaseVerbosity, 0)
.parse(process.argv);
console.log(' int: %j', program.integer);
console.log(' float: %j', program.float);
console.log(' optional: %j', program.optional);
program.range = program.range || [];
console.log(' range: %j..%j', program.range[0], program.range[1]);
console.log(' list: %j', program.list);
console.log(' collect: %j', program.collect);
console.log(' verbosity: %j', program.verbose);
console.log(' args: %j', program.args);
```
## Regular Expression
```js
program
.version('0.1.0')
.option('-s --size <size>', 'Pizza size', /^(large|medium|small)$/i, 'medium')
.option('-d --drink [drink]', 'Drink', /^(coke|pepsi|izze)$/i)
.parse(process.argv);
console.log(' size: %j', program.size);
console.log(' drink: %j', program.drink);
```
## Variadic arguments
The last argument of a command can be variadic, and only the last argument. To make an argument variadic you have to
append `...` to the argument name. Here is an example:
```js
#!/usr/bin/env node
/**
* Module dependencies.
*/
var program = require('commander');
program
.version('0.1.0')
.command('rmdir <dir> [otherDirs...]')
.action(function (dir, otherDirs) {
console.log('rmdir %s', dir);
if (otherDirs) {
otherDirs.forEach(function (oDir) {
console.log('rmdir %s', oDir);
});
}
});
program.parse(process.argv);
```
An `Array` is used for the value of a variadic argument. This applies to `program.args` as well as the argument passed
to your action as demonstrated above.
## Specify the argument syntax
```js
#!/usr/bin/env node
var program = require('commander');
program
.version('0.1.0')
.arguments('<cmd> [env]')
.action(function (cmd, env) {
cmdValue = cmd;
envValue = env;
});
program.parse(process.argv);
if (typeof cmdValue === 'undefined') {
console.error('no command given!');
process.exit(1);
}
console.log('command:', cmdValue);
console.log('environment:', envValue || "no environment given");
```
Angled brackets (e.g. `<cmd>`) indicate required input. Square brackets (e.g. `[env]`) indicate optional input.
## Git-style sub-commands
```js
// file: ./examples/pm
var program = require('commander');
program
.version('0.1.0')
.command('install [name]', 'install one or more packages')
.command('search [query]', 'search with optional query')
.command('list', 'list packages installed', {isDefault: true})
.parse(process.argv);
```
When `.command()` is invoked with a description argument, no `.action(callback)` should be called to handle sub-commands, otherwise there will be an error. This tells commander that you're going to use separate executables for sub-commands, much like `git(1)` and other popular tools.
The commander will try to search the executables in the directory of the entry script (like `./examples/pm`) with the name `program-command`, like `pm-install`, `pm-search`.
Options can be passed with the call to `.command()`. Specifying `true` for `opts.noHelp` will remove the subcommand from the generated help output. Specifying `true` for `opts.isDefault` will run the subcommand if no other subcommand is specified.
If the program is designed to be installed globally, make sure the executables have proper modes, like `755`.
### `--harmony`
You can enable `--harmony` option in two ways:
* Use `#! /usr/bin/env node --harmony` in the sub-commands scripts. Note some os version dont support this pattern.
* Use the `--harmony` option when call the command, like `node --harmony examples/pm publish`. The `--harmony` option will be preserved when spawning sub-command process.
## Automated --help
The help information is auto-generated based on the information commander already knows about your program, so the following `--help` info is for free:
```
$ ./examples/pizza --help
Usage: pizza [options]
An application for pizzas ordering
Options:
-h, --help output usage information
-V, --version output the version number
-p, --peppers Add peppers
-P, --pineapple Add pineapple
-b, --bbq Add bbq sauce
-c, --cheese <type> Add the specified type of cheese [marble]
-C, --no-cheese You do not want any cheese
```
## Custom help
You can display arbitrary `-h, --help` information
by listening for "--help". Commander will automatically
exit once you are done so that the remainder of your program
does not execute causing undesired behaviors, for example
in the following executable "stuff" will not output when
`--help` is used.
```js
#!/usr/bin/env node
/**
* Module dependencies.
*/
var program = require('commander');
program
.version('0.1.0')
.option('-f, --foo', 'enable some foo')
.option('-b, --bar', 'enable some bar')
.option('-B, --baz', 'enable some baz');
// must be before .parse() since
// node's emit() is immediate
program.on('--help', function(){
console.log('')
console.log('Examples:');
console.log(' $ custom-help --help');
console.log(' $ custom-help -h');
});
program.parse(process.argv);
console.log('stuff');
```
Yields the following help output when `node script-name.js -h` or `node script-name.js --help` are run:
```
Usage: custom-help [options]
Options:
-h, --help output usage information
-V, --version output the version number
-f, --foo enable some foo
-b, --bar enable some bar
-B, --baz enable some baz
Examples:
$ custom-help --help
$ custom-help -h
```
## .outputHelp(cb)
Output help information without exiting.
Optional callback cb allows post-processing of help text before it is displayed.
If you want to display help by default (e.g. if no command was provided), you can use something like:
```js
var program = require('commander');
var colors = require('colors');
program
.version('0.1.0')
.command('getstream [url]', 'get stream URL')
.parse(process.argv);
if (!process.argv.slice(2).length) {
program.outputHelp(make_red);
}
function make_red(txt) {
return colors.red(txt); //display the help text in red on the console
}
```
## .help(cb)
Output help information and exit immediately.
Optional callback cb allows post-processing of help text before it is displayed.
## Custom event listeners
You can execute custom actions by listening to command and option events.
```js
program.on('option:verbose', function () {
process.env.VERBOSE = this.verbose;
});
// error on unknown commands
program.on('command:*', function () {
console.error('Invalid command: %s\nSee --help for a list of available commands.', program.args.join(' '));
process.exit(1);
});
```
## Examples
```js
var program = require('commander');
program
.version('0.1.0')
.option('-C, --chdir <path>', 'change the working directory')
.option('-c, --config <path>', 'set config path. defaults to ./deploy.conf')
.option('-T, --no-tests', 'ignore test hook');
program
.command('setup [env]')
.description('run setup commands for all envs')
.option("-s, --setup_mode [mode]", "Which setup mode to use")
.action(function(env, options){
var mode = options.setup_mode || "normal";
env = env || 'all';
console.log('setup for %s env(s) with %s mode', env, mode);
});
program
.command('exec <cmd>')
.alias('ex')
.description('execute the given remote cmd')
.option("-e, --exec_mode <mode>", "Which exec mode to use")
.action(function(cmd, options){
console.log('exec "%s" using %s mode', cmd, options.exec_mode);
}).on('--help', function() {
console.log('');
console.log('Examples:');
console.log('');
console.log(' $ deploy exec sequential');
console.log(' $ deploy exec async');
});
program
.command('*')
.action(function(env){
console.log('deploying "%s"', env);
});
program.parse(process.argv);
```
More Demos can be found in the [examples](https://github.com/tj/commander.js/tree/master/examples) directory.
## License
[MIT](https://github.com/tj/commander.js/blob/master/LICENSE)
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,38 @@
{
"name": "commander",
"version": "2.20.3",
"description": "the complete solution for node.js command-line programs",
"keywords": [
"commander",
"command",
"option",
"parser"
],
"author": "TJ Holowaychuk <tj@vision-media.ca>",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/tj/commander.js.git"
},
"scripts": {
"lint": "eslint index.js",
"test": "node test/run.js && npm run test-typings",
"test-typings": "tsc -p tsconfig.json"
},
"main": "index",
"files": [
"index.js",
"typings/index.d.ts"
],
"dependencies": {},
"devDependencies": {
"@types/node": "^12.7.8",
"eslint": "^6.4.0",
"should": "^13.2.3",
"sinon": "^7.5.0",
"standard": "^14.3.1",
"ts-node": "^8.4.1",
"typescript": "^3.6.3"
},
"typings": "typings/index.d.ts"
}
@@ -0,0 +1,310 @@
// Type definitions for commander 2.11
// Project: https://github.com/visionmedia/commander.js
// Definitions by: Alan Agius <https://github.com/alan-agius4>, Marcelo Dezem <https://github.com/mdezem>, vvakame <https://github.com/vvakame>, Jules Randolph <https://github.com/sveinburne>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
declare namespace local {
class Option {
flags: string;
required: boolean;
optional: boolean;
bool: boolean;
short?: string;
long: string;
description: string;
/**
* Initialize a new `Option` with the given `flags` and `description`.
*
* @param {string} flags
* @param {string} [description]
*/
constructor(flags: string, description?: string);
}
class Command extends NodeJS.EventEmitter {
[key: string]: any;
args: string[];
/**
* Initialize a new `Command`.
*
* @param {string} [name]
*/
constructor(name?: string);
/**
* Set the program version to `str`.
*
* This method auto-registers the "-V, --version" flag
* which will print the version number when passed.
*
* @param {string} str
* @param {string} [flags]
* @returns {Command} for chaining
*/
version(str: string, flags?: string): Command;
/**
* Add command `name`.
*
* The `.action()` callback is invoked when the
* command `name` is specified via __ARGV__,
* and the remaining arguments are applied to the
* function for access.
*
* When the `name` is "*" an un-matched command
* will be passed as the first arg, followed by
* the rest of __ARGV__ remaining.
*
* @example
* program
* .version('0.0.1')
* .option('-C, --chdir <path>', 'change the working directory')
* .option('-c, --config <path>', 'set config path. defaults to ./deploy.conf')
* .option('-T, --no-tests', 'ignore test hook')
*
* program
* .command('setup')
* .description('run remote setup commands')
* .action(function() {
* console.log('setup');
* });
*
* program
* .command('exec <cmd>')
* .description('run the given remote command')
* .action(function(cmd) {
* console.log('exec "%s"', cmd);
* });
*
* program
* .command('teardown <dir> [otherDirs...]')
* .description('run teardown commands')
* .action(function(dir, otherDirs) {
* console.log('dir "%s"', dir);
* if (otherDirs) {
* otherDirs.forEach(function (oDir) {
* console.log('dir "%s"', oDir);
* });
* }
* });
*
* program
* .command('*')
* .description('deploy the given env')
* .action(function(env) {
* console.log('deploying "%s"', env);
* });
*
* program.parse(process.argv);
*
* @param {string} name
* @param {string} [desc] for git-style sub-commands
* @param {CommandOptions} [opts] command options
* @returns {Command} the new command
*/
command(name: string, desc?: string, opts?: commander.CommandOptions): Command;
/**
* Define argument syntax for the top-level command.
*
* @param {string} desc
* @returns {Command} for chaining
*/
arguments(desc: string): Command;
/**
* Parse expected `args`.
*
* For example `["[type]"]` becomes `[{ required: false, name: 'type' }]`.
*
* @param {string[]} args
* @returns {Command} for chaining
*/
parseExpectedArgs(args: string[]): Command;
/**
* Register callback `fn` for the command.
*
* @example
* program
* .command('help')
* .description('display verbose help')
* .action(function() {
* // output help here
* });
*
* @param {(...args: any[]) => void} fn
* @returns {Command} for chaining
*/
action(fn: (...args: any[]) => void): Command;
/**
* Define option with `flags`, `description` and optional
* coercion `fn`.
*
* The `flags` string should contain both the short and long flags,
* separated by comma, a pipe or space. The following are all valid
* all will output this way when `--help` is used.
*
* "-p, --pepper"
* "-p|--pepper"
* "-p --pepper"
*
* @example
* // simple boolean defaulting to false
* program.option('-p, --pepper', 'add pepper');
*
* --pepper
* program.pepper
* // => Boolean
*
* // simple boolean defaulting to true
* program.option('-C, --no-cheese', 'remove cheese');
*
* program.cheese
* // => true
*
* --no-cheese
* program.cheese
* // => false
*
* // required argument
* program.option('-C, --chdir <path>', 'change the working directory');
*
* --chdir /tmp
* program.chdir
* // => "/tmp"
*
* // optional argument
* program.option('-c, --cheese [type]', 'add cheese [marble]');
*
* @param {string} flags
* @param {string} [description]
* @param {((arg1: any, arg2: any) => void) | RegExp} [fn] function or default
* @param {*} [defaultValue]
* @returns {Command} for chaining
*/
option(flags: string, description?: string, fn?: ((arg1: any, arg2: any) => void) | RegExp, defaultValue?: any): Command;
option(flags: string, description?: string, defaultValue?: any): Command;
/**
* Allow unknown options on the command line.
*
* @param {boolean} [arg] if `true` or omitted, no error will be thrown for unknown options.
* @returns {Command} for chaining
*/
allowUnknownOption(arg?: boolean): Command;
/**
* Parse `argv`, settings options and invoking commands when defined.
*
* @param {string[]} argv
* @returns {Command} for chaining
*/
parse(argv: string[]): Command;
/**
* Parse options from `argv` returning `argv` void of these options.
*
* @param {string[]} argv
* @returns {ParseOptionsResult}
*/
parseOptions(argv: string[]): commander.ParseOptionsResult;
/**
* Return an object containing options as key-value pairs
*
* @returns {{[key: string]: any}}
*/
opts(): { [key: string]: any };
/**
* Set the description to `str`.
*
* @param {string} str
* @param {{[argName: string]: string}} argsDescription
* @return {(Command | string)}
*/
description(str: string, argsDescription?: {[argName: string]: string}): Command;
description(): string;
/**
* Set an alias for the command.
*
* @param {string} alias
* @return {(Command | string)}
*/
alias(alias: string): Command;
alias(): string;
/**
* Set or get the command usage.
*
* @param {string} str
* @return {(Command | string)}
*/
usage(str: string): Command;
usage(): string;
/**
* Set the name of the command.
*
* @param {string} str
* @return {Command}
*/
name(str: string): Command;
/**
* Get the name of the command.
*
* @return {string}
*/
name(): string;
/**
* Output help information for this command.
*
* @param {(str: string) => string} [cb]
*/
outputHelp(cb?: (str: string) => string): void;
/** Output help information and exit.
*
* @param {(str: string) => string} [cb]
*/
help(cb?: (str: string) => string): never;
}
}
declare namespace commander {
type Command = local.Command
type Option = local.Option
interface CommandOptions {
noHelp?: boolean;
isDefault?: boolean;
}
interface ParseOptionsResult {
args: string[];
unknown: string[];
}
interface CommanderStatic extends Command {
Command: typeof local.Command;
Option: typeof local.Option;
CommandOptions: CommandOptions;
ParseOptionsResult: ParseOptionsResult;
}
}
declare const commander: commander.CommanderStatic;
export = commander;
+90
View File
@@ -0,0 +1,90 @@
{
"name": "webpack-bundle-analyzer",
"version": "2.13.1",
"description": "Webpack plugin and CLI utility that represents bundle content as convenient interactive zoomable treemap",
"author": "Yury Grunin <grunin.ya@ya.ru>",
"license": "MIT",
"homepage": "https://github.com/webpack-contrib/webpack-bundle-analyzer",
"changelog": "https://github.com/webpack-contrib/webpack-bundle-analyzer/blob/master/CHANGELOG.md",
"bugs": {
"url": "https://github.com/webpack-contrib/webpack-bundle-analyzer/issues"
},
"repository": {
"type": "git",
"url": "git+https://github.com/webpack-contrib/webpack-bundle-analyzer.git"
},
"main": "lib/index.js",
"bin": "lib/bin/analyzer.js",
"engines": {
"node": ">= 4"
},
"scripts": {
"start": "gulp watch",
"build": "gulp build",
"npm-publish": "npm run lint && npm run build && npm test && npm publish",
"lint": "eslint --ext js,jsx .",
"test": "mocha --exit --require babel-core/register",
"test-dev": "mocha --watch --require babel-core/register"
},
"files": [
"public",
"lib",
"src",
"views"
],
"dependencies": {
"acorn": "^5.3.0",
"bfj-node4": "^5.2.0",
"chalk": "^2.3.0",
"commander": "^2.13.0",
"ejs": "^2.5.7",
"express": "^4.16.2",
"filesize": "^3.5.11",
"gzip-size": "^4.1.0",
"lodash": "^4.17.4",
"mkdirp": "^0.5.1",
"opener": "^1.4.3",
"ws": "^4.0.0"
},
"devDependencies": {
"babel-core": "6.26.0",
"babel-eslint": "8.2.1",
"babel-loader": "7.1.2",
"babel-plugin-transform-class-properties": "6.24.1",
"babel-plugin-transform-object-rest-spread": "6.26.0",
"babel-plugin-transform-react-jsx": "6.24.1",
"babel-preset-env": "1.6.1",
"chai": "4.1.2",
"chai-subset": "1.6.0",
"classnames": "2.2.5",
"css-loader": "0.28.9",
"del": "3.0.0",
"eslint": "4.16.0",
"eslint-config-th0r": "1.0.0",
"eslint-config-th0r-react": "1.0.0",
"eslint-plugin-react": "7.6.1",
"exports-loader": "0.6.4",
"gulp": "4.0.0",
"gulp-babel": "7.0.1",
"mocha": "5.0.0",
"nightmare": "2.10.0",
"preact": "8.2.7",
"sinon": "4.2.2",
"stream-combiner2": "1.1.1",
"style-loader": "0.20.1",
"webpack": "3.10.0",
"webpack-dev-server": "2.11.1"
},
"keywords": [
"webpack",
"bundle",
"analyzer",
"modules",
"size",
"interactive",
"chart",
"treemap",
"zoomable",
"zoom"
]
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+128
View File
@@ -0,0 +1,128 @@
const bfj = require('bfj-node4');
const path = require('path');
const mkdir = require('mkdirp');
const { bold } = require('chalk');
const Logger = require('./Logger');
const viewer = require('./viewer');
class BundleAnalyzerPlugin {
constructor(opts) {
this.opts = {
analyzerMode: 'server',
analyzerHost: '127.0.0.1',
analyzerPort: 8888,
reportFilename: 'report.html',
defaultSizes: 'parsed',
openAnalyzer: true,
generateStatsFile: false,
statsFilename: 'stats.json',
statsOptions: null,
excludeAssets: null,
logLevel: 'info',
// deprecated
startAnalyzer: true,
...opts
};
this.server = null;
this.logger = new Logger(this.opts.logLevel);
}
apply(compiler) {
this.compiler = compiler;
const done = stats => {
stats = stats.toJson(this.opts.statsOptions);
const actions = [];
if (this.opts.generateStatsFile) {
actions.push(() => this.generateStatsFile(stats));
}
// Handling deprecated `startAnalyzer` flag
if (this.opts.analyzerMode === 'server' && !this.opts.startAnalyzer) {
this.opts.analyzerMode = 'disabled';
}
if (this.opts.analyzerMode === 'server') {
actions.push(() => this.startAnalyzerServer(stats));
} else if (this.opts.analyzerMode === 'static') {
actions.push(() => this.generateStaticReport(stats));
}
if (actions.length) {
// Making analyzer logs to be after all webpack logs in the console
setImmediate(() => {
actions.forEach(action => action());
});
}
};
if (compiler.hooks) {
compiler.hooks.done.tap('webpack-bundle-analyzer', done);
} else {
compiler.plugin('done', done);
}
}
async generateStatsFile(stats) {
const statsFilepath = path.resolve(this.compiler.outputPath, this.opts.statsFilename);
mkdir.sync(path.dirname(statsFilepath));
try {
await bfj.write(statsFilepath, stats, {
space: 2,
promises: 'ignore',
buffers: 'ignore',
maps: 'ignore',
iterables: 'ignore',
circular: 'ignore'
});
this.logger.info(
`${bold('Webpack Bundle Analyzer')} saved stats file to ${bold(statsFilepath)}`
);
} catch (error) {
this.logger.error(
`${bold('Webpack Bundle Analyzer')} error saving stats file to ${bold(statsFilepath)}: ${error}`
);
}
}
async startAnalyzerServer(stats) {
if (this.server) {
(await this.server).updateChartData(stats);
} else {
this.server = viewer.startServer(stats, {
openBrowser: this.opts.openAnalyzer,
host: this.opts.analyzerHost,
port: this.opts.analyzerPort,
bundleDir: this.getBundleDirFromCompiler(),
logger: this.logger,
defaultSizes: this.opts.defaultSizes,
excludeAssets: this.opts.excludeAssets
});
}
}
generateStaticReport(stats) {
viewer.generateReport(stats, {
openBrowser: this.opts.openAnalyzer,
reportFilename: path.resolve(this.compiler.outputPath, this.opts.reportFilename),
bundleDir: this.getBundleDirFromCompiler(),
logger: this.logger,
defaultSizes: this.opts.defaultSizes,
excludeAssets: this.opts.excludeAssets
});
}
getBundleDirFromCompiler() {
return (this.compiler.outputFileSystem.constructor.name === 'MemoryFileSystem') ? null : this.compiler.outputPath;
}
}
module.exports = BundleAnalyzerPlugin;
+51
View File
@@ -0,0 +1,51 @@
const LEVELS = [
'debug',
'info',
'warn',
'error',
'silent'
];
const LEVEL_TO_CONSOLE_METHOD = new Map([
['debug', 'log'],
['info', 'log'],
['warn', 'log']
]);
class Logger {
static levels = LEVELS;
static defaultLevel = 'info';
constructor(level = Logger.defaultLevel) {
this.activeLevels = new Set();
this.setLogLevel(level);
}
setLogLevel(level) {
const levelIndex = LEVELS.indexOf(level);
if (levelIndex === -1) throw new Error(`Invalid log level "${level}". Use one of these: ${LEVELS.join(', ')}`);
this.activeLevels.clear();
for (const [i, level] of LEVELS.entries()) {
if (i >= levelIndex) this.activeLevels.add(level);
}
}
_log(level, ...args) {
console[LEVEL_TO_CONSOLE_METHOD.get(level) || level](...args);
}
};
LEVELS.forEach(level => {
if (level === 'silent') return;
Logger.prototype[level] = function (...args) {
if (this.activeLevels.has(level)) this._log(level, ...args);
};
});
module.exports = Logger;
+137
View File
@@ -0,0 +1,137 @@
const fs = require('fs');
const path = require('path');
const _ = require('lodash');
const gzipSize = require('gzip-size');
const Logger = require('./Logger');
const Folder = require('./tree/Folder').default;
const { parseBundle } = require('./parseUtils');
const { createAssetsFilter } = require('./utils');
const FILENAME_QUERY_REGEXP = /\?.*$/;
module.exports = {
getViewerData,
readStatsFromFile
};
function getViewerData(bundleStats, bundleDir, opts) {
const {
logger = new Logger(),
excludeAssets = null
} = opts || {};
const isAssetIncluded = createAssetsFilter(excludeAssets);
// Sometimes all the information is located in `children` array (e.g. problem in #10)
if (_.isEmpty(bundleStats.assets) && !_.isEmpty(bundleStats.children)) {
bundleStats = bundleStats.children[0];
}
// Picking only `*.js` assets from bundle that has non-empty `chunks` array
bundleStats.assets = _.filter(bundleStats.assets, asset => {
// Removing query part from filename (yes, somebody uses it for some reason and Webpack supports it)
// See #22
asset.name = asset.name.replace(FILENAME_QUERY_REGEXP, '');
return _.endsWith(asset.name, '.js') && !_.isEmpty(asset.chunks) && isAssetIncluded(asset.name);
});
// Trying to parse bundle assets and get real module sizes if `bundleDir` is provided
let bundlesSources = null;
let parsedModules = null;
if (bundleDir) {
bundlesSources = {};
parsedModules = {};
for (const statAsset of bundleStats.assets) {
const assetFile = path.join(bundleDir, statAsset.name);
let bundleInfo;
try {
bundleInfo = parseBundle(assetFile);
} catch (err) {
const msg = (err.code === 'ENOENT') ? 'no such file' : err.message;
logger.warn(`Error parsing bundle asset "${assetFile}": ${msg}`);
continue;
}
bundlesSources[statAsset.name] = bundleInfo.src;
_.assign(parsedModules, bundleInfo.modules);
}
if (_.isEmpty(bundlesSources)) {
bundlesSources = null;
parsedModules = null;
logger.warn('\nNo bundles were parsed. Analyzer will show only original module sizes from stats file.\n');
}
}
const modules = getBundleModules(bundleStats);
const assets = _.transform(bundleStats.assets, (result, statAsset) => {
const asset = result[statAsset.name] = _.pick(statAsset, 'size');
if (bundlesSources && _.has(bundlesSources, statAsset.name)) {
asset.parsedSize = bundlesSources[statAsset.name].length;
asset.gzipSize = gzipSize.sync(bundlesSources[statAsset.name]);
}
// Picking modules from current bundle script
asset.modules = _(modules)
.filter(statModule => assetHasModule(statAsset, statModule))
.each(statModule => {
if (parsedModules) {
statModule.parsedSrc = parsedModules[statModule.id];
}
});
asset.tree = createModulesTree(asset.modules);
}, {});
return _.transform(assets, (result, asset, filename) => {
result.push({
label: filename,
// Not using `asset.size` here provided by Webpack because it can be very confusing when `UglifyJsPlugin` is used.
// In this case all module sizes from stats file will represent unminified module sizes, but `asset.size` will
// be the size of minified bundle.
// Using `asset.size` only if current asset doesn't contain any modules (resulting size equals 0)
statSize: asset.tree.size || asset.size,
parsedSize: asset.parsedSize,
gzipSize: asset.gzipSize,
groups: _.invokeMap(asset.tree.children, 'toChartData')
});
}, []);
}
function readStatsFromFile(filename) {
return JSON.parse(
fs.readFileSync(filename, 'utf8')
);
}
function getBundleModules(bundleStats) {
return _(bundleStats.chunks)
.map('modules')
.concat(bundleStats.modules)
.compact()
.flatten()
.uniqBy('id')
.value();
}
function assetHasModule(statAsset, statModule) {
// Checking if this module is the part of asset chunks
return _.some(statModule.chunks, moduleChunk =>
_.includes(statAsset.chunks, moduleChunk)
);
}
function createModulesTree(modules) {
const root = new Folder('.');
_.each(modules, module => root.addModule(module));
return root;
}
+143
View File
@@ -0,0 +1,143 @@
#! /usr/bin/env node
const { resolve, dirname } = require('path');
const _ = require('lodash');
const commander = require('commander');
const { magenta } = require('chalk');
const analyzer = require('../analyzer');
const viewer = require('../viewer');
const Logger = require('../Logger');
const SIZES = new Set(['stat', 'parsed', 'gzip']);
const program = commander
.version(require('../../package.json').version)
.usage(
`<bundleStatsFile> [bundleDir] [options]
Arguments:
bundleStatsFile Path to Webpack Stats JSON file.
bundleDir Directory containing all generated bundles.
You should provided it if you want analyzer to show you the real parsed module sizes.
By default a directory of stats file is used.`
)
.option(
'-m, --mode <mode>',
'Analyzer mode. Should be `server` or `static`.' +
br('In `server` mode analyzer will start HTTP server to show bundle report.') +
br('In `static` mode single HTML file with bundle report will be generated.'),
'server'
)
.option(
'-h, --host <host>',
'Host that will be used in `server` mode to start HTTP server.',
'127.0.0.1'
)
.option(
'-p, --port <n>',
'Port that will be used in `server` mode to start HTTP server.',
Number,
8888
)
.option(
'-r, --report <file>',
'Path to bundle report file that will be generated in `static` mode.',
'report.html'
)
.option(
'-s, --default-sizes <type>',
'Module sizes to show in treemap by default.' +
br(`Possible values: ${[...SIZES].join(', ')}`),
'parsed'
)
.option(
'-O, --no-open',
"Don't open report in default browser automatically."
)
.option(
'-e, --exclude <regexp>',
'Assets that should be excluded from the report.' +
br('Can be specified multiple times.'),
array()
)
.option(
'-l, --log-level <level>',
'Log level.' +
br(`Possible values: ${[...Logger.levels].join(', ')}`),
Logger.defaultLevel
)
.parse(process.argv);
let {
mode,
host,
port,
report: reportFilename,
defaultSizes,
logLevel,
open: openBrowser,
exclude: excludeAssets,
args: [bundleStatsFile, bundleDir]
} = program;
const logger = new Logger(logLevel);
if (!bundleStatsFile) showHelp('Provide path to Webpack Stats file as first argument');
if (mode !== 'server' && mode !== 'static') showHelp('Invalid mode. Should be either `server` or `static`.');
if (mode === 'server' && !host) showHelp('Invalid host name');
if (mode === 'server' && isNaN(port)) showHelp('Invalid port number');
if (!SIZES.has(defaultSizes)) showHelp(`Invalid default sizes option. Possible values are: ${[...SIZES].join(', ')}`);
bundleStatsFile = resolve(bundleStatsFile);
if (!bundleDir) bundleDir = dirname(bundleStatsFile);
let bundleStats;
try {
bundleStats = analyzer.readStatsFromFile(bundleStatsFile);
} catch (err) {
logger.error(`Could't read webpack bundle stats from "${bundleStatsFile}":\n${err}`);
logger.debug(err.stack);
process.exit(1);
}
if (mode === 'server') {
viewer.startServer(bundleStats, {
openBrowser,
port,
host,
defaultSizes,
bundleDir,
excludeAssets,
logger: new Logger(logLevel)
});
} else {
viewer.generateReport(bundleStats, {
openBrowser,
reportFilename: resolve(reportFilename),
defaultSizes,
bundleDir,
excludeAssets,
logger: new Logger(logLevel)
});
}
function showHelp(error) {
if (error) console.log(`\n ${magenta(error)}`);
program.outputHelp();
process.exit(1);
}
function br(str) {
return `\n${_.repeat(' ', 28)}${str}`;
}
function array() {
const arr = [];
return (val) => {
arr.push(val);
return arr;
};
}
+6
View File
@@ -0,0 +1,6 @@
const { start } = require('./viewer');
module.exports = {
start,
BundleAnalyzerPlugin: require('./BundleAnalyzerPlugin')
};
+245
View File
@@ -0,0 +1,245 @@
const fs = require('fs');
const _ = require('lodash');
const acorn = require('acorn');
const walk = require('acorn/dist/walk');
module.exports = {
parseBundle
};
function parseBundle(bundlePath) {
const content = fs.readFileSync(bundlePath, 'utf8');
const ast = acorn.parse(content, {
sourceType: 'script',
// I believe in a bright future of ECMAScript!
// Actually, it's set to `2050` to support the latest ECMAScript version that currently exists.
// Seems like `acorn` supports such weird option value.
ecmaVersion: 2050
});
const walkState = {
locations: null
};
walk.recursive(
ast,
walkState,
{
CallExpression(node, state, c) {
if (state.locations) return;
const args = node.arguments;
// Main chunk with webpack loader.
// Modules are stored in first argument:
// (function (...) {...})(<modules>)
if (
node.callee.type === 'FunctionExpression' &&
!node.callee.id &&
args.length === 1 &&
isSimpleModulesList(args[0])
) {
state.locations = getModulesLocations(args[0]);
return;
}
// Async Webpack < v4 chunk without webpack loader.
// webpackJsonp([<chunks>], <modules>, ...)
// As function name may be changed with `output.jsonpFunction` option we can't rely on it's default name.
if (
node.callee.type === 'Identifier' &&
mayBeAsyncChunkArguments(args) &&
isModulesList(args[1])
) {
state.locations = getModulesLocations(args[1]);
return;
}
// Async Webpack v4 chunk without webpack loader.
// (window.webpackJsonp=window.webpackJsonp||[]).push([[<chunks>], <modules>, ...]);
// As function name may be changed with `output.jsonpFunction` option we can't rely on it's default name.
if (isAsyncChunkPushExpression(node)) {
state.locations = getModulesLocations(args[0].elements[1]);
return;
}
// Walking into arguments because some of plugins (e.g. `DedupePlugin`) or some Webpack
// features (e.g. `umd` library output) can wrap modules list into additional IIFE.
_.each(args, arg => c(arg, state));
}
}
);
let modules;
if (walkState.locations) {
modules = _.mapValues(walkState.locations,
loc => content.slice(loc.start, loc.end)
);
} else {
modules = {};
}
return {
src: content,
modules
};
}
function isModulesList(node) {
return (
isSimpleModulesList(node) ||
// Modules are contained in expression `Array([minimum ID]).concat([<module>, <module>, ...])`
isOptimizedModulesArray(node)
);
}
function isSimpleModulesList(node) {
return (
// Modules are contained in hash. Keys are module ids.
isModulesHash(node) ||
// Modules are contained in array. Indexes are module ids.
isModulesArray(node)
);
}
function isModulesHash(node) {
return (
node.type === 'ObjectExpression' &&
_(node.properties)
.map('value')
.every(isModuleWrapper)
);
}
function isModulesArray(node) {
return (
node.type === 'ArrayExpression' &&
_.every(node.elements, elem =>
// Some of array items may be skipped because there is no module with such id
!elem ||
isModuleWrapper(elem)
)
);
}
function isOptimizedModulesArray(node) {
// Checking whether modules are contained in `Array(<minimum ID>).concat(...modules)` array:
// https://github.com/webpack/webpack/blob/v1.14.0/lib/Template.js#L91
// The `<minimum ID>` + array indexes are module ids
return (
node.type === 'CallExpression' &&
node.callee.type === 'MemberExpression' &&
// Make sure the object called is `Array(<some number>)`
node.callee.object.type === 'CallExpression' &&
node.callee.object.callee.type === 'Identifier' &&
node.callee.object.callee.name === 'Array' &&
node.callee.object.arguments.length === 1 &&
isNumericId(node.callee.object.arguments[0]) &&
// Make sure the property X called for `Array(<some number>).X` is `concat`
node.callee.property.type === 'Identifier' &&
node.callee.property.name === 'concat' &&
// Make sure exactly one array is passed in to `concat`
node.arguments.length === 1 &&
isModulesArray(node.arguments[0])
);
}
function isModuleWrapper(node) {
return (
// It's an anonymous function expression that wraps module
((node.type === 'FunctionExpression' || node.type === 'ArrowFunctionExpression') && !node.id) ||
// If `DedupePlugin` is used it can be an ID of duplicated module...
isModuleId(node) ||
// or an array of shape [<module_id>, ...args]
(node.type === 'ArrayExpression' && node.elements.length > 1 && isModuleId(node.elements[0]))
);
}
function isModuleId(node) {
return (node.type === 'Literal' && (isNumericId(node) || typeof node.value === 'string'));
}
function isNumericId(node) {
return (node.type === 'Literal' && Number.isInteger(node.value) && node.value >= 0);
}
function isChunkIds(node) {
// Array of numeric or string ids. Chunk IDs are strings when NamedChunksPlugin is used
return (
node.type === 'ArrayExpression' &&
_.every(node.elements, isModuleId)
);
}
function isAsyncChunkPushExpression(node) {
const {
callee,
arguments: args
} = node;
return (
callee.type === 'MemberExpression' &&
callee.property.name === 'push' &&
callee.object.type === 'AssignmentExpression' &&
callee.object.left.object &&
(
callee.object.left.object.name === 'window' ||
// Webpack 4 uses `this` instead of `window`
callee.object.left.object.type === 'ThisExpression'
) &&
args.length === 1 &&
args[0].type === 'ArrayExpression' &&
mayBeAsyncChunkArguments(args[0].elements) &&
isModulesList(args[0].elements[1])
);
}
function mayBeAsyncChunkArguments(args) {
return (
args.length >= 2 &&
isChunkIds(args[0])
);
}
function getModulesLocations(node) {
if (node.type === 'ObjectExpression') {
// Modules hash
const modulesNodes = node.properties;
return _.transform(modulesNodes, (result, moduleNode) => {
const moduleId = moduleNode.key.name || moduleNode.key.value;
result[moduleId] = getModuleLocation(moduleNode.value);
}, {});
}
const isOptimizedArray = (node.type === 'CallExpression');
if (node.type === 'ArrayExpression' || isOptimizedArray) {
// Modules array or optimized array
const minId = isOptimizedArray ?
// Get the [minId] value from the Array() call first argument literal value
node.callee.object.arguments[0].value :
// `0` for simple array
0;
const modulesNodes = isOptimizedArray ?
// The modules reside in the `concat()` function call arguments
node.arguments[0].elements :
node.elements;
return _.transform(modulesNodes, (result, moduleNode, i) => {
if (!moduleNode) return;
result[i + minId] = getModuleLocation(moduleNode);
}, {});
}
return {};
}
function getModuleLocation(node) {
return {
start: node.start,
end: node.end
};
}
+92
View File
@@ -0,0 +1,92 @@
import _ from 'lodash';
import Node from './Node';
export default class BaseFolder extends Node {
constructor(name, parent) {
super(name, parent);
this.children = Object.create(null);
}
get src() {
if (!_.has(this, '_src')) {
this._src = this.walk((node, src) => (src += node.src || ''), '', false);
}
return this._src;
}
get size() {
if (!_.has(this, '_size')) {
this._size = this.walk((node, size) => (size + node.size), 0, false);
}
return this._size;
}
getChild(name) {
return this.children[name];
}
addChildModule(module) {
const { name } = module;
const currentChild = this.children[name];
// For some reason we already have this node in children and it's a folder.
if (currentChild && currentChild instanceof BaseFolder) return;
if (currentChild) {
// We already have this node in children and it's a module.
// Merging it's data.
currentChild.mergeData(module.data);
} else {
// Pushing new module
module.parent = this;
this.children[name] = module;
}
delete this._size;
delete this._src;
}
addChildFolder(folder) {
folder.parent = this;
this.children[folder.name] = folder;
delete this._size;
delete this._src;
return folder;
}
walk(walker, state = {}, deep = true) {
let stopped = false;
_.each(this.children, child => {
if (deep && child.walk) {
state = child.walk(walker, state, stop);
} else {
state = walker(child, state, stop);
}
if (stopped) return false;
});
return state;
function stop(finalState) {
stopped = true;
return finalState;
}
}
toChartData() {
return {
label: this.name,
path: this.path,
statSize: this.size,
groups: _.invokeMap(this.children, 'toChartData')
};
}
};
+68
View File
@@ -0,0 +1,68 @@
import _ from 'lodash';
import Module from './Module';
import ContentModule from './ContentModule';
import ContentFolder from './ContentFolder';
import { getModulePathParts } from './utils';
export default class ConcatenatedModule extends Module {
constructor(name, data, parent) {
super(name, data, parent);
this.name += ' (concatenated)';
this.children = Object.create(null);
this.fillContentModules();
}
fillContentModules() {
_.each(this.data.modules, moduleData => this.addContentModule(moduleData));
}
addContentModule(moduleData) {
const pathParts = getModulePathParts(moduleData);
if (!pathParts) {
return;
}
const [folders, fileName] = [pathParts.slice(0, -1), _.last(pathParts)];
let currentFolder = this;
_.each(folders, folderName => {
let childFolder = currentFolder.getChild(folderName);
if (!childFolder) {
childFolder = currentFolder.addChildFolder(new ContentFolder(folderName, this));
}
currentFolder = childFolder;
});
const module = new ContentModule(fileName, moduleData, this);
currentFolder.addChildModule(module);
}
getChild(name) {
return this.children[name];
}
addChildModule(module) {
module.parent = this;
this.children[module.name] = module;
}
addChildFolder(folder) {
folder.parent = this;
this.children[folder.name] = folder;
return folder;
}
toChartData() {
return {
...super.toChartData(),
concatenated: true,
groups: _.invokeMap(this.children, 'toChartData')
};
}
};
+35
View File
@@ -0,0 +1,35 @@
import BaseFolder from './BaseFolder';
export default class ContentFolder extends BaseFolder {
constructor(name, ownerModule, parent) {
super(name, parent);
this.ownerModule = ownerModule;
}
get parsedSize() {
return this.getSize('parsedSize');
}
get gzipSize() {
return this.getSize('gzipSize');
}
getSize(sizeType) {
const ownerModuleSize = this.ownerModule[sizeType];
if (ownerModuleSize !== undefined) {
return Math.floor((this.size / this.ownerModule.size) * ownerModuleSize);
}
}
toChartData() {
return {
...super.toChartData(),
parsedSize: this.parsedSize,
gzipSize: this.gzipSize,
inaccurateSizes: true
};
}
};
+33
View File
@@ -0,0 +1,33 @@
import Module from './Module';
export default class ContentModule extends Module {
constructor(name, data, ownerModule, parent) {
super(name, data, parent);
this.ownerModule = ownerModule;
}
get parsedSize() {
return this.getSize('parsedSize');
}
get gzipSize() {
return this.getSize('gzipSize');
}
getSize(sizeType) {
const ownerModuleSize = this.ownerModule[sizeType];
if (ownerModuleSize !== undefined) {
return Math.floor((this.size / this.ownerModule.size) * ownerModuleSize);
}
}
toChartData() {
return {
...super.toChartData(),
inaccurateSizes: true
};
}
};
+64
View File
@@ -0,0 +1,64 @@
import _ from 'lodash';
import gzipSize from 'gzip-size';
import Module from './Module';
import BaseFolder from './BaseFolder';
import ConcatenatedModule from './ConcatenatedModule';
import { getModulePathParts } from './utils';
export default class Folder extends BaseFolder {
get parsedSize() {
return this.src ? this.src.length : 0;
}
get gzipSize() {
if (!_.has(this, '_gzipSize')) {
this._gzipSize = this.src ? gzipSize.sync(this.src) : 0;
}
return this._gzipSize;
}
addModule(moduleData) {
const pathParts = getModulePathParts(moduleData);
if (!pathParts) {
return;
}
const [folders, fileName] = [pathParts.slice(0, -1), _.last(pathParts)];
let currentFolder = this;
_.each(folders, folderName => {
let childNode = currentFolder.getChild(folderName);
if (
// Folder is not created yet
!childNode ||
// In some situations (invalid usage of dynamic `require()`) webpack generates a module with empty require
// context, but it's moduleId points to a directory in filesystem.
// In this case we replace this `File` node with `Folder`.
// See `test/stats/with-invalid-dynamic-require.json` as an example.
!(childNode instanceof Folder)
) {
childNode = currentFolder.addChildFolder(new Folder(folderName));
}
currentFolder = childNode;
});
const ModuleConstructor = moduleData.modules ? ConcatenatedModule : Module;
const module = new ModuleConstructor(fileName, moduleData, this);
currentFolder.addChildModule(module);
}
toChartData() {
return {
...super.toChartData(),
parsedSize: this.parsedSize,
gzipSize: this.gzipSize
};
}
};
+63
View File
@@ -0,0 +1,63 @@
import _ from 'lodash';
import gzipSize from 'gzip-size';
import Node from './Node';
export default class Module extends Node {
constructor(name, data, parent) {
super(name, parent);
this.data = data;
}
get src() {
return this.data.parsedSrc;
}
set src(value) {
this.data.parsedSrc = value;
delete this._gzipSize;
}
get size() {
return this.data.size;
}
set size(value) {
this.data.size = value;
}
get parsedSize() {
return this.src ? this.src.length : undefined;
}
get gzipSize() {
if (!_.has(this, '_gzipSize')) {
this._gzipSize = this.src ? gzipSize.sync(this.src) : undefined;
}
return this._gzipSize;
}
mergeData(data) {
if (data.size) {
this.size += data.size;
}
if (data.parsedSrc) {
this.src = (this.src || '') + data.parsedSrc;
}
}
toChartData() {
return {
id: this.data.id,
label: this.name,
path: this.path,
statSize: this.size,
parsedSize: this.parsedSize,
gzipSize: this.gzipSize
};
}
};
+20
View File
@@ -0,0 +1,20 @@
export default class Node {
constructor(name, parent) {
this.name = name;
this.parent = parent;
}
get path() {
const path = [];
let node = this;
while (node) {
path.push(node.name);
node = node.parent;
}
return path.reverse().join('/');
}
};
+21
View File
@@ -0,0 +1,21 @@
import _ from 'lodash';
const MULTI_MODULE_REGEXP = /^multi /;
export function getModulePathParts(moduleData) {
if (MULTI_MODULE_REGEXP.test(moduleData.identifier)) {
return [moduleData.identifier];
}
const parsedPath = _
// Removing loaders from module path: they're joined by `!` and the last part is a raw module path
.last(moduleData.name.split('!'))
// Splitting module path into parts
.split('/')
// Removing first `.`
.slice(1)
// Replacing `~` with `node_modules`
.map(part => (part === '~' ? 'node_modules' : part));
return parsedPath.length ? parsedPath : null;
}
+34
View File
@@ -0,0 +1,34 @@
const { inspect } = require('util');
const _ = require('lodash');
exports.createAssetsFilter = createAssetsFilter;
function createAssetsFilter(excludePatterns) {
const excludeFunctions = _(excludePatterns)
.castArray()
.compact()
.map(pattern => {
if (typeof pattern === 'string') {
pattern = new RegExp(pattern);
}
if (_.isRegExp(pattern)) {
return (asset) => pattern.test(asset);
}
if (!_.isFunction(pattern)) {
throw new TypeError(
`Pattern should be either string, RegExp or a function, but "${inspect(pattern, { depth: 0 })}" got.`
);
}
return pattern;
})
.value();
if (excludeFunctions.length) {
return (asset) => _.every(excludeFunctions, fn => fn(asset) !== true);
} else {
return () => true;
}
}
+176
View File
@@ -0,0 +1,176 @@
const path = require('path');
const fs = require('fs');
const http = require('http');
const WebSocket = require('ws');
const _ = require('lodash');
const express = require('express');
const ejs = require('ejs');
const opener = require('opener');
const mkdir = require('mkdirp');
const { bold } = require('chalk');
const Logger = require('./Logger');
const analyzer = require('./analyzer');
const projectRoot = path.resolve(__dirname, '..');
module.exports = {
startServer,
generateReport,
// deprecated
start: startServer
};
async function startServer(bundleStats, opts) {
const {
port = 8888,
host = '127.0.0.1',
openBrowser = true,
bundleDir = null,
logger = new Logger(),
defaultSizes = 'parsed',
excludeAssets = null
} = opts || {};
const analyzerOpts = { logger, excludeAssets };
let chartData = getChartData(analyzerOpts, bundleStats, bundleDir);
if (!chartData) return;
const app = express();
// Explicitly using our `ejs` dependency to render templates
// Fixes #17
app.engine('ejs', require('ejs').renderFile);
app.set('view engine', 'ejs');
app.set('views', `${projectRoot}/views`);
app.use(express.static(`${projectRoot}/public`));
app.use('/', (req, res) => {
res.render('viewer', {
mode: 'server',
get chartData() { return JSON.stringify(chartData) },
defaultSizes: JSON.stringify(defaultSizes)
});
});
const server = http.createServer(app);
await new Promise(resolve => {
server.listen(port, host, () => {
resolve();
const url = `http://${host}:${server.address().port}`;
logger.info(
`${bold('Webpack Bundle Analyzer')} is started at ${bold(url)}\n` +
`Use ${bold('Ctrl+C')} to close it`
);
if (openBrowser) {
opener(url);
}
});
});
const wss = new WebSocket.Server({ server });
wss.on('connection', ws => {
ws.on('error', err => {
// Ignore network errors like `ECONNRESET`, `EPIPE`, etc.
if (err.errno) return;
logger.info(err.message);
});
});
return {
ws: wss,
http: server,
updateChartData
};
function updateChartData(bundleStats) {
const newChartData = getChartData(analyzerOpts, bundleStats, bundleDir);
if (!newChartData) return;
chartData = newChartData;
wss.clients.forEach(client => {
if (client.readyState === WebSocket.OPEN) {
client.send(JSON.stringify({
event: 'chartDataUpdated',
data: newChartData
}));
}
});
}
}
function generateReport(bundleStats, opts) {
const {
openBrowser = true,
reportFilename = 'report.html',
bundleDir = null,
logger = new Logger(),
defaultSizes = 'parsed',
excludeAssets = null
} = opts || {};
const chartData = getChartData({ logger, excludeAssets }, bundleStats, bundleDir);
if (!chartData) return;
ejs.renderFile(
`${projectRoot}/views/viewer.ejs`,
{
mode: 'static',
chartData: JSON.stringify(chartData),
assetContent: getAssetContent,
defaultSizes: JSON.stringify(defaultSizes)
},
(err, reportHtml) => {
if (err) return logger.error(err);
const reportFilepath = path.resolve(bundleDir || process.cwd(), reportFilename);
mkdir.sync(path.dirname(reportFilepath));
fs.writeFileSync(reportFilepath, reportHtml);
logger.info(
`${bold('Webpack Bundle Analyzer')} saved report to ${bold(reportFilepath)}`
);
if (openBrowser) {
opener(`file://${reportFilepath}`);
}
}
);
}
function getAssetContent(filename) {
return fs.readFileSync(`${projectRoot}/public/${filename}`, 'utf8');
}
function getChartData(analyzerOpts, ...args) {
let chartData;
const { logger } = analyzerOpts;
try {
chartData = analyzer.getViewerData(...args, analyzerOpts);
} catch (err) {
logger.error(`Could't analyze webpack bundle:\n${err}`);
logger.debug(err.stack);
chartData = null;
}
if (_.isPlainObject(chartData) && _.isEmpty(chartData)) {
logger.error("Could't find any javascript bundles in provided stats file");
chartData = null;
}
return chartData;
}
+8
View File
@@ -0,0 +1,8 @@
<% if (mode === 'static') { %>
<!-- <%= filename %> -->
<script>
<%- assetContent(filename) %>
</script>
<% } else { %>
<script src="/<%= filename %>"></script>
<% } %>
+18
View File
@@ -0,0 +1,18 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<title>Webpack Bundle Analyzer</title>
<%- include('script', { filename: 'viewer.js' }) %>
</head>
<body>
<div id="app"></div>
<script>
window.chartData = <%- chartData %>;
window.defaultSizes = <%- defaultSizes %>;
</script>
</body>
</html>