Skip to content

Commit

Permalink
[Major] Integrate inspectpack3 - faster, better, and more colorful! (#…
Browse files Browse the repository at this point in the history
…249)

This is a major update to `webpack-dashboard` with breaking changes. And a lot of speed and coolness.

## Breaking changes

- Requires node6+.
- Use the webpack internal stats object instead of real bundles. This is faster and with less futzing / parsing on the dashboard's end. Should permanently fix the `no code sections` found errors.
- We've removed command flags / plugin options that no longer apply like `--root`, etc.

### Modules

- Removed `min` and `min+gz` estimated file sizes from modules outputs. This dramatically speeds up the dashboard and reduces CPU usage.

### Versions

- Version skews should be detected more accurately and much faster thanks to dramatically improved heuristics from `inspectpack`.

### Duplicates

- Duplicates now reports on duplicate files that are (1) completely identical, and (2) the same package name and file path but with different sources. The first are low hanging fruit for optimizations in your build -- you've literally got the same thing multiple times. The latter needs to be coalesced at the package dependency level -- something the versions output can help with.

## Issues

- Upgrade hooks to work in webpack1-4 with abstraction. Fixes #240

## Refactoring

- Switch from `chalk` to using Blessed's built-in tags for coloring. This is definitely the way to go as things like table spacing don't work with chalk strings and work like a charm with Blessed tags.
- Update dependencies and remove lots of unused things (like `lodash`, `chalk`, `bluebird` and a whole lot of `babel*` that apparently wasn't used anyways).
    - New `inspectpack` is way slimmer and faster, with no more binary dependencies!
- Switch to yarn-only workflow in dev.
- Bump Travis node versions.
  • Loading branch information
ryan-roemer authored May 24, 2018
1 parent 9a9a020 commit 8a32cd1
Show file tree
Hide file tree
Showing 18 changed files with 3,737 additions and 7,679 deletions.
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ node_modules

# misc
.DS_Store
npm-debug.log
npm-debug.log*
.nyc_output
.coverage
.lankrc*
yarn-error.log
package-lock.json
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
language: node_js

node_js:
- "4"
- "6"
- "8"
- "10"

sudo: false

Expand Down
25 changes: 1 addition & 24 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,31 +57,12 @@ You would change that to:
```
Now you can just run your start script like normal, except now, you are awesome. Not that you weren't before. I'm just saying. More so.

### InspectPack and Node Environments

Webpack Dashboard does additional analysis of individual module sizes (including minified + gzipped), asset sizes, and any problems when your bundle is unminified and not in a production environment. The Webpack Plugin automatically adds `pathinfo = true` to your configuration’s output object. Environments are defined through the `DefinePlugin` with `process.env["NODE_ENV"]` being `"production"`. Webpack Dashboard will produce a warning if a production configuration is run.

#### Run it
### Run it

Finally, start your server using whatever command you have set up. Either you have `npm run dev` or `npm start` pointed at `node devServer.js` or something along those lines.

Then, sit back and pretend you're an astronaut.

#### CPU usage + tips

By default, `inspectpack` produces an approximated minified + gzip size for each individual module. For large bundles, this can be very CPU intensive. While we parallelize and cache these results, if you are experiencing system slowdown from lots of Node.js processes, you can disable either minification and/or gzip approximate calculations by adding the following options to the plugin configuration:

```js
plugins: [
new DashboardPlugin({
minified: false,
gzip: false
})
]
```

It's also worth noting that under-the-hood `inspectpack` caches previous calculated results for dramatic speedups (like 50-100x faster than uncached in some instances). To enable best results, make sure that the `optionalDependencies` of `better-sqlite` and `farmhash` were installed, and the default cache file location of `${HOME}/.webpack-dashboard-cache.db` is writable by the dashboard processes.

### Supported Operating Systems and Terminals
**macOS →**
Webpack Dashboard works in Terminal, iTerm 2, and Hyper. For mouse events, like scrolling, in Terminal you will need to ensure *View → Enable Mouse Reporting* is enabled. This is supported in macOS El Capitan, Sierra, and High Sierra. In iTerm 2, to select full rows of text hold the <kbd>⌥ Opt</kbd> key. To select a block of text hold the <kbd>⌥ Opt</kbd> + <kbd>⌘ Cmd</kbd> key combination.
Expand All @@ -90,7 +71,6 @@ Webpack Dashboard works in Terminal, iTerm 2, and Hyper. For mouse events, like

**Linux →** Webpack Dashboard has been verified in the built-in terminal app for Debian-based Linux distributions such as Ubuntu or Mint. Mouse events and scrolling are supported automatically. To highlight or select lines hold the <kbd>⇧ Shift</kbd> key.


### API

#### webpack-dashboard (CLI)
Expand All @@ -110,9 +90,6 @@ Webpack Dashboard works in Terminal, iTerm 2, and Hyper. For mouse events, like

- `host` - Custom host for connection the socket client
- `port` - Custom port for connecting the socket client
- `root` - Custom full path to project root (where `package.json` + `node_modules` are if not in `process.cwd()`)
- `minified` - Calculate and use minified asset sizes? (default: `true`)
- `gzip` - Calculate and use gzipped asset sizes? True implies `minified = true`. (default: `true`)
- `handler` - Plugin handler method, i.e. `dashboard.setData`

*Note: you can also just pass a function in as an argument, which then becomes the handler, i.e. `new DashboardPlugin(dashboard.setData)`*
Expand Down
1 change: 1 addition & 0 deletions bin/webpack-dashboard.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#!/usr/bin/env node

"use strict";

const commander = require("commander");
Expand Down
168 changes: 73 additions & 95 deletions dashboard/index.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
"use strict";

const _ = require("lodash/fp");
const chalk = require("chalk");
const blessed = require("blessed");

const formatOutput = require("../utils/format-output.js");
const formatModules = require("../utils/format-modules.js");
const formatAssets = require("../utils/format-assets.js");
const formatProblems = require("../utils/format-problems.js");
const deserializeError = require("../utils/error-serialization").deserializeError;
const { deserializeError } = require("../utils/error-serialization");

const PERCENT_MULTIPLIER = 100;

Expand All @@ -26,16 +25,43 @@ const DEFAULT_SCROLL_OPTIONS = {
};

class Dashboard {
constructor(options) {
constructor(options) { // eslint-disable-line max-statements
// Options, params
options = options || {};
const title = options.title || "webpack-dashboard";

this.color = options.color || "green";
this.minimal = options.minimal || false;
this.setData = this.setData.bind(this);

this.stats = null;

// Data binding, lookup tables.
this.setData = this.setData.bind(this);
this.actionForMessageType = {
progress: this.setProgress.bind(this),
operations: this.setOperations.bind(this),
status: this.setStatus.bind(this),
stats: this.setStats.bind(this),
log: this.setLog.bind(this),
clear: this.clear.bind(this),
sizes: _data => {
if (this.minimal) { return; }
if (_data.value instanceof Error) {
this.setSizesError(_data.value);
} else {
this.setSizes(_data);
}
},
problems: _data => {
if (this.minimal) { return; }
if (_data.value instanceof Error) {
this.setProblemsError(_data.value);
} else {
this.setProblems(_data);
}
}
};

// Start UI stuff.
this.screen = blessed.screen({
title,
smartCSR: true,
Expand All @@ -62,42 +88,15 @@ class Dashboard {
}

setData(dataArray) {
const actionForMessageType = data => {
const map = {
progress: this.setProgress.bind(this),
operations: this.setOperations.bind(this),
status: this.setStatus.bind(this),
stats: this.setStats.bind(this),
nodeEnv: this.setNodeEnv.bind(this),
log: this.setLog.bind(this),
clear: this.clear.bind(this),
sizes: _data => {
if (this.minimal) { return; }
if (_data.value instanceof Error) {
this.setSizesError(_data.value);
} else {
this.setSizes(_data);
}
},
problems: _data => {
if (this.minimal) { return; }
if (_data.value instanceof Error) {
this.setProblemsError(_data.value);
} else {
this.setProblems(_data);
}
}
};
return map[data.type](data);
};

dataArray
.map(data => data.error ?
Object.assign({}, data, {
.map(data => data.error
? Object.assign({}, data, {
value: deserializeError(data.value)
}) : data
)
.forEach(actionForMessageType);
.forEach(data => {
this.actionForMessageType[data.type](data);
});

this.screen.render();
}
Expand All @@ -122,13 +121,6 @@ class Dashboard {
this.operations.setContent(data.value);
}

setNodeEnv(data) {
this.nodeEnv = data.value;
if (this.nodeEnv === "production") {
this.setProductionConfigurationWarning();
}
}

setStatus(data) {
let content;

Expand All @@ -147,15 +139,9 @@ class Dashboard {

setStats(data) {
const stats = {
hasErrors: () => {
return data.value.errors;
},
hasWarnings: () => {
return data.value.warnings;
},
toJson: () => {
return data.value.data;
}
hasErrors: () => data.value.errors,
hasWarnings: () => data.value.warnings,
toJson: () => data.value.data
};

// Save for later when merging inspectpack sizes into the asset list
Expand All @@ -175,26 +161,26 @@ class Dashboard {
}

setSizes(data) {
this.modulesMenu.setLabel("Modules");
const { assets } = data.value;

// Start with top-level assets.
this.assets.setLabel("Assets");
this.assetTable.setData(formatAssets(assets));

const result = _.flow(
_.groupBy("path"),
_.mapValues(_.reduce((acc, bundle) =>
Object.assign({}, acc, bundle), {}
)),
_.mapValues(bundle => () => {
this.moduleTable.setData(formatModules(bundle));
// Then split modules across assets.
const previousSelection = this.modulesMenu.selected;
const modulesItems = Object.keys(assets).reduce((memo, name) => Object.assign({}, memo, {
[name]: () => {
this.moduleTable.setData(formatModules(assets[name].files));
this.screen.render();
})
)(data.value);
}
}), {});

const previousSelection = this.modulesMenu.selected;
this.modulesMenu.setItems(result);
this.modulesMenu.setLabel("Modules");
this.modulesMenu.setItems(modulesItems);
this.modulesMenu.selectTab(previousSelection);

this.assetTable.setData(formatAssets(this.stats, data.value));

// Final render.
this.screen.render();
}

Expand All @@ -205,36 +191,26 @@ class Dashboard {
this.logText.log(chalk.red(err));
}

setProductionConfigurationWarning() {
this.modulesMenu.setLabel(chalk.yellow("Modules (warning)"));
this.assets.setLabel(chalk.yellow("Assets (warning)"));
this.moduleTable.setData([[
// eslint-disable-next-line max-len
"It appears you are using a production config. Therefore there are no code sections that could be analyzed. To see more on modules and assets switch to a development configuration."
]]);
this.assetTable.setData([[
"Unable to list specific asset data."
]]);
this.problemsMenu.setLabel(chalk.yellow("Problems (warning)"));
this.problems.setContent("Unable to analyze problems.");
}

setProblems(data) {
this.problemsMenu.setLabel("Problems");
const { duplicates, versions } = data.value;

const result = _.flow(
_.groupBy("path"),
_.mapValues(_.reduce((acc, bundle) =>
Object.assign({}, acc, bundle), {}
)),
_.mapValues(bundle => () => {
this.problems.setContent(formatProblems(bundle));
this.screen.render();
})
)(data.value);
// Separate across assets.
// Use duplicates as the "canary" to get asset names.
const assetNames = Object.keys(duplicates.assets);

const previousSelection = this.problemsMenu.selected;
this.problemsMenu.setItems(result);
const problemsItems = assetNames.reduce((memo, name) => Object.assign({}, memo, {
[name]: () => {
this.problems.setContent(formatProblems({
duplicates: duplicates.assets[name],
versions: versions.assets[name]
}));
this.screen.render();
}
}), {});

this.problemsMenu.setLabel("Problems");
this.problemsMenu.setItems(problemsItems);
this.problemsMenu.selectTab(previousSelection);

this.screen.render();
Expand Down Expand Up @@ -351,7 +327,8 @@ class Dashboard {
left: 1
},
align: "left",
data: [["Name", "Size", "Percentage"]]
data: [["Name", "Size", "Percent"]],
tags: true
})
);

Expand Down Expand Up @@ -436,7 +413,8 @@ class Dashboard {
border: {
fg: this.color
}
}
},
tags: true
})
);

Expand Down
Loading

0 comments on commit 8a32cd1

Please sign in to comment.