webpack
是一个现代JavaScript
应用程序的静态模块打包器。
当webpack
处理应用程序时,会递归构建一个依赖关系图,其中包含应用程序需要的每个模块,然后将这些模块打包成一个或多个bundle
。
核心概念
-
entry
入口 -
output
输出 -
loader
模块转换器,用于把模块原内容按照需求转换成新内容; -
plugins
扩展插件,在webpack
构建流程中的特定时机注入扩展逻辑,来改变构建结果或做你想要做的事情。
作用:模块化打包
webpack
是一个模块打包工具,一个打包模块化JavaScript
的工具,是工程化、自动化思想在前端开发中的体现。
webpack
将项目的资源文件当成一个个模块,模块之间会有依赖关系,它会对这些有依赖关系的文件进行处理 --
从入口模块出发,识别出模块之间的依赖关系(模块化导入语句),递归找出入口文件的所有依赖,将它们打包到一个或多个文件中(bundle
)。
初始化项目
- 新建一个目录,
npm init -y
- 安装核心依赖
npm i webpack webpack-cli -D // webpack4.x.x
局部安装的命令无法像全局安装那样直接使用,需要配合 npx
,它会从当前项目的node_modules
中查找,比如 npx webpack -v
(查看版本号)
从 wepack V4.0.0
开始, webpack
是开箱即用的,在不引入任何配置文件的情况下就可以使用。
- 新建
src/index.js
class Animal {
constructor(name) {
this.name = name;
}
}
const dog = new Animal('dog');
- 构建:
npx webpack --mode=development
,默认production
模式。development
模式可以更好的查看打包后的代码。
(function(module, exports) {
eval("class Animal {\r\n constructor(name) {\r\n ......
})
默认输出目录和文件:dist/main.js
,更多默认配置在node_modules/webpack/lib/WebpackOptionsDefaulter.js
查看dist/main.js
可知,src/index.js
中的代码并没有被转译为低版本代码,这显然不是我们想要的。
babel-loader
loader
是webpack
的四大核心之一,用于对源代码进行转换,而转换工具正是Babel
。
babel-loader
是 Babel
在webpack
的使用方式,可以将JS代码向低版本转换。
- 安装
Babel
相关的依赖npm i @babel/core @babel/preset-env @babel/plugin-transform-runtime -D npm i @babel/runtime @babel/runtime-corejs3 -S npm i babel-loader -D
- 新建
webpack
的配置文件webpack.config.js
建议给// ! Webpack是基于NodeJS的,必须使用CommonJS规范导出一个对象 module.exports = { module: { rules: [ { test: /\.js$/, use: ['babel-loader'], exclude: /node_modules/ //排除 node_modules 目录 } ] } }
loader
指定include
或exclude
,有效提升编译效率 - 新建
Babel
配置文件.babelrc
{ "presets": ["@babel/preset-env"], "plugins": [ ["@babel/plugin-transform-runtime", { "corejs": 3 }] ] }
重新编译,发现代码已经被Babel
降级转换了。
如果不想创建.babelrc
,还可以在 webpack.config.js
配置 Babel
use: {
loader: 'babel-loader',
options: {
presets: ["@babel/preset-env"],
plugins: [
["@babel/plugin-transform-runtime", { "corejs": 3 }]
]
}
},
loader配置项
loader
需要配置在 module.rules
中,rules
是一个数组。
- 字段
test
表示匹配规则,对项目中符合规则的文件进行处理; -
loader
的配置方式有多种- 只适用于一个
loader
的情况{ test: /\.js$/, loader: 'babel-loader', options: { //... } }
- 使用
use
字段则比较自由,可以是一个字符串{ test: /\.js$/, use: 'babel-loader' }
- 可以是一个数组
{ test: /\.css$/, use: ['style-loader', 'css-loader'] }
- 还可以是一个对象,其
options
属性用于配置loader
{ test: /\.js$/, use: { loader: 'babel-loader', options: { presets: ["@babel/preset-env"] } }, }
- 只适用于一个
mode
mode
用于指定打包构建时的模式,支持两个配置项
-
development
将process.env.NODE_ENV
的值设置为development
,启用NamedChunksPlugin、NamedModulesPlugin
- production
将process.env.NODE_ENV
的值设置为production
,启用FlagDependencyUsagePlugin、FlagIncludedChunksPlugin、ModuleConcatenationPlugin、NoEmitOnErrorsPlugin、OccurrenceOrderPlugin、SideEffectsFlagPlugin、UglifyJsPlugin(JS压缩)
mode
可以配置在webpack.config.js
中,然后直接使用 npx webpack
进行编译。
module.exports = {
mode: "development",
// ...
}
cross-env
这是一个运行跨平台设置和使用环境变量的脚本。
在配置文件中,经常会使用 process.env.NODE_ENV
来判断当前是development
还是production
,但process.env
默认并没有NODE_ENV
。
cross-env
可以兼容运行平台去设置环境变量NODE_ENV
,比如Windows
系统不支持NODE_ENV=development
的设置方式。
cross-env
能够提供一个设置环境变量的scripts
,让你能够以unix
方式设置环境变量,然后在Windows
上兼容运行。
- 安装
npm i cross-env -D
- 配置
package.json
"scripts": { "dev": "cross-env NODE_ENV=development webpack", "build": "cross-env NODE_ENV=production webpack" }
- 在
webpack.config.js
中使用process.env.NODE_ENV
,如动态配置mode
const ENV = process.env.NODE_ENV module.exports = { mode: ENV, //... }
构建HTML页面
编译HTML
的webpack
插件:html-webpack-plugin
- 安装插件
npm i html-webpack-plugin -D
- 在根目录下创建
index.html
,快捷键html:5
生成HTML
- 配置
webpack.config.js
,plugins
字段是一个数组,存放所有的webpack
插件// 引入插件 const HtmlWebpackPlugin = require('html-webpack-plugin'); module.exports = { //... plugins: [ new HtmlWebpackPlugin({ template: './index.html', filename: 'index.html', //打包后的文件名 minify: { minimize: true, // 是否打包为最小值 removeAttributeQuotes: true, //是否删除属性的引号 collapseWhitespace: false, //去除空格 removeComments: true, // 去除注释 minifyCSS: true, // 压缩HTML内的CSS minifyJS: true, //压缩HTML内的JS removeEmptyElements: true //清理内容为空的元素 }, hash: true //引入产出资源时加上哈希,避免缓存 }) ] }
- 执行
npm run dev
构建,生成dist/index.html
,且自动注入了<script>
脚本,引入打包后的js
文件<script src="main.js?ccec26148a41640c2cf0"></script> // hash: true 在引入资源后加上哈希值
html-webpack-plugin
中的 config
妙用
html-webpack-plugin
插件在解析HTML
时,支持类似ejs
语法。同时会向HTML
中注入该插件的对象htmlWebpackPlugin
,使用其中配置的各种属性。
例如,我们想自由配置开发版和发布版的页面展示不同的内容;
- 新增一个自定义的配置文件
public/config.js
module.exports = { development: { // 开发配置项 template: { title: 'Hello development', header: false, // 不要头部和尾部 footer: false } }, production: { // 发布配置项 template: { title: 'Hi production', header: true, // 只要头部 footer: false } } }
-
webpack.config.js
中配置html-webpack-plugin
的config
属性const HtmlWebpackPlugin = require('html-webpack-plugin'); const ENV = process.env.NODE_ENV const config = require('./public/config')[ENV] module.exports = { mode: ENV, //... plugins: [ new HtmlWebpackPlugin({ template: './index.html', filename: 'index.html', config: config.template }) ] }
-
index.html
<!DOCTYPE html> <html lang="en"> <head> // ... <% if(htmlWebpackPlugin.options.config.header) { %> <link rel="stylesheet" type="text/css" href="//common/css/header.css"> <% } %> <title><%= (htmlWebpackPlugin.options.config.title) %></title> </head> <body> hello back </body> <% if(htmlWebpackPlugin.options.config.footer) { %> <script src="//common/header.min.js" type="text/javascript"></script> <% } %> </html>
- 分别使用
npm run dev
和npm run build
构建,对比dist/index.html
的不同
实时展示页面
webpack-dev-server
用于配置本地服务器,可以为webpack
打包生成的资源文件提供web
服务
- 为静态文件提供
web
服务 - 自动刷新和热替换
(HMR)
npm i webpack-dev-server -D
修改package.json
"scripts": {
"dev": "cross-env NODE_ENV=development webpack-dev-server",
"build": "cross-env NODE_ENV=production webpack"
},
webpack.config.js
中进行webpack-dev-server
的其他配置
module.exports = {
//...
devServer: {
open: true, // 自动打开首页,默认false
port: '3000', // 默认是8080
hot: true, // 热加载
clientLogLevel: "warning", // 日志等级
compress: true, // 是否启用 gzip 压缩
proxy: // 代理
// ...
}
}
执行npm run dev
,现在修改 src/index.js
,页面控制台会实时刷新,当前刷新的是整个页面。
更多的配置可以点击查看。
devtool
sourcemap
是为了解决实际运行代码(打包后)出现问题时,无法定位到开发环境中的源代码;
查看页面控制台上打印的log
日志,会发现对应的行号是代码编译后的,而不是当前源码中的行号。
devtool
配置项可以帮助我们将编译后的代码映射回源码中,不同的值会明显影响构建和重新构建的速度。
综合构建速度,在开发模式下通常设置为 cheap-module-eval-source-map
;在生产环境下则设置为source-map
// webpack.config.js
module.exports = {
devtool: 'cheap-module-eval-source-map'
}
-
sourcemap
的5种基本devtool
选项-
eval
每个模块都使用eval()
执行,每个模块后会增加sourceURL
来关联模块处理前后的对应关系;
由于会映射到转换后的代码,而不是映射到原始代码(没有从loader
中获取sourcemap
),所以不能正确显示行号;又因为不需要生成模块的sourcemap
,所以打包速度很快。 -
source-map
会为模块生成独立的sourcemap(.map)
文件,我们可以根据报错信息和.map
文件进行错误分析,定位到源码; -
inline
不会生成独立的.map
文件,sourcemap
转换为DataUrl
后添加到bundle
中; -
cheap
在打包后同样会为每个模块生成.map
文件,但与source-map
的区别在于,它生成的.map
文件会忽略原始代码中的列信息,也不包含loader
的sourcemap
; -
moudle
:包含了loader
模块之间的sourcemap
,将loader source map
简化为每行一个映射;
-
- 基本类型之间可以相互搭配,如
eval-source-map、inline-cheap-model-source-map、cheap-module-eval-source-map、hidden-source-map、nosources-source-map
- 开发环境常用:
eval、eval-source-map、cheap-eval-source-map、cheap-module-eval-source-map
- 生产环境常用:
none(省略devtool选项,不生成sourcemap)、source-map、hidden-source-map、nosources-source-map
- 开发环境常用:
-
source-map
和hidden-source-map
都会打包生成单独的.map
文件。区别在于,source-map
会在打包出的js
文件中增加一个引用注释,以便开发工具知道在哪里可以找到它;hidden-source-map
则不会在打包的js
中增加引用注释。
但是我们一般不会直接将 .map
文件部署到CDN
,因为会直接映射到源码,更希望将 .map
文件传到错误解析系统,然后根据上报的错误信息,直接解析到出错的源码位置。
CSS样式
webpack
处理css
也必须借助loader
:style-loader、css-loader
考虑到兼容性问题,还需要 postcss-loader
配合 autoprefixer
插件。
对于Less、Sass
,还需要 less-loader
和 sass-loader
// 以 less 为例
npm i style-loader css-loader less-loader postcss-loader autoprefixer less -D
-
style-loader
动态创建style
标签,将css
插入到head
中; -
css-loader
负责编译CSS
,处理@import
等语句; -
postcss-loader
和autoprefixer
,自动生成浏览器兼容性前缀; -
less-loader
负责处理编译.less
文件,将其转为css
配置 webpack.config.js
test: /\.(le|c)ss$/,
use: ['style-loader', 'css-loader', {
loader: 'postcss-loader',
options: {
plugins: [require('autoprefixer')({ // 应该配置到 package.json 或 .browserslistrc
overrideBrowserslist: ['ie >= 8', 'Firefox >= 20', 'Safari >= 5',
'Android >= 4', 'Ios >= 6',
'last 4 version' // 浏览器最新的4个版本
]
})]
}
}, 'less-loader']
应该将 overrideBrowserslist
配置到package.json
中
// package.json
"browserslist": [
"ie >= 8", "Firefox >= 20", "Safari >= 5",
"Android >= 4", "Ios >= 6", "last 4 version"
]
// webpack.config.js
test: /\.(le|c)ss$/,
use: ['style-loader', 'css-loader', {
loader: 'postcss-loader',
options: {
plugins: [require('autoprefixer')]
}
}, 'less-loader']
在 src/index.js
中引入CSS
尝试一下吧:import './base.less';
注意:loader
的执行顺序 从右向左。另外,loader
中还有一个指定优先级的参数enforce --> 值 pre(优先执行),post (滞后执行)
文件处理
CSS
中引入图片、字体等资源文件时,如background-image: url(./a.png)
,需要使用 url-loader
或 file-loader
来处理。
它们功能类似,但url-loader
可以限制文件大小,返回DataURL
。另外,使用url-loader
时也必须安装file-loader
,但不要同时配置,会冲突。
-
安装与配置
npm install url-loader file-loader -D //webpack.config.js test: /\.(png|jpg|gif|jpeg|webp|svg|eot|ttf|woff|woff2)$/, use: [ { loader: 'url-loader', options: { limit: 10240, // 10K 的限制 esModule: false } } ]
-
limit
资源小于10K
时,则转为base64
;超过10K
则将图片拷贝到dist
目录。base64
可以较少网络请求,但base64
数据太大或资源太多,都会导致加载变慢,所以设置limit
时要两者兼顾; -
esModule
设置为false
,否则<img src={require('XXX.jpg')} />
会出现<img src=[Module Object] />
-
-
在
CSS
中引入图片资源body { box-sizing: border-box; background-image: url(./images/bg.png); }
-
默认情况下,打包生成的资源名是文件内容的
MD5
哈希值,并会保留其扩展名。// 图片默认拷贝到 dist 根目录 background-image: url(c5c9a52347e5c5899c2bca539cd016f2.png);
url-loader options
的更多配置
- 值
-
[hash]
:内容的哈希值,作为打包后的默认文件名,[hash:6]
表示取哈希值的前6
位作为文件名; -
[ext]
:资源文件的原扩展名; -
[name]
:资源文件的原名称; -
[path]
:资源相对于context
的路径,即默认相对于webpack.config.js
的路径。
-
- 属性
-
name
:配置打包后的文件名,默认值为[hash].[ext]
-
context
:配置文件的上下文,默认为webpack.config.js
与[path]
配合使用可以控制打包后的文件目录,默认相对于webpack.config.js
1. 假设 bg.png 存放目录 src/images,src目录与webpack.config.js同级别 2. 配置文件名 options: { name: '[path][name].[ext]' } 3. 编译打包后,bg.png 输出目录 dist/src/images background-image: url(src/images/bg.png); 4. 配置context,假设项目目录名为webtest options: { name: '[path][name].[ext]', context: '../' // 向上一级,webpack.config.js上一级目录是项目目录 } 5. 编译打包后,bg.png 输出目录 dist/webtest/src/images background-image: url(webtest/src/images/bg.png);
-
publicPath
:文件的public
发布目录,可以使用该属性配置资源的CDN
路径options: { name: '[name]_[hash:6].[ext]', publicPath: 'http://www.xxx.com/img' } background-image: url(http://www.xxx.com/img/bg_c5c9a5.png);
-
outputPath
:文件的output
输出目录,默认会拷贝到dist
根目录options: { name: '[name].[ext]', outputPath: 'assets/' } // 匹配的资源文件会拷贝到 dist/assets 目录中 background-image: url(assets/bg.png);
-
HTML中的本地资源
标签<img />
也可以引入图片资源,但url-loader
是不能直接处理的,需要使用html-loader
。
html-loader
处理HTML
中的<img />
时,还需要结合url-loader/file-loader
才可以将<img />
的路径正确打包,同时记得配置url-loader
的 esModule: false
<img src="./imgs/a.png" />
npm i html-loader -D
# webpack.config.js
test: /\.html$/,
loader: 'html-loader',
options: {
attributes: false,
// attributes: {
// list: [
// { tag: 'img', attribute: 'src', type: 'src' },
// // ...
// ]
// },
}
除了img src
,html-loader
还可以处理video src
。
但是,html-loader
会导致html-webpack-plugin
解析HTML
中的<% ... %>
失败。
- 删除
html-loader
- 修改
img src
图片会经过<img src="<%= require('./imgs/a.png') %>" />
url-loader
的处理,小于10K(limit: 10240)
将被转为base64