webpack入门篇
// package.json
{
"name": "webpack-demo",
"version": "1.0.0",
"description": "webpack学习项目",
"main": "index.js",
"scripts": {
"dev": "webpack --mode development --module-bind js=babel-loader",
"build": "webpack --mode production --output ./output/main.js"
},
"author": "chen",
"license": "ISC",
"devDependencies": {
"@babel/core": "^7.10.2",
"@babel/preset-env": "^7.10.2",
"babel-loader": "^8.1.0",
"webpack": "^4.43.0",
"webpack-cli": "^3.3.11"
}
}
webpack.config.js 配置文件
// webpack.config.js文件
// path 模块提供用于处理文件路径和目录路径的实用工具。 它可以使用以下方式访问:
const path = require('path')
// 然后通过module.exports将 Webpack 的配置导出
module.exports = {
mode: 'development',
entry: './src/index.js',
output: {
// path.resolve( )方法 可以将路径或者路径片段解析成绝对路径
// 只传入__dirname也可以自动调用path.resolve方法 但是无法和后面的路径进行拼接
// __dirname代表的是当前文件的绝对路径
path: path.resolve(__dirname, 'dist'),
filename: 'foo.bundle.js',
},
}
// 😀如果我们只使用一个配置文件来区分生产环境(production)和开发环境(development)
// 则可以使用函数类型的 Webpack 配置,函数类型的配置必须返回一个配置对象,如下面:
// Webpack 配置函数接受两个参数env和argv:分别对应着 环境对象 和 Webpack-CLI 的命令行选项,例如上面代码中的--optimize-minimize。
module.exports = (env, argv) => {
return {
mode: env.production ? 'production' : 'development',
devtool: env.production ? 'source-maps' : 'eval',
plugins: [
new TerserPlugin({
terserOptions: {
compress: argv['optimize-minimize'], // 只有传入 -p 或 --optimize-minimize
},
}),
],
}
}
配置的使用
默认情况下,Webpack 会查找执行目录下面的webpack.config.js
作为配置,如果需要指定某个配置文件,可以使用下面的命令:
--config build/webpack.dev.js
Webpack 常见名词解释
参数 | 说明 |
---|---|
entry |
项目入口 |
module |
开发中每一个文件都可以看做 module,模块不局限于 js,也包含 css、图片等 |
chunk |
代码块,一个 chunk 可以由多个模块组成 |
loader |
模块转化器,模块的处理器,对模块进行转换处理 |
plugin |
扩展插件,插件可以处理 chunk,也可以对最后的打包结果进行处理,可以完成 loader 完不成的任务 |
bundle |
最终打包完成的文件,一般就是和 chunk 一一对应的关系,bundle 就是对 chunk 进行编译压缩打包等处理后的产出 |
mode 模式
Webpack4.0 开始引入了mode
配置,通过配置mode=development
或者mode=production
来制定是开发环境打包,还是生产环境打包,比如生产环境代码需要压缩,图片需要优化,Webpack 默认mode
是生产环境,即mode=production
。
在配置文件中设置mode
:
module.exports = {
mode: 'development'
};
Webpack 的入口(entry)和输出(output)
webpack 是一个模块打包工具,能够从一个需要处理的 JavaScript 文件开始,构建一个依赖关系图(dependency graph),该图映射到了项目中每个模块,然后将这个依赖关系图输出到一个或者多个 bundle 中
Webpack 的两个核心概念:entry
和output
,即入口和输出
Webpack 是从指定的入口文件(entry)开始,经过加工处理,最终按照output
设定输出固定内容的 bundle;而这个加工处理的过程,就用到了loader
和plugin
两个工具;loader
是源代码的处理器,plugin
解决的是 loader
处理不了的事情。
在介绍entry
之前,介绍下context
(上下文),context
即项目打包的相对路径上下文,如果指定了context="/User/test/webpack"
,那么我们设置的entry
和output
的相对路径都是相对于/User/test/webpack
的,包括在 JavaScript 中引入模块也是从这个路径开始的。由于context
的作用,决定了context
值必须是一个绝对路径。
// webpack.config.js
module.exports = {
context: '/Users/test/webpack'
};
Tips:在实际开发中 context
一般不需要配置,不配置则默认为process.cwd()
即工作目录。
entry入口
Webpack 的entry
支持多种类型,包括字符串、对象、数组。从作用上来说,包括了单文件入口和多文件入口两种方式。
单文件入口
单文件的用法如下:
module.exports = {
entry: 'path/to/my/entry/file.js'
};
// 或者使用对象方式
module.exports = {
entry: {
main: 'path/to/my/entry/file.js'
}
};
单文件入口可以快速创建一个只有单一文件入口的情况,例如 library 的封装,但是单文件入口的方式相对来说比较简单,在扩展配置的时候灵活性较低。
module.exports = {
mode: 'development',
entry: ['./src/app.js', './src/home.js'],
output: {
filename: 'array.js'
}
};
Tips:上面配置无论是字符串还是字符串数组的 entry,实际上都是只有一个入口,但是在打包产出上会有差异:
多文件入口
多文件入口是使用对象语法来通过支持多个entry
,多文件入口的对象语法相对于单文件入口,具有较高的灵活性,例如多页应用、页面模块分离优化。多文件入口的语法如下:
module.exports = {
entry: {
home: 'path/to/my/entry/home.js',
search: 'path/to/my/entry/search.js',
list: 'path/to/my/entry/list.js'
}
};
👉上面的语法将entry分成了 3 个独立的入口文件,这样会打包出来三个对应的 bundle,
Tips:对于一个 HTML 页面,我们推荐只有一个
entry
,通过统一的入口,解析出来的依赖关系更方便管理和维护。
output 输出
webpack 的output
是指定了entry
对应文件编译打包后的输出 bundle。output
的常用属性是:
path
:此选项制定了输出的 bundle 存放的路径,比如dist
、output
等filename
:这个是 bundle 的名称publicPath
:指定了一个在浏览器中被引用的 URL 地址,后面详细介绍
当不指定 output 的时候,默认输出到 dist/main.js
,即 output.path
是dist
,output.filename
是 main
。
一个 webpack 的配置,可以包含多个entry
,但是只能有一个output
。对于不同的entry
可以通过output.filename
占位符语法来区分,比如:
module.exports = {
entry: {
home: 'path/to/my/entry/home.js',
search: 'path/to/my/entry/search.js',
list: 'path/to/my/entry/list.js'
},
output: {
filename: '[name].js',
path: __dirname + '/dist'
}
};
其中[name]
就是占位符,它对应的是entry
的key
(home
、search
、list
),所以最终输出结果是:
path/to/my/entry/home.js → dist/home.js
path/to/my/entry/search.js → dist/search.js
path/to/my/entry/list.js → dist/list.js
我将 Webpack 目前支持的占位符列出来:
占位符 | 含义 |
---|---|
[hash] |
模块标识符的 hash |
[chunkhash] |
chunk 内容的 hash |
[name] |
模块名称 |
[id] |
模块标识符 |
[query] |
模块的 query,例如,文件名 ? 后面的字符串 |
[function] |
一个 return 出一个 string 作为 filename 的函数 |
[hash]
和 [chunkhash]
的长度可以使用 [hash:16]
(默认为 20)来指定。或者,通过指定output.hashDigestLength
在全局配置长度,那么他们之间有什么区别吗?
[hash]
:是整个项目的 hash 值,其根据每次编译内容计算得到,每次编译之后都会生成新的 hash,即修改任何文件都会导致所有文件的 hash 发生改变;在一个项目中虽然入口不同,但是 hash 是相同的;hash 无法实现前端静态资源在浏览器上长缓存,这时候应该使用 chunkhash;
[chunkhash]
:根据不同的入口文件(entry)进行依赖文件解析,构建对应的 chunk,生成相应的 hash;只要组成 entry 的模块文件没有变化,则对应的 hash 也是不变的,所以一般项目优化时,会将公共库代码拆分到一起,因为公共库代码变动较少的,使用 chunkhash 可以发挥最长缓存的作用;
[hash]、[chunkhash]和[contenthash]都支持[xxx:length]的语法。
Tips: 占位符是可以组合使用的,例如[name]-[hash:8]
output.publicPath
对于使用<script>
和 <link>
标签时,当文件路径不同于他们的本地磁盘路径(由output.path
指定)时,output.publicPath
被用来作为src
或者link
指向该文件。这种做法在需要将静态文件放在不同的域名或者 CDN 上面的时候是很有用的。
module.exports = {
output: {
path: '/home/git/public/assets',
publicPath: '/assets/'
}
};
则输出:
<head>
<link href="/assets/logo.png" />
</head>
上面的/assets/logo.png
就是根据publicPath
输出的,output.path
制定了输出到本地磁盘的路径,而output.publicPath
则作为实际上线到服务器之后的 url 地址。所以我们在上 CDN 的时候可以这样配置:
module.exports = {
output: {
path: '/home/git/public/assets',
publicPath: 'http://cdn.example.com/assets/'
}
};
则输出:
<head>
<link href="http://cdn.example.com/assets/logo.png" />
</head>
output.library
如果我们打包的目的是生成一个供别人使用的库,那么可以使用output.library
来指定库的名称,库的名称支持占位符和普通字符串:
module.exports = {
output: {
library: 'myLib' // '[name]'
}
};
resolve
Webpack 进行构建的时候会从入口文件开始(entry)遍历寻找各个模块的依赖,resolve 配置是帮助 Webpack 查找依赖模块的
通过 resolve 的配置,可以帮助 Webpack 快速查找依赖,也可以替换对应的依赖(比如开发环境用 dev 版本的 lib 等)。resolve 的基本配置语法如下:
module.exports = {
resolve: {
// resolve的配置
}
};
下面来介绍下常用的 resolve 配置。
resolve.extensions
resolve.extensions
是帮助 Webpack 解析扩展名的配置,默认值:['.wasm', '.mjs', '.js', '.json']
,所以我们引入 js 和 json 文件,可以不写它们的扩展名,通常我们可以加上 .css
、.less
等,但是要确保同一个目录下面没有重名的 css 或者 js 文件,如果存在的话,还是写全路径吧。
module.exports = {
resolve: {
extensions: ['.js', '.json', '.css']
}
};
resolve.alias
resolve.alias
是最常用的配置,通过设置 alias
可以帮助 webpack 更快查找模块依赖,而且也能使我们编写代码更加方便。例如,我们在实际开发中经常会把源码都放到src文件夹,目录结构如下:
src
├── lib
│ └── utils.js
└── pages
└── demo
└── index.js
🎇这时可以通过设置 alias 来缩短这种写法,例如:
module.exports = {
resolve: {
alias: {
src: path.resolve(__dirname, 'src'),
'@lib': path.resolve(__dirname, 'src/lib')
}
}
};
🎉经过设置了 alias,我们可以在任意文件中,不用理会目录结构,直接使用require('@lib/utils')或者require('src/lib/utils')来帮助 Webpack 定位模块。
module
在 webpack 解析模块的同时,不同的模块需要使用不同类型的模块处理器来处理,这部分的设置就在module
配置中。module 有两个配置:module.noParse
和module.rules
,
module.noParse
module.noParse
配置项可以让Webpack忽略对部分没采用模块化的文件的递归解析和处理,这样做的好处是能提高构建性能,接收的类型为正则表达式、正则表达式数组或者接收模块路径参数的一个函数:
module.exports = {
module: {
// 使用正则表达式
noParse: /jquery|lodash/
// 使用函数,从 Webpack 3.0.0 开始支持
noParse: (content) => {
// content 代表一个模块的文件路径
// 返回 true or false
return /jquery|lodash/.test(content);
}
}
}
Tips:这里一定要确定被排除出去的模块代码中不能包含
import
、require
、define
等内容,以保证 webpack 的打包包含了所有的模块,不然会导致打包出来的 js 因为缺少模块而报错。
module.rules
module.rules是在处理模块时,将符合规则条件的模块,提交给对应的处理器来处理,通常用来配置 loader,其类型是一个数组,数组里每一项都描述了如何去处理部分文件。每一项 rule 大致可以由以下三部分组成:
-
条件匹配:通过
test
、include
、exclude
等配置来命中可以应用规则的模块文件;🎈 Tips :
exclude
的优先级高于 test 和 include -
应用规则:对匹配条件通过后的模块,使用
use
配置项来应用loader
,可以应用一个 loader 或者按照从后往前的顺序应用一组 loader,当然我们还可以分别给对应 loader 传入不同参数; -
重置顺序:一组 loader 的执行顺序默认是从后到前(或者从右到左)执行,通过
enforce
选项可以让其中一个 loader 的执行顺序放到最前(pre)或者是最后(post)。
条件匹配
如上所述,条件匹配相关的配置有test
、include
、exclude
、resource
、resourceQuery
和issuer
。条件匹配的对象包括三类:resource
,resourceQuery
和issuer
。
- resource:请求文件的绝对路径。它已经根据 resolve 规则解析;
- issuer: 被请求资源(requested the resource)的模块文件的绝对路径,即导入时的位置。
举例来说明:从 app.js
导入 './style.css?inline'
:
resource
是/path/to/style.css
;resourceQuery
是?
之后的inline
;issuer
是/path/to/app.js
。
来看下 rule 对应的配置与匹配的对象关系表:
rule 配置项 | 匹配的对象 |
---|---|
test |
resource 类型 |
include |
resource 类型 |
exclude |
resource 类型 |
resource |
resource 类型 |
resourceQuery |
resourceQuery 类型 |
issuer |
issuer 类型 |
举例说明,下面rule 的配置项,匹配的条件为:
匹配的条件为:来自src
和test
文件夹,不包含node_modules
和bower_modules
子目录,模块的文件路径为.tsx
和.jsx
结尾的文件。
{
test: [/\.jsx?$/, /\.tsx?$/],
include: [
path.resolve(__dirname, 'src'),
path.resolve(__dirname, 'test')
],
exclude: [
path.resolve(__dirname, 'node_modules'),
path.resolve(__dirname, 'bower_modules')
]
}
Loader 配置
loader
是解析处理器,通过loader我们可以将 ES6 语法的 js 转化成 ES5 的语法,可以将图片转成 base64 的dataURL,在 JavaScript 文件中直接import css 和 html
也是通过对应的loader来实现的。
我们在使用对应的loader
之前,需要先安装它,例如,我们要在 JavaScript 中引入 less,则需要安装less-loader
:
npm i -D less-loader
然后在module.rules
中指定*.less
文件都是用less-loader
:
module.exports = {
module:{
rules:[
test: /\.less$/,
use:'less-loader'
]
}
}
简单来理解上面的配置,test
项使用 /\.less$/
正则匹配需要处理的模块文件(即 less 后缀的文件),然后交给less-loader
来处理,这里的less-loader
是个 string,最终会被作为require()
的参数来直接使用。
这样 less 文件都会被less-loader
处理成对应的 css 文件。
除了直接在webpack.config.js
使用 loader 的方式之外,还可以在对应的 JavaScript 文件中使用 loader:
Loader 的参数
给 loader 传参的方式有两种:
- 通过
options
传入 - 通过
query
的方式传入:
// config内写法,通过 options 传入
module: {
rules: [{
test: /\.html$/,
use: [{
loader: 'html-loader',
options: {
minimize: true,
removeComments: false,
collapseWhitespace: false
}
}]
}]
}
// config内写法,通过 query 传入
module: {
rules: [{
test: /\.html$/,
use: [ {
loader: 'html-loader?minimize=true&removeComments=false&collapseWhitespace=false',
}]
}]
}
Loader 的解析顺序
对于一些类型的模块,简单配置一个 loader 是不能够满足需求的,例如 less 模块类型的文件,只配置了 less-loader 仅仅是将 Less 语法转换成了 CSS 语法,但是 JS 还是不能直接使用,所以还需要添加css-loader
来处理,这时候就需要注意 Loader 的解析顺序了。前面已经提到了,Webpack 的 Loader 解析顺序是从右到左(从后到前)
的,即:
// 数组写法,从后到前
module.exports = {
module: {
rules: [
{
test: /\.less$/,
use: [
{
loader: 'style-loader'
},
{
loader: 'css-loader'
},
{
loader: 'less-loader'
}
]
}
]
}
};
如果需要调整 Loader 的执行顺序,可以使用enforce,enforce取值是pre|post,pre表示把放到最前,post是放到最后:
use: [
{
loader: 'babel-loader',
options: {
cacheDirectory: true
},
// enforce:'post' 的含义是把该 loader 的执行顺序放到最后
// enforce 的值还可以是 pre,代表把 loader 的执行顺序放到最前
enforce: 'post'
}
];
plugin 插件
plugin
是Webpack 的重要组成部分,通过plugin
可以解决loader
解决不了的问题。
例如有Gzip压缩
Webpack 本身就是有很多插件组成的,所以内置了很多插件,我们可以直接通过webpack
对象的属性来直接使用,例如:webpack.optimize.UglifyJsPlugin
module.exports = {
//....
plugins: [
// 压缩js
new webpack.optimize.UglifyJsPlugin();
]
}
除了内置的插件,我们也可以通过 NPM 包的方式来使用插件:
// 非默认的插件
const ExtractTextPlugin = require('extract-text-webpack-plugin');
module.exports = {
//....
plugins: [
// 导出css文件到单独的内容
new ExtractTextPlugin({
filename: 'style.css'
})
]
};
Tips:
loader
面向的是解决某个或者某类模块的问题,而plugin
面向的是项目整体,解决的是loader
解决不了的问题。
小结
在本小节中,我们讲解了Webpack 相关的除entry
和output
外的基础配置项,这里总结下项目经常配置的并且比较重要的配置项列表,供大家复习本小节内容:
resolve:模块依赖查找相关的配置
resolve.extensions
:可以省略解析扩展名的配置,配置太多反而会导致webpack 解析效率下降;resolve.alias
:通过设置 alias 可以帮助 webpack 更快查找模块依赖,精简代码书写时相对路径的书写;
module.rules:loader 相关的配置,每个 rule 重要的内容有:
test
:正则匹配需要处理的模块文件;use
:loader 数组配置,内部有loader
和options
;include
:包含;exclude
:排除;
plugins:插件。