diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..0f7d54f
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,11 @@
+# EditorConfig: http://EditorConfig.org
+
+root = true
+
+[*]
+end_of_line = lf
+charset = utf-8
+trim_trailing_whitespace = true
+insert_final_newline = true
+indent_style = space
+tab_width = 4
diff --git a/.eslintignore b/.eslintignore
new file mode 100644
index 0000000..af2ff43
--- /dev/null
+++ b/.eslintignore
@@ -0,0 +1,4 @@
+node_modules
+demo_stats
+lib
+dist-site
diff --git a/.eslintrc b/.eslintrc
index 8167c4a..a363364 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -1,90 +1,36 @@
{
- "extends": "eslint:recommended",
- "plugins": [
- "react"
- ],
- "env": {
- "browser": true,
- "es6": true,
- "node": true
- },
- "ecmaFeatures": {
- "experimentalObjectRestSpread": true,
- "jsx": true,
- "modules": true
+ "parser": "@babel/eslint-parser",
+ "extends": ["eslint:recommended", "plugin:react/recommended", "plugin:prettier/recommended", "prettier"],
+ "parserOptions": {
+ "sourceType": "module",
+ "ecmaVersion": 2020,
+ "ecmaFeatures": {
+ "jsx": true
+ },
+ "requireConfigFile": false,
+ "babelOptions": {
+ "presets": ["@babel/preset-env", "@babel/preset-react"],
+ "plugins": [
+ [
+ "@babel/plugin-proposal-class-properties",
+ {
+ "loose": true
+ }
+ ]
+ ]
+ }
},
"rules": {
- "no-alert": 2,
- "no-array-constructor": 2,
- "no-bitwise": 1,
- "no-catch-shadow": 2,
- "no-empty": 1,
- "no-eval": 2,
- "no-extend-native": 2,
- "no-extra-bind": 1,
- "no-implied-eval": 2,
- "no-iterator": 2,
- "no-label-var": 2,
- "no-labels": 2,
- "no-lone-blocks": 2,
- "no-loop-func": 2,
- "no-multi-spaces": 1,
- "no-native-reassign": 2,
- "no-new-func": 2,
- "no-new-wrappers": 2,
- "no-octal-escape": 2,
- "no-proto": 2,
- "no-return-assign": 2,
- "no-sequences": 2,
- "no-shadow": 2,
- "no-shadow-restricted-names": 2,
- "no-spaced-func": 2,
- "no-undef-init": 2,
- "no-unused-vars": [2, {"vars": "all", "args": "none"}],
- "no-use-before-define": [2, "nofunc"],
- "no-with": 2,
-
- "arrow-spacing": 1,
- "brace-style": [2, "1tbs"],
- "camelcase": 1,
- "comma-dangle": 1,
- "comma-spacing": 1,
- "curly": [2, "all"],
- "dot-notation": [1, {"allowKeywords": true}],
- "eqeqeq": [2, "smart"],
- "indent": 2,
- "jsx-quotes": 1,
- "key-spacing": 1,
- "new-cap": 1,
- "new-parens": 2,
- "quotes": [2, "single"],
- "semi": 2,
- "semi-spacing": 1,
- "space-infix-ops": 1,
- "space-return-throw-case": 1,
- "space-unary-ops": 1,
- "strict": [0, "function"],
- "wrap-iife": [2, "any"],
- "yoda": [1, "never"],
-
- "react/jsx-closing-bracket-location": 1,
- "react/jsx-curly-spacing": 1,
- "react/jsx-indent-props": 1,
- "react/jsx-max-props-per-line": [1, {"maximum": 3}],
- "react/jsx-no-duplicate-props": 2,
- "react/jsx-no-undef": 2,
- "react/jsx-sort-prop-types": 1,
- "react/jsx-uses-react": 2,
- "react/jsx-uses-vars": 2,
- "react/no-did-mount-set-state": [2, "allow-in-func"],
- "react/no-did-update-set-state": 2,
- "react/no-direct-mutation-state": 2,
- "react/no-multi-comp": 1,
- "react/no-unknown-property": 2,
- "react/prop-types": [2, {ignore: "children"}],
- "react/react-in-jsx-scope": 2,
- "react/self-closing-comp": 1,
- "react/sort-comp": 2,
- "react/wrap-multilines": 2
+ "prettier/prettier": 1
+ },
+ "settings": {
+ "react": {
+ "version": "detect"
+ }
+ },
+ "env": {
+ "browser": true,
+ "node": true,
+ "es6": true
}
}
diff --git a/.gitignore b/.gitignore
index ca810e3..5465ccb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,3 +2,6 @@ dist-site
lib
node_modules
npm-debug.log
+package-lock.json
+.vscode
+yarn.lock
diff --git a/.prettierrc b/.prettierrc
new file mode 100644
index 0000000..376e703
--- /dev/null
+++ b/.prettierrc
@@ -0,0 +1,8 @@
+{
+ "endOfLine": "lf",
+ "semi": true,
+ "trailingComma": "all",
+ "singleQuote": true,
+ "printWidth": 120,
+ "tabWidth": 4
+}
diff --git a/README.md b/README.md
index f8bd9fc..4f81079 100644
--- a/README.md
+++ b/README.md
@@ -1,37 +1,35 @@
-# Webpack Visualizer
-Visualize and analyze your Webpack bundle to see which modules are taking up space and which might be duplicates.
+# Webpack Visualizer 2
-This tool is still pretty new, so please submit issues or feature requests!
+This is a working fork of the unmaintained [webpack-visualizer-plugin](https://github.com/chrisbateman/webpack-visualizer)
+It works with webpack 5.x
-## Site Usage
-
-Upload your stats JSON file to the site: [chrisbateman.github.io/webpack-visualizer/](http://chrisbateman.github.io/webpack-visualizer/)
-
-## Plugin Usage
+## Installation
```
-npm install webpack-visualizer-plugin
+npm i -D webpack-visualizer-plugin2
```
-```javascript
-var Visualizer = require('webpack-visualizer-plugin');
-//...
-plugins: [new Visualizer()],
-//...
-```
-This will output a file named `stats.html` in your output directory. You can modify the name/location by passing a `filename` parameter into the constructor.
+## Usage in webpack configuration
+This will generate the statistics page in /stats/ folder
+NOTE: "filename" points to the webpack output path, not the project root path
+
+```js
+const Visualizer = require('webpack-visualizer-plugin2');
+
+module.exports = {
+ plugins: [
+ new Visualizer(
+ {
+ filename: path.join('..', 'stats', 'statistics.html'),
+ throwOnError: true,
+ },
+ {
+ chunkModules: true
+ }),
+ ],
+}
-```javascript
-var Visualizer = require('webpack-visualizer-plugin');
-
-//...
-plugins: [new Visualizer({
- filename: './statistics.html'
-})],
-//...
```
----
-

diff --git a/package.json b/package.json
index edd9941..94febba 100644
--- a/package.json
+++ b/package.json
@@ -1,47 +1,75 @@
{
- "name": "webpack-visualizer-plugin",
- "version": "0.1.11",
- "main": "lib/plugin.js",
- "author": "Chris Bateman (http://cbateman.com/)",
- "license": "MIT",
- "files": [
- "lib",
- "README.md"
- ],
- "repository": {
- "type": "git",
- "url": "git@github.com:chrisbateman/webpack-visualizer.git"
- },
- "scripts": {
- "build": "npm run buildsite && npm run buildplugin",
- "prebuildplugin": "rm -rf lib && mkdir lib",
- "buildplugin": "webpack src/plugin/main.jsx lib/pluginmain.js --config webpack.prod.js",
- "postbuildplugin": "babel src/plugin/plugin.js --out-file lib/plugin.js && cp src/shared/style.css lib",
- "prebuildsite": "rm -rf dist-site && mkdir dist-site",
- "buildsite": "webpack src/site/main.jsx dist-site/build.js --config webpack.prod.js && babel-node src/site/serverRender.js",
- "postbuildsite": "cp src/shared/style.css test/stats-demo.json dist-site",
- "dev": "webpack-dev-server --config webpack.dev.js",
- "lint": "eslint src --ext .js,.jsx",
- "preversion": "npm run lint && npm run build",
- "publishSite": "git checkout gh-pages && cp dist-site/* . && git add . && git commit -m 'release' && git push origin gh-pages && git checkout master"
- },
- "dependencies": {
- "d3": "^3.5.6",
- "mkdirp": "^0.5.1",
- "react": "^0.14.0",
- "react-dom": "^0.14.0"
- },
- "devDependencies": {
- "babel": "^5.8.23",
- "babel-core": "^5.8.25",
- "babel-loader": "^5.3.2",
- "eslint": "^1.6.0",
- "eslint-plugin-react": "^3.5.1",
- "merge": "^1.2.0",
- "webpack": "^1.12.2",
- "webpack-dev-server": "^1.12.0"
- },
- "engines": {
- "npm": ">=2.13.0"
- }
+ "name": "webpack-visualizer-plugin2",
+ "description": "Generate webpack bundle chart",
+ "version": "1.2.0",
+ "main": "lib/plugin.js",
+ "author": "Chris Bateman (http://cbateman.com/)",
+ "contributors": [
+ {
+ "name": "Jonathan Mataloni",
+ "url": "https://github.com/jonamat",
+ "email": "jo.mataloni@gmail.com"
+ }
+ ],
+ "keywords": [
+ "webpack",
+ "statistics",
+ "bundle",
+ "chunks"
+ ],
+ "license": "MIT",
+ "files": [
+ "lib",
+ "README.md"
+ ],
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/jonamat/webpack-visualizer"
+ },
+ "scripts": {
+ "build": "npm run build:site && npm run build:plugin",
+ "build:plugin": "shx rm -rf ./lib/** && webpack --config ./webpack/plugin/webpack.prod.js && babel ./src/plugin/plugin.js --out-file lib/plugin.js --presets=@babel/preset-env",
+ "build:site": "webpack --config ./webpack/site/webpack.prod.js",
+ "start:plugin": "webpack serve --config ./webpack/plugin/webpack.dev.js",
+ "start:site": "webpack serve --config ./webpack/site/webpack.dev.js",
+ "lint": "eslint src --ext .js,.jsx",
+ "lint:fix": "eslint src --fix --ext .js,.jsx",
+ "preversion": "npm run build",
+ "publishSite": "git checkout gh-pages && cp dist-site/* . && git add . && git commit -m 'release' && git push origin gh-pages && git checkout master"
+ },
+ "dependencies": {
+ "d3": "^3.5.6",
+ "mkdirp": "^0.5.1",
+ "prop-types": "^15.7.2",
+ "react": "^17.0.1",
+ "react-dom": "^17.0.1"
+ },
+ "devDependencies": {
+ "@babel/cli": "^7.12.8",
+ "@babel/core": "^7.12.9",
+ "@babel/eslint-parser": "^7.12.1",
+ "@babel/plugin-proposal-class-properties": "^7.12.1",
+ "@babel/preset-env": "^7.12.7",
+ "@babel/preset-react": "^7.12.7",
+ "babel-loader": "^8.2.2",
+ "clean-webpack-plugin": "^3.0.0",
+ "css-loader": "^5.0.1",
+ "eslint": "^7.14.0",
+ "eslint-config-prettier": "^6.15.0",
+ "eslint-plugin-prettier": "^3.1.4",
+ "eslint-plugin-react": "^7.21.5",
+ "eslint-webpack-plugin": "^2.4.0",
+ "html-webpack-plugin": "^4.5.0",
+ "mini-css-extract-plugin": "^1.3.1",
+ "prettier": "^2.2.1",
+ "shx": "^0.3.3",
+ "style-loader": "^2.0.0",
+ "webpack": "^5.9.0",
+ "webpack-cli": "^4.2.0",
+ "webpack-dev-server": "^3.11.0",
+ "webpack-merge": "^5.4.0"
+ },
+ "engines": {
+ "npm": ">=5.0.0"
+ }
}
diff --git a/src/plugin/main.jsx b/src/plugin/main.jsx
index 25c3054..ed1157c 100644
--- a/src/plugin/main.jsx
+++ b/src/plugin/main.jsx
@@ -2,5 +2,6 @@ import React from 'react';
import ReactDOM from 'react-dom';
import App from './plugin-app';
+import '../shared/style.css';
ReactDOM.render(, document.getElementById('App'));
diff --git a/src/plugin/plugin-app.jsx b/src/plugin/plugin-app.jsx
index b2482d6..bf081fd 100644
--- a/src/plugin/plugin-app.jsx
+++ b/src/plugin/plugin-app.jsx
@@ -1,36 +1,27 @@
import React from 'react';
+import PropTypes from 'prop-types';
import ChartWithDetails from '../shared/components/chart-with-details';
import Footer from '../shared/components/footer';
import buildHierarchy from '../shared/buildHierarchy';
-import {getAssetsData, getBundleDetails, ERROR_CHUNK_MODULES} from '../shared/util/stat-utils';
+import { getAssetsData, getBundleDetails, ERROR_CHUNK_MODULES } from '../shared/util/stat-utils';
+class App extends React.Component {
+ constructor(props) {
+ super(props);
-export default React.createClass({
- propTypes: {
- stats: React.PropTypes.object
- },
-
- getInitialState() {
- return {
- assets: [],
- chartData: null,
- selectedAssetIndex: 0
+ this.state = {
+ assets: (this.props.stats && getAssetsData(this.props.stats.assets, this.props.stats.chunks)) || [],
+ chartData: (this.props.stats && buildHierarchy(this.props.stats.modules)) || null,
+ selectedAssetIndex: 0,
+ stats: this.props.stats,
};
- },
-
- componentWillMount() {
- let stats = this.props.stats;
- let assets = getAssetsData(stats.assets, stats.chunks);
+ }
- this.setState({
- assets,
- chartData: buildHierarchy(stats.modules),
- selectedAssetIndex: 0,
- stats
- });
- },
+ static propTypes = {
+ stats: PropTypes.object,
+ };
- onAssetChange(ev) {
+ onAssetChange = (ev) => {
let selectedAssetIndex = Number(ev.target.value);
let modules, chartData, error;
@@ -50,18 +41,18 @@ export default React.createClass({
this.setState({
chartData,
error,
- selectedAssetIndex
+ selectedAssetIndex,
});
- },
+ };
render() {
let assetList;
let bundleDetails = {};
- if (this.state.stats){
+ if (this.state.stats) {
bundleDetails = getBundleDetails({
assets: this.state.assets,
- selectedAssetIndex: this.state.selectedAssetIndex
+ selectedAssetIndex: this.state.selectedAssetIndex,
});
}
@@ -70,7 +61,11 @@ export default React.createClass({
);
@@ -90,4 +85,6 @@ export default React.createClass({
);
}
-});
+}
+
+export default App;
diff --git a/src/plugin/plugin.js b/src/plugin/plugin.js
index 0aa1aa6..4abe674 100644
--- a/src/plugin/plugin.js
+++ b/src/plugin/plugin.js
@@ -1,42 +1,75 @@
-/* eslint no-console:0 */
-
import path from 'path';
import fs from 'fs';
import mkdirp from 'mkdirp';
-let cssString = fs.readFileSync(path.join(__dirname, './style.css'), 'utf8');
-let jsString = fs.readFileSync(path.join(__dirname, './pluginmain.js'), 'utf8');
+let cssString = fs.readFileSync(path.join(__dirname, './style.css'), 'utf8');
+let jsString = fs.readFileSync(path.join(__dirname, './main.js'), 'utf8');
-export default class VisualizerPlugin {
- constructor(opts = {filename: 'stats.html'}) {
- this.opts = opts;
+module.exports = class VisualizerPlugin {
+ constructor(opts = {}, statOpts = {}) {
+ this.opts = {
+ filename: 'stats.html',
+ throwOnError: true,
+ ...opts,
+ };
+ this.statOpts = {
+ chunkModules: true,
+ ...statOpts,
+ };
}
apply(compiler) {
- compiler.plugin('emit', (compilation, callback) => {
- let stats = compilation.getStats().toJson({chunkModules: true});
- let stringifiedStats = JSON.stringify(stats);
- stringifiedStats = stringifiedStats.replace(/
-
- Webpack Visualizer
-
-
-
-
- `;
+ compiler.hooks.emit.tapAsync('Visualizer', (compilation, callback) => {
+ let html;
+
+ try {
+ let stats = compilation.getStats().toJson(this.statOpts);
+ let stringifiedStats = JSON.stringify(stats).replace(//g, '>');
+
+ html = `
+
+
+
+
+
+ Webpack Visualizer
+
+
+
+
+
+
+
+
+ `;
+ } catch (error) {
+ console.error('webpack-visualizer-plugin: error creating stats file');
+ if (this.opts.throwOnError) {
+ return callback(error);
+ } else {
+ console.error(error);
+ return callback();
+ }
+ }
let outputFile = path.join(compilation.outputOptions.path, this.opts.filename);
mkdirp(path.dirname(outputFile), (mkdirpErr) => {
if (mkdirpErr) {
- console.log('webpack-visualizer-plugin: error writing stats file');
+ console.error('webpack-visualizer-plugin: error writing stats file');
+ if (this.opts.throwOnError) {
+ return callback(mkdirpErr);
+ } else {
+ return callback();
+ }
}
fs.writeFile(outputFile, html, (err) => {
if (err) {
- console.log('webpack-visualizer-plugin: error writing stats file');
+ console.error('webpack-visualizer-plugin: error writing stats file');
+ if (this.opts.throwOnError) {
+ return callback(err);
+ }
}
callback();
@@ -44,4 +77,4 @@ export default class VisualizerPlugin {
});
});
}
-}
+};
diff --git a/src/shared/buildHierarchy.js b/src/shared/buildHierarchy.js
index 2fb3244..48bded5 100644
--- a/src/shared/buildHierarchy.js
+++ b/src/shared/buildHierarchy.js
@@ -1,11 +1,11 @@
export default function buildHierarchy(modules) {
let maxDepth = 1;
-
+
let root = {
children: [],
- name: 'root'
+ name: 'root',
};
-
+
modules.forEach(function addToTree(module) {
// remove this module if either:
// - index is null
@@ -15,53 +15,52 @@ export default function buildHierarchy(modules) {
if (extractInIdentifier || extractInIssuer || module.index === null) {
return;
}
-
+
let mod = {
id: module.id,
fullName: module.name,
size: module.size,
- reasons: module.reasons
+ reasons: module.reasons,
};
-
+
let depth = mod.fullName.split('/').length - 1;
if (depth > maxDepth) {
maxDepth = depth;
}
-
+
let fileName = mod.fullName;
-
+
let beginning = mod.fullName.slice(0, 2);
if (beginning === './') {
fileName = fileName.slice(2);
}
-
+
getFile(mod, fileName, root);
});
-
+
root.maxDepth = maxDepth;
-
+
return root;
}
-
function getFile(module, fileName, parentTree) {
let charIndex = fileName.indexOf('/');
-
+
if (charIndex !== -1) {
let folder = fileName.slice(0, charIndex);
if (folder === '~') {
folder = 'node_modules';
}
-
+
let childFolder = getChild(parentTree.children, folder);
if (!childFolder) {
childFolder = {
name: folder,
- children: []
+ children: [],
};
parentTree.children.push(childFolder);
}
-
+
getFile(module, fileName.slice(charIndex + 1), childFolder);
} else {
module.name = fileName;
@@ -69,7 +68,6 @@ function getFile(module, fileName, parentTree) {
}
}
-
function getChild(arr, name) {
for (let i = 0; i < arr.length; i++) {
if (arr[i].name === name) {
diff --git a/src/shared/colors.js b/src/shared/colors.js
index 224ae8c..c9c0705 100644
--- a/src/shared/colors.js
+++ b/src/shared/colors.js
@@ -1,21 +1,20 @@
let colors = {
- '__file__': '#db7100',
+ __file__: '#db7100',
//'node_modules': '#599e59',
//'node_modules': '#215E21',
//'node_modules': '#326589', //#26587A',
- '__default__': '#487ea4'
+ __default__: '#487ea4',
};
-
export function getColor(obj) {
let name = obj.name;
let dotIndex = name.indexOf('.');
-
+
if (dotIndex !== -1 && dotIndex !== 0 && dotIndex !== name.length - 1) {
return colors.__file__;
} else if (obj.parent && obj.parent.name === 'node_modules') {
return '#599e59';
}
-
+
return colors[name] || colors.__default__;
}
diff --git a/src/shared/components/breadcrumbs.jsx b/src/shared/components/breadcrumbs.jsx
index c331fce..af850a4 100644
--- a/src/shared/components/breadcrumbs.jsx
+++ b/src/shared/components/breadcrumbs.jsx
@@ -1,7 +1,7 @@
-import React, {PropTypes} from 'react';
+import React from 'react';
+import PropTypes from 'prop-types';
-
-let Breadcrumbs = props => (
+let Breadcrumbs = (props) => (
{props.nodes.map((node, i) => {
let result = ' > ';
@@ -14,7 +14,7 @@ let Breadcrumbs = props => (
);
Breadcrumbs.propTypes = {
- nodes: PropTypes.array
+ nodes: PropTypes.array,
};
export default Breadcrumbs;
diff --git a/src/shared/components/chart-details.jsx b/src/shared/components/chart-details.jsx
index 23d2b48..3d269e6 100644
--- a/src/shared/components/chart-details.jsx
+++ b/src/shared/components/chart-details.jsx
@@ -1,16 +1,16 @@
-import React, {PropTypes} from 'react';
+import React from 'react';
+import PropTypes from 'prop-types';
import formatSize from '../util/formatSize';
-
export default function ChartDetails(props) {
let title, bigText, sizeText;
- let {bundleDetails, details} = props;
+ let { bundleDetails, details } = props;
if (details) {
let rawSize = formatSize(details.size);
if (bundleDetails.actual) {
- let actualSize = formatSize(bundleDetails.actual * details.percentage.replace('%', '') * .01, 0);
+ let actualSize = formatSize(bundleDetails.actual * details.percentage.replace('%', '') * 0.01, 0);
sizeText = `${actualSize} actual | ${rawSize} raw`;
} else {
sizeText = `${rawSize} raw`;
@@ -18,7 +18,6 @@ export default function ChartDetails(props) {
title = details.name;
bigText = details.percentage;
-
} else if (bundleDetails.assetName) {
title = bundleDetails.assetName;
if (bundleDetails.type === 'collection') {
@@ -36,7 +35,7 @@ export default function ChartDetails(props) {
}
return (
-
+
{title}
{bigText}
{sizeText &&
{sizeText}
}
@@ -47,5 +46,5 @@ export default function ChartDetails(props) {
ChartDetails.propTypes = {
bundleDetails: PropTypes.object,
details: PropTypes.object,
- topMargin: PropTypes.number
+ topMargin: PropTypes.number,
};
diff --git a/src/shared/components/chart-with-details.jsx b/src/shared/components/chart-with-details.jsx
index 74b0d02..4644bc5 100644
--- a/src/shared/components/chart-with-details.jsx
+++ b/src/shared/components/chart-with-details.jsx
@@ -1,9 +1,9 @@
-import React, {PropTypes} from 'react';
+import React from 'react';
+import PropTypes from 'prop-types';
import Chart from './chart';
import ChartDetails from './chart-details';
import Breadcrumbs from './breadcrumbs';
-
export default class ChartWithDetails extends React.Component {
constructor(props) {
super(props);
@@ -11,33 +11,35 @@ export default class ChartWithDetails extends React.Component {
this.state = {
breadcrumbNodes: [],
hoverDetails: null,
- paddingDiff: 0
+ paddingDiff: 0,
};
-
- this.onChartHover = this.onChartHover.bind(this);
- this.onChartUnhover = this.onChartUnhover.bind(this);
- this.onChartRender = this.onChartRender.bind(this);
}
- onChartHover(details) {
+ static propTypes = {
+ breadcrumbNodes: PropTypes.array,
+ bundleDetails: PropTypes.object,
+ chartData: PropTypes.object,
+ };
+
+ onChartHover = (details) => {
this.setState({
hoverDetails: details,
- breadcrumbNodes: details.ancestorArray
+ breadcrumbNodes: details.ancestorArray,
});
- }
+ };
- onChartUnhover() {
+ onChartUnhover = () => {
this.setState({
hoverDetails: null,
- breadcrumbNodes: []
+ breadcrumbNodes: [],
});
- }
+ };
- onChartRender(details) {
+ onChartRender = (details) => {
this.setState({
- paddingDiff: details.removedTopPadding
+ paddingDiff: details.removedTopPadding,
});
- }
+ };
render() {
let chartAreaClass = 'chart';
@@ -52,7 +54,11 @@ export default class ChartWithDetails extends React.Component {
return (
-
+
{
let details = createVisualization({
- svgElement: this.refs.svg,
+ svgElement: this.svg.current,
root,
onHover: this.props.onHover,
- onUnhover: this.props.onUnhover
+ onUnhover: this.props.onUnhover,
});
-
+
if (this.props.onRender) {
this.props.onRender(details);
}
- },
-
+ };
+
render() {
if (!this.props.data) {
return null;
}
-
- return ;
+
+ return ;
}
-});
+}
diff --git a/src/shared/components/footer.jsx b/src/shared/components/footer.jsx
index 19ce531..af7318d 100644
--- a/src/shared/components/footer.jsx
+++ b/src/shared/components/footer.jsx
@@ -1,20 +1,41 @@
import React from 'react';
+import PropTypes from 'prop-types';
+export default function Footer(props) {
+ return (
+
-);
+Footer.propTypes = {
+ children: PropTypes.node,
+};
diff --git a/src/shared/createVisualization.js b/src/shared/createVisualization.js
index 046702e..dbaf2c1 100644
--- a/src/shared/createVisualization.js
+++ b/src/shared/createVisualization.js
@@ -1,108 +1,106 @@
import d3 from 'd3';
-import {getColor} from './colors';
-import {markDuplicates, getAllChildren, getAncestors} from './partitionedDataUtils';
-
+import { getColor } from './colors';
+import { markDuplicates, getAllChildren, getAncestors } from './partitionedDataUtils';
const FADE_OPACITY = 0.5;
let paths, vis, totalSize;
-
-export default function createVisualization({svgElement, root, onHover, onUnhover}) {
- let chartSize = (root.maxDepth > 9) ? 950 : 750;
+export default function createVisualization({ svgElement, root, onHover, onUnhover }) {
+ let chartSize = root.maxDepth > 9 ? 950 : 750;
let radius = Math.min(chartSize, chartSize) / 2;
-
- let partition = d3.layout.partition()
+ let partition = d3.layout
+ .partition()
.size([2 * Math.PI, radius * radius])
- .value(d => d.size);
+ .value((d) => d.size);
-
- let arc = d3.svg.arc()
- .startAngle(d => d.x)
- .endAngle(d => d.x + d.dx)
+ let arc = d3.svg
+ .arc()
+ .startAngle((d) => d.x)
+ .endAngle((d) => d.x + d.dx)
//.innerRadius(d => d.y / 400 + 60)
//.outerRadius(d => (d.y + d.dy) / 400 + 60);
- .innerRadius(d => Math.sqrt(d.y))
- .outerRadius(d => Math.sqrt(d.y + d.dy));
-
+ .innerRadius((d) => Math.sqrt(d.y))
+ .outerRadius((d) => Math.sqrt(d.y + d.dy));
if (vis) {
svgElement.innerHTML = '';
}
-
// Filter out very small nodes
- let nodes = partition.nodes(root).filter(d => d.dx > 0.005); // 0.005 radians
+ let nodes = partition.nodes(root).filter((d) => d.dx > 0.005); // 0.005 radians
markDuplicates(nodes);
-
- vis = d3.select(svgElement)
+ vis = d3
+ .select(svgElement)
.attr('width', chartSize)
.attr('height', chartSize)
.append('svg:g')
.attr('id', 'svgWrapper')
.attr('transform', `translate(${chartSize / 2}, ${chartSize / 2})`);
-
- paths = vis.data([root]).selectAll('path')
+ paths = vis
+ .data([root])
+ .selectAll('path')
.data(nodes)
.enter()
.append('svg:path')
- .attr('display', d => (d.depth ? null : 'none'))
+ .attr('display', (d) => (d.depth ? null : 'none'))
.attr('d', arc)
.attr('fill-rule', 'evenodd')
- .style('stroke', d => (d.duplicate) ? '#000' : '')
- .style('fill', d => getColor(d))
+ .style('stroke', (d) => (d.duplicate ? '#000' : ''))
+ .style('fill', (d) => getColor(d))
.style('opacity', 1)
- .on('mouseover', object => {
+ .on('mouseover', (object) => {
mouseover(object, onHover);
});
totalSize = paths.node().__data__.value;
-
let svgWrapper = vis[0][0];
let chart = svgElement.parentNode;
let visHeight = svgWrapper.getBoundingClientRect().height;
- let topPadding = (svgWrapper.getBoundingClientRect().top + window.scrollY) - (d3.select(chart)[0][0].getBoundingClientRect().top + window.scrollY);
+ let topPadding =
+ svgWrapper.getBoundingClientRect().top +
+ window.scrollY -
+ (d3.select(chart)[0][0].getBoundingClientRect().top + window.scrollY);
d3.select(svgElement).attr('height', visHeight);
- vis.attr('transform', `translate(${chartSize / 2}, ${(chartSize / 2) - topPadding})`);
+ vis.attr('transform', `translate(${chartSize / 2}, ${chartSize / 2 - topPadding})`);
d3.select(chart.querySelector('.details')).style('margin-top', `${-topPadding}px`);
-
- d3.select(svgWrapper).on('mouseleave', object => {
+ d3.select(svgWrapper).on('mouseleave', (object) => {
mouseleave(object, onUnhover);
});
return {
removedTopPadding: topPadding,
- vis
+ vis,
};
}
-
function mouseover(object, callback) {
let childrenArray = getAllChildren(object);
let ancestorArray = getAncestors(object);
// Fade all the segments.
paths.style({
- 'opacity': FADE_OPACITY,
- 'stroke-width': FADE_OPACITY
+ opacity: FADE_OPACITY,
+ 'stroke-width': FADE_OPACITY,
});
// Highlight only those that are children of the current segment.
- paths.filter(node => childrenArray.indexOf(node) >= 0)
+ paths
+ .filter((node) => childrenArray.indexOf(node) >= 0)
.style({
'stroke-width': 2,
- 'opacity': 1
+ opacity: 1,
});
- let percentage = (100 * object.value / totalSize).toFixed(1);
+ let percentage = ((100 * object.value) / totalSize).toFixed(1);
let percentageString = percentage + '%';
if (percentage < 0.1) {
percentageString = '< 0.1%';
@@ -112,14 +110,14 @@ function mouseover(object, callback) {
ancestorArray,
name: object.name,
size: object.value,
- percentage: percentageString
+ percentage: percentageString,
});
}
function mouseleave(object, callback) {
paths.style({
- 'opacity': 1,
- 'stroke-width': 1
+ opacity: 1,
+ 'stroke-width': 1,
});
callback();
diff --git a/src/shared/partitionedDataUtils.js b/src/shared/partitionedDataUtils.js
index c65eae5..a610c09 100644
--- a/src/shared/partitionedDataUtils.js
+++ b/src/shared/partitionedDataUtils.js
@@ -1,48 +1,45 @@
-
export function getAncestors(node) {
let ancestors = [];
let current = node;
-
+
while (current.parent) {
ancestors.unshift(current);
current = current.parent;
}
-
+
return ancestors;
}
-
export function getAllChildren(rootNode) {
let allChildren = [];
-
- let getChildren = function(node) {
+
+ let getChildren = function (node) {
allChildren.push(node);
-
+
if (node.children) {
- node.children.forEach(child => {
+ node.children.forEach((child) => {
getChildren(child);
});
}
};
-
+
getChildren(rootNode);
-
+
return allChildren;
}
-
export function markDuplicates(nodes) {
let fullNameList = {};
-
- nodes.forEach(item => {
+
+ nodes.forEach((item) => {
if (!item.fullName) {
return;
}
-
+
let lastIndex = item.fullName.lastIndexOf('~');
if (lastIndex !== -1) {
let fullName = item.fullName.substring(lastIndex);
-
+
if (fullName in fullNameList) {
item.duplicate = true;
fullNameList[fullName].duplicate = true;
diff --git a/src/shared/util/dragdrop.js b/src/shared/util/dragdrop.js
index 3fd6eb0..0026a66 100644
--- a/src/shared/util/dragdrop.js
+++ b/src/shared/util/dragdrop.js
@@ -1,21 +1,19 @@
-
-export default function addDragDrop({el, onDragStart, onDragEnd, callback}) {
+export default function addDragDrop({ el, onDragStart, onDragEnd, callback }) {
el.addEventListener('dragenter', onDragStart);
el.addEventListener('dragleave', onDragEnd);
el.addEventListener('dragover', onDragOver);
el.addEventListener('drop', onDrop);
-
function onDragOver(ev) {
ev.preventDefault();
}
-
+
function onDrop(ev) {
ev.preventDefault();
-
+
let file = ev.dataTransfer.files[0];
-
+
onDragEnd();
callback(file);
}
-}
\ No newline at end of file
+}
diff --git a/src/shared/util/formatSize.js b/src/shared/util/formatSize.js
index b9abbd0..80feb18 100644
--- a/src/shared/util/formatSize.js
+++ b/src/shared/util/formatSize.js
@@ -1,12 +1,11 @@
-
export default function formatSize(size, precision = 1) {
let kb = {
label: 'k',
- value: 1024
+ value: 1024,
};
let mb = {
label: 'M',
- value: 1024 * 1024
+ value: 1024 * 1024,
};
let denominator;
@@ -14,7 +13,7 @@ export default function formatSize(size, precision = 1) {
denominator = mb;
} else {
denominator = kb;
- if (size < (kb.value * 0.92) && precision === 0) {
+ if (size < kb.value * 0.92 && precision === 0) {
precision = 1;
}
}
diff --git a/src/shared/util/readFile.js b/src/shared/util/readFile.js
index 6a1caf0..1d021b1 100644
--- a/src/shared/util/readFile.js
+++ b/src/shared/util/readFile.js
@@ -1,12 +1,11 @@
-
export default function readFile(file, callback) {
let reader = new FileReader();
-
- reader.onloadend = ev => {
+
+ reader.onloadend = (ev) => {
if (ev.target.readyState === FileReader.DONE) {
callback(reader.result);
}
};
-
+
reader.readAsText(file);
}
diff --git a/src/shared/util/stat-utils.js b/src/shared/util/stat-utils.js
index f420d86..8af4b68 100644
--- a/src/shared/util/stat-utils.js
+++ b/src/shared/util/stat-utils.js
@@ -1,42 +1,38 @@
-
export const ERROR_CHUNK_MODULES = `Unfortunately, it looks like your stats don't include chunk-specific module data. See below for details.`;
-
export function getAssetsData(assets, chunks) {
let chunksMap = {};
- chunks.forEach(chunk => {
+ chunks.forEach((chunk) => {
chunksMap[chunk.id] = chunk;
});
return assets
- .filter(asset => asset.name.indexOf('.js') === asset.name.length - 3)
- .map(asset => {
+ .filter((asset) => asset.name.indexOf('.js') === asset.name.length - 3)
+ .map((asset) => {
let chunkIndex = asset.chunks[0];
return {
...asset,
- chunk: chunksMap[chunkIndex]
+ chunk: chunksMap[chunkIndex],
};
});
}
-
-export function getBundleDetails({assets, chunks, selectedAssetIndex}) {
-
+export function getBundleDetails({ assets, selectedAssetIndex }) {
if (selectedAssetIndex === 0) {
if (assets.length === 1) {
return {
type: 'normal',
assetName: assets[0].name,
actual: assets[0].size,
- raw: assets.reduce((total, thisAsset) => total + thisAsset.chunk.size, 0)
+ raw: assets.reduce((total, thisAsset) => total + thisAsset.chunk.size, 0),
};
} else {
return {
type: 'collection',
assetName: 'All Modules',
actual: '',
- raw: ''
+ raw: '',
};
}
} else {
@@ -46,8 +42,7 @@ export function getBundleDetails({assets, chunks, selectedAssetIndex}) {
type: 'normal',
assetName: asset.name,
actual: asset.size,
- raw: asset.chunk.size
+ raw: asset.chunk.size,
};
}
-
}
diff --git a/src/site/app.jsx b/src/site/app.jsx
index c103504..5586a00 100644
--- a/src/site/app.jsx
+++ b/src/site/app.jsx
@@ -1,54 +1,58 @@
-import React from 'react';
+import React, { createRef } from 'react';
import ChartWithDetails from '../shared/components/chart-with-details';
import Footer from '../shared/components/footer';
import addDragDrop from '../shared/util/dragdrop';
import readFile from '../shared/util/readFile';
import formatSize from '../shared/util/formatSize';
-import {getAssetsData, getBundleDetails, ERROR_CHUNK_MODULES} from '../shared/util/stat-utils';
+import { getAssetsData, getBundleDetails, ERROR_CHUNK_MODULES } from '../shared/util/stat-utils';
import buildHierarchy from '../shared/buildHierarchy';
+export default class App extends React.Component {
+ constructor(props) {
+ super(props);
-export default React.createClass({
- getInitialState() {
- return {
+ this.state = {
assets: [],
needsUpload: true,
dragging: false,
chartData: null,
- selectedAssetIndex: 0
+ selectedAssetIndex: 0,
};
- },
+
+ this.UploadArea = createRef();
+ this.FileInput = createRef();
+ }
componentDidMount() {
addDragDrop({
- el: this.refs.UploadArea,
- callback: file => {
+ el: this.UploadArea.current,
+ callback: (file) => {
readFile(file, this.handleFileUpload);
},
onDragStart: () => {
this.setState({
- dragging: true
+ dragging: true,
});
},
onDragEnd: () => {
this.setState({
- dragging: false
+ dragging: false,
});
- }
+ },
});
- },
+ }
- uploadAreaClick() {
+ uploadAreaClick = () => {
if (this.state.needsUpload) {
- this.refs.FileInput.click();
+ this.FileInput.current.click();
}
- },
+ };
- onFileChange(ev) {
+ onFileChange = (ev) => {
readFile(ev.target.files[0], this.handleFileUpload);
- },
+ };
- handleFileUpload(jsonText) {
+ handleFileUpload = (jsonText) => {
let stats = JSON.parse(jsonText);
let assets = getAssetsData(stats.assets, stats.chunks);
@@ -57,13 +61,13 @@ export default React.createClass({
chartData: buildHierarchy(stats.modules),
needsUpload: false,
selectedAssetIndex: 0,
- stats
+ stats,
});
- },
+ };
- loadDemo() {
+ loadDemo = () => {
this.setState({
- demoLoading: true
+ demoLoading: true,
});
let request = new XMLHttpRequest();
@@ -71,7 +75,7 @@ export default React.createClass({
request.onload = () => {
this.setState({
- demoLoading: false
+ demoLoading: false,
});
if (request.status >= 200 && request.status < 400) {
@@ -80,9 +84,9 @@ export default React.createClass({
};
request.send();
- },
+ };
- onAssetChange(ev) {
+ onAssetChange = (ev) => {
let selectedAssetIndex = Number(ev.target.value);
let modules, chartData, error;
@@ -102,28 +106,23 @@ export default React.createClass({
this.setState({
chartData,
error,
- selectedAssetIndex
+ selectedAssetIndex,
});
- },
+ };
- renderUploadArea(uploadAreaClass) {
+ renderUploadArea = (uploadAreaClass) => {
if (this.state.needsUpload) {
return (
-
+
);
}
- },
+ };
render() {
let demoButton, assetList;
@@ -142,13 +141,17 @@ export default React.createClass({
demoClass += ' demoLoading';
}
- demoButton =
;
+ demoButton = (
+
+ );
}
- if (this.state.stats){
+ if (this.state.stats) {
bundleDetails = getBundleDetails({
assets: this.state.assets,
- selectedAssetIndex: this.state.selectedAssetIndex
+ selectedAssetIndex: this.state.selectedAssetIndex,
});
}
@@ -157,7 +160,11 @@ export default React.createClass({
);
@@ -177,14 +184,29 @@ export default React.createClass({
How do I get stats JSON from webpack?
- webpack --json > stats.json
- If you're customizing your stats output or using webpack-stats-plugin, be sure to set chunkModules
to true
(see here for an example).
+
+ webpack --json > stats.json
+
+
+ If you're customizing your stats output or using webpack-stats-plugin, be sure to set{' '}
+ chunkModules
to true
(see{' '}
+
+ here
+ {' '}
+ for an example).
+
Try the Plugin!
- This tool is also available as a webpack plugin. See here for usage details.
- npm install webpack-visualizer-plugin
+
+ This tool is also available as a webpack plugin. See{' '}
+ here for usage
+ details.
+
+
+ npm install webpack-visualizer-plugin
+
);
}
-});
+}
diff --git a/src/site/index.ejs b/src/site/index.ejs
new file mode 100644
index 0000000..0a8eac1
--- /dev/null
+++ b/src/site/index.ejs
@@ -0,0 +1,33 @@
+
+
+
+
+
+ Webpack Visualizer
+
+
+
+
+
+
+
+
diff --git a/src/site/index.html.js b/src/site/index.html.js
deleted file mode 100644
index 53d6c8f..0000000
--- a/src/site/index.html.js
+++ /dev/null
@@ -1,25 +0,0 @@
-
-export default function(cfg) {
- return (
-`
-
-
- Webpack Visualizer
-
-
-
-
-
- ${cfg.appHTML}
-
-
-`
- );
-}
diff --git a/src/site/main.jsx b/src/site/main.jsx
index 2af0d58..91ba57d 100644
--- a/src/site/main.jsx
+++ b/src/site/main.jsx
@@ -2,5 +2,6 @@ import React from 'react';
import ReactDOM from 'react-dom';
import App from './app';
+import '../shared/style.css';
ReactDOM.render(, document.getElementById('App'));
diff --git a/src/site/serverRender.js b/src/site/serverRender.js
deleted file mode 100644
index 0c8c5b5..0000000
--- a/src/site/serverRender.js
+++ /dev/null
@@ -1,12 +0,0 @@
-import fs from 'fs';
-import React from 'react';
-import ReactDOM from 'react-dom/server';
-import App from './app';
-import createHTMLString from './index.html.js';
-
-
-let pageHTML = createHTMLString({
- appHTML: ReactDOM.renderToString()
-});
-
-fs.writeFile('dist-site/index.html', pageHTML);
diff --git a/test/stats-demo.json b/stats_demo/stats-demo.json
similarity index 100%
rename from test/stats-demo.json
rename to stats_demo/stats-demo.json
diff --git a/test/stats-multiple-without-chunkModules.json b/stats_demo/stats-multiple-without-chunkModules.json
similarity index 100%
rename from test/stats-multiple-without-chunkModules.json
rename to stats_demo/stats-multiple-without-chunkModules.json
diff --git a/test/stats-multiple.json b/stats_demo/stats-multiple.json
similarity index 100%
rename from test/stats-multiple.json
rename to stats_demo/stats-multiple.json
diff --git a/test/stats-simple.json b/stats_demo/stats-simple.json
similarity index 100%
rename from test/stats-simple.json
rename to stats_demo/stats-simple.json
diff --git a/test/stats-single.json b/stats_demo/stats-single.json
similarity index 100%
rename from test/stats-single.json
rename to stats_demo/stats-single.json
diff --git a/webpack.base.js b/webpack.base.js
deleted file mode 100644
index d4c04bd..0000000
--- a/webpack.base.js
+++ /dev/null
@@ -1,21 +0,0 @@
-'use strict';
-
-var path = require('path');
-var webpack = require('webpack');
-
-
-module.exports = {
- context: __dirname,
- module: {
- loaders: [
- {
- test: /\.(js|jsx)$/,
- loaders: ['babel'],
- exclude: /node_modules/
- }
- ]
- },
- resolve: {
- extensions: ['', '.js', '.jsx']
- }
-};
diff --git a/webpack.dev.js b/webpack.dev.js
deleted file mode 100644
index dc18f37..0000000
--- a/webpack.dev.js
+++ /dev/null
@@ -1,24 +0,0 @@
-'use strict';
-
-var path = require('path');
-var webpack = require('webpack');
-var merge = require('merge');
-
-var baseConfig = require('./webpack.base.js');
-
-var devConfig = {
- entry: './src/site/main',
- output: {
- filename: 'build.js'
- },
- devtool: 'source-map', // @see http://webpack.github.io/docs/configuration.html#devtool
- devServer: {
- inline: true,
- contentBase: 'dist-site',
- host: process.env.IP,
- port: process.env.PORT
- }
-};
-
-
-module.exports = merge({}, baseConfig, devConfig);
diff --git a/webpack.prod.js b/webpack.prod.js
deleted file mode 100644
index f3de829..0000000
--- a/webpack.prod.js
+++ /dev/null
@@ -1,24 +0,0 @@
-'use strict';
-
-var path = require('path');
-var webpack = require('webpack');
-var merge = require('merge');
-
-var baseConfig = require('./webpack.base.js');
-
-
-var config = merge(true, baseConfig);
-
-if (!config.plugins) config.plugins = [];
-
-config.plugins = config.plugins.concat([
- new webpack.DefinePlugin({
- 'process.env': {
- NODE_ENV: JSON.stringify('production')
- }
- }),
- new webpack.optimize.UglifyJsPlugin({compress: {warnings: false}}),
- new webpack.NoErrorsPlugin()
-]);
-
-module.exports = config;
diff --git a/webpack/plugin/webpack.common.js b/webpack/plugin/webpack.common.js
new file mode 100644
index 0000000..ea8ab12
--- /dev/null
+++ b/webpack/plugin/webpack.common.js
@@ -0,0 +1,56 @@
+const path = require('path');
+const webpack = require('webpack');
+const ESLintPlugin = require('eslint-webpack-plugin');
+const MiniCssExtractPlugin = require('mini-css-extract-plugin');
+
+const rules = [
+ {
+ test: /\.(js|jsx)$/,
+ exclude: /node_modules/,
+ use: {
+ loader: 'babel-loader',
+ options: {
+ comments: false,
+ sourceMaps: true,
+ presets: ['@babel/preset-env', '@babel/preset-react'],
+ plugins: [
+ [
+ '@babel/plugin-proposal-class-properties',
+ {
+ loose: true,
+ },
+ ],
+ ],
+ },
+ },
+ },
+ {
+ test: /\.css$/,
+ use: [MiniCssExtractPlugin.loader, 'css-loader'],
+ },
+];
+
+module.exports = {
+ entry: {
+ main: path.resolve(__dirname, '..', '..', 'src', 'plugin', 'main.jsx'),
+ },
+ resolve: {
+ extensions: ['.js', '.jsx'],
+ },
+ module: { rules },
+ plugins: [
+ new ESLintPlugin({
+ extensions: ['.js', '.jsx'],
+ }),
+
+ new webpack.DefinePlugin({
+ 'process.env': {
+ NODE_ENV: JSON.stringify('development'),
+ },
+ }),
+
+ new MiniCssExtractPlugin({
+ filename: 'style.css',
+ }),
+ ],
+};
diff --git a/webpack/plugin/webpack.dev.js b/webpack/plugin/webpack.dev.js
new file mode 100644
index 0000000..fa480d7
--- /dev/null
+++ b/webpack/plugin/webpack.dev.js
@@ -0,0 +1,37 @@
+const merge = require('webpack-merge').merge;
+const HtmlWebpackPlugin = require('html-webpack-plugin');
+var common = require('./webpack.common.js');
+const demoStats = require('../../demo_stats/stats-demo.json');
+const stringifiedStats = JSON.stringify(demoStats).replace(//g, '>');
+
+module.exports = merge(common, {
+ mode: 'development',
+ devtool: 'source-map',
+ plugins: [
+ new HtmlWebpackPlugin({
+ templateContent: `
+
+
+
+
+
+ Webpack Visualizer
+
+
+
+
+
+
+
+ `,
+ minify: false,
+ inject: true,
+ }),
+ ],
+ devServer: {
+ inline: true,
+ host: process.env.IP || 'localhost',
+ port: process.env.PORT || '3000',
+ open: true,
+ },
+});
diff --git a/webpack/plugin/webpack.prod.js b/webpack/plugin/webpack.prod.js
new file mode 100644
index 0000000..20e1c23
--- /dev/null
+++ b/webpack/plugin/webpack.prod.js
@@ -0,0 +1,11 @@
+const path = require('path');
+const merge = require('webpack-merge').merge;
+var common = require('./webpack.common.js');
+
+module.exports = merge(common, {
+ mode: 'production',
+ output: {
+ path: path.resolve(__dirname, '..', '..', 'lib'),
+ filename: 'main.js',
+ },
+});
diff --git a/webpack/site/webpack.common.js b/webpack/site/webpack.common.js
new file mode 100644
index 0000000..74a0300
--- /dev/null
+++ b/webpack/site/webpack.common.js
@@ -0,0 +1,42 @@
+const path = require('path');
+const ESLintPlugin = require('eslint-webpack-plugin');
+
+const rules = [
+ {
+ test: /\.(js|jsx)$/,
+ exclude: /node_modules/,
+ use: {
+ loader: 'babel-loader',
+ options: {
+ comments: true,
+ sourceMaps: true,
+ presets: ['@babel/preset-env', '@babel/preset-react'],
+ plugins: [
+ [
+ '@babel/plugin-proposal-class-properties',
+ {
+ loose: true,
+ },
+ ],
+ ],
+ },
+ },
+ },
+ {
+ test: /\.css$/,
+ use: ['style-loader', 'css-loader'],
+ },
+];
+
+module.exports = {
+ entry: [path.resolve(__dirname, '..', '..', 'src', 'site', 'main.jsx')],
+ module: { rules },
+ plugins: [
+ new ESLintPlugin({
+ extensions: ['.js', '.jsx'],
+ }),
+ ],
+ resolve: {
+ extensions: ['.js', '.jsx'],
+ },
+};
diff --git a/webpack/site/webpack.dev.js b/webpack/site/webpack.dev.js
new file mode 100644
index 0000000..149104b
--- /dev/null
+++ b/webpack/site/webpack.dev.js
@@ -0,0 +1,30 @@
+const path = require('path');
+const webpack = require('webpack');
+const merge = require('webpack-merge').merge;
+const HtmlWebpackPlugin = require('html-webpack-plugin');
+const common = require('./webpack.common.js');
+
+module.exports = merge(common, {
+ mode: 'development',
+ devtool: 'source-map',
+ plugins: [
+ new webpack.DefinePlugin({
+ 'process.env': {
+ NODE_ENV: JSON.stringify('development'),
+ },
+ }),
+
+ new HtmlWebpackPlugin({
+ template: path.resolve(__dirname, '..', '..', 'src', 'site', 'index.ejs'),
+ minify: false,
+ inject: true,
+ }),
+ ],
+ devServer: {
+ inline: true,
+ contentBase: 'dist-site',
+ host: process.env.IP || 'localhost',
+ port: process.env.PORT || '3000',
+ open: true,
+ },
+});
diff --git a/webpack/site/webpack.prod.js b/webpack/site/webpack.prod.js
new file mode 100644
index 0000000..aedd279
--- /dev/null
+++ b/webpack/site/webpack.prod.js
@@ -0,0 +1,30 @@
+const path = require('path');
+const webpack = require('webpack');
+const merge = require('webpack-merge').merge;
+const common = require('./webpack.common.js');
+const { CleanWebpackPlugin } = require('clean-webpack-plugin');
+const HtmlWebpackPlugin = require('html-webpack-plugin');
+
+module.exports = merge(common, {
+ mode: 'production',
+ output: {
+ path: path.resolve(__dirname, '..', '..', 'dist-site'),
+ filename: 'build.js',
+ },
+
+ plugins: [
+ new CleanWebpackPlugin(),
+
+ new webpack.DefinePlugin({
+ 'process.env': {
+ NODE_ENV: JSON.stringify('production'),
+ },
+ }),
+
+ new HtmlWebpackPlugin({
+ template: path.resolve(__dirname, '..', '..', 'src', 'site', 'index.ejs'),
+ minify: true,
+ inject: true,
+ }),
+ ],
+});