浅尝 Webpack

本文主要根据阮一峰老师的webpack-demos进行一些提炼。Webpack作为目前作为火热的前端构建工具,redux的官方示例中也使用到了webpack-dev-middlewarewebpack-hot-middleware等,便想再次总结学习一下。

入口与出口文件

entry为入口文件,output为出口文件,通过webpack的处理,当前目录下的main.js会转变为bundle.js。

1
2
3
4
5
6
7
// webpack.config.js
module.exports = {
entry: './main.js',
output: {
filename: 'bundle.js'
}
};

当然多入口与多出口文件也是被允许的,例如下面的例子,main1.js转变为bundle1.js,main2.js转变为bundle2.js。

1
2
3
4
5
6
7
8
9
module.exports = {
entry: {
bundle1: './main1.js',
bundle2: './main2.js'
},
output: {
filename: '[name].js'
}
};

path是打包文件存放的绝对路径,publicPath是网站运行时的访问路径,也可以说是生产环境吧。

Babel加载

Babel目前也非常常见,ES6/7的新特性使用,React的JSX的编译,都需要Babel的帮助。而在webpack的配置中可以集成Babel。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
module.exports = {
entry: './main.jsx',
output: {
filename: 'bundle.js'
},
module: {
loaders: [
{
test: /\.jsx?$/,
exclude: /node_modules/,
loader: 'babel',
query: {
presets: ['es2015', 'react']
// npm install --save-dev babel-preset-stage-0
// presets: ['es2015', 'react', 'stage-0']
}
}
]
}
};

即使这样配置,你可能还会遇到某些特性的使用依然报错,这时你就需要查阅一下啦,可能这些特性仍然处于语法提案阶段,npm install相应的babel-preset-stage,在presets中添加即可。

CSS加载

CSS也可以直接require在JS代码当中,在main.js中require(‘./app.css’),在webpack中配置:

1
2
3
4
5
6
7
8
9
10
11
module.exports = {
entry: './main.js',
output: {
filename: 'bundle.js'
},
module: {
loaders:[
{ test: /\.css$/, loader: 'style-loader!css-loader' },
]
}
};

CSS需要先通过css-loader被读取,然后通过style-loader以内联的形式添加进入HTML页面。!来维持了前后顺序。当然要使用Less、Sass等预处理语言也是没有问题的,这些都可以被集成在webpack的配置当中。

1
2
3
4
5
6
module: {
loaders: [
{ test: /\.scss$/, loaders: ['style', 'css', 'sass'] }
// { test: /\.less$/, loaders: ['style', 'css', 'less'] }
]
}

图片加载

JS或是CSS中包含着图片,比如JS插入一个img,CSS中的background,意味着多一次http请求,webpack可以将小图片转化为Data URL。

1
2
3
4
5
6
7
8
9
10
11
12
module.exports = {
entry: './main.js',
output: {
filename: 'bundle.js'
},
module: {
loaders:[
// 图片大小是要小于8192B的
{ test: /\.(png|jpg)$/, loader: 'url-loader?limit=8192' }
]
}
};

webpack自身插件

在webpack中存在很多插件去扩展它的功能,其中有一个就是UglifyJs Plugin,主要是用于混淆和压缩。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var webpack = require('webpack');
var uglifyJsPlugin = webpack.optimize.UglifyJsPlugin;
module.exports = {
entry: './main.js',
output: {
filename: 'bundle.js'
},
plugins: [
new uglifyJsPlugin({
compress: {
warnings: false
}
})
]
};

webpack第三方插件

你也可以使用很多webpack的第三方插件,比如html-webpack-plugin会为你创建一个index.html,open-browser-webpack-plugin会为你自动打开地址。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var HtmlwebpackPlugin = require('html-webpack-plugin');
var OpenBrowserPlugin = require('open-browser-webpack-plugin');
module.exports = {
entry: './main.js',
output: {
filename: 'bundle.js'
},
plugins: [
new HtmlwebpackPlugin({
title: 'Webpack-demos'
}),
new OpenBrowserPlugin({
url: 'http://localhost:8080'
})
]
};

环境标记

通过环境标志可以区分生产环境与开发环境,如果只想在开发环境中执行某些操作,可以在执行这些操作前判断__DEV__

1
2
3
4
5
6
7
8
9
10
11
var webpack = require('webpack');
var devFlagPlugin = new webpack.DefinePlugin({
__DEV__: JSON.stringify(JSON.parse(process.env.DEBUG || 'false'))
});
module.exports = {
entry: './main.js',
output: {
filename: 'bundle.js'
},
plugins: [devFlagPlugin]
};

相应OS对应的命令

1
2
3
4
# Linux & Mac
$ env DEBUG=true webpack-dev-server
# Windows
$ DEBUG=true webpack-dev-server

模块化js

对于大型js应用来说,必定需要采用模块化。webpack很好地支持js模块化(require,支持ES6可使用import),并会将各个js汇总打包成一个bundle.js。

多个js文件可能会共用一些js模块,webpack可以使用CommonsChunkPlugin自动提取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// main1.jsx
var React = require('react');
var ReactDOM = require('react-dom');
ReactDOM.render(
<h1>Hello World</h1>,
document.getElementById('a')
);
// main2.jsx
var React = require('react');
var ReactDOM = require('react-dom');
ReactDOM.render(
<h2>Hello Webpack</h2>,
document.getElementById('b')
);

main1.jsx与main2.jsx共用react与react-dom,共用模块会生成init.js,main1.jsx生成bundle1.js,main2.jsx生成bundle2.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
var CommonsChunkPlugin = require("webpack/lib/optimize/CommonsChunkPlugin");
module.exports = {
entry: {
bundle1: './main1.jsx',
bundle2: './main2.jsx'
},
output: {
filename: '[name].js'
},
module: {
loaders:[
{
test: /\.js[x]?$/,
exclude: /node_modules/,
loader: 'babel-loader',
query: {
presets: ['es2015', 'react']
}
},
]
},
plugins: [
new CommonsChunkPlugin('init.js')
]
}

对于一些常用的第三方js库,比如jquery,我们不必把它合并到自己的js当中,也可以将其设置为全局变量,不必再require

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var webpack = require('webpack');
module.exports = {
entry: {
app: './main.js',
vendor: ['jquery'],
},
output: {
filename: 'bundle.js'
},
plugins: [
// 全局预先require('jquery')
new webpack.ProvidePlugin({
$: "jquery",
jQuery: "jquery",
"window.jQuery": "jquery"
}),
// 与上一种共用模块集成有相似之处
new webpack.optimize.CommonsChunkPlugin(/* chunkName= */'vendor', /* filename= */'vendor.js')
]
};

也可以添加一些自定义的js,如果你不想包含在webpack的bundle中。通过webpack后,直接require。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
module.exports = {
entry: './main.jsx',
output: {
filename: 'bundle.js'
},
module: {
loaders:[
{
test: /\.js[x]?$/,
exclude: /node_modules/,
loader: 'babel-loader',
query: {
presets: ['es2015', 'react']
}
},
]
},
externals: {
// 可以直接在main.jsx中require('data')
'data': 'data'
}
};

webpack热加载

webpack热加载在开发环境下,无需刷新,即可看到当前代码改动效果,目前共有两种方法:

(1) 使用命令行模式$ webpack-dev-server --hot --inline

--hot--inline相当于添加了HotModuleReplacementPlugin切换到服务器热加载模式,添加webpack/hot/dev-server入口,在bundle中运行webpack-dev-server

(2) 配置webpack.config.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
var webpack = require('webpack');
var path = require('path');
module.exports = {
entry: [
'webpack/hot/dev-server',
'webpack-dev-server/client?http://localhost:8080',
'./index.js'
],
output: {
filename: 'bundle.js',
publicPath: '/static/'
},
plugins: [
new webpack.HotModuleReplacementPlugin()
],
module: {
loaders: [{
test: /\.jsx?$/,
exclude: /node_modules/,
loaders: ['babel-loader'],
query: {
presets: ['es2015', 'react']
},
include: path.join(__dirname, '.')
}]
}
};

省略文件扩展名

在require时可省略文件扩展名,只需要在webpack.config.js中添加resolve.extensions来配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// webpack.config.js
module.exports = {
entry: './main.js',
output: {
filename: 'bundle.js'
},
module: {
loaders: [
{ test: /\.coffee$/, loader: 'coffee-loader' },
{
test: /\.js$/,
loader: 'babel-loader',
query: {
presets: ['es2015', 'react']
}
}
]
},
resolve: {
// 现在你require文件的时候可以直接使用require('file'),不用使用require('file.coffee')
extensions: ['', '.js', '.json', '.coffee']
}
};

Express与webpack

在Express中主要是添加webpack-dev-middlewarewebpack-hot-middleware两个中间件。

webpack-dev-middleware与上文webpack-dev-server类似,用于处理静态资源。

webpack-hot-middleware就是HMR(Hot Module Replacement),用于无刷新更新。

参考资料

THE WEBPACK DOCUMENTATION

webpack - howto

webpack - demos

Webpack 一探究竟

Webpack 入门指迷