chushihua
This commit is contained in:
+249
@@ -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
@@ -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
@@ -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.
|
||||
|
||||

|
||||
|
||||
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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -0,0 +1,9 @@
|
||||
'use strict';
|
||||
|
||||
var _require = require('./viewer'),
|
||||
start = _require.start;
|
||||
|
||||
module.exports = {
|
||||
start,
|
||||
BundleAnalyzerPlugin: require('./BundleAnalyzerPlugin')
|
||||
};
|
||||
+198
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||
}
|
||||
+419
@@ -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 doesn’t 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
@@ -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
@@ -0,0 +1,428 @@
|
||||
# Commander.js
|
||||
|
||||
|
||||
[](http://travis-ci.org/tj/commander.js)
|
||||
[](https://www.npmjs.org/package/commander)
|
||||
[](https://npmcharts.com/compare/commander?minimal=true)
|
||||
[](https://packagephobia.now.sh/result?p=commander)
|
||||
[](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 don’t 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)
|
||||
+1224
File diff suppressed because it is too large
Load Diff
+38
@@ -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"
|
||||
}
|
||||
+310
@@ -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
@@ -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"
|
||||
]
|
||||
}
|
||||
+2
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
+128
@@ -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
@@ -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
@@ -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
@@ -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
@@ -0,0 +1,6 @@
|
||||
const { start } = require('./viewer');
|
||||
|
||||
module.exports = {
|
||||
start,
|
||||
BundleAnalyzerPlugin: require('./BundleAnalyzerPlugin')
|
||||
};
|
||||
+245
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -0,0 +1,8 @@
|
||||
<% if (mode === 'static') { %>
|
||||
<!-- <%= filename %> -->
|
||||
<script>
|
||||
<%- assetContent(filename) %>
|
||||
</script>
|
||||
<% } else { %>
|
||||
<script src="/<%= filename %>"></script>
|
||||
<% } %>
|
||||
+18
@@ -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>
|
||||
Reference in New Issue
Block a user