主要参考

官网传送
思否文章总结

just do it

前置条件

安装最新稳定版node,最好是node8以上版本

安装

yarn init -y yarn add webpack --dev yarn add webpack@<version> --dev yarn add webpack-cli --dev

注意:

  • yarn init 的 -y 参数表示不需要交互式操作
  • webpack-cli是webpack4必须的

所谓零配置

webpack的零配置不是真正意义的零配置,零配置是跑不起来的

执行命令

运行webpack命令可以进行打包:

webpack

一般会用别名如build指代webpack命令,写在package.json中的scripts里,:

{ "name": "webpack-learning", "version": "1.0.0", "main": "index.js", "author": "geekzl", "license": "MIT", "scripts": { "build":"webpack" }, "devDependencies": { "webpack": "^4.28.4", "webpack-cli": "^3.2.1" } }

这样可以通过运行yarn build执行webpack命令,别名命令的意义在于简化输入的命令,如:

"scripts": { "dev": "node build/dev-server.js", "prod": "cross-env NODE_ENV=production node build/dev-server.js", "build": "node build/build.js" },

执行命令报错

上述webpack命令报错:

webfirsterror.png

这是因为,webpack项目默认src下的内容是入口文件,默认输出目录是dist,因此至少要有一个src文件夹,我们可以将目录结构写成:

index.png

其中index.js中随便写一句js代码,如console.log(“first log”)

再次运行yarn build:

build.png

此时,只有一个要求使用配置文件配置mode的警告,目录下生成了打包后的dist文件夹和打包后的文件main.js:

buildindex.png
buildhtml.png

小结

这说明,webpack默认要找到src下的js打包压缩混淆输出到dist文件夹下

修改默认输入输出路径

方法1:修改webpack命令

"scripts": { "dev": "webpack --mode development ./foo/src/js/index.js --output ./foo/main.js", "build": "webpack --mode production ./foo/src/js/index.js --output ./foo/main.js" }
  • 这样既可以改变输入输出目录,也可以更改mode(消除上一步的警告信息)
  • 更改mode表示更改webpack两种工作状态,对应开发模式环境和生成模式环境
  • 开发环境需要本地服务器、代理等
  • 生产环境不需要本地服务器,但需要压缩混淆
  • 可以通过本方法的script来分别运行yarn dev和yarn build对比两者间的不同,即是否压缩导致的体积大小不同

方法2:添加配置文件webpack.config.js

避免了方法1添加参数到webpack命令中,直接在webpack.config.js中写:

const path = require('path'); module.exports = { mode:'development', entry: './src/index.js', output: { filename: 'bundle.js', path: path.resolve(__dirname, 'dist') } };
  • mode设置为development或production也可以看到上一节yarn dev和yarn build的不同效果
  • path是node自带路径处理函数库,path.resolve用来合成当前目录+dist构成的绝对路径

webpack4零配置体现

  • entry 默认设置为 ./src/index.js
  • output 默认设置为 ./dist/main.js
  • production 和 development 两种模式(直接使用命令webpack是production模式)

其他功能的配置方法

常规需求

显然,只有上面配置是不够的,我们的需求还有:

  • js的处理:转换 ES6 代码,解决浏览器兼容问题
  • css的处理:编译css,自动添加前缀,抽取css到独立文件
  • html的处理:复制并压缩html文件
  • dist的清理:打包前清理源目录文件
  • assets的处理:静态资源处理
  • server的启用:development - 模式下启动服务器并实时刷新

怎么做

  • webpack的扩展性体现在它的插件架构上,通过扩展插件完成复杂功能
  • webpack本身只能处理js文件,要完成对其他文件的处理,需要“第三方插件”
  • “第三方插件”在webpack里由module、plugins等选项配置
    • module意为模块,是webpack模块化工具的核心,它通过一个个loader来完成模块的功,,loader 用于对模块的源代码进行转换。loader可以支持对多种语言和预处理器的模块化,并且在bundle中引入这些依赖
    • plugins是webpack的骨架,webpack本身也建立在我们在webpack.config.js中用到的插件系统上;当然插件也完成loader不能完成的工作

逐项配置

1. 使用babel支持ES6的转码

请参考英文官网,中文官网翻译较慢
babel官网链接

(1)安装
yarn add babel-loader @babel/core @babel/preset-env --dev

安装3个插件,第一个是webpack的babel插件;第二个是babel的node API,是原babel拆分出的node api包;第三个是babel的官方推荐转义规则包

(2)使用
  • 每个loader的配置最好是在webpack.config.js中module选项下新增一个rule实现
  • loader也支持cli或者import后内联,但那对代码侵入较大,不应该是工具应该做的事
rules: [ { test: /\.m?js$/, exclude: /(node_modules|bower_components)/, use: { loader: 'babel-loader', options: { presets: ['@babel/preset-env'] } } } ]
  • test代表正则匹配,即匹配所有js和mjs文件,mjs扩展名表示ES6的模块文件。mjs出现在node环境,node原支持的commonjs模块语法生成cjs文件,ES6模块语法生成mjs
  • babel中文官网的有关babelrc文档表明,你需要在项目根目录下写.babelrc文件。在webpack项目中可以不写,因为webpack的babel-loader的use选项中options就完成了同样功能,.babelrc文件仅用于babel配置项过多不适宜写在webpack配置里的时候。故以下两种配置意义相同:

配置一:在webpack.config.js中

rules: [ { test: /\.m?js$/, exclude: /(node_modules|bower_components)/, use: { loader: 'babel-loader', options: { presets: [ [ '@babel/preset-env', { targets: { browsers: ['> 1%'] } } ] ], babelrc: false } } } ]

配置解析:

  1. presets是babel配置项,babel还有plugins等配置项
  2. presets的语法可参考官网文档
  3. targets内规则表示支持市面上市场占有率多于1%的浏览器,更多配置参考文档
  4. babelrc规则表示默认不自动查找项目根目录下.babelrc文件;babelrc这个命名中的rc是linux范畴概念,表示run commond,即运行时命令,通常在程序运行状态下被调用

配置二:在webpack.config.js中和项目根目录下.babelrc中

<!--webpack.config.js--> rules: [ { test: /\.m?js$/, exclude: /(node_modules|bower_components)/, use: { loader: 'babel-loader' } } ]
<!--.babelrc中--> { presets: [ [ '@babel/preset-env', { targets: { browsers: ['> 1%', 'last 2 versions'] } } ] ] }

配置解析:
.babelrc中的内容是由webpack原配置中options内容拆分迁移过来的

(3)进阶使用

仅有上述babel功能是不够的,因为以上功能只负责将ES6的语法糖转变为ES5语法,但ES6新定义的API无法转换,如promise,这时需要兼容处理,即polyfill。polyfill意为垫片、填充物,指用于实现浏览器并不支持的原生API的代码。

  1. polyfill安装和使用
yarn add babel-polyfill --dev

在js文件中引入即可使用

import 'babel-polyfill'
  • 这样导致污染全局环境,且引入了babel-polyfill的完整包
  • 在有webpack等开发框架的前提下,可以使用babel-runtime和 babel-plugin-transform-runtime插件实现根据用到的代码按需要引用polyfill的相应函数,打包后比全局引入polyfill的包小
  • 没有webpack工具的项目可以像这样直接使用babel-polyfill
  1. polyfill插件化非侵入实现

(1)首先安装辅助插件:

yarn add @babel/runtime @babel/plugin-transform-runtime --dev
  • babel-runtime插件用polyfill重写的api代码,是一个充满polyfill的包
  • babel-plugin-transform-runtime用来链接原始代码文件和babel-runtime中的代码,用来按需加载需要转义的ES6 api,实现按需加载polyfill的方法

(2)然后修改配置文件:
在webpack.config.js或者babelrc的babel配置的options中添加一行

{ "presets": [ [ "@babel/preset-env", { "targets": { "browsers": ["> 1%", "last 2 versions"] } } ] ], "plugins": ["@babel/transform-runtime"] // 添加插件 }

提到插件,必须说明babel的编译原理:

  • parser:通过 babylon 解析成 AST。
  • transform[s]:All the plugins/presets ,进一步的做语法等自定义的转译,仍然是 AST。
  • generator: 最后通过 babel-generator 生成 output string。

可以看到,plugins和presets都是加强转义处理的。

2. html文件动态生成js

(0)简介
  • 第1节中,我们将入口src/index.js文件编译为出口dist/main.js文件
  • 但一般的开发流程是根目录有一个文件目录存放html,通常是这样:
    ./html/index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta http-equiv="X-UA-Compatible" content="ie=edge" /> <title>Document</title> </head> <body> <script src="./dist/bundle.js"></script> </body> </html>

这里js直接写死了

  • 实际开发时,我们需要的是script能动态生成并链接到webpack输出的bundle文件上
  • 这个功能由webpack插件HtmlWebpackPlugin完成
  • 补充说明:由于类似webpack构建工具直接完成了html与js的映射,结果就是没有显式引入js的html语句;学习vue/cli时会遇到这种疑惑,看起来html模板和js入口文件好像没有直接关联
(1)安装和配置
yarn add html-webpack-plugin --dev

在webpack.config.js中

const path = require('path'); const HtmlWebpackPlugin = require('html-webpack-plugin'); // 这里引入 module.exports = { // mode: 'development', mode: 'production', entry: './src/index.js', output: { filename: 'bundle.js', path: path.resolve(__dirname, 'dist') }, module: { rules: [] }, plugins: [new HtmlWebpackPlugin()] // 这里使用 };

运行yarn build,可以看到在dist下生成了一个html文件,其中js关联了最终输出bundle.js:

<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Webpack App</title> </head> <body> <script type="text/javascript" src="bundle.js"></script></body> </html>
(2)HtmlWebpackPlugin插件配置
plugins: [ new HtmlWebpackPlugin({ title: 'My App', // 改默认title为My App filename: 'assets/admin.html' // 更改默认index.html名字为dist下assets目录下admin.html template: 'html/index.html', hash: true, // 是否生成的js带hash串 inject:'body' // js插入的body中,可选head等 }) ]

其中,模板template是开发时的重要选项,该选项表示以根目录下html/index.html为模板插入最终生成的bundle.js

  • 配置多个html入口
    写多个插件实例即可:
plugins: [ new HtmlWebpackPlugin({ title: 'My App', // 改默认title为My App filename: 'assets/admin.html' // 更改默认index.html名字为dist下assets目录下admin.html template: 'html/index.html', hash: true, // 是否生成的js带hash串 }), new HtmlWebpackPlugin() new HtmlWebpackPlugin() ]

3. css处理与合并

(1) 基础配置

需要用到:

  • css-loader: 使webpack能解析css,即将css装载到js中成为webpack模块
  • style-loader: 使解析的css套入html中,即把css放入html的style标签内
yarn add css-loader style-loader --dev

webpack.config.js中:

module: { rules: [ { test: /\.css$/, use: [ 'style-loader', 'css-loader' ] } ] }

注意:use中的读取顺序是从右往左

(2) 基础配置实例

css-loader官方文档

  • css-loader作用是把import/require方式引入的css填入js中,对css中的@import和url()(通常是background-image的url)引入并像import/require那样解析成一个js模块
  • 因此css引入方式如下,这样引入能被webpack模块化处理:
require('./css/index.css'); import './css/other.css';

./css/index.css

.demo { font-size: 20pt; color: blue; text-align: center; /* background-image: url('../img/animal.jpeg'); */ }

此时background-image注释掉,因为尽管css-loader能处理url,但url资源是图片,还需要引入其他loader来处理
./css/other.css

.demo { background-color: lightblue; font-size: 2rem; } @import url(./inner.css);

./css/inner.css

.demo { font-size: 20px; color: red; text-align: center; }

此时运行yarn build生成的html文件是这样的:

bundle.png

只有js,没有css,因为css已经被打包进bundle.js文件中
打开该html文件,样式已经存在,通过右键检查页面可以看到style-loader生成的页面css:

style.png

共有三个style文件,分别是上面引入的require和import,同时可以看到css-loader将other.css中的@import也解析为一个单独的style标签

注意: 不要去“查看源代码”,style标签并没有直接静态生成到html中


总结:该方法是css的style标签引入方式,生成的css在bundle.js中,查看页面后发现在html文档的head中的style标签中

(3) style-loader生成link标签
  • 除了将生成的css放到bundle.js中,也可以单独提取成css文件,即css的link引入方式
  • webpack.config.js中更改style-loader为style-loader/url,css-loader作用是提取css到js中,因此也要去掉换成file-loader:

安装

yarn add file-loader —D

配置:

module: { rules: [ { test: /\.css$/, use: [ 'style-loader/url', 'file-loader' ] } ] }

file-loader可以解析项目中的url引入
,此时页面能生成link标签,但并没有样式,如下:

styleloaderurl.png

上图可见,link中css的生成地址是错误的,只有文件名系统无法找到css文件地址。产生问题的根源在,file-loader默认的生成css目录是dist目录,而目前为止html的配置文件在./dist/html/admin.html中,这时候需要配置file-loader(file-loader官网传送)的publicPath,如:

use: [ 'style-loader/url', { loader: 'file-loader', options: { publicPath: __dirname + '/dist/' } } ]

更好的办法是,在webpack.config.js的出口处配置公共资源路径publicPath:

output: { filename: 'bundle.js', publicPath: __dirname + '/dist/', path: path.resolve(__dirname, 'dist') }, use: [ 'style-loader/url', 'file-loader' ]

此时生成link标签正常:

styleloaderurlok.png

dist目录正常:

distindex.png

当然,也可以通过file-loader的选项配置(file-loader官网传送)生成的单独css文件的规则,如:

{ loader: 'file-loader', options: { // name: '[path][name].[ext]' name: '[name][hash:5].[ext]', outputPath: 'css/' } }

构建后的dist目录结构:

cssout.png

(4) css提取
  1. css提取需求
  • (2)的css存在于js中,生成的style中的css也没有合并
  • (3)的link引入的外部css也没有统一
  • 构建的生产环境css要求合并为一个文件且有压缩
  1. 安装与配置
    安装:
yarn add mini-css-extract-plugin css-loader -D

webpack.config.js配置:

const MiniCssExtractPlugin = require('mini-css-extract-plugin'); module: { rules: [ { test: /\.css$/, exclude: /(node_modules|bower_components)/, use: [ MiniCssExtractPlugin.loader, 'css-loader' ] } ] }, plugins: [ new MiniCssExtractPlugin({ filename: 'css/[name].[contenthash:5].css', chunkFilename: '[id].[contenthash:5].css' }) ]
  1. 实际用法
  • 一般来说,开发环境配置和生产环境配置是不同的
  • 如基于webpack封装的vue脚手架vue-cli2采用了两套配置,分别对应开发环境和生产环境
  • webpack4也可以根据环境变量配置为:
module.exports = (env, argv) => { const devMode = argv.mode !== 'production' // ... 省略无关配置 { test: /\.css$/, use: [ devMode ? 'style-loader' : MiniCssExtractPlugin.loader, 'css-loader' ] } }

其中devMode可以获取当前mode参数,来确定devMode是开发环境或生产环境

  • 根据环境变量的做法也可以是这样:
    package.json中:
"scripts": { "dev": "webpack-dev-server", "prod": "NODE_ENV=production webpack -p" },

webpack.config.js 文件中:

const devMode = process.env.NODE_ENV === 'production'
(5) css-loader配置及其他
  1. css-loader配置(官网传送)

css-loader除了将css填入js中,还具备的常用功能有:

  • 解析css路径-root
    默认不转义以/开头的url,即url(/image.png) => url(/image.png)

    { loader: 'css-loader', options: { root: '.' } }

    配置了root选项之后,url(/image.png) => require(’./image.png’),会在/前添加root的值

  • css压缩
    注意,css-loader的压缩功能已经去掉,中文官网滞后,参考英文官网

  • sourceMap
    详细介绍参考sourceMap一节

    options:{ sourceMap: true }
  • css module功能

    options: { modules: true, localIdentName: '[path][name]__[local]--[hash:base64:5]' }

注意: css-module开启之后,js中要显式写cssmodules作用域标识符local/global

:local(.className) { background: red; }

转义为:

._23_aKvs-b8bW2Vg3fwHozO { background: red; }
  1. css预处理器loader、自动补全浏览器前缀等功能
  • css预处理插件sass-loader
yarn add sass-loader node-sass -D

webpack.config.js:

{ test: /\.scss$/, exclude: /(node_modules|bower_components)/, use: ['style-loader','css-loader','sass-loader'] }

注意: sass-loader加入后,要记得更改匹配的正则表达式为.scss

  • css兼容性有关插件

如浏览器兼容性前缀补全安装

yarn add post-loader autoprefixer postcss-import postcss-cssnext cssnano -D

webpack.config.js中配置:

use: [ 'style-loader', { loader: 'css-loader', options: { importLoaders: 2 }, }, { loader: 'postcss-loader', options: { ident: 'postcss', plugins: (loader) => [ require('postcss-import')({ root: loader.resourcePath }), require('postcss-cssnext')(), require('autoprefixer')(), require('cssnano')() ] } } 'sass-loader', ],

importLoaders代表在 css-loader 前应用的 loader 的数量
ident是一个标识符,命名为唯一值即可,用来启用plugins中的require语法
postcss插件介绍:

- import  @import形式css内联
- cssnext 允许使用未来的css语法
- cssnano 优化css包大小
- autoprefixer 自动补全css浏览器兼容前缀

安装插件后build,有警告提示autoprefixer功能已经被postcss-cssnext实现,可以删掉autoprefixer

(6) 第3节最后的实用配置可以写成这样

webpack.config.js的modules中:

{ { test: /\.scss$/, use: [ devMode !== 'production' ? 'style-loader' : MiniCssExtractPlugin.loader, { loader: 'css-loader', options: { importLoaders: 2, } }, { loader: 'postcss-loader', options: { ident: 'postcss', plugins: loader => [ require('postcss-import')({ root: loader.resourcePath }), require('postcss-cssnext')(), require('cssnano')() ] } }, 'sass-loader' ]}, { test: /\.(png|svg|jpg|jpeg|gif)$/, exclude: /(node_modules|bower_components)/, use: [ { loader: 'url-loader', options: { // 这里的options选项参数可以定义多大的图片转换为base64 limit: 1000, // 表示小于1000Byte的图片转为base64,大于1000Byte的是路径 outputPath: 'imgages', //定义输出的图片文件夹 name: '[name].[ext]' } } ] } }

webpack.config.js的涉及的plugins中:

plugins:[ new MiniCssExtractPlugin({ filename: 'css/[name].[contenthash:5].css', chunkFilename: '[id].[contenthash:5].css' }), ]

4. 图片处理

(1) 普通图片处理

background-image url中的图片css-loader不能解析,需要使用file-loader

yarn add file-loader -D
{ test: /\.(png|svg|jpg|jpeg|gif)$/, exclude: /(node_modules|bower_components)/, use: [ { loader: 'file-loader', options: { name: '[name].[ext]', outputPath: 'images/' } } ] }
(2) 图片按需处理

小图片生成base64编码,大图片直接保存,这需要url-loader插件

{ loader: 'url-loader', options: { limit: 8192, outputPath: 'imgages', //定义输出的图片文件夹 name: '[name].[ext]' } },

注意:

  • url-loader相当于file-loader增强版,配置url-loader时需要删除file-loader的图片配置;但url-loader依赖file-loader,故不能删除file-loader包
  • url-loader不能工作在style-loader/url和file-loader作用的link css模式下(测试3(3)配置不可用,原因待寻)
(3) 字体处理

file loader和url-loader可以接受任何类型文件并处理,故字体的配置可以这样:

{ test: /\.(woff|woff2|eot|ttf|otf)$/, use: ['file-loader'] }

5. web服务器与代理

(1)本地web服务器安装与配置

安装:

yarn add webpack-dev-server -D

webpack支持的所有配置传送,这里列出常用配置:

module.exports = { // ...省略其他配置 devServer: { port: 9000, https: false, //contentBase: './html', historyApiFallback: false, open:true, inline: true, hot: false, proxy: { '/api1': 'http://localhost:3000', '/api2': { target: 'https://other-server.example.com', secure: false, changeOrigin: true }, '/api3': { target: 'http://localhost:3000', pathRewrite: { '^/api3': '' } } } }, }
参数 说明
port 本地端口号
https 是否启用https
contentBase 本地服务器作用的根目录----注意: 指的是localhost/[contentBash] 形式访问HtmlWebpackPlugin中filename: 'html/index.html’定义的路径html
historyApiFallback 设为true后SPA本地刷新不出错,以当前目录index.html为首页,可配置
open 设为true则配置生效后自动打开相应网址
inline 文件修改自动刷新,注意:这不是热更新
hot 热更新HMR
proxy 代理配置,secure允许https,changeOrigin是跨域配置,pathRewrite是匹配后url重写,用于多个不同前缀的url代理到不同地址
publicPath 不设置时默认与output中的publicPath相同,设置后将webpack-dev-server生成的运行时代码(存放在内存中)放到这个目录下,官方推荐设置成与output的publicPath相同
(2)实际项目配置-配置分离

实际开发中,我们只需要开发环境使用本地服务器,生产环境代码不需要服务器(当然也可以配置),我们通常在package.json中这样配置:

"scripts": { "start": "NODE_ENV=development webpack-dev-server --mode development", "build": "NODE_ENV=production webpack --mode production" }
  • 并删除webpack.config.js中的mode:'development’代码
  • NODE_ENV参数要写在webpack前面
  • webpack.config.js配置文件中通过node环境变量 process.env.NODE_ENV取到值
(3)本地服务器原理
  1. 本地服务器原理
  • 使用yarn start运行后,本地node server伺服的文件不存在于硬盘上,而是内存中
  • 这与yarn build后生成文件存储到硬盘上的dist文件夹不同
  • 几种重要路径配置的区分(详细解释传送)
    • output的path指的是生成项目的本地绝对路径,生产模式下是必须的;开发模式因为生成在内存中,故与这个配置无关;
    • output的publicPath表示的是打包生成的index.html文件里面引用资源的前缀,各种loader加载的路径前缀都受该选项控制;如css引入资源的路径补全等,output.publicPath,不仅可以影响虚拟目录的取值(通过缺省devserver.publicPath),也影响利用html-webpack-plugin插件生成的index.html中引用的js、css、img等资源的引用路径,它会自动在资源路径前面追加设置的output.publicPath。
    • devServer的publicPath可以看作是devServer对生成目录dist设置的虚拟目录,devServer首先从devServer.publicPath中取值,如果它没有设置,就取output.publicPath的值作为虚拟目录,如果它也没有设置,就取默认值 “/”
    • devServer的contentBase是用来区分不同静态文件的,意思是本地服务器提取静态资源的目录位置,与HtmlWebpackPlugin的html地址有关
  1. 配置实例

实例一

output: { filename: 'js/bundle.js', publicPath: '/', path: path.resolve(__dirname, 'dist') }, new HtmlWebpackPlugin({ filename: 'index.html', template: 'src/html/index.html', }),

此时:

11.png

  • 本地服务器作用于项目根目录
  • webpack打包虚拟地址在本地服务器作用的项目根目录

12.png


实例二

output: { filename: 'js/bundle.js', publicPath: '/', path: path.resolve(__dirname, 'dist') }, new HtmlWebpackPlugin({ filename: 'html/index.html', // 生成index.html位置在html目录中 template: 'src/html/index.html', }),

此时,webpack打包虚拟目录是服务器根目录,打开显示的也是项目根目录

21.png

说明在服务器根目录没找到index.html,这时的地址应该是:

22.png


实例三

output: { filename: 'js/bundle.js', publicPath: '/', path: path.resolve(__dirname, 'dist') }, devServer: { port: 9000, publicPath: '/dist/', }, new HtmlWebpackPlugin({ filename: 'index.html', template: 'src/html/index.html', }),

结果:

31.png

localhost/dist/目录能找到虚拟目录位置,但css没能渲染到/dist/虚拟目录下


实例四

output: { filename: 'js/bundle.js', publicPath: '/assets/', path: path.resolve(__dirname, 'dist') }, devServer: { port: 9000, publicPath: '/dist', }, new HtmlWebpackPlugin({ filename: 'index.html', template: 'src/html/index.html', }),

资源目录前缀是assets,虚拟目录前缀dist:

41.png

css仍然渲染失败


推荐实例

资源前缀为web服务器根目录,虚拟目录缺省也为web服务器根目录,htmlWebpackPlugin的html模板生成位置也是服务器根目录,三者统一,最容易调试:

output: { filename: 'js/bundle.js', publicPath: '/', path: path.resolve(__dirname, 'dist') }, devServer: { port: 9000, //contentBase: path.join(__dirname, "src/html"), }, new HtmlWebpackPlugin({ filename: 'index.html', template: 'src/html/index.html', }),

devServer的contentBase一般是绝对路径,只用来访问本地静态文件,如以上配置中的src/html里的静态index.html

(4)服务器自动刷新与热更新
  1. 配置说明
options配置 配置作用 解析
inline:false 配置成iframe刷新模式 整个页面刷新,会添加额外iframe到页面结构中
inline:true 配置成inline刷新模式 整个页面刷新,推荐做法
hot:true 开启热更新HMR 局部更新,组件级更新,推荐做法
  1. 配置注意事项

如果直接配置,会提示缺组件,并不会像官网说的自动添加相应插件,报错:

hotconfig.png

需要去掉hot配置项,更改script命令,增加hot选项:

"start": "NODE_ENV=development webpack-dev-server --mode development --hot --open",

这样webpack能自动找到相应plugin安装。
这时更改css能局部刷新,但更改js可能仍然是整个页面刷新

  1. 热更新的原理
    热更新的原理是websocket连接,服务器将变更的文件通知浏览器

6. sourceMap

(1)基本配置
  • webpack.config.js中devtool选项控制sourceMap及其规则
  • devtool有很多值,不同值影响构建速度
  • 开发环境一般用inline-source-map
  • 生产环境使用source-map或者不使用sourceMap功能
  • 项目较大需要研究构建速度构建文件体积的影响,需要对比权衡多种devtool选项
devtool: 'inline-source-map'

注意: 这个选项仅对js有效,css的sourceMap需要单独设置

(2)css的sourceMap

在webpack.config.js中,css-loader和sass-loader中都要设置sourceMap选项:

{ loader: 'css-loader', options: { importLoaders: 2, sourceMap: true // modules: true, // localIdentName: // '[path][name]__[local]--[hash:base64:5]' } }, { loader: 'postcss-loader', options: { ident: 'postcss', plugins: loader => [ require('postcss-import')({ root: loader.resourcePath }), require('postcss-cssnext')(), require('cssnano')() ] } }, { loader: 'sass-loader', options: { sourceMap: true } }

7. 过期文件清除

(1) 原因

根据webpack.config.js中的css提取插件配置

new MiniCssExtractPlugin({ filename: '[name].[contenthash:5].css', chunkFilename: '[id].[contenthash:5].css' })

每次改动代码文件,会出现无用的打包文件,如:

hash.png

只有最新的hash文件才会被用到,其他文件已经过期不再使用。不止css,也包括js文件也存在这个问题,因此需要在每次build构建前清除输出目录的内容

(2) 配置方法

安装:

yarn add clean-webpack-plugin -D

配置:

const CleanWebpackPlugin = require('clean-webpack-plugin'); let pathsToClean = [ 'dist', ] module.exports = { ... plugins: [ new CleanWebpackPlugin(pathsToClean) ], ... };

webpack进阶功能

1. 代码分离

简介

通常我们一个入口生成一个bundle,但一个bundle可能很大,如果能合理拆分并行加载将提高页面的加载速度

常见方法三种

  • 手动拆分
  • 避免重复
  • 侵入式按需拆分
(1)手动拆分
entry: { index: './src/index.js', other: './src/other.js' }, output: { filename: 'js/[name].bundle.js', publicPath: '/', path: path.resolve(__dirname, 'dist') },

注意: output的输出必须写[name]占位符,否则打包的两个文件同名会覆盖

  • 这种方法就是写两个入口文件,分别打包为两个bundle.js
  • 缺点明显:
    • 重复引用,可能两者用到的公共引用会被各自重复打包
    • 不能互相之间有关联,两个文件必须是完全独立的,否则就有相互引用的问题
  • 实际上这样拆分是很“粗糙”的处理,一般不这么做
(2)避免重复(Prevent Duplication)
  • 基于SplitChunks插件(webpack 4.x以前使用CommonsChunkPlugin)
  • “避免重复”是官方用词,做的工作其实是将第三方包或者库独立生成bundle.js

a. 配置方法(与entry同级选项)

entry: { index: './src/index.js' }, output: { filename: 'js/[name].bundle.js', publicPath: '/', path: path.resolve(__dirname, 'dist') }, optimization: { splitChunks: { chunks: 'all' } }

b. 打包结构:

chunk.png

可以看到,第三方包都被单独打包到vendos~index中,源文件index.js打包生成的index.bundle.js只有18.2KB

c. 生成文件的目录结构

splitindex.png

可以看到,第三方依赖作为单独bundle使用

d. SplitChunks插件解析

以上基于SplitChunks插件的默认配置,这种配置下才分割代码块(chunk),即:

1. New chunk can be shared OR modules are from the node_modules folder 共享的或者来自npm包的代码块
2. New chunk would be bigger than 30kb (before min+gz)  分割前大于30k的公共模块,如果只是几k的公共代码,是不会分割提取的
3. 在按需加载时,并行请求数量小于等于5
4. 在初始化页面加载时,请求数量小于等于3  

实际上相当于如下默认条件:

optimization { splitChunks: { chunks: "all", minSize:30000, minChunks: 1, // 最小 chunk ,默认1 maxAsyncRequests: 5, // 最大异步请求数, 默认5 maxInitialRequests : 3, // 最大初始化请求书,默认3 automaticNameDelimiter: '~',// 打包分隔符 name:true, // 使下面用到的包的重命名起作用 cacheGroups: { vendors: { test: /[\\/]node_modules[\\/]/, priority: -10 }, hellolodash:{ test: /lodash/, priority:0, name:'lodashlib' }, default: { minChunks: 2, priority: -20, reuseExistingChunk: true, name:'lib' // 生成的js/vendor~index.bundle.js改为js/lib.bundle.js } } } }

以上关键配置说明:

  • optimization是webpack的优化选项,splitChunks对全局生效根据规则分离公共代码;针对不同的要提取的公共部分进行单独设置,需要在cacheGroups中单独命名键值对,键是自己命名的,主要是test规则去匹配,值一般是配置选项,上面hellolodash是一个例子,可以将lodash包单独分离出来
  • chunks常用有三种选项,推荐使用all
    • initial:表示对异步引入的模块不处理
    • async:表示只处理异步模块(默认)
    • all:无论同步还是异步,都会处理
  • miniSize是30k,只有共用组件大于这个值才会被分割抽取,否则抽取意义不大,多了http请求
  • 默认有两个cacheGroup,一个为vendors用于处理第三方依赖库;一个是default(处理当module被引用等于或超过2次时情况)
  • 默认优先级都是负的,可以保证我们设置的自定义缓存组优先起作用

该插件可以更改默认条件实现更灵活的配置,参见参考文档,这里介绍些常用选项:

1. minSize(默认是30000):形成一个新代码块最小的体积
2. name(打包的chunks的名字):字符串或者函数(函数可以根据条件自定义名字)
3. priority :缓存组打包的先后优先级
(3)动态插入-动态加载依赖
  1. 简介
  • 一般配置到第(2)节就可以了,因为分离了manifest、第三方包、公共代码、自己写的源码,已经基本完成分割任务,一般自己写的源码不会太大。
  • 前两节内容是Bundle Split,本节讲的是Code Split,上面切割包,这里切割源代码
  1. 动态加载依赖方法有两种
  • webpack的API-require.ensure()
    commonjs语法,传送门
const { getData } = require('./main') let arr = [1,2,123,21,3,21,321,1] getData('./index.html') setTimeout(() => { require.ensure(['./math'], function(require) { const { findMaxIndex } = require('./math') console.log(findMaxIndex(arr)) }) }, 3000)

当执行到require.ensure时,./math依赖才会被加载,如果math中还有依赖,也会被执行,这就是动态依赖加载;动态加载本质还是webpack创建script标签来实现的;原理上,webpack 在编译时,会静态地解析代码中的 require.ensure(),同时将模块添加到一个分开的 chunk 当中。这个新的 chunk 会被 webpack 通过 jsonp 来按需加载。

import('lodash').then(_ => { // Do something with lodash (a.k.a '_')... })

使用这种方法在代码中写业务时,webpack会在语法静态分析阶段直接把import的内容打包为独立的chunk,在浏览器运行到这一行代码时,就会自动请求这个资源,实现异步加载

注意:

  1. ES6的动态加载要求import(url)中url是相对路径,import()匹配符总是相对于被调用的文件
  2. 如果报错import不支持,可以按错误提示安装插件
yarn add @babel/plugin-syntax-dynamic-import -D

在.babelrc中增加插件

"plugins": ["@babel/plugin-syntax-dynamic-import"]
  1. 通过注释添加chunk名
import( /* webpackChunkName: "my-chunk-name" */ './a.js').then(module => { console(module); }).catch(err => { console(err.message); });

chunkname.png

2. 按标识符加载相应库插件-ProvidePlugin

功能

  • 智能按需加载使用的库,没使用的不会打包进去
  • 避免每个模块都引入import、require,根据是否有标识符webpack自动判断

使用

yarn add jquery underscore

不必安装ProvidePlugin,这是webpack内置插件

new webpack.ProvidePlugin({ //它是一个插件,所以需要按插件的用法new一个 $:'jquery', //接收名字:模块名 _:'underscore' //引入underscore库 }),

注意: webpack变量本身要再webpack.config.js中引入

3. webpack可视化查看器

文档

  1. 安装
yarn add webpack-bundle-analyzer -D
  1. webpack.config.js配置
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; module.exports = { plugins: [ new BundleAnalyzerPlugin() ] }
  1. 使用
yarn build

默认启动本地服务器,自动打开分析页面

  1. 更多插件功能选项请查看官方文档
    重要插件选项有:
new BundleAnalyzerPlugin({ analyzerMode: 'static', openAnalyzer: false })

openAnalyzer默认true自动打开分析页
analyzerMode分为server启服务,static生成静态html,disabled不使用三种
此项可以根据process.env.NODE_ENV环境变量来设置只在yarn build时开启

4. 缓存问题

简介

  • webpack生成的最终文件如果只有文件名而不包含hash,浏览器通过命中缓存,以降低网络流量,使网站加载速度更快
  • 但部署时候却可能得不到最新版本的文件
  • 实现缓存的方法是不包含hash,如果包含hash则不会被缓存(相当于加载新文件)

实现

webpack.config.js配置:

output: { filename: 'js/[name].[hash:5].bundle.js', //chunkFilename: '[name].[chunkhash:6].js', chunkFilename: '[name].js', publicPath: '/', path: path.resolve(__dirname, 'dist') },

主要在分离包之后对不常用包直接命名,对常用包加hash的方法实现

hashcache.png