webpack

安装

1
2
3
$ mkdir webpack-demo && cd webpack-demo
$ npm init -y
$ npm install --save-dev webpack

lodash

1
2
3
4
5
6
7
8
import _ from 'lodash';
function component() {
var element = document.createElement('div');
// Lodash, now imported by this script
element.innerHTML = _.join(['Hello', 'webpack'], ' ');
return element;
}
document.body.appendChild(component());
1
2
3
4
5
6
7
8
<html>
<head>
<title>Getting Started</title>
</head>
<body>
<script src="bundle.js"></script>
</body>
</html>
1
2
3
4
# npx webpack 等于 ./node_modules/.bin/webpack
$ npm install --save lodash
$ npx webpack src/index.js dist/bundle.js # 打包
$ npx webpack --config webpack.config.js # 加了 webpack.config.js 后

Loading CSS

1
$ npm install --save-dev style-loader css-loader
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
},
module: {
rules: [
{
test: /\.css$/,
use: [
'style-loader',
'css-loader'
]
}
]
}
};

Loading Images、Fonts

优化、压缩图片(image-webpack-loader、url-loader)

1
$ npm install --save-dev file-loader
1
2
3
4
import Icon from './beauty.jpg';

var myIcon = new Image();
myIcon.src = Icon;

Loading Data

1
$ npm install --save-dev csv-loader xml-loader
1
import Data from './data.xml';

html-webpack-plugin

为了解决打包后 index.html 需要经常改变的问题

1
$ npm install --save-dev html-webpack-plugin

1
2
3
4
5
6
7
8
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
plugins: [
new HtmlWebpackPlugin({
title: 'Output Management'
})
]
};

clean-webpack-plugin

清理 /dist 目录

1
$ npm install --save-dev clean-webpack-plugin

1
2
3
4
5
6
const CleanWebpackPlugin = require('clean-webpack-plugin');
module.exports = {
plugins: [
new CleanWebpackPlugin(['dist'])
]
};

Using source maps

1
2
3
module.exports = {
devtool: 'inline-source-map'
};

Choosing a Development Tool

1、webpack’s Watch Mode
缺点是要刷新

1
2
3
4
5
{
"scripts": {
"watch": "webpack --watch"
}
}

1
$ npm run watch

2、webpack-dev-server

1
$ npm install --save-dev webpack-dev-server

package.json

1
2
3
4
5
{
"scripts": {
"start": "webpack-dev-server --open"
}
}

webpack.config.js

1
2
3
4
5
module.exports = {
devServer: {
contentBase: './dist'
}
};

1
$ npm start

3、webpack-dev-middleware
从 express 中启动调试,缺点要刷新

1
$ npm install --save-dev express webpack-dev-middleware

package.json

1
2
3
4
5
{
"scripts": {
"server": "node server.js"
}
}

webpack.config.js

1
2
3
4
5
module.exports = {
output: {
publicPath: '/'
}
};

server.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const express = require('express');
const webpack = require('webpack');
const webpackDevMiddleware = require('webpack-dev-middleware');

const app = express();
const config = require('./webpack.config.js');
const compiler = webpack(config);

// Tell express to use the webpack-dev-middleware and use the webpack.config.js
// configuration file as a base.
app.use(webpackDevMiddleware(compiler, {
publicPath: config.output.publicPath
}));

// Serve the files on port 3000.
app.listen(3000, function () {
console.log('Example app listening on port 3000!\n');
});

1
$ npm run server

HMR

可能不起效,解决办法更新全局 webpack-dev-server

webpack.config.js

1
2
3
4
5
6
7
8
9
10
module.exports = {
devServer: {
contentBase: './dist',
hot: true
},
plugins: [
new webpack.NamedModulesPlugin(),
new webpack.HotModuleReplacementPlugin()
]
};

index.js

1
2
3
4
5
6
if (module.hot) {
module.hot.accept('./print.js', function () {
console.log('Accepting the updated printMe module!');
printMe();
})
}

HRM Via the Node.js API

webpack.config.js 去掉 devServer 选项

server.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const webpackDevServer = require('webpack-dev-server');
const webpack = require('webpack');

const config = require('./webpack.config.js');
const options = {
contentBase: './dist',
hot: true,
host: 'localhost'
};

webpackDevServer.addDevServerEntrypoints(config, options);
const compiler = webpack(config);
const server = new webpackDevServer(compiler, options);

server.listen(5000, 'localhost', () => {
console.log('dev server listening on port 5000');
});

HMR with Stylesheets

1
$ npm install --save-dev style-loader css-loader

webpack.config.js

1
2
3
4
5
6
7
8
9
10
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
}
]
}
};

Tree Shaking

1
$ npm install --save-dev uglifyjs-webpack-plugin

uglifyjs-webpack-plugin 替代方案
BabelMinifyWebpackPlugin
ClosureCompilerPlugin

webpack.config.js

1
2
3
4
5
6
const UglifyJSPlugin = require('uglifyjs-webpack-plugin');
module.exports = {
plugins: [
new UglifyJSPlugin()
]
};

webpack-merge

1
$ npm install --save-dev webpack-merge

webpack.common.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const path = require('path');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
entry: {
app: './src/index.js'
},
plugins: [
new CleanWebpackPlugin(['dist']),
new HtmlWebpackPlugin({
title: 'Production'
})
],
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist')
}
};

webpack.dev.js

1
2
3
4
5
6
7
8
9
const merge = require('webpack-merge');
const common = require('./webpack.common.js');

module.exports = merge(common, {
devtool: 'inline-source-map',
devServer: {
contentBase: './dist'
}
});

webpack.prod.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const webpack = require('webpack');
const merge = require('webpack-merge');
const UglifyJSPlugin = require('uglifyjs-webpack-plugin');
const common = require('./webpack.common.js');

module.exports = merge(common, {
devtool: 'source-map',
plugins: [
new UglifyJSPlugin({
sourceMap: true
}),
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify('production')
})
]
});

Specify the Environment

注意 webpack.config.js 中不能判断 process.env.NODE_ENV === ‘production’

webpack.prod.js

1
2
3
4
5
6
7
module.exports = {
plugins: [
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify('production')
})
]
};

index.js

1
2
3
if (process.env.NODE_ENV !== 'production') {
console.log('Looks like we are in development mode!');
}

Code Splitting

1、Entry Points: Manually split code using entry configuration.
缺点:各个 bundle 之间存在重复代码

2、revent Duplication: Use the CommonsChunkPlugin to dedupe and split chunks.
用 CommonsChunkPlugin 解决
webpack.config.js

1
2
3
4
5
6
7
module.exports = {
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: 'common' // Specify the common bundle's name.
})
]
};

3、Dynamic Imports: Split code via inline function calls within modules.
webpack.config.js

1
2
3
4
5
module.exports = {
output: {
chunkFilename: '[name].bundle.js'
}
};

index.js

1
2
3
4
5
6
7
8
9
10
11
12
function component() {
var element = document.createElement('div');
element.innerHTML = 'Hello world';
element.onclick = function () {
import(/* webpackChunkName: "lodash" */ 'lodash').then(_ => {
// Lodash, now imported by this script
element.innerHTML = _.join(['Hello', 'webpack'], ' ');
}).catch(error => 'An error occurred while loading the component');
};
return element;
}
document.body.appendChild(component());

Caching

两种产生唯一 chunkhash 的方式:NamedModulesPlugin(开发环境,易读) 和
HashedModuleIdsPlugin(效率更高)
webpack.config.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
module.exports = {
entry: {
main: './src/index.js',
vendor: [ // 第三方包单独打成一个包
'lodash'
]
},
plugins: [
new webpack.HashedModuleIdsPlugin(),
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor'
}),
new webpack.optimize.CommonsChunkPlugin({
name: 'manifest' // 项目信息?
})
],
output: {
filename: '[name].[chunkhash].js', // 使用 chunkhash 而不是 hash
path: path.resolve(__dirname, 'dist')
}
};

Shimming

全局变量

1
2
3
4
5
6
7
module.exports = {
plugins: [
new webpack.ProvidePlugin({
join: ['lodash', 'join']
})
]
};

全局 this 指向 window,会导致 index.js 的 import 报错

1
$ npm install --save-dev imports-loader

1
2
3
4
5
6
7
8
9
10
module.exports = {
module: {
rules: [
{
test: require.resolve('./src/index.js'),
use: 'imports-loader?this=>window'
}
]
}
};

导入非 ES5 模块,使用 exports-loader

1
$ npm install --save-dev exports-loader

1
2
3
4
5
6
7
8
9
10
module.exports = {
module: {
rules: [
{
test: require.resolve('./src/globals.js'),
use: 'exports-loader?file,parse=helpers.parse'
}
]
}
};

Loading Polyfills

1
2
$ npm install --save babel-polyfill
$ npm install --save whatwg-fetch

webpack.config.js

1
2
3
4
5
6
7
8
9
10
module.exports = {
entry: {
polyfills: './src/polyfills.js',
index: './src/index.js'
},
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist')
}
};

Work offline

1
$ npm install workbox-webpack-plugin --save-dev

webpack.config.js

1
2
3
4
5
6
7
8
9
10
module.exports = {
plugins: [
new WorkboxPlugin({
// these options encourage the ServiceWorkers to get in there fast
// and not allow any straggling "old" SWs to hang around
clientsClaim: true,
skipWaiting: true
})
]
};

index.js

1
2
3
4
5
6
7
8
9
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/sw.js').then(registration => {
console.log('SW registered: ', registration);
}).catch(registrationError => {
console.log('SW registration failed: ', registrationError);
});
});
}

Environment Variables

webpack.config.js

1
2
3
4
5
6
7
8
9
10
11
12
13
module.exports = env => {
// Use env.<YOUR VARIABLE> here:
console.log('NODE_ENV: ', env.NODE_ENV) // 'local'
console.log('Production: ', env.production) // true

return {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
}
}
}

1
$ webpack --env.NODE_ENV=local --env.production --progress