前言
现如今不论是工作中遇到的项目开发,还是自己的私人项目,webpack
已经成为开发的核心骨干,对于webpack的了解几乎是所有前端开发人员必不可少的知识。
但webpack
官方的文档对于初学webpack
的人来说是比较为难的,由于webpack
功能的强大,具有一定的复杂性,文档也是很难做到面面俱到。
下面我会对自己学习webpack
做一个总结,可以说是一个初学指南,希望可以给更多想学习webpack
的人有一个了解webpack
的指引。
本章会带领大家搭建基本的前端开发环境,然后详细介绍webpack
的基础常用配置
本文不会仅仅局限于初级,目前已更新至webpack优化前端项目,GITHUB地址:github.com/Jason9708/w… , 如果能帮到各位学习webpack
,希望能点个赞(小弟有个理想,当掘金优秀作者)
Webpack 基本概念
webpack 本质上是一个打包工具,它会根据代码的内容解析模块依赖,帮助我们把多个模块的代码打包。
webpack 会把我们项目中使用到的多个代码模块(可以是不同文件类型),打包构建成项目运行仅需要的几个静态文件。webpack 有着十分丰富的配置项,提供了十分强大的扩展能力,可以在打包构建的过程中做很多事情。
入口(Enrty)
在多个代码模块中会有一个js
文件作为webpack
构建的入口。webpack
通过读取这个文件,从它开始进行解析依赖,然后打包。(默认入口文件为./src/index.js
)
根据项目的不同需求,可能是单页面应用,也可能是多页面应用,如果是单页面应用,入口就只有一个;而如果项目比较大型,是多页面应用,那么入口通常会设置多个,一个页面对应一个入口
以下为配置入口的例子
// 单页面
module.exports = {
entry: './src/index.js'
}
// 多页面
module.exports = {
entry: {
application1: './src/application1.js',
application2: './src/application2.js',
// ...
}
}
// 使用数组来对多个文件进行打包
module.exports = {
entry: {
main: [
'./src/application1.js',
'./src/application2.js
]
}
}
// 最后这个例子,可以理解为多个文件作为一个入口,webpack会解析两个文件的依赖然后进行打包
Loader
webpack
通过loader
提供了一种处理多种文件格式的机制。我们可以将loader
理解为是转换器,负责把某种文件格式的内容转换成webpack
可以支持打包的模块
Ps:在没有添加额外插件的情况下,webpack
会默认把所有依赖打包成 js
文件,如果入口文件依赖一个 .hbs
的模板文件以及一个 .css
的样式文件,那么我们需要 handlebars-loader
来处理 .hbs
文件,需要 css-loader,style-loader
来处理 .css
文件,最终把不同格式的文件都解析成 js
代码,以便打包后在浏览器中运行。
如何配置loader规制呢? 当我们需要使用不同的loader
来解析不同类型的文件时,在module.rules
下配置规则
举个爪子:使用Babel来处理.js文件
module: {
//...
rules: [
{
test:/\.jsx?/, // test匹配文件路径的正则表达式,通常都是匹配文件类型后缀
include: [
path.resolve(__dirname,'src') // 指定哪些路径下的文件需要经过loader处理,通常都是src下的文件
],
use: 'babel-loader', // 指定使用的 loader
}
]
}
loader支撑了webpack处理文件的功能,所以它是比较重要,也比较复杂的,想要玩好webpack,就要玩好loader
Plugin
在webpack
的构建流程中,plugin
用于处理更多其他的构建任务(模块代码转换的工作由loader
处理,而其他任何工作都可以交由plugin
来完成)。我们依据需求来添加所需的plugin
,实现我们所需要的功能(Such as HtmlWebpackPlugin
可以生成创建html入口文件)
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
// ...
plugins: [
new HtmlWebpackPlugin
]
}
plugin
可以作用于webpack
的整个构建流程,可以在流程的每一个步骤中定制自己的构建需求。在必须时,我们还可以在webpack
的基础上自己开发plugin
来适应我们的需求
输出 output
输出即webpack
最终构建出来的静态文件。可以通过output
来配置构建结果的文件名、路径等
module.exports = {
//...
output: {
path:path.resolve(__dirname,'dist'), // 根目录下的dist
filename:'index.js'
}
}
// 可以配置不同入口的不同输出文件
module.exports = {
entry: {
application1: './src/application1.js',
application2: './src/application2.js',
},
output: {
filename: '[name].js',
path: __dirname + '/dist',
}
}
❗ 如果没有进行配置,默认创建的输出内容就是./dist/main.js
一个简单的webpack配置
❗ webpack
运行时默认读取项目下的 webpack-config.js
,所以我们手动创建一个webpack.config.js
文件
webpack
的配置其实是一个Node.js
的脚本,这个脚本对外暴露一个配置对象,webpack
通过这个对象来读取相关的一些配置。因为是Node.js
脚本,所以可玩性非常高,你可以使用任何的Node.js
模块,如path
模块,或者第三方的模块也可以。
const path = require('path')
const UglifyPlugin = require('uglifyjs-webpack-plugin') // 用于缩小(压缩)JsW文件
module.exports = {
entry: './src/index.js', // 入口
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'output.js'
},
module: {
rules: [{
test: /\.jsx/,
include: [
path.resolve(__dirname, 'src')
],
use: 'babel-loader'
}]
},
// 代码模块路径解析的配置
resolve: {
modules: [
"node_modules",
path.resolve(__dirname, 'src')
],
extensions: [".wasm", ".mjs", ".js", ".json", ",jsx"],
},
plugins: [
new UglifyPlugin()
// 使用 uglifyjs-webpack-plugin 来压缩 JS 代码
// 如果你留意了我们一开始直接使用 webpack 构建的结果,你会发现默认已经使用了 JS 代码压缩的插件
// 这其实也是我们命令中的 --mode production 的效果,后续的小节会介绍 webpack 的 mode 参数
]
}
前端社区三大框架基于 webpack 的脚手架工具
- create-react-app
- angular/devkit/build-webpack
- vue/cli
搭建基本的前端开发环境
基础前端开发环境需求分析
- 构建我们发布需要的HTML,JS,CSS文件
- 使用CSS预处理器来编写样式
- 处理和压缩图片
- 使用Babel支持ES6语法
- 本地提供静态服务方便开发调式
HTML
通常来说,一个前端项目都是从一个页面(即HTML
)出发的,最简单的方法是,创建一个.html
文件,使用script
引用构建好的.js
文件
<script src="./dist/output.js"></script>
如果我们的文件名或者路径会变化,例如使用[hash]
来进行命名,那么最好是将HTML引用路径和我们的构建结果关联起来,就会使用html-webpack-plugin
html-webpack-plugin
是独立的依赖包,所以在使用之前要安装它。
npm install html-webpack-plugin -D
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
// ...
plugins: [
new HtmlWebpackPlugin
]
}
这里配置之后,构造时
html-webpack-plugin
会为我们创建.html
文件,其中会引用构建出来的html-webpack-plugin
,传递一个写好的.html
模板
module.exports = {
// ...
plugins: [
new HtmlWebpackPlugin({
filename: 'index.html', // 配置输出文件名和路径
template: path.join(__dirname,'./src/index.html'), //指定模板页面
}),
],
}
html-webpack-plugin
插件生成的内存中的页面会帮我们创建并正确引用了打包编译生成的index.js
的<script></script>
Css
当我们希望使用webpack
对我们所编写的css
进行构建,则需要在配置中引入相关loader
来解析和处理.css
文件(需要用到style-loader,css-loader
,两者都是依赖包,需要先进行安装)
module.exports = {
rules: [
{
test: /\.css/,
include: [
path.resolve(__dirname,'src')
],
use:[
'style-loader',
'css-loader'
]
}
]
}
css-loader,style-loader
的作用
- css-loader:负责解析css代码,处理css中的依赖,例如
@import
以及url()
等引用外部文件的声明 - style-loader:将
css-loader
解析出来的结果转变为js
代码,运行时会插入到<style></style>
中来让css
代码运作
经过css-loader,style-loader
的处理,css
代码就会转变为js
代码,最后一起打包出来。
如果需要将.css
文件单独分离,需要用到extract-text-webpack-plugin
插件(这里不对其进行解释)
const ExtractTextPlugin = require('extract-text-webpack-plugin')
module.exports = {
// ...
module: {
rules: [
{
test: /\.css$/,
// 因为这个插件需要干涉模块转换的内容,所以需要使用它对应的 loader
use: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: 'css-loader',
}),
},
],
},
plugins: [
// 引入插件,配置文件名,这里同样可以使用 [hash]
new ExtractTextPlugin('index.css'),
],
}
Css预处理器(Less/Sass)
Less/Sass
可以说是在工作中必不可少的,webpack
可以通过配置loader
,来支持Less/Sass
的运行
举个爪子,以Less为例子进行配置
需要用到less-loader
将 css-loader、 style-loader 和 less-loader 链式调用, 可以把所有样式立即应用于 DOM
module.exports = {
// ...
module: {
rules: [
{
test: /\.less$/,
use: [{
loader: 'style-loader'
}, {
loader: 'css-loader'
}, {
loader: 'less-loader'
}]
}
]
}
}
我们也可以修改上面的webpack配置
module.exports = {
// ...
module: {
rules: [
{
test: /\.less$/,
// 因为这个插件需要干涉模块转换的内容,所以需要使用它对应的 loader
use: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: [
'css-loader',
'less-loader',
],
}),
},
],
},
// ...
}
处理图片文件
在前端工作流程中,绝对少不了图片,虽然我们已经在css-loader
样式解析中对url()
引用文件路径做了处理,但webpack
处理不了jpg/png/gif
等文件格式,所以我们还需要添加一个loader
来配置,让我们能够使用图片文件
file-loader
可以用于处理很多类型的文件,它主要是通过输出文件,把构建后的文件路径返回。
配置file-loader
,在rules
中添加图片类型文件的解析配置
module.exports = {
// ...
modules: {
// ...
rules: [
{
test: /\.(png|jpg|gif)$/,
use: [
{
loader: 'file-loader',
options: {},
},
],
}
]
}
}
PS: 还有一个loader可以处理,那就是url-loader
Babel 处理ES6、ES7标准
Babel
是一个让我们能够使用ES
新特性的JS编译工具,我们可以在webpack
中配置Babel
,让我们用ES6
,ES7
标准编写JS
代码
module.exports = {
// ...
module: {
rules: [
{
test: /\.jsx?/, // 支持 js 和 jsx
include: [
path.resolve(__dirname, 'src'), // src 目录下的才需要经过 babel-loader 处理
],
loader: 'babel-loader',
},
],
},
}
我们需要在根目录下创建一个.babelrc文件来对Babel进行配置
启动静态服务
开启静态服务,可以通过webpack-dev-server
在本地开启一个简单的静态服务来进行开发
安装webpack-dev-server
,然后配置package.json
中
"scripts": {
"build": "webpack --mode production",
"start": "webpack-dev-server --mode development"
}
使用npm run start
运行项目,访问http://localhost:8080/
(默认访问的是index.html)
通过ctrl + C
可以结束运行
webpack 如何解析代码模块路径
模块化开发的前端工作规范已经深入人心,在webpack支持的模块化中,我们可以使用import xxx from 'xxx.js
又例如我们用VUE
进行开发的时候,必须使用import vue from 'Vue'
,webpack
在构建的时候,会解析依赖,然后再去加载依赖的模块文件
那么webpack
是如何将 xxx.js
或者 Vue
解析成模块文件路径呢❓
模块解析规则
- 解析相对路径
- 查找相对当前模块的路径下是否有对应文件或者是文件夹
- 是文件则直接加载
- 是文件夹则继续查找文件夹下的
package.json
- 存在
package.json
文件则按照文件中main
字段的文件名来查找文件 - 若无
package.json
或者没有main
字段,则默认查找index.js
- 解析模块名
- 查找当前文件目录下,父级目录及以上目录下的
node_modules
文件夹,看是否有对应名称的模块
- 查找当前文件目录下,父级目录及以上目录下的
- 解析绝对路径
- 直接查找对应路径的文件
在webpack
中,和模块路径解析有关的配置都在resolve
字段里
module.exports = {
resolve: {
//...
}
}
resolve 常用配置
- resolve.alias 设置别名
先假设我们有个utils
模块用来作为我们的工具文件,并且在整个项目中经常需要引入,那么如果我们每次引入都用路径去引入,就会显得很麻烦,所以我们可以通过配置别名,达到import 'utils'
的效果
alias: {
utils: path.resolve(__dirname,'src/utils') // 通过path.resolve和__dirname来获取绝对路径
}
这样配置别名采用的是模糊匹配,即只要模块中携带了utils
就会被替换掉
import 'utils/form.js' // 相当于引入了 'src/utils/form.js'
若想采用精确匹配,需要在别名后加上$
alias: {
utils$: path.resolve(__dirname,'src/utils') // 通过path.resolve和__dirname来获取绝对路径
}
resolve.extensions
这个配置的作用与文件后缀名有关系。我们可以通过它定义在进行模块路径解析的时候,webpack
自己去尝试补全后缀名来进行查找
例如上述utils
目录下有一个common.js文件,你可以这样引用
import * as common from './src/utils/common'
webpack
会尝试给你依赖的路径添加上extensions
字段所配置的后缀,然后开始按照依赖路径查找所有可以命中src/utils/common.js文件
❗ 但如果是要引用src/style
目录下的common.css
文件,而你是import './src/style/common
这样引入的,webpack
则会报出无法解析模块的错误
解决方式:
- 添加后缀
- 在extensions
字段中添加.css
的配置
resolve.modules
对于直接声明依赖名的模块(上述的Vue
),webpack
会类似Node.js
一样进行路径搜索,搜索node_modules
目录,这个目录就是使用resolve.modules
字段进行配置的
resolve: {
modules:['node_modules'], (默认就是node_modules)
}
技巧:通常我们不会去调整这个配置,但如果我们可以确定项目内所有第三方依赖模块都是在项目根目录下的node_modules
中的话,那么可以在node_modules
之前配置一个确定的绝对路径(这种配置可以轻微提高构建速度)
resolve: {
modules: [
path.resolve(__dirname, 'node_modules'), // 指定当前目录下的 node_modules 优先查找,如果有一些类库是放在其他地方的,你可以添加自定义的路径或者目录
'node_modules',
],
},
resolve.mainFields (少用)
可用于配置入口文件,当引用的是一个模块或者是一个目录时,会先看是否存在package.json
,存在是就会去查找某一字段下指定文件,这个某一个字段,就可以通过mainFields
来配置
默认配置
resolve: {
// 配置 target === "web" 或者 target === "webworker" 时 mainFields 默认值是:
mainFields: ['browser', 'module', 'main'],
// target 的值为其他时,mainFields 默认值为:
mainFields: ["module", "main"],
},
通常情况下,package.json
不会声明brower
或者module
,所以就会去查找main
字段里的文件了
resolve.mainFiles (少用)
当目录下没有package.json
文件时,我们说会默认使用目录的index.js
这个文件,其实这个通过mainFiles
也是可以配置的(但通常我们无需修改这个配置)
默认配置
resolve: {
mainFiles: ['index'] // 可以添加其他默认使用的文件名
}
resolve.resolveLoader (少用)
resolveLoader
用于配置解析loader
时的resolve
配置,原本resolve
的配置项在这个字段下基本都有
// 默认配置
resolve: {
resolveLoader: {
extensions: ['.js', '.json'],
mainFields: ['loader', 'main'],
},
}
这里提供的配置相对少用,我们一般遵从标准的使用方式,使用默认配置,然后把 loader
安装在项目根路径下的 node_modules
下就可以了。
配置loader
loader匹配规则
我们在使用loader
的时候,都是在modules.rules
中添加新的配置项,在该字段中,每一项被视为一条匹配使用loader的规则
最常用的例子
module.exports = {
// ...
module: {
rules: [
{
test: /\.jsx?/, // 正则匹配条件
include: [
path.resolve(__dirname,'src')
], // 规定规则范围
use: 'babel-loader' // 规则应用结果
}
// ...
]
}
}
在rules
的匹配规则中有两个关键因素:一个是匹配条件,一个是匹配规则后的应用
匹配条件通常都是使用请求资源文件的绝对路径来进行匹配
官方文档里将匹配条件定义为resource
,此外还有比较少用到的issuer
,则是声明依赖请求的源文件的绝对路径
例如:在/xx/xxx/xxxx.js
中声明引入import './src/utils.js
,resource
是/xx/xxx/src/utils.js
,issuer
是/xx/xxx/xxxx.js
,规则条件会对这两个值进行尝试匹配
module.exports = {
// ...
rules: [
{
resource: {
test: /\.jsx?/,
include: [
path.resolve(__dirname,'src'),
],
},
// 如果是使用issuer来匹配,则是issuer: { test: ... }
use: 'babel-loader'
}
// ...
]
}
规则条件匹配
webpack
的规则提供了多种配置形式:
- test: ... 正则匹配特定条件
- include: ... 匹配特定路径
- exclude: ... 排除特定路径
- and: [...] 必须匹配数组中所有条件
- or: [...] 匹配数组中任意一个条件
- not: [...] 排除匹配数组中所有条件
上述这些条件的值可以是以下5种
- 字符串 必须以提供的字符串开始,所以是字符串的话,这里我们需要提供绝对路径
- 正则表达式 用正则表达式的方式判断条件
- 函数 (path) => string 返回true代表匹配成功
- 数组 至少包含一个条件的数组
- 对象 匹配所有属性值的条件
module type
什么是module type
❓
module type
即模块类型的概念,不同模块类型类似于配置了不同的loader
,webpack
会有针对性地进行处理
5种模块类型
javascript/auto
:即 webpack 3 默认的类型,支持现有的各种 JS 代码模块类型 —— CommonJS、AMD、ESMjavascript/esm
:ECMAScript modules,其他模块系统,例如 CommonJS 或者 AMD 等不支持,是 .mjs 文件的默认类型javascript/dynamic
:CommonJS 和 AMD,排除 ESMjavascript/json
:JSON 格式数据,require 或者 import 都可以引入,是 .json 文件的默认类型webassembly/experimental
:WebAssembly modules,当前还处于试验阶段,是 .wasm 文件的默认类型
如果不希望使用默认的类型的话,在确定好匹配规则条件时,我们可以使用 type
字段来指定模块类型,例如把所有的 JS
代码文件都设置为强制使用 ESM
类型:
{
test: /\.js/,
include: [
path.resolve(__dirname,'src')
],
type: 'javascript/esm', // 指定模块类型 ( 默认是 javascript/auto )
}
使用loader配置
rules: [
{
test: /\.less/,
use: [
'style-loader', // 直接用字符串表示loader
{
loader:'css-loader',
options:{
importLoaders: 1
}
}, // 用对象表示 loader,可以传递loader配置等
{
loader: 'less-loader',
options: {
noIeCompoat: true
}, // 传递loader配置
}
]
}
]
上面这个例子种,use
可以是一个数组,也可以是一个字符串或者表示loader
的对象。
PS: 我们还可以使用options
给对应的loader
传递一些配置项
loader执行顺序
在一个匹配规则中可以配置使用多个loader
,即一个模块文件可以经过多个loader
的转换处理,执行的顺序是从最后配置的loader
开始,由后配置到先配置的顺序,例如上个例子中,.less
文件会经理less-loader
到css-loader
再到style-loader
的处理,最后成一个可以打包的模块
从后配置到先配置的顺序是在同一个rule
中的执行方案,那么又有另外一个问题!当一个模块文件同时匹配到多个rule
,loader
又会按照什么顺序去应用呢?
rules: [
{
test: /\.js$/,
exclude: /node_modules/, // 排除node_modules
loader: "eslint-loader",
},
{
test: /\.js$/,
exclude: /node_modules/, // 排除node_modules
loader: "babel-loader",
},
],
上面的例子中我们需要在babel-loader
应用之前先应用eslint-loader
,我们无法保证按照这种顺序执行
webpack
在rules
中提供了一个enforce
字段,用来配置当前rule
的loader
类型,默认是普通类型,而我们可以配置成pre
或者是post
- pre - 前置
- post - 后置
所有的loader
会按照前置 → 行内 → 普通 → 后置的顺序执行,所以我们要确保eslint-loader
在babel-loader
之前使用,就需要添加enforce
字段
rules:[
{
enforce: "pre", // 前置
test: /\.js$/,
exclude: /node_modules/,
loader: "eslint-loader"
}
]
使用 noParse
module.noParse
可以用于配置哪些模块文件的内容不需要进行解析。
对于一些不需要解析依赖的库(例如jquery
),可以通过noParse
来配置(可提高整体构建速度)
module.exports = {
// ...
module: {
noParse: /jquery|lodash/, // 正则表达式
<!-- 或者使用 function
noParse(content) {
return /jquery|lodash/.test(content)
}, -->
}
}
❗ 使用noParse
进行忽略的模块文件中不能使用import / require / define
等引用方式
配置plugin
plugin
为webpack
提供额外的功能,由于需要提供不同的功能,不同的插件本身的配置比较多样化
webpack
插件可以上 github.com/webpack-con… 进行查阅
几个常用插件
DefinePlugin
DefinePlugin
是webpack
的内置插件,不需要安装,直接通过webpack.DefinePlugin
使用
作用:创建一些在编译时可以配置的全局常量,这些常量的值我们可以在webpack
的配置中指定
module.exports = {
// ...
plugins: [
new webpack.DefinePlugin({
_GET:JSON.stringify('get'), // const _GET = 'get'
_POST:JSON.stringify('post'), // const _POST = 'post'
_DELETE:JSON.stringify('delete'), // const _DELETE = 'delete'
_PUT:JSON.stringify('put'), // const _PUT = 'put'
})
]
}
这些配置好的全局常量,可以在代码中直接使用
console.log('this Api type is' + _GET)
配置规则
- 如果配置的值是字符串,那么字符串会被当成代码片段来执行,其结果作为最终变量的值,例如
'1 + 1'
,最后结果会是2
- 如果配置的值不是字符串,也不是对象字面量,那么值会转换为一个字符串,例如
true
,会被转换成'true'
- 如果配置的是一个对象字面量,那么该对象的所有
key
会以相同的方式定义
extract-text-webpack-plugin
作用:用来把依赖的css
分离出来成为单独的文件
const ExtractTextPlugin = require('extract-text-webpack-plugin')
module.exports = {
// ...
module: {
rules: [
{
test: /\.css$/,
use: ExtractTextPlugin.extract({
fallback: 'style-loaer',
use: 'css-loader'
})
}
]
},
plugins: [
new ExtractTextPlugin('index.css') // 配置文件名
]
}
有时候我们构建的入口不止一个,那么ExtractTextPlugin会为每一个入口创建单独分离的文件
plugins: [
new ExtractTextPlugin('[name].css')
]
❗ 使用ExtractTextPlugin,还需要调整loader
对应的配置
ProvidePlugin
ProvidePlugin
是webpack
内置的,直接通过webpack.ProvidePlugin
来使用
作用: 引用某些模块作为程序运行时的变量,不必每次使用都需要import/require
plugins: [
new webpack.ProvidePlugin({
identifier:'xxx', // 类似于 import 'xxx'
// identifier: ['xxx','xxxx'] 类似于 import xxxx from 'xxxx'
})
]
IgnorePlugin
IgnorePlugin
是webpack
内置的,直接通过webpack.IgnorePlugin
来使用
作用:用于忽略某些特定的模块,让webpack不把这些指定的模块打包进去,例如我们使用了moment.js
,如果直接引用后,里面会有大量的i18n
代码,会导致打包出来的文件比较大,而实际上我们并不需要,这就是IgnorePlugin
的使用场景
plugins: [
new webpack.IgnorePlugin(/^\.\/locale$/,/moment$/)
]
IgnorePlugin
的参数
1 - 匹配引入模块路径的正则表达式
2 - 匹配模块的对应上下文,即所在目录名
配置webpack-dev-server
在开发流程中,我们在部署到生产环境之前,都应该是在本地上先开发,运行我们写的代码,我们称为本地环境,这个环境相当于提供了一个简单的服务器,用于访问webpack
构建好的静态文件。
(在开发中我们用它来调式代码)
webpack-dev-server
是webpack
提供的一个工具,可以基于当前的webpack
配置快速启动一个静态服务.
当mode
为development
时,会具备热更新的功能(实时根据修改刷新当前页面)
webpack-dev-server 官方文档
webpack.docschina.org/configurati…
webpack-dev-server的基础使用
webpack-dev-server
是一个依赖包,需要手动安装,然后在已经有webpack
配置文件的项目目录下直接使用即可
npm install webpack-dev-server -D
webpack-dev-server --mode development
我们也可以通过配置package.json
来更改启动命令
package.json
{
// ...
"scripts": {
"dev": "webpack-dev-server --mode development"
}
}
然后命令行使用 npm run dev 即可运行
PS:webpack-dev-server
默认使用8080
端口,如果webpack
已经配置好了html-webpack-plugin
来构建html
文件,那么当我们访问http://localhost:8080
就可以看到index.html
页面,而如果没有进行配置,那么webpack-dev-server
会自己生成一个页面用于展示静态资源
配置 webpack-dev-server
devServer
字段是webpack
用于配置webpack-dev-server
的核心,我们可以在其中实现修改端口等功能
常用 devServer 配置
- port 用于指定静态服务开启的端口(默认8080)
- host 用于指定静态服务的主机名(默认是localhost)
- publicPath 用于指定构建好的静态文件在浏览器中以什么路径去访问(默认是/)
- 例如有一个构建好的文件 output.js,完整的访问路径为 http://localhost:8080/output.js , 如果配置了 publicPath: 'assets/',那么 output.js 的访问路径就是 http://localhost:8080/assets/output.js
- 建议生产环境的 devServer.publicPath 与 output.publicPath 的值一致
- proxy 用于设置请求代理,即将特定URL的请求代理到另外一台服务器上(如果需要请求单独的后端服务API时,可以通过这个配置进行代理)
举个爪子
proxy: {
'/api': {
target: "http://localhost:8000", // 将URL中带有 /api 的请求代理到 http://localhost:8000 上
pathRewrite: { '^/api', '' } // 去掉URL中的 api 部分
changeOrigin: true // 本地会虚拟一个服务器接受你的请求并代你发送该请求,可以解决跨域问题
}
}
- contentBase 用于配置提供额外静态文件内容的目录,即配置额外静态文件内容的访问路径(那些不经过webpack构建,但在webpack-dev-server中提供访问的静态资源)
举个爪子:
contenBase: path.join(__dirname, "public") // 当前目录下的 public
constBase: [ path.join(__dirname, "public"), path.join(__dirname, "assets" )}
- before & after 用于配置用于在`webpack-dev-server`定义额外的中间件
- before 在`webpack-dev-server`静态资源中间件处理之后,可以用于拦截部分请求返回特定内容,或者实现简单的数据mock
- after 在webpack-dev-server静态资源中间件处理之后,可以用于打印日志等操作
配置 webpack-dev-middleware 中间件
webpack-dev-middleware
就是在Express
中提供webpack-dev-server
静态服务能力的一个中间件
webpack-dev-middleware
是一个依赖,需要手动安装
npm install webpack-dev-middleware --save-dev
在装有express的node服务里
const middleware = require("webpack-dev-middleware")
app.use(middleware(xxx,{
xxx
}))
实现一个简单的mock服务
在日常的工作中,前端人员常常会因为后端接口未完成或者数据返回参差不齐,导致页面开发完后,进度停滞不前,那么我们就需要mock
服务来帮助我们模拟后端数据,而webpack-dev-server
的before
或proxy
配置,又或者webpack-dev-middleware
结合Express
,都可以帮助我们实现简单的mock
服务
当我们请求某一个特定的路径时(如/market/shopsList
),可以访问我们想要的数据内容
我们先基于Express app
实现一个简单的mock
功能方法
module.export = function mock(app) {
app.get('/market/shopsList', (req,res) => {
res.json({
data:'' // 模拟返回数据
})
})
// ...
}
然后配置webpack-dev-server
中的before
字段
const mock= require('./mock')
before(app) {
mock(app) // 调用mock函数
}
区分开发与生产环境
webpack 4.x
引入了mode
的概念,在运行webpacks
时需要指定使用production
还是development
,这个功能说明我们需要具备运行两套构建环境的能力
当你指定了使用production mode
时,默认会启动各种性能优化的功能,而development mode
,则会开启debug工具,运行时打印详细错误信息。
拆分配置
我们可以将webpack
的配置按照不同的环境拆分成多个文件,运行时直接根据环境变量加载对应的配置文件
举个爪子,当我们用webpack
构建vue
项目时,即vue init webpack projectname
,我们可以看到build
文件夹下已经区分了mode
根据这个例子,我们可以划分成下面几个文件
- webpack.base.js 基础部分,即共享的配置
- webpack.dev.js 开发环境使用的配置
- webpack.pro.js 生产环境使用的配置
如何处理这样的配置拆分
对于webpack
的配置,其实就是对外暴露一个js
对象,所以对于这个对象,我们可以使用js
代码来修改
const config = {
// webpack配置
}
config.plugins.push(...) // 修改这个配置对象
module.exports = config // 暴露这个对象
此外我们要有一个工具(webpack-merge
)帮助我们合并多个配置对象,这样我们就可以很轻松地拆分webpack
配置,然后判断当前的环境变量,使用工具将对应环境的配置对象整合后提供给webpack
举个爪子
基础共享部分(webpack.base.js
module.exports = {
entry: ...,
output: {
...
},
resolve: {
...
},
module: {
...
},
plugins: [
...
]
}
webpack.dev.js
需求: 添加loader/plugin
const merge = require('webpack-merge')
const webpack = require('webpack')
const baseWebpackConfig = require('./webpack.base.js')
module.exports = merga(baseWebpackConfig, {
module: {
rules: [
// 注意:当这里use的值是字符串或者是对象的话,会替换掉原本规则use的值
...
]
},
plugins: [
// plugins会与baseWebpackConfig中的plugins数组进行合并
...
]
})
小结
至此,webpack
的大致功能已经有了一个系统的了解,那么如何熟悉熟练的使用webpack
,则需要我们从实践中探索,依据不同的需求,配置适合项目的开发环境和本地环境,后续中,我会更新如何通过webpack
优化项目
本文面向初学webpack
的程序员,如果对你有帮助,点个👍哦