webpack渐入佳境之打包优化篇

继上一篇,我们已经做了build操作,但我们只做了webpack的构建工作,它目前并不具备服务器功能,接下来我们先来完善它

webpack-dev-server

webpack-dev-server跟webpack不同点在于它提供了实时加载的服务,并且只能用于开发环境

修改 package.json

1
2
3
4
"scripts": {
...
"dev": "webpack-dev-server --config webpack.config.js"
},

配置区分对应模式

  • package.json
1
2
3
4
5
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "cross-env NODE_ENV=production webpack --config webpack.config.js",
"dev": "cross-env NODE_ENV=development webpack-dev-server --config webpack.config.js"
},

这里需要安装cross-env这个包来处理不同平台(osx、win)的差异,如win需要配置成set NODE_ENV= development 之类

  • webpack.config.js
1
2
...
+ const isDev = process.env.NODE_ENV === 'development'

可以通过process.env去读取在命令设置的变量名NODE_ENV

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
module.exports = {
+ target: 'web' //运行在浏览器
...
};
+ if (isDev) {
+ config.devtool = '#cheap-module-eval-source-map' //帮助调试代码 sourceMap
+ config.devServer = {
+ port: '8000',
+ host: '0.0.0.0',
+ overlay: {
+ errors: true //捕获webpack编译过程错误,并显示到页面
+ },
+ open: true //自动打开浏览器
+ }
+ }

上面就是简单的服务器配置

创建入口文件 index.html

到目前为止我们还看不到效果,我们需要把前面打包压缩的文件放到一个html文件去显示。正好webpack已经为我们提供了一个插件–html-webpack-plugin,会自动为我们创建一个简单的html文件,文档是这么说明的,这里贴上就不做翻译。

This is a webpack plugin that simplifies creation of HTML files to serve your webpack bundles. This is especially useful for webpack bundles that include a hash in the filename which changes every compilation. You can either let the plugin generate an HTML file for you, supply your own template using lodash templates or use your own loader.

  • webpack.config.js
1
2
3
4
5
6
7
8
9
10
11
12
+ const HtmlPlugin = require('html-webpack-plugin')
+ const webpack = require('webpack')
...
+ plugins: [
+ new webpack.DefinePlugin({
+ 'process.env': {
+ NODE_ENV: isDev ? '"development"' :'"production"'
+ }
+ }),
+ new HtmlPlugin()
+ ]

此外我们添加了一个webpack提供的一个插件DefinePlugin,它会在编译时期创建全局变量,我们在写js代码也可以引用process.env.NODE_ENV去做逻辑判断。

敲下npm run dev你会看到如下:

code

  • HMR配置

webpack为我们提供了热加载的功能,这样不会因为节点的增删改而刷新整个页面影响体验

1
2
3
4
5
6
7
config.devServer = {
+ hot: true
}
+ config.plugins.push(
+ new webpack.HotModuleReplacementPlugin(),
+ new webpack.NoEmitOnErrorsPlugin()
+ )

分离css打包

从这里开始讲会webpack的打包优化,首先我们先看看bundle.js打包后的样子

code

code

上面的图片可以看到,css依旧以js的形式打包在一起,显然这样子做浏览器缓存并不合理.

  • extract-text-webpack-plugin
1
npm i extract-text-webpack-plugin

通过装这个插件,webpack会自动帮我们把除javascript文件以外的文件打包成静态文件,最终作为外链的形式加入到html的head中

修改配置文件对开发环境和正式环境打包进行调整

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
28
29
30
31
32
33
34
35
36
37
38
39
if (isDev) {
+ config.module.rules.push({
+ test: /\.styl$/,
+ use: [
+ 'style-loader', //将 JS 字符串生成为 style 节点
+ 'css-loader', //将 CSS 转化成 CommonJS 模块
+ {
+ loader: 'postcss-loader',
+ options: {
+ sourceMap: true //复用上一个loader的sourceMap
+ }
+ },
+ 'stylus-loader'
+ ]
})
...
+ } else {
+ config.output.filename = '[name].[chunkhash:8].js'
+ config.module.rules.push(
+ {
+ test: /\.styl$/,
+ use: ExtractPlugin.extract({
+ fallback: 'style-loader',
+ use: [
+ 'css-loader',
+ {
+ loader: 'postcss-loader',
+ options: {
+ sourceMap: true
+ }
+ },
+ 'stylus-loader'
+ ]
+ })
+ },
+ )
+ config.plugins.push(
+ new ExtractPlugin('styles.[contentHash:8].js')
+ )

注意:所有打包出来的js模块的hash值都是一样的,chunkhash会根据每个chunk(entry里面定义的不同节点)生产不同的hash值,如果我们用了不同的entry或者把类库文件单独打包,必须使用chunkhash.

1
npm run buid

code

第三方类库打包

第三方类库对比业务代码比较稳定,我们希望浏览器能够长缓存它,所以我们也要讲它们分离出来减少http请求和流量

先看代码

1
2
3
4
5
6
7
8
9
10
11
12
13
+ config.entry = {
+ app: path.join(__dirname, './src/index'),
+ vendor: ['vue']
+ }
config.output.filename = '[name].[chunkhash:8].js'
...
config.plugins.push(
new ExtractPlugin('styles.[contentHash:8].js'),
+ new webpack.optimize.CommonsChunkPlugin({
+ name: 'vendor'
+ })
)

我们定了一个新的entry,指定了app和vendor,app是原来打包的入口模块, vendor则是存放第三方库,如vue

code

我们发现生成app.xxx和vendor.xxx,并且app的体积变小了

分离webpack相关代码

使用webpack构建应用程序中,主要有三种代码类型:

  • 你编写的代码
  • 代码中依赖的第三方库
  • webpack的runtime和manifest

这里的Manifest不是android里面的manifest.xml。试想一下,当你敲下npm run build之后会生成一个html去承载分离出来的各种资源文件如css,这时候你在里面所引用的绝对路径或者相对路径对应目录都不复存在,webpack就是利用manifest数据来处理所有模块的交互。
当浏览器开始执行、解析和映射应用程序时,它会保存所有模块的详细要点,这个数据集合就是Manifest,当完成打包应发送给浏览器,会在运行时通过Manifest来解析和加载模块,这时候你代码中的import或者require都会转换为webpack_require方法。,通过使用manifest中的数据,runtime能够通过ID检索对对应模块,在模块交互的时候,runtime处理内容包括: 链接模块所需的加载和解析逻辑(如浏览器中的已加载模块的连接以及懒加载模块的执行逻辑)

webpack.config.js

1
2
3
4
5
6
config.plugins.push(
...
+ new webpack.optimize.CommonsChunkPlugin({
+ name: 'runtime'
+ })
)

这里把app.js中连接各个模块的webpack代码分离出来, 当有新模块加入,webpack给每个模块加id区分,插入顺序可能会在中间,从而是id发生变化, 这样会使hash值发生变化导致长缓存失效。

code

1
npm run build

code

好了,一些webpack的基本操作就先写这些, 等后面再更新一篇关于webpack深入高级的用法。

感谢您的阅读,本文由 lynhao 原创提供。如若转载,请注明出处:lynhao(http://www.lynhao.cn
mvvm底层原理及伪代码实现
团队协作代码规范