1 链接
个人博客: https://alex-my.xyz
CSDN: https://blog.csdn.net/alex_my
Keylala在线工具: https://www.keylala.cn
程序员导航: https://list.keylala.cn
2 说明
- webpack是一个现在很流行的Javascript应用程序的模块打包器
- 之前学习
node.js
的时候各种源码基本上都有这个,对于其的配置文件感觉就像天书,都是边抄边搜索学习,感觉好累 - 经过一些学习之后,对各个配置就慢慢清晰了,于是就有了本文
- 本文并不是一个配置大全,并不会把每个选项的每个配置都详细的讲解,本文只有涉及常用的加载器和一些插件。阅读过本文后基本上对webpack有一定的了解,再深入学习也容易的多
- 如果需要更深入的学习,可以在本文最后的参考文献中挑选学习
- 最最最后要说的是**
亲自动手
**
3 环境安装
-
安装node.js
- 强烈推荐使用
nvm
来安装和管理node.js
- 进入github creationix/nvm,按照介绍来使用最新版本
- 关闭终端后再执行
npm
命令时发现npm
找不到命令之类的问题,可以执行nvm use [version]
,比如nvm use 8.4.0
- 也可以执行一次
nvm alias default v8.4.0
,设置默认的就是8.4.0
- 强烈推荐使用
-
安装国内的版本
cnpm
, 淘宝的镜像npm install -g cnpm
-
创建一个文件夹, 比如
learn-webpack
做为工程目录, 并初始化工程cd learn-webpack cnpm init
- 执行初始化命令的时候可以一路确定,使用默认值
- 这样在根目录下会出现一个
package.json
,以后安装的各种包都会记录在上面
-
本文使用的环境
- macOS Sierra
- node v8.4.0
- webpack 3.5.6
- IDE vscode
4 第一个简单的例子
-
先安装
webpack
cnpm install webpack --save-dev
- 在根目录下会生成一个
node_modules
的目录,同时webpack安装包信息也会被记录到package.json
中
- 在根目录下会生成一个
-
在根目录创建一个
src
文件夹, 用于存放源码, 并在内部创建2个文件index.js
,hello.js
mkdir src cd src touch index.js touch hello.js
-
在根目录创建一个public目录, 并在内部创建1个文件
index.html
cd .. mkdir public touch index.html
-
在根目录创建一个
webpack
的配置文件webpack.config.js
cd .. touch webpack.config.js
-
当前的目录结构如下
- learn-webpack
- node_modules
- public
- index.html
- src
- index.js
- hello.js
- package.json
- webpack.config.js
- learn-webpack
-
index.html
源码如下<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>learn webpack</title> </head> <body> <div id="root"></div> <script src="bundle.js"></script> </body> </html>
- 这个文件的目的在于加载webpack打包出来的文件, 我们命名打包出来的这个文件为
bundle.js
- 这个文件的目的在于加载webpack打包出来的文件, 我们命名打包出来的这个文件为
-
hello.js
源码如下var helloFunc = function() { let helloElement = document.createElement('div'); helloElement.textContent = 'Hello world'; return helloElement; } module.exports = helloFunc;
-
index.js
源码如下const hello = require('./hello'); document.getElementById('root').appendChild(hello());
-
webpack.config.js
配置如下const config = { entry: __dirname + '/src/index.js', output: { path: __dirname + '/public', filename: 'bundle.js' } } module.exports = config;
entry
: 告诉webpack从这个指定的文件开始,将所牵涉到的文件统统打包起来output
: 告诉webpack如何去输出,去哪里输出打包好的文件
-
通过命令行执行打包
-
在根目录下执行
node_modules/.bin/webpack
命令, 就会进行打包,你会发现,在public
目录下多了一个文件bundle.js
// 输出结果 Hash: 121ceb0c1b3711b0d009 Version: webpack 3.5.6 Time: 82ms Asset Size Chunks Chunk Names bundle.js 2.83 kB 0 [emitted] main [0] ./src/index.js 88 bytes {0} [built] [1] ./src/hello.js 184 bytes {0} [built]
-
webpack
命令默认使用webpack.config.js
文件做为配置文件,也可以指定配置文件node_modules/.bin/webpack --config ./webpack.config.js
-
在浏览器上打开
public
下的index.html
文件,就能看到hello world
的输出了
-
-
通过
npm
命令执行打包(推荐)-
我们的
webpack
是安装在本地的,并非全局(不推荐全局),所以要通过node_modules/.bin/webpack
来执行,这么长的路径比较烦人,再加上后面可能会在命令行上再加额外的参数,实在是不方便 -
因此可以通过
package.json
中对scripts
进行设置,辅助打包(就像makefile文件一样) -
package.json
完整文件如下(删除了没什么用的信息,避免篇幅过长){ "name": "learn-webpack", "version": "1.0.0", "description": "", "scripts": { "start": "webpack" }, "license": "MIT", "devDependencies": { "webpack": "^3.5.6" } }
-
package.json
中的script
会按照一定的顺序查找命令,node_modules
也在寻找的范围之内,所以就不需要再写成node_modules/.bin/webpack
了 -
一般正常情况下执行
npm
的script
命令为npm run scriptName
,但是start
是比较特殊的,可以直接执行npm start
-
在根目录下执行
npm run start
,同样的在public
目录下生成了一个bundle.js
文件
-
5 加载器loaders
-
加载器尝试对工程中的资源文件进行转换,它们是函数,把资源文件做为参数,把转换的内容做为结果。使用不同的加载器,可以对不同格式的文件进行处理
-
加载器配置在
module.rules
中, 每一个loader
都有以下选项test
: 用于匹配所处理文件扩展名的正则表达式(必须)use
: 是每一个rule
的属性,用于指定用什么loader
(必须)include/exclude
: 添加要处理的文件(夹)或者是屏蔽不需要处理的文件(夹)(可选)
6 babel-loader
-
这是第一个介绍到的加载器
-
babel
用于转换js文件,具有强大的功能- 你可以在代码中使用ES6/ES7功能,即使这些特性还未被当前的浏览器完全的支持
- 可以使用基于js进行扩展的语言,比如后文中要用到的
React
的jsx
-
babel
的核心功能在babel-core
中,可以使用扩展功能,但要额外安装,比如用的最多的就是解析ES6的babel-preset-es2015
包和解析jsx的babel-preset-react
包 -
安装这些包
cnpm install babel-core babel-loader babel-preset-es2015 babel-preset-react --save-dev
-
在
webpack.config.js
中配置babel-loader
,当前完整的webpack.config.js
如下const config = { entry: __dirname + '/src/index.js', output: { path: __dirname + '/public', filename: 'bundle.js' }, module: { rules: [{ test: /(\.js|\.jsx)$/, use: { loader: "babel-loader", }, exclude: /node_modules/ }] } } module.exports = config;
- 我们添加了
module
,并在其中添加了babel-loader
配置,这个配置的意思是告诉webpack,如果遇到js或者jsx文件,请调用babel-loader进行转码 - 这个转码过程可能会很慢,因为匹配了
js
文件,有可能会转码node_modules
中的内容,为了避免这个情况,我们将node_modules
排除了。也可以使用cacheDirectory
选项把转换的结果缓存起来,就可以加快以后的转换速度了,这个选项默认不开启
- 我们添加了
-
babel-core
由于配置项比较多,通常我们将babel
的一些选项放到.babelrc
的配置文件中,这个文件我们放到根目录下, 完整如下:{ "presets": ["react", "es2015"] }
-
这个加载器有一个特别的选项
babelrc
,默认为true,当设置为false时,会忽略.babelrc
文件 -
这样我们已经可以使用ES6以及jsx了,接下来我们会使用到
React
,先安装它们cnpm install react react-dom --save
-
使用
react
来改造hello.js
和index.js
// hello.js const React = require('react'); class Hello extends React.Component{ render() { return ( <div> <h1>Hello world 2</h1> </div> ); } } module.exports = Hello;
// index.js const React = require('react'); const ReactDom = require('react-dom'); const Hello = require('./Hello'); ReactDom.render( <Hello />, document.getElementById('root') );
-
运行
npm run start
打包,并打开index.html
,看看是否有Hello world 2
输出
7 webpack-dev-server
-
每次修改代码后,要打包,刷新界面,比较繁琐,所以这里将使用
webpack-dev-server
来帮助我们完成这些任务 -
安装
cnpm install webpack-dev-server --save-dev
-
在
webpack.config.js
中配置webpack-dev-server
,当前完整的webpack.config.js
如下const config = { entry: __dirname + '/src/index.js', output: { path: __dirname + '/public', filename: 'bundle.js' }, devServer: { contentBase: "./public/", historyApiFallback: true, inline: true, port: 8080 }, module: { rules: [{ test: /(\.js|\.jsx)$/, use: { loader: "babel-loader", options: { presets: [ "es2015", "react" ] } }, exclude: /node_modules/ }] } } module.exports = config;
inline
: 设置为true,当源文件发生改变的时候会自动刷新页面historyApiFallback
: 当访问不存在的页面时会重定向到index.htmlport
:webpack-dev-server
其实是一个小型的服务器,这个是监听端口,默认8080
-
在
package.json
中的scripts
添加命令,当前完整的package.json
如下{ "name": "learn-webpack", "version": "1.0.0", "description": "", "scripts": { "start": "webpack", "dev": "webpack-dev-server --open" }, "license": "ISC", "devDependencies": { "babel-core": "^6.26.0", "babel-loader": "^7.1.2", "babel-preset-es2015": "^6.24.1", "babel-preset-react": "^6.24.1", "webpack": "^3.5.6", "webpack-dev-server": "^2.7.1" }, "dependencies": { "react": "^15.6.1", "react-dom": "^15.6.1" } }
-
在根目录运行
npm run dev
,可以在http://localhost:8080/
查看结果 -
当我们手动修改代码时,比如
hello.js
中的内容修改hello world 3
,就会自动运行打包,然后刷新网页。
8 css-loader
-
另一个经常用到的就是各种css处理器了,比如通过
css-loader
,我们可以在js文件中通过require
来引入css。同时还要安装一个style-loader
,用于在html中以style方式将css插入。 -
还有
less-loader
将less文件编译成css,sass-loader
将sass文件编译成css,不过本文没有使用到这两个 -
首先安装
css-loader
和style-loader
cnpm install css-loader style-loader --save-dev
-
在
webpack.config.js
中配置css-loader
和style-loader
,当前完整的webpack.config.js
如下const config = { entry: __dirname + '/src/index.js', output: { path: __dirname + '/public', filename: 'bundle.js' }, devServer: { contentBase: "./public/", historyApiFallback: true, inline: true, port: 8080 }, module: { rules: [{ test: /(\.js|\.jsx)$/, use: { loader: "babel-loader", options: { presets: [ "es2015", "react" ] } }, exclude: /node_modules/ }, { test: /(\.css)$/, use: [{ loader: "style-loader" }, { loader: "css-loader", options: { modules: true } }] }] } } module.exports = config;
css-loader
中的options.modules
意思是是否启用css-modules
功能,关于css-modules
可以查看阮一峰 CSS Modules 用法教程。大意就是模块化思想,把CSS的类名传递到组件的代码中,且只对当前组件有效,不用担心在不同的模块中使用相同的类名造成的冲突。
-
在
src/
中创建static
文件夹,用于存放css文件,并在其中创建hello.css
和index.css
# hello.css .hello { color: blue }
# index.css h1, h2, h3, h4, h5, h6, p, ul { margin: 0; padding: 0; text-align: center; }
-
在
hello.js
和index.js
引用hello.css
和index.css
// hello.js const React = require('react'); const helloCSS = require('./static/hello.css'); class Hello extends React.Component{ render() { return ( <div> <h1 className={helloCSS.hello}> Hello world 7 </h1> </div> ); } } module.exports = Hello;
// index.js const React = require('react'); const ReactDom = require('react-dom'); const Hello = require('./Hello'); require('./static/index.css'); ReactDom.render( <Hello />, document.getElementById('root') );
-
运行
npm run dev
, 我们可以看到, 文字居中且变成蓝色了
9 插件plugins
-
加载器loaders是告诉webpack,将符合条件的文件用指定的loader进行编译转换,而插件plugins则是扩展webpack功能的
-
加载器配置在
plugins
中
10 BannerPlugin
-
该插件使用简单,无需额外安装,做为第一个介绍的插件
-
该插件的作用是对打包出的每一个chunk头部添加信息
-
我们修改
webpack.config.js
,在其中添加plugins模块,并添加BannerPlugin配置, 当前完整的webpack.config.js
如下const webpack = require('webpack'); const config = { entry: __dirname + '/src/index.js', output: { path: __dirname + '/public', filename: 'bundle.js' }, devServer: { contentBase: "./public/", historyApiFallback: true, inline: true, port: 8080 }, module: { rules: [{ test: /(\.js|\.jsx)$/, use: { loader: "babel-loader", options: { presets: [ "es2015", "react" ] } }, exclude: /node_modules/ }, { test: /(\.css)$/, use: [{ loader: "style-loader" }, { loader: "css-loader", options: { modules: true } }] }] }, plugins: [ new webpack.BannerPlugin('这里是banner,请添加信息') ] } module.exports = config;
-
执行
npm run start
,打开public/bundle.js
看看顶部是否加了一行信息
11 热加载模块
-
前面我们使用
webpack-dev-server
来辅助我们打包,刷新页面,比如我们修改了hello world
内容,可以很明显的看到页面进行了刷新。 -
实际上
webpack-dev-server
的inline
模式下,具有热加载功能,即尝试重新加载组件的改变部分,而不是重新加载整个页面。 -
我们的组件使用的是
react
,所以我们需要安装两个插件帮助React只加载改变的部分cnpm install babel-plugin-react-transform react-transform-hmr --save-dev
-
在
.babelrc
中需要配置这两个插件{ "presets": ["react", "es2015"], "env": { "development": { "plugins": [ ["react-transform", { "transforms": [{ "transform": "react-transform-hmr", "imports": ["react"], "locals": ["module"] }] }] ] } } }
-
在
webpack.config.js
中需要修改两处地方const webpack = require('webpack'); const config = { entry: __dirname + '/src/index.js', output: { path: __dirname + '/public', filename: 'bundle.js' }, devServer: { contentBase: "./public/", historyApiFallback: true, inline: true, port: 8080, // 添加hot hot: true }, module: { rules: [{ test: /(\.js|\.jsx)$/, use: { loader: "babel-loader", }, exclude: /node_modules/ }, { test: /(\.css)$/, use: [{ loader: "style-loader" }, { loader: "css-loader", options: { modules: true } }] }] }, plugins: [ new webpack.BannerPlugin('这里是banner,请添加信息'), // 开启热加载 new webpack.HotModuleReplacementPlugin() ] } module.exports = config;
- 在
devServer
中添加hot: true
- 在插件中开启热加载功能
webpack.HotModuleReplacementPlugin()
- 在
-
执行
npm run dev
,然后修改hello world
内容,我们可以发现,不需要刷新整个界面,内容却随之改变了
12 HtmlWebpackPlugin
-
有一个问题就是缓存问题,比如我们生成的文件名都叫做
bundle.js
,我们对内容进行了修改,但是由于文件名没有改变,浏览器很可能直接从缓存中读取旧的数据了 -
如果我们给打包出来的文件加一些后缀,比如加一个hash值,内容发生变化后,hash值不同,文件名称也不同,这样就不会出现上面的问题了,比如
bundle-a61f37756a508cb151b5.js
-
新问题来了,由于我们之前
public/index.html
中,是直接引用的bundle.js
,由于hash值是不确定的,怎样才能让index.html
能够正确的使用新打出的bundle-[hash].js
文件? -
这就使用到
HtmlWebpackPlugin
了,该插件能够根据一个模板文件,自动生成一个index.html
,该文件会自动引用打包后的js文件。 -
之前的
public
文件夹可以删除不用了,我们按照惯例在根目录创建一个名为build
文件夹 -
在
src
目录下建一个文件template.html
做为模板文件<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>learn webpack</title> </head> <body> <div id="root"></div> </body> </html>
-
安装插件
cnpm install html-webpack-plugin --save-dev
-
在
webpack.config.js
中对HtmlWebpackPlugin
进行配置,当前完整的webpack.config.js
如下const webpack = require('webpack'); // 引入插件 const HtmlWebpackPlugin = require('html-webpack-plugin'); const config = { entry: __dirname + '/src/index.js', output: { path: __dirname + '/build', // 修改输出的文件名称 filename: 'bundle-[hash].js' }, devServer: { contentBase: "./public/", historyApiFallback: true, inline: true, port: 8080, hot: true }, module: { rules: [{ test: /(\.js|\.jsx)$/, use: { loader: "babel-loader", }, exclude: /node_modules/ }, { test: /(\.css)$/, use: [{ loader: "style-loader" }, { loader: "css-loader", options: { modules: true } }] }] }, plugins: [ new webpack.BannerPlugin('这里是banner,请添加信息'), new webpack.HotModuleReplacementPlugin(), // 使用插件,指定模板位置 new HtmlWebpackPlugin({ template: __dirname + '/src/template.html' }) ] } module.exports = config;
-
执行
npm run start
后,我们可以在build
文件夹看到生成了bundle-[hash].js
文件和一个index.html
文件,且index.html
文件正确的引用了bundle-[hash].js
13 UglifyJsPlugin
-
在产品发布阶段,可以使用一些优化插件,比如将要介绍的
UglifyJsPlugin
,它可以压缩JS代码,大家可以进入到build
文件夹,执行du -sh
看看各个文件的大小920K bundle-94e84680995a92af5838.js 4.0K index.html
-
该插件使用很简单,如果都使用默认值,则在
webpack.config.js
中的plugins
添加上配置信息就行,无需额外安装... plugins: [ new webpack.BannerPlugin('这里是banner,请添加信息'), new webpack.HotModuleReplacementPlugin(), new HtmlWebpackPlugin({ template: __dirname + '/src/template.html' }), // 全部使用默认值 new webpack.optimize.UglifyJsPlugin() ]
-
重新运行
npm run start
后可以发现bundle-[hash].js
变得很紧凑了,文件也变小了268K bundle-94e84680995a92af5838.js 4.0K index.html
-
这个插件也有一些选项,具体的可以查看webpack uglifyjsplugin
-
当前完整版的
webpack.config.js
如下const webpack = require('webpack'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const config = { entry: __dirname + '/src/index.js', output: { path: __dirname + '/build', filename: 'bundle-[hash].js' }, devServer: { contentBase: "./public/", historyApiFallback: true, inline: true, port: 8080, hot: true }, module: { rules: [{ test: /(\.js|\.jsx)$/, use: { loader: "babel-loader", }, exclude: /node_modules/ }, { test: /(\.css)$/, use: [{ loader: "style-loader" }, { loader: "css-loader", options: { modules: true } }] }] }, plugins: [ new webpack.BannerPlugin('这里是banner,请添加信息'), new webpack.HotModuleReplacementPlugin(), new HtmlWebpackPlugin({ template: __dirname + '/src/template.html' }), new webpack.optimize.UglifyJsPlugin({ // 最紧凑的输出 beautify: false, // 不要注释 comments: false, compress: { // 删除console语句 drop_console: true, // 内嵌定义了但是只用到一次的变量 collapse_vars: true, // 提取出出现多次但是没有定义成变量去引用的静态值 reduce_vars: true } }) ] } module.exports = config;
14 CommonsChunkPlugin
-
到目前为止,我们的应用都是把所有牵涉到的文件打包到一个文件里面(
bundle-[hash].js
),加载的时候把所有的文件都加载进来,不管是否有用到。实际上,当工程复杂了以后,包括各种依赖,打包后的文件实际上挺大的,这样就浪费流量,也增加了加载的时间。如果我们只需要加载所用到的代码或者将公共代码缓存起来,那就省事多了 -
比如我们将
react
和react-dom
做为公共文件提取出来,最终打包成一个文件,浏览器缓存起来后,去访问例如hello2.js
,hello3.js
等,都可以从缓存中取出来,而不是每次访问一个新页面的时候,都重复加载这个文件 -
无需安装这个插件,当前完整的
webpack.config.js
如下const webpack = require('webpack'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const config = { entry: { index: __dirname + '/src/index.js', // 把react,react-dom放到一起打包 vendor: ['react', 'react-dom'] }, output: { path: __dirname + '/build', // 按照实际名称输出 filename: '[name]-[hash].js', publicPath: '', chunkFilename: '[name]-[hash].js' }, devServer: { contentBase: "./public/", historyApiFallback: true, inline: true, port: 8080, hot: true }, module: { rules: [{ test: /(\.js|\.jsx)$/, use: { loader: "babel-loader", }, exclude: /node_modules/ }, { test: /(\.css)$/, use: [{ loader: "style-loader" }, { loader: "css-loader", options: { modules: true } }] }] }, plugins: [ new webpack.BannerPlugin('这里是banner,请添加信息'), new webpack.HotModuleReplacementPlugin(), new HtmlWebpackPlugin({ template: __dirname + '/src/template.html' }), new webpack.optimize.UglifyJsPlugin({ beautify: false, comments: false, compress: { drop_console: true, collapse_vars: true, reduce_vars: true } }), // 把公共代码提取出来,命名为vendor.js,当然受到output配置影响,会追加一个hash new webpack.optimize.CommonsChunkPlugin({ names: ['vendor'] }) ] } module.exports = config;
-
执行
npm run start
后,在build
文件夹下出现vender-[hash].js
文件44K build/index-c57eda02b7e22fad43c3.js 4.0K build/index.html 228K build/vendor-c57eda02b7e22fad43c3.js
-
但还存在一个问题,那就是当我们修改了文件内容,比如
hello.js
中输出的内容修改为hello world 123
,然后执行npm run start
,会发现index.js
和vendor.js
都重新打包了,实际上我们并不希望vendor.js
重新打包,所以还要再做一些修改 -
首先,文件名称就不能用hash了,而是改用chunkhash
chunkhash
: chunk就是指模块,如果chunk内容不变,chunkhash的值就不会变化,非常符合我们的要求hash
: 当项目文件发生改变的时候,就会生成一个compilation对象,就是这个对象的hash值
-
修改
webpack.config.js
,当前完整版如下const webpack = require('webpack'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const config = { entry: { index: __dirname + '/src/index.js', vendor: ['react', 'react-dom'] }, output: { // 使用chunkhash替换hash,个人感觉hash太长,只保留十位 path: __dirname + '/build', filename: '[name]-[chunkhash:10].js', publicPath: '', chunkFilename: '[name]-[chunkhash:10].js' }, devServer: { contentBase: "./public/", historyApiFallback: true, inline: true, port: 8080, hot: true }, module: { rules: [{ test: /(\.js|\.jsx)$/, use: { loader: "babel-loader", }, exclude: /node_modules/ }, { test: /(\.css)$/, use: [{ loader: "style-loader" }, { loader: "css-loader", options: { modules: true } }] }] }, plugins: [ new webpack.BannerPlugin('这里是banner,请添加信息'), // 注释掉热更新,否则会报错: Cannot use [chunkhash] for chunk in '[name]-[chunkhash:10].js' (use [hash] instead) // new webpack.HotModuleReplacementPlugin(), new HtmlWebpackPlugin({ template: __dirname + '/src/template.html' }), new webpack.optimize.UglifyJsPlugin({ beautify: false, comments: false, compress: { drop_console: true, collapse_vars: true, reduce_vars: true } }), // 这里也要修改 new webpack.optimize.CommonsChunkPlugin({ name: 'manifest', chunks: ['vendor'] }), new ExtractTextPlugin('[name]-[contenthash:10].css') ] } module.exports = config;
-
现在修改逻辑内容后再运行
npm run start
进行打包,不会再生成新的vender.js
文件了。
15 ExtractTextPlugin
-
我们之前的打包的文件中也包含了css信息,我们现在也将它们提取出来,让它可以单独缓存
-
安装插件
cnpm install extract-text-webpack-plugin --save-dev
-
在
webpack.config.js
中进行配置,当前完整的webpack.config.js
如下const webpack = require('webpack'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const ExtractTextPlugin = require('extract-text-webpack-plugin'); const config = { entry: { index: __dirname + '/src/index.js', vendor: ['react', 'react-dom'] }, output: { path: __dirname + '/build', filename: '[name]-[chunkhash:10].js', publicPath: '', chunkFilename: '[name]-[chunkhash:10].js' }, devServer: { contentBase: "./public/", historyApiFallback: true, inline: true, port: 8080, hot: true }, module: { rules: [{ test: /(\.js|\.jsx)$/, use: { loader: "babel-loader", }, exclude: /node_modules/ }, { test: /(\.css)$/, use: ExtractTextPlugin.extract({ fallback: "style-loader", use: [{ loader: "css-loader", options: { modules: true } }] }) }] }, plugins: [ new webpack.BannerPlugin('这里是banner,请添加信息'), // 注释掉热更新,否则会报错: Cannot use [chunkhash] for chunk in '[name]-[chunkhash:10].js' (use [hash] instead) // new webpack.HotModuleReplacementPlugin(), new HtmlWebpackPlugin({ template: __dirname + '/src/template.html' }), new webpack.optimize.UglifyJsPlugin({ beautify: false, comments: false, compress: { drop_console: true, collapse_vars: true, reduce_vars: true } }), new webpack.optimize.CommonsChunkPlugin({ name: 'manifest', chunks: ['vendor'] }), // 命名生成的文件 new ExtractTextPlugin('[name]-[contenthash:10].css') ] } module.exports = config;
-
执行
npm run start
,我们可以在build
目录下发现css文件4.0K build/index-0f50bc847f.css 36K build/index-bac776987c.js ... ...
-
该插件中文版配置选项webpack extract-text-webpack-plugin
16 OccurenceOrderPlugin
- 在当前版本(3.5.6),该插件已经是默认开启的,所以无需在
webpack.config.js
中设置
17 NamedModulesPlugin
-
在插件
CommonsChunkPlugin
中解决了当修改文件内容的时候vender.js
随之改变的问题,但实际上,并不能完全保证vendor.js
的chunkhash值不发生改变,webpack的作者提到了这一点:默认情况下,模块的id是这个模块在模块数组中的索引。OccurenceOrderPlugin 会将引用次数多的模块放在前面,在每次编译时模块的顺序都是一致的……如果你修改代码时新增或删除了一些模块,这将会影响到所有模块的id
-
为了测试,我们在
src
目录下新建了一个文件content.js
// content.js module.exports = function() { return 'hello world'; };
-
然后在
hello.js
中引用,只添加一行require('./content');
,然后打包,我们发现又出现了一个新的vendor.js
文件 -
实际上,从webpack作者的话来看,问题的根源在于模块的id是这个模块在模块数组中的索引,当我们新增文件的时候,在
OccurenceOrderPlugin
的影响下,可能会影响到索引的变化。 -
NamedModulesPlugin
这个插件,使用模块的相对路径做为模块的id,只要我们不重命名一个模块文件,那么它的id就不会发生改变 -
此插件不需要另外安装,只要在
plugins
加上一句new webpack.NamedModulesPlugin()
-
这样,新增或减少模块,也不会让公共模块发生变化了。但是带来的问题就是包会变大一些,使用前后文件大小变化
// 未使用NamedModulesPlugin插件 4.0K build/index-0f50bc847f.css 256K build/index-5ea7cf14d7.js 4.0K build/index.html 4.0K build/manifest-e40eafac92.js 220K build/vendor-2615736a31.js
// 使用NamedModulesPlugin插件 4.0K build/index-0f50bc847f.css 324K build/index-6333c0bc61.js 4.0K build/index.html 4.0K build/manifest-0d2ca7dca3.js 268K build/vendor-70142bb2ee.js
-
我们发现
index.js
还是增大挺多的,所以NamedModulesPlugin
对于复杂的应用来说,并不是很满意,这就需要用到下一个要介绍的插件HashedModuleIdsPlugin
18 HashedModuleIdsPlugin
-
这个插件会根据模块的相对路径生成一个长度只有四位的字符串做为模块id
-
使用也非常简单,不需要额外安装,直接配置在
webpack.config.js
中的plugins
即可// 不再使用NamedModulesPlugin // new webpack.NamedModulesPlugin() new webpack.HashedModuleIdsPlugin()
-
这样打包出的文件并不会大多少,且增加和减少模块也不会引起
vendor.js
改变4.0K build/index-0f50bc847f.css 260K build/index-b6e4ad2c69.js 4.0K build/index.html 4.0K build/manifest-3671567576.js 224K build/vendor-e6102e2bfe.js
19 指定环境
-
有的库通过与
process.env.NODE_ENV
环境变量关联,当非生产模式下时,为了调试方便,可能会添加额外的日志记录和测试。当处于生产模式的时候,某些库可能会对代码优化。 -
有没优化看不出来,不过大小倒是有变化。
-
不需要安装,在
plugins
添加以下内容:new webpack.DefinePlugin({ 'process.env': { 'NODE_ENV': JSON.stringify('production') } })
-
下面的代码中css文件提取到static目录下了,没有和js文件放一起
new ExtractTextPlugin('./static/[name]-[contenthash:10].css')
-
重新执行打包后的大小:
# 修改前 260K build/index-432c275a7c.js 4.0K build/index.html 4.0K build/manifest-3671567576.js 8.0K build/static 224K build/vendor-e6102e2bfe.js # 修改后 184K build/index-74daaba778.js 4.0K build/index.html 4.0K build/manifest-ccb57023d0.js 8.0K build/static 148K build/vendor-2ca39a59c2.js
20 CleanWebpackPlugin
-
当每次执行打包的时候,旧的文件并没有被删除,加上带着hash值,看上去糟糕透了。
-
现在可以使用
CleanWebpackPlugin
来清理这些文件 -
安装
cnpm install clean-webpack-plugin --save-dev
-
配置
const CleanWebpackPlugin = require('clean-webpack-plugin'); ... plugins: [ ... // 也可以指定 build/index.*.js 来指定删除 new CleanWebpackPlugin(['build'], { root: __dirname, verbose: true, dry: false }) ]
-
这样每次就会清理掉build文件夹
21 两份webpack配置
-
在插件
CommonsChunkPlugin
中有使用到chunkhash
,但是在热加载模式下并不适合使用,所以我们可以创建两份webpack配置- webpack.config.dev.js
- webpack.config.prod.js
-
开发板支持热更新,最终版支持
chunkhash
-
可以修改
package.json
中的scripts
"scripts": { "start": "webpack --config ./webpack.config.dev.js", "build": "webpack --config ./webpack.config.prod.js", "dev": "webpack-dev-server --open" }
22 package.json
-
该文件记录了本文中用到的所有的插件和加载器
{ "name": "learn-webpack", "version": "1.0.0", "description": "", "scripts": { "start": "webpack", "dev": "webpack-dev-server --open" }, "license": "MIT", "devDependencies": { "babel-core": "^6.26.0", "babel-loader": "^7.1.2", "babel-plugin-react-transform": "^2.0.2", "babel-preset-es2015": "^6.24.1", "babel-preset-react": "^6.24.1", "css-loader": "^0.28.7", "extract-text-webpack-plugin": "^3.0.0", "html-webpack-plugin": "^2.30.1", "react-transform-hmr": "^1.0.4", "style-loader": "^0.18.2", "webpack": "^3.5.6", "webpack-dev-server": "^2.7.1" }, "dependencies": { "react": "^15.6.1", "react-dom": "^15.6.1" } }
23 参考文献
Webpack 配置文件 英文文档
Webpack 配置文件 中文文档
Pro React
github工程 fairy
使用 Webpack 打包单页应用的正确姿势