webpack5学习
- 1. Why Webpack?
- 2. Webpack上手
- 3. Webpack配置文件
- 4. webpack依赖图
- 5. CSS_Loader的具体使用
- 6. style-loader
- 7. less-loader
- 8. Browserslistrc配置
- 9 postcss工作流程
- 10 postcss-loader处理兼容
- 11 importLoaders
- 12 file-loader处理图片
- 13 设置图片名称与输出tscproj
- 14 url-loader处理图片
- 15 asset处理图片
- 16 asset处理图标字体(iconfont)
- 17 webpack插件使用
- 18 html_webpack_plugin
- 19 copy-webpack-plugin
- 20 babel使用
- 21 babel-loader使用
- 22 polyfill
- 23 webpack-dev-server
- 24 webpack-dev-middleware
- 25 HMR功能的使用
- 26 React组件支持热更新
- 27 Vue组件热更新
- 28 output中的path
- 29 devServer中的path
- 30 devServer中的常用配置
- 31 devServer中的proxy代理设置
- 32 resolve模块解析规则
- 33 source-map作用
- 34 devtool配置详解
- 35 ts-loader编译TS
- 36 babel-loader编译TS
- 37 加载vue文件
- 38 区分打包环境
- 39 合并生产环境配置
1. Why Webpack?
我们需要工具让开发者在入口的地方随心所欲用我们喜欢的技术栈去完成开发,从而不用去关心过程,但是最终的结果又可以展示到浏览器上.
--save 生产和开发依赖 dependencies
-D 开发依赖 devDependencies
2. Webpack上手
Webpack:为现代JavaScript应用提供静态模块打包的工具.
2.1 Webpack功能
- 打包: 将不同类型资源按模块处理进行打包.
- 静态: 打包后最终产出静态资源.
- 模块: webpack支持不同规范的模块化开发.
2.2 需要安装的包
webpack
npm install webpackwebpack-cli
npm install webpack-cli
2.3 简易命令
webpack --version
查看webpack版本webpack
直接可以全局webpack打包,默认去项目目录下src下的index.js,然后对其中资源查找依赖并统一打包.
3. Webpack配置文件
3.1 局部webpack打包
npx webpack
局部webpack对项目整体进行打包npx webpack --entry ./src/main.js
将src/index.js改为main.js后,再直接执行会报错,可以自定义入口名称.使用 --entry 入口地址.npx webpack --entry ./src/main.js --output-path ./build
output-path可以更改输出文件的地址.- 也可以在package.json的"srtipts"里将"build"改为
npx webpack --entry ./src/main.js --output-path ./build
, 然后就可以直接使用npm run build
了.
3.2 方法改进
不难发现上述方法比较复杂,我们可以在package.json里scripts增加一行:
"build": "webpack --entry ./src/main.js --output-path ./build"
然后使用指令npm run build
同样可以完成webpack局部打包操作
3.3 webpack配置文件
可以在项目根目录下建立一个webpack.config.js文件,进行如下配置,此时再把package.json里的build改为webpack
就可以了.
/*
可以导出具体的配置项
*/
const path = require("path")
module.exports = {
entry: "./src/index.js", /* 打包的入口地址,可以使用相对路径 */
output:{
filename: "build.js", /* 打包后的文件名字 */
path: path.resolve(__dirname, "dist")/* 绝对地址,使用path包来解决 */
}
}
entry和output是两个最常规的配置项.
4. webpack依赖图
4.1 webpack依赖概念
webpack依赖指的是在入口文件index.js中引用到的文件(import 和 require),以及引用到的文件中各级引入关系构成依赖,只有这些文件在webpack最后会被打包,而各级没有引用到的文件已经没有使用到的函数等,不会被打包.所以说需要进行打包的css/js等文件,必须要加到webpack依赖图里面.
4.2 webpack.config.js的改名
在现实开发环境中,我们可能会协同开发,每个人都会有自己的配置文件,此时就有不同配置文件名字的需要,我们可以在package.json中build修改为以下代码(以改名为lg.webpack.js为例).
"build": "webpack --config lg.webpack.js"
5. CSS_Loader的具体使用
5.1 为什么需要loader
loader是必需的, 起着转化js文件以外文件的功能.(md,txt,jpg,css等).
5.2 loader是什么?
是一个模块,功能是比如说让css转化为可以识别的模块.
5.3 css-loader的使用
首先需要安装css-loader, npm install --save-dev css-loader
.
5.3.1 行内loader
在导入前加入 css-loader!
,最终行内代码如所下所示:
import "css-loader!../css/login.css"
5.3.2 配置文件里的loader
lg.webpack.js,如果只需要css-loader且不需要别的选项参数就可以使用第二种简写方式,第三种是有多个loader去处理但是不需要别的参数的情况下使用.
/*
可以导出具体的配置项
*/
const path = require("path")
module.exports = {
entry: "./src/index.js", /* 打包的入口地址,可以使用相对路径 */
output:{
filename: "main.js", /* 打包后的文件名字 */
path: path.resolve(__dirname, "dist")/* 绝对地址,使用path包来解决 */
},
module:{
rules:[
// {
// test: /\.css$/, // 一般就是一个正则表达式, 用来匹配我们需要处理的文件类型
// use: [
// {
// loader: "css-loader"
// // options: // 用户自定义选项
// }
// ]
// },
// {
// test: /\.css$/,
// loader:"css-loader"
// },
{
test: /\.css$/,
use:["css-loader"]
}
]
}
}
6. style-loader
我们想让css-loader处理的css文件生效,就必须使用style-loader.
安装:npm install style-loader
use里的loader默认执行顺序是从右往左, 或者从下往上.
我们应该先执行css-loader,再执行style-loader,所以我们rules应该写为如下代码:
rules:[
{
test: /\.css$/,
use:["style-loader","css-loader"]
}
]
然后在执行npm run build
,就会发现样式生效了.
7. less-loader
我们想让less文件打包,就需要使用到less-loader.
老规矩,首先安装:npm install less
准备一个login.less:
@bgColor: "seagreen";
@fontSize: 100px;
.title {
background-color: @bgColor;
font-size: @fontSize;
}
首先使用less来对login.less进行处理,使用命令npx less ./src/css/login.less index.css
来生成index.css.
我们不能每写一次less, 都要手动的执行一次代码将less文件转化为css文件, 所以我们需要"自动化",需要less-loader.
安装less-loader:npm install less-loader
.
在配置文件中添加新的rules:
{
test: /\.less$/,
use:["style-loader","css-loader","less-loader"]
}
然后再执行npm run build
, 就可以打包并且具有效果了!
8. Browserslistrc配置
browsers-list-rc
8.1 why?
我们在用webpack处理css兼容或者js的兼容之前需要一些配置上的东西, 我们需要从原理上了解他们.
8.2 what?
目前我们做前端开发都是工程化的,当然要考虑它的兼容性,比如兼容CSS新特性,JS新语法等. 至于如何实现这些兼容我们已经知道了很多工具,比如bable,那么我们到底应该兼容哪些平台呢? 这个时候就要引出browserslistrc. 我们可以在http://caniuse.com 里面查到各个浏览器版本的市场占有率,从而帮我们做出配置.
browserslist是安装webpack时自带的库,如果没有也可以通过npm安装,它可以这个网站调用caniuse-lite,来查找各个浏览器的市场占有率.
npx browserslist '>1%, last 2 version'
是在npx测试环境下得到市场占有率大于1%, 或者是某个浏览器平台的最新两个版本.
npx browserslist
,一般情况下运行这个就可以了
关于browserslist配置的几个常用选项:
-
1% 意味着要去找到市面上大于百分之一的浏览器
-
default 市场占有率大于0.5%,采用最新的两个版本, 和not dead
-
dead 24个月没有官方支持或更新
-
last 2 version: 某浏览器最新的两个版本
8.3 How?
8.3.1 package.json里配置
package.json里新添一个字段 "browserslist",然后加入配置,例如图下所示:
"browserslist": [
">1%",
"last 2 version",
"not dead"
]
8.3.2 在src下新建一个.browserslistrc文件
直接在文件中写配置,换行表示且,示例代码如下所示,然后可以用npx browserslist
测试,就会得到市场占有率大于1%,最新的两个版本并且没有停止更新超过24个月的浏览器.
>1%
last 2 version
not dead
9 postcss工作流程
9.1 postcss是什么?
postcss是 javascript 转化样式的工具.
使用他的过程与之前使用less的过程类似:
less (less-loader) --> css --> css-loader
9.2 安装postcss及其相关的插件
npm i postcss postcss-cli autoprefixer
以下代码为postcss不使用autoprefixer的方式, -o表示为output输出, ret.css为输出css文件的地址及名字, ./src/css/test.css 代表读取文件的地址及名字.
npx postcss -o ret.css ./src/css/test.css
以下代码为postcss使用autoorefixer的方式:
npx postcss --use autoprefixer -o ret.css ./src/css/test.css
autoprefixer为为各种浏览器所兼容的样式添加前缀的工具.其官网为https://autoprefixer.github.io/.
9.3 postcss工作详细流程
首先要告诉他浏览器平台, 通过条件去筛选, 然后使用命令行就可以将其转化为条件筛选完成的浏览器平台所支持的css样式格式.
10 postcss-loader处理兼容
10.1 postcss-loader
首先安装postcss-loader:
npm install postcss-loader
postcss在 css-loader之前进行工作,处理css文件,然后css-loader工作,最后style-loader将css文件生效,理清楚这个逻辑之后,就可以修改rules:
{
test: /\.css$/,
use:[
"style-loader",
"css-loader",
{
loader:"postcss-loader",
options:{
postcssOptions: {
plugins: [
require("autoprefixer")
]
}
}
}
]
},
目前已知的是用十六进制的形式来表达颜色等例如#123456, 是可以正常转换的, 但是如果是#12345678, 在谷歌浏览器也是可以的, 可以看到样式发生了改变, 颜色变浅了, 分析可知是rgba格式, 最后两位为透明度, 有的浏览器并不会适配, 我们看到转化完的还是#12345678, 以后可能会存在问题, 我们要进行处理就需要别的插件.
10.2 postcss-preset-env
预设: 插件的集合,可以实现上述代码转rgba.
安装(npm i postcss-preset-env
), 然后在webpack配置里再加入,做一个简写,代码如下所示:
{
test: /\.css$/,
use:[
"style-loader",
"css-loader",
{
loader:"postcss-loader",
options:{
postcssOptions: {
plugins: [
"postcss-preset-env"
]
}
}
}
]
},
此时再看一下关于less的rules,发现也可以使用postcss做优化, 所以我们也修改一下配置,发现在postcss-loader上很多代码都是重复的, 为提高复用性,我们在根目录下新建一个postcss.config.js来将统一的配置放进去,代码如下:
module.exports = {
plugins: [
require("postcss-preset-env"),
]
}
然后rules就可以简写了, 代码如下所示, 然后打包测试, 无问题.
rules:[
{
test: /\.css$/,
use:[
"style-loader",
"css-loader",
"postcss-loader"
]
},
{
test: /\.less$/,
use:[
"style-loader",
"css-loader",
"postcss-loader",
"less-loader"
]
}
]
11 importLoaders
- login.css @import 语句导入了test.css.
- login.css 可以被匹配,当它被匹配到之后postcss-loader进行工作.
- 基于当前的代码, poscss-loader拿到了login.css当中的代码之后分析基于我们的筛选条件并不需要做额外的处理.
- 最终就将代码直接交给了css-loader.
- 此时css-loader是可以处理@import media url..., 这个时候它又拿到了test.css文件, 但是loader不会回头找.
- 最终将处理好的css代码交给style.loader进行展示.最终没有处理.
但是目前webpack5没有问题,要解决以上的问题,我们配置importLoaders:
{
test: /\.css$/,
use:[
"style-loader",
{
loader:"css-loader",
options:{
importLoaders: 1, // 往前找一个,即postcss-loader
}
},
"postcss-loader"
]
},
再次打包查看,问题解决.
12 file-loader处理图片
当我们想对图片文件进行打包时,比如在image标签中的src属性和在background样式中的url属性所引入的image图片, 如果直接进行打包会出错, 因为我们没有合适的loader去处理这种文件类型, 所以我们就需要file-loader去进行处理.
-
安装:
npm install file-loader -D
-
针对image标签中的src属性
- 使用 require 导入图片, 此时如果不配置 esModule: false, 则需.default 导出
- 也可以在配置当中设置 esModule: false
- 使用 import xxx from 图片资源, 此时可以直接使用 xxx
Image.js
import oImgSrc from "../img/01.timo.png" // 3 function packImg() { // 01 创建一个容器 const oEle = document.createElement("div") // 02 创建image标签 const oImage = document.createElement("img") oImage.width = 600 // oImage.src = require("../img/01.timo.png").default // 1 // oImage.src = require("../img/01.timo.png") // 2 oImage.src = oImgSrc // 3 oEle.appendChild(oImage) return oEle } document.body.appendChild(packImg())
lg.webpack.js:
...... { { test: /\.(png|svg|gif|jpe?g)$/, // 2 // use:[ // { // loader:"file-loader", // options:{ // esModule:false, // 不转为esModule // } // } // ] // 1 3时所用的配置项 use:["file-loader"] } } ......
-
针对background样式中的url
因为是在css文件中填写的url, 且css文件中不能使用js语法, 也就是说我们必须要在css-loader里配置options.
img.css
.bgBox{ width: 600px; height: 300px; border: 1px solid #000; background-image: url("../img/02.huli.png"); background-size: contain; }
Image.js
... import "../css/img.css" ... function packImg() { ...... // 03 设置背景图片 const oBgImg = document.createElement("div") oBgImg.className = "bgBox" oEle.appendChild(oBgImg) return oEle } document.body.appendChild(packImg())
lg.webpack.js
...... { test: /\.css$/, use:[ "style-loader", { loader:"css-loader", options:{ importLoaders: 1, // 往前找一个,即postcss-loader esModule:false, // 不转为esModule } }, "postcss-loader", }, ......
13 设置图片名称与输出tscproj
我们想要控制输出后文件的路径及名字, 这个时候就需要对file-loader的options进行配置:
首先我们需要知道几个必要的表达式含义:
[ext]: 扩展名
[name]: 文件名
[hash]: 文件内容+M4算法
[contentHash]: 同上
[hash:<length>]:输出指定长度的hash文件内容
[path]: 不常用,path名
然后此时我们就可以配置了:
......
{
test: /\.(png|svg|gif|jpe?g)$/,
use:[
{
loader:"file-loader",
options: {
name: 'img/[name].[hash:6].[ext]',
//outputPath: "img"
}
}
]
}
......
有两种方式可以配置产出地址, 第一种就是配置outputPath属性, 第二种就是简写方案, 直接在name中前缀配置.
img/[name].[hash:6].[ext]
中img/代表的是在img目录下生成img文件, [name]表示文件开头是源文件的名字, [hash:6]代表的是长度为6的代表文件内容的hash字符串,[ext]代表的是源文件的后缀.
14 url-loader处理图片
安装: npm i url-loader -D
使用与file-loader类似, 把要打包的图片资源以base64uri的方式去加载到代码当中(data:image.png...), 好处是减少了请求的次数, 风险是当图片很大时加载文件的速度会变慢, 我们一般是希望网页先加载出来, 然后再加载图片.
也可以在配置项中增加一个limit属性, 其可以配置一个阈值, 当大于该值时拷贝, 小于此值转化.
{
test: /\.(png|svg|gif|jpe?g)$/,
use:[
{
loader:"url-loader",
options: {
name: 'img/[name].[hash:6].[ext]',
limit: 1.6 * 1024 * 1024 // B为单位
}
}
]
}
15 asset处理图片
webpack5提供了asset模块处理图片, 是处理图片的终极方式, 我们就不需要再用file-loader 和 url-loader来处理图片了.
其模式如下所示:
- asset/resource --> file-loader 将资源分割为单独的文件, 并导出url.
- asset/inline --> url-loader 将资源导出为dataURL(url(data:))的形式.
- asset/source --> raw-loader 不常用.
- asset 可以配置参数, 比如说文件体积大小等.(parser)
- 若我们使用asset/resource, 打包出来的文件是hash命名的且与源文件同级的:
{
test: /\.(png|svg|gif|jpe?g)$/,
type: "asset/resource"
}
如果我们想把经过asset处理的文件都放在同一文件夹下, 就需要在配置文件的output中添加一个属性assetModuleFilename,例如:assetModuleFilename:"img/[name].[hash:6][ext]"
第二种:在rules里面配置generator属性
...
{
test: /\.(png|svg|gif|jpe?g)$/,
type: "asset/resource",
generator: {
filename: "img/[name].[hash:6][ext]"
}
}
- 若我们使用asset/inline, 打包出来的资源是data uri 形式
{
test: /\.(png|svg|gif|jpe?g)$/,
type: "asset/inline"
}
- 使用asset, 并进行文件体积大小配置:
{
test: /\.(png|svg|gif|jpe?g)$/,
type: "asset",
generator: {
filename: "img/[name].[hash:6][ext]",
},
parser: {
dataUrlCondition: {
maxSize: 1.6 * 1024 * 1024
}
}
},
16 asset处理图标字体(iconfont)
-
首先把所有与图标字体有关的资源放入font目录里, 查看其核心iconfont.css, 得知我们使用图标字体必须要给标签加iconfont的class属性, 然后再根据情况使用具体的图标资源加入到class属性中.
-
然后我们写必要的文件依赖:
index.js
import "./js/Font"
Font.js
import "../font/iconfont.css" import "../css/index.css" function packFont() { const oEle = document.createElement("div") const oSpan = document.createElement("span") oSpan.className = 'iconfont icon-moon lg-icon' oEle.appendChild(oSpan) return oEle } document.body.appendChild(packFont())
index.css
.lg-icon { color: yellow; font-size: 48px; }
-
在lg.webpack.js rules中添加配置
{ test: /\.(ttf|woff2?)$/, type: "asset/resource", generator: { filename: "font/[name].[hash:3][ext]" } }
-
打包运行, 一切正常.
17 webpack插件使用
插件与loader之间的关系:
- loader 转换 特定类型 识别和读取文件内容时工作.
- plugin: 更多事情 可以贯穿webpack工作的整个生命周期.
我们想要实现在每次打包的时候, 将原来打包出来的内容先清空再打包,这时候我们就需要第三方插件clean-webpack-plugin:
-
安装:
npm i clean-webpack-plugin -D
. -
在webpack配置文件中使用require导入
const {CleanWebpackPlugin} = require("clean-webpack-plugin")
. -
然后在模块中添加plugins属性并配置:
...... module.exports = { ...... plugins:[ new CleanWebpackPlugin() ] }
18 html_webpack_plugin
安装: npm install html-webpack-plugin -D
然后新建一个public文件夹, 使该文件夹不会被打包,里面放的是index.html的模板以及一些图标和css文件等.
index.html:
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8">
<!-- 针对ie以最高级别的渲染级别渲染页面-->
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<!---开启移动端理想视口-->
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<!--配置页签图标-->
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<!--配置网页标题-->
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<!--如果浏览器不支持js时,noscript标签的东西会渲染--->
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<!---容器-->
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
其中有一个BASE_URL是一个常量, webpack需要读取到他, 我们可以在webpack文件夹里设置如何给自定义的模板填充数据:
const autoprefixer = require("autoprefixer")
const { DefinePlugin } = require("webpack")
const path = require("path")
const {CleanWebpackPlugin} = require("clean-webpack-plugin")
const HtmlWebpackPlugin = require("html-webpack-plugin")
module.exports = {
entry: "./src/index.js", /* 打包的入口地址,可以使用相对路径 */
output:{
filename: "main.js", /* 打包后的文件名字 */
path: path.resolve(__dirname, "dist"),/* 绝对地址,使用path包来解决 */
},
......
plugins:[
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
title:"html-webpack-plugin", // 标题是html-webpack-plugin
template: "./public/index.html", // 模板地址
}),
new DefinePlugin({
BASE_URL:'"./"', // 变量名字值是, 注意双引号
})
]
}
19 copy-webpack-plugin
当涉及到资源的拷贝问题时, 就需要使用copy-webpack-plugin.例如public文件夹下的资源不希望webpack进行打包操作,而是在打包完成后的静态资源中直接复制过去(例如favicon.ico, css文件等)。
安装: npm install copy-webpack-plugin -D
使用:
lg.webpack.config.js
...
const CopyWebpackPlugin = require("copy-webpack-plugin")
module.exports = {
entry: "./src/index.js", /* 打包的入口地址,可以使用相对路径 */
output:{
filename: "js/main.js", /* 打包后的文件名字 */
path: path.resolve(__dirname, "dist"),/* 绝对地址,使用path包来解决 */
},
......
plugins:[
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
title:"copyWebpackPlugin",
template: "./public/index.html"
}),
new DefinePlugin({
BASE_URL:'"./"'
}),
new CopyWebpackPlugin({
patterns:[
{
from: 'public',
// to 不需要配置复制到的地址, 因为此插件可以根据webpack配置的output来配置。
globOptions: {
ignore: ['**/index.html']
}
}
]
})
]
注意,这时候的**/
代表的是从上面配置的from路径中寻找。
20 babel使用
babel-loader是对js代码进行兼容处理的工具, 我们要想透彻的使用它, 需要我们对babel的原理工作的流程有所理解.
20.1 为什么需要babel?
JSX,TS,ES6+语法浏览器不能直接识别, 我们希望可以产出浏览器可以直接使用的代码, 就可以使用babel, 就类似于postcss可以将css转化为浏览器可以使用的css, 其可以处理JS兼容, 把源代码中不被所有浏览器都能识别的代码进行转换之后, 再去交给浏览器使用.
20.2 如何使用?
安装:npm i @babel/core @babel/cli -D
- @babel/core 借助各种babel插件来进行转换js文件.
- @babel/core不能在终端直接工作, 需要借助@babel/cli来进行.
使用npx babel src --out-dir build
可以在build目录下对src的所有js文件进行babel处理.
安装: npm i @babel/plugin-transform-arrow-functions -D
处理箭头函数
使用插件处理箭头函数: npx babel src --out-dir build --plugins=@babel/plugin-transform-arrow-functions
安装:npm i @babel/plugin-transform-block-scoping -D
处理作用域关键字
使用插件处理箭头函数和作用域关键字:npx babel src --out-dir build --plugins=@babel/plugin-transform-arrow-functions,@babel/plugin-transform-block-scoping
安装:npm i @babel/preset-env -D
preset-env包含多个包集合来处理js使其兼容
使用presets来处理JS使其兼容:npx babel src --out-dir build --presets=@babel/preset-env
处理前:
const title = "前端"
const foo = () => {
console.log(title);
}
foo()
处理后:
"use strict";
var title = "前端";
var foo = function foo() {
console.log(title);
};
foo();
21 babel-loader使用
安装:npm i babel-loader
- babel-loader如果直接使用会跟babel直接使用一样, 不会对js文件进行兼容处理, 需要搭配插件使用, 第一种是直接指定插件的名字, 第二种是使用presets来进行批量处理:
{
test: /\.js$/,
use: [
{
loader: 'babel-loader',
options: {
// 第一种方式
// plugins:[
// '@babel/plugin-transform-arrow-functions',
// '@babel/plugin-transform-block-scoping'
// ]
presets:['@babel/preset-env'] // 第二种方式
}
}
]
}
- babel-loader有两种方式可以得到兼容的平台, 第一个是通过.browserslistrc文件, 第二种是在presets里配置targets, 以第二种为主:
{
test: /\.js$/,
use: [
{
loader: 'babel-loader',
options: {
// plugins:[
// '@babel/plugin-transform-arrow-functions',
// '@babel/plugin-transform-block-scoping'
// ]
presets:[
[
'@babel/preset-env',
{
targets: "chrome 91"
}
]
]
}
}
]
}
- 可以看到现在写是一层嵌套一层, 是比较麻烦的, 还是用相关的配置文件比较好:
bebel.config.js(json cjs mjs) 目前用的多 多包管理方式
babelrc.json(js) babel7 以前以前用的多
目前我们就用babel.config.js来进行配置:
module.exports = {
presets:['@babel/preset-env']
}
然后在webpack配置文件里直接写成如下方式就可以了,非常简洁:
{
test: /\.js$/,
use: ['babel-loader']
}
到目前为止, 我们配置的文件一共有四个:
.browserslistrc
: 配置css和js所兼容的平台.
babel.config.js
:配置babel来处理兼容JS.
lg.webpack.js
:我们改名之后的webpack配置文件.
postcss.config.js
:配置postcss来处理兼容CSS.
22 polyfill
preset-env并不能将所有的js语法都转成浏览器所需要的ss语法, polyfill是为了解决形如Promise的函数在很多浏览器并不兼容, 需要我们转换为浏览器所能识别的函数等.
如何使用?
Webpack5之前不需要处理.
Webpack5按需配置, 使用core-js 或 regenerator-runtime就可以:
babel.config.js:
module.exports = {
presets:[
[
'@babel/preset-env',
{
// false: 不对当前JS处理做 polyfill的填充
// usage: 根据用户源代码中所使用到的新语法进行填充.
// entry: 依据当前所要兼容的浏览器做相应的填充.需要下面两个导入
// import "core-js/stable"
// import "regenerator-runtime/runtime"
useBuiltIns: 'usage',
corejs: 3
}
]
]
}
useBuiltIns
是选择处理方式:
-
false: 不对当前JS处理做 polyfill的填充
-
usage: 根据用户源代码中所使用到的新语法进行填充.
-
entry: 依据当前所要兼容的浏览器做相应的填充.需要下面两个安装并导入.
import "core-js/stable" import "regenerator-runtime/runtime"
corejs
是选择core-js的版本, 现在安装的版本是3.X, 默认是2, 所以必须要填写此项为3.
在实际开发中我们所使用的第三方的包(node_modules),他们是已经进行过polyfill填充的, 我们不需要再次将其填充, 所以我们需要配置一个exclude选项.
exclude
是将/node_modules/里面的外部安装的包代码不进行polyfill填充处理.
lg.webpack.js
{
test: /\.js$/,
exclude: /node_modules/,
use: ['babel-loader'],
}
23 webpack-dev-server
如何通过搭建一个本地的服务器从而来提高开发的效率?项目开发到一定阶段了, 想要进行打包测试, 该如何做呢?
现在的问题是当我们打包后以live server来测试时, 我们修改源代码时, 打包没有被自动触发。我们希望可以实现这样的事情:当我们项目下文件的内容发生修改时,它可以自动的去帮我们完成编译, 之后再结合着live server 把编译之后的数据重新展示出来。这样就非常利于我们在开发阶段查看和调试, 从而提高我们的开发效率。
- watch的打包模式, 在package.json build配置项中添加后缀
--watch
; - lg.webpack.js中加一个字段watch,值为true:
watch:true,
。
不足:
1. 一旦文件发生改变, 所有的源代码都会重新编译;
2. 每次编译成功之后都需要进行文件读写。(性能消耗大);
3. live server;
4. 不能实现局部刷新。
所以我们需要使用webpack-dev-server:
安装:npm i webpack-dev-server
使用:
在package.json中scripts选项添加一个配置项serve
:
package.json:
{
......
"scripts": {
"build": "webpack --config lg.webpack.js",
"serve": "webpack serve --config lg.webpack.js"
},
......
}
好处:
-
不会生成dist目录, 结果直接在内存中显示而不是在硬盘中,不需要做磁盘读写, 响应速度更快。
-
不需要再依赖live server, 可以实现热更新(局部更新)。
24 webpack-dev-middleware
webpack-dev-middleware是一个容器(wrapper),它可以把webpack处理后的文件传递给一个服务器(server)。
应用场景是自定义性比较高的开发环境,通常情况下还是用dev-server更多一点。
安装:npm i express webpack-dev-middleware
.
使用:
Server.js
const express = require("express")
const webpackDevMiddleware = require("webpack-dev-middleware")
const webpack = require("webpack")
const app = express()
// 让webpack继续打包 获取配置文件
const config = require("./webpack.config.js")
const compiler = webpack(config)
app.use(webpackDevMiddleware(compiler))
// 开启端口上的服务监听
app.listen(3000, ()=> {
console.log("服务运行在 3000端口上")
})
然后在终端使用node .\Server.js
再访问localhost:3000即可。
25 HMR功能的使用
HMR模块热替换功能
如果想要使用热替换, 就要采用已经实现好了的功能在webpack-dev-server当中,我们依据相应的配置把它展示出来就行。
什么是模块热替换功能?
Hot Model Replacement(HMR)功能可以实现局部更新的功能, 因为我们一般是组件式编程, 我们希望通过局部更新的方式来进行编程, 而不是每修改一次源代码就重新全部打包更新一次,所以我们需要使用HMR功能
使用方法:
-
首先在webpack.config.js中添加一个字段devSever,其内容如下:
...... module.exports = { ...... devServer:{ hot: true, }, ...... }
-
然后在入口文件index.js中配置要使用热更新的模块
index.js
import './title' if(module.hot) { module.hot.accept(['./title.js'], () => { console.log("title.js模块更新了!"); }) }
title.js
module.exports = "前端开发" console.log("这是title模块444");
-
然后我们就可以通过
npm run serve
来运行,然后修改title.js中的代码, 发现可以实现热更新了, 并且input框内的内容不会因为title.js中,并且index.js中可以写模块热更新后的回调。
26 React组件支持热更新
安装:
npm i @babel/preset-env @babel/preset-react -D
npm i @pmmmwh/react-refresh-webpack-plugin react-refresh -D
npm i react react-dom
使用:
-
首先我们要知道React是基于jsx语法, 所以我们要修改webpack.config.js中modules中对于js及jsx语法的规则。
...... module.exports ={ ...... modules:{ rules:[ ...... { test: /\.jsx?$/, exclude:/node_modules/, use:["babel-loader"] } ] } ...... }
-
我们要导入在webpack中导入能够让React热更新的插件。
...... const ReactRefreshWebpackPlugin = require("@pmmmwh/react-refresh-webpack-plugin") modules.export = { ...... plugins:[ ...... new ReactRefreshWebpackPlugin() ] }
-
单单导入这个插件是没有用的,我们还需要在babel.config.js中配置具体使用的react-refresh。
babel.config.js
module.exports = { presets:[ ['@babel/preset-env'], ['@babel/preset-react'] ], plugins: [ ['react-refresh/babel'] ] }
-
然后使用
npm run serve
命令, 发现已经可以正常的进行热更新了,这样就实现了React组件热更新。
27 Vue组件热更新
首先需要能让webpack识别并打包.vue文件。
然后再让Vue组件具有热更新功能:
vue-loader16版本之前, 比如15是对vue2进行处理的,16及以后的版本是对vue3进行处理的
- 安装vue2:
npm i vue@2.x vue-template-compiler
App.vue:
<template>
<div class="example">{{ msg }}</div>
</template>
<script>
export default {
data() {
return {
msg: "Hello World"
}
},
}
</script>
<style>
.example {
color: red
}
</style>
index.js:
import './title'
import Vue from "vue"
import App from "./App.vue"
if(module.hot) {
module.hot.accept(['./title.js'], () => {
console.log("title.js模块更新了!");
})
}
new Vue({
render: h => h(App)
}).$mount("#app")
npm i vue-loader@14 -D
设置为开发依赖,不需要进行配置插件,但是不推荐使用,因为会出现莫名错误。
......
module.exports = {
......
modules:{
rules:[
......
{
test: /\.vue$/,
use:['vue-loader']
}
]
}
......
}
npm i vue-loader@15 -D
设置为开发依赖,需要进行配置,建议使用。
......
const VueLoaderPlugin = require("vue-loader/lib/plugin")
module.exports = {
......
modules:{
rules:[
......
{
test: /\.vue$/,
use:['vue-loader']
}
]
}
plugins:[
......
new VueLoaderPlugin(),
]
}
- 然后就可以正常热更新了。
28 output中的path
webpack.config.js中 output 和 devServer:
- output中的path:项目打包后资源产出的地址:
path: path.resolve(__dirname, "dist")
__dirname
是打包后的地址,“dist”
是打包后的文件夹名字。
publicPath
默认为空''
,告知index.html内部的引用路径,使其知道如何去加载内部的资源, 规则是 域名 + publicPath + filename。
默认为空的情况下,右键以default server查看没有任何问题,以dev-server的方式启动也没有问题,所以一般都是用默认设置。
也可以设置为/
,只不过这次是自己在文件名filename前加/
,而不是像空字符串那样浏览器帮我们加,但是在本地以default server的方式打开是找不到资源,如果要在本地建议使用./
,但是使用./
会无法在dev-server上使用。
最后的建议是设置publicPath为/
并且在devServer中进行设置来完成相关操作。
29 devServer中的path
webpack.config.js中的output和devServer:
- devServer中的publicPath:指定本地服务所在的目录,告知浏览器客户端去哪个目录找服务所在的目录。
不配置的默认情况下为/
,强烈建议把output和devServer的publicPath设置为相同的值。
- static:我们打包之后的资源如果说依赖了其他的资源,此时就告知去哪找。
值一般为path.resolve(__dirname, "public")
30 devServer中的常用配置
- hot: 是否开启热更新,
true
代表开启,false
代表关闭, 需要搭配插件使用. - port: 指定devServer开启的端口号.
- open:指定当devServer启动时会不会自动开启一个视口并跳转到devSever所在的网址和端口号. 建议设置为
false
, 因为每次修改后会新开启一个视口, 影响操作. - compress: 开启服务端的gzip压缩, 会对main.js来进行压缩, 进行性能的提升.默认为true.
- historyApiFallback: 可以解决在路由跳转后进行刷新出现404 Not Found的情况, 建议设置为
true
.
31 devServer中的proxy代理设置
为了解决跨域的问题, 所以需要在devServer中设置代理:
......
module.exports = {
......,
devServer:{
proxy: {
'/api': {
target: "https://api.github.com",
pathRewrite: {"^/api": ""},
changeOrigin: true
}
}
},
......
}
target
是目标请求网站转发.
pathRewrite
是对地址进行重写, 比如我们这里有/api
我们删除掉.
changOrigin
是将host假性改变, 设置为true
则改变,比如https://api.github.com
github的api就需要是需要origin访问, 否则无法得到资源.
32 resolve模块解析规则
resolve
配置模块如何被解析. 例如,当在 ES2015 中调用 import 'lodash'
,resolve
选项能够对 webpack
查找 'lodash'
的方式去做修改.
例如, 如果不进行配置, 如下导入就无法进行打包:
import Home from "./components/Home" // 原文件是jsx
模块导入一般涉及到三种路径:
- 绝对路径
- 相对路径
- 模块名称
如果是解析的绝对路径或者是相对路径, 当解析到最后一个的时候比如说Home, 它会去找是否有相应的文件名或者文件夹, 如果有文件就会调用resolve.extensions
进行补全, 默认是[string] = ['.js', '.json', '.wasm']
.
还有一种情况是我们可能在React项目中一个文件夹下直接创建一个index.js
或index.jsx
, 而在导入的时候路径只需要写到该文件夹就行, 不需要写该文件, 例如:
import Hello from "./components/"
这个时候我们就需要配置resolve里mainFiles
的选项, 其用来解析目录时要使用的文件名。
......
module.exports = {
......
resolve:{
extensions:['.js', '.json', '.jsx', '.ts','.vue'],
mainFiles: ['index']
}
......
}
......
还有一个就是我们所查找的模块路径, 是在resolve的modules
属性里面配置, 默认是node_modules
, 跟我们平时所创建出来的一样, 一般不需要进行设置.
resolve.alias
, 创建 import
或 require
的别名,来确保模块引入变得更简单。
......
module.exports = {
......
resolve:{
extensions:['.js', '.json', '.jsx', '.ts','.vue'],
mainFiles: ['index'],
alias: {
'@':path.resolve(__dirname, 'src'),
Utilities: path.resolve(__dirname, 'src/utilities/'),
Templates: path.resolve(__dirname, 'src/templates/'),
},
},
......
}
......
然后就可以改变导入文件的方式了:
import Home from "@/components/Home"
import About from "@/components/About"
import Hello from "@/components/"
33 source-map作用
mode
首先我们对项目进行打包一下, 然后打包时有操作提醒我们mode
还没设置, 我们先去配置文件里设置一下mode, 告知 webpack 使用相应模式的内置优化。
string = 'production': 'none' | 'development' | 'production'
用法
......
module.exports = {
......
mode:'development',
......
}
......
选项 | 描述 |
---|---|
development |
会将 DefinePlugin 中 process.env.NODE_ENV 的值设置为 development . 为模块和 chunk 启用有效的名. |
production |
会将 DefinePlugin 中 process.env.NODE_ENV 的值设置为 production 。为模块和 chunk 启用确定性的混淆名称,FlagDependencyUsagePlugin ,FlagIncludedChunksPlugin ,ModuleConcatenationPlugin ,NoEmitOnErrorsPlugin 和 TerserPlugin . |
none |
不使用任何默认优化选项. |
Devtool
此选项控制是否生成,以及如何生成 source map。
string = 'eval'
false
目前的需求是当你用Webpack打包编译之后的文件, 如果程序上出现了错误, 你很难根据该文件和报错信息去定位报错文件位置信息.这个时候我们就需要source - map.
这是因为我们自己在网站上运行的代码和我们自己编写的源代码是有差异了, 是因为经过了webpack的处理和压缩, 这个代码如果开启source-map功能, 就能识别和使用.
source-map 是一种映射技术, 在调试的时候可以定位到源代码中的信息.
简单使用方法:
webpack.config.js:
......
module.exports = {
......
devtool: "source-map",
......
}
......
34 devtool配置详解
情况一:
mode
为development时,也就是devtool
为当eval.
打包后以静态文件的方式运行时 ,当出现bug时,可以定位到源文件以及错误位置,报错信息有行有列,当以devServer方式运行时,发现无法定位源文件和错误信息。
情况二:
mode
为development, devtool
为source-map
时,会多打包出一个.map文件, 这是一个映射表, 映射着编码之后的代码和打包之前的代码的对应关系.
打包后以静态文件的方式运行时 ,当出现bug时,可以定位到源文件以及错误位置,报错信息只有行, 当以devServer方式运行时,可以定位源文件和错误信息。
情况三:
mode
为development, devtool
为eval-source-map
, 不会出现.map文件, 但是eval中以base64方式把map保存起来进行查找。需要每次都请求。
打包后以静态文件的方式运行时 ,当出现bug时,可以定位到源文件以及错误位置,报错信息只有行, 当以devServer方式运行时,可以定位源文件和错误信息。
情况四:
mode
为development, devtool
为inline-source-map
, 不会出现.map文件,他把.map文件在最后一行以base64的方式保存起来进行查找。只需要请求一次。
打包后以静态文件的方式运行时 ,当出现bug时,可以定位到源文件以及错误位置,报错信息只有行, 当以devServer方式运行时,可以定位源文件和错误信息。
情况五:
mode
为development, devtool
为cheap-module-source-map
, 会出现.map文件,只会出现行信息, 会还原源代码信息。
打包后以静态文件的方式运行时 ,当出现bug时,可以定位到源文件以及错误位置,报错信息只有行, 当以devServer方式运行时,可以定位源文件和错误信息。
情况六:
mode
为development, devtool
为hidden-source-map
, 会出现.map文件.
打包后以静态文件的方式运行时 ,当出现bug时,无法定位到源文件以及错误位置,当以devServer方式运行时,无法定位源文件和错误信息。 但是只要在生产环境下把map文件引入就可以了。
情况七:
mode
为development, devtool
为nosources-source-map
, 会出现.map文件.
打包后以静态文件的方式运行时 ,当出现bug时,虽然会出现报错文件提示, 但是找不到源代码文件。
最后, 建议使用cheap-module-source-map
35 ts-loader编译TS
首先需要安装typescript:
npm install -g typescript
然后需要配置一下typescript:
- win+x 选择Windows PowerShell 即可打开shell.
- 运行 set-ExecutionPolicy RemoteSigned 命令,在询问更改执行策略的时候 输入 Y.
- 运行 get-ExecutionPolicy 命令,可看到脚本的执行策略已被更改为 RemoteSigned.
- 回到vscode的终端,输入命令 tsc -v 不再报错,也可对ts文件进行编译.
使用tsc --init
生成tsconfig.json文件。
若想编译ts文件,要么使用typescript-compilier, 要么使用ts-loader编译.
安装:npm install ts-loader -D
配置方法:
webpack.config.js
......
module.exports = {
......
module:{
rules:[
......
{
test: /\.ts?$/,
use:["ts-loader"]
}
]
}
......
}
......
36 babel-loader编译TS
上一节中ts-loader不能对一些新语法比如Promise进行转化填充处理, 那么这个时候就需要我们使用babel-loader进行处理。在语法有错误的时候不会在编译阶段看到, 而ts-loader可以看到语法错误。
首先要安装:npm install @babel/preset-typescript -D
配置:
webpack.config.js:
......
module.exports = {
......
module:{
rules:[
......
{
test: /\.ts?$/,
use:["babel-loader"]
}
]
}
......
}
......
babel.config.js:
module.exports = {
presets:[
['@babel/preset-env', {
useBuiltIns: 'usage',
corejs: 3,
}],
['@babel/preset-react'],
['@babel/preset-typescript']
],
plugins: [
['react-refresh/babel']
]
}
完美的解决方案:
在package.json中增加一条命令进行语法校验: --noEmit
是不生成js文件。
"ck": "tsc --noEmit"
然后使用 npm run ck
查看, 就可以看到具体的代码错误所出现的行和列, 从而可以进行语法的校验操作。
最后跟打包的操作结通过&&
合起来, 这样既可以进行打包操作又可以进行语法检验操作:
package.json:
{
......,
"scripts": {
"build": "npm run ck && webpack",
"serve": "webpack serve",
"ck": "tsc --noEmit"
},
......
}
37 加载vue文件
首先需要安装 vue、vue-loader和vue-template-compiler:
npm i vue@2.x vue-template-compiler vue-loader@15
然后写好对应的vue文件和对应的index.js配置文件。
最后配置webpack文件:
webpack.config.js:
......
const VueLoaderPlugin = require("vue-loader/lib/plugin")
module.exports = {
......
module:{
rules:[
......
{
test: /\.less$/,
use:[
"style-loader",
{
loader:"css-loader",
options:{
importLoaders: 2, // 往前找2个,即postcss-loader和less-loader
}
},
"postcss-loader",
"less-loader"
]
},
{
test: /\.vue$/,
use:["vue-loader"]
}
]
},
plugins:[
......
new VueLoaderPlugin()
]
......
}
......
38 区分打包环境
区分mode中的development
和production
. 当我们把环境区分了之后, 再去对我们当前所有的配置进行拆分, 独立到当前具体的配置文件中, 后续我们就可以依据环境来进行具体的打包工作.
Target
由于 JavaScript 既可以编写服务端代码也可以编写浏览器代码,所以 webpack 提供了多种部署 target.
如果是服务端代码, target值为node
, 如果是浏览器代码, target值为web
, 目前我们所使用的是web
......
module.exports = {
......
target: 'web',
......
}
......
现在的情况是有的webpack
配置只能在devSever中使用, 而不能在打包时使用, devServer对应的是开发环境development
, 而打包对应的是生产环境production
, 所以我们现在需要进行环境的区分, 从而实现资源和配置的打包.
- 首先我们在工程根目录下建立一个
config
文件夹, 里面分别有三个文件夹, 分别是:
- webpack.common.js 开发环境和生产环境都共有的配置选项.
- webpack.dev.js 开发环境所独有的配置选项.
- webpack.prod.js 生产环境所独有的配置选项.
- 然后我们需要在package.json里添加新的命令用来区分开发或者是生产环境:其中
--config
是用来标识主要的配置文件, 我们都选择是webpack.common.js, 然后--env
可以传递一个参数给webpack.common.js, 可以在参数中接收他, 我们根据这个参数来区分是生产环境还是开发环境.
{
......,
"scripts": {
"build": "webpack",
"serve": "webpack serve",
"build2": "webpack --config ./config/webpack.common.js --env production",
"serve2": "webpack serve --config ./config/webpack.common.js --env development",
"ck": "tsc --noEmit"
},
......
}
- 在webpack.common.js中区分开发环境还是生产环境.
如果isPrduction
为true, 则证明为生产环境, 如果是undefinded
, 则证明是开发环境, 我们后续可以通过isPrduction
来进行判断, 从而区分开发环境还是生产环境.
module.exports = (env) => {
const isPrduction = env.production
return {
}
}
39 合并生产环境配置
安装webpack-merge, 这是用来合并环境配置对象的:
npm i webpack-merge -D
首先我们需要在config
中创建一个path.js
模块, 用来解析绝对路径, 根据目录把与你想要找的目录所结合起来, 限制一下从哪儿开始找文件.
const path = require("path")
const appDir = process.cwd()
console.log(appDir, "<---------")
const resovleApp = (relativePath) => {
// 把根目录与你想要找的目录所结合起来, 限制一下从哪儿开始找文件
return path.resolve(appDir, relativePath)
}
module.exports = resovleApp
然后将webpack.common.js
、webpack.dev.js
和webpack.prod.js
分别配置一下, 使用webpack-merge中的merge函数来进行合并, 这样就可以了:
webpack.common.js
const resovleApp = require("./paths")
const HtmlWebpackPlugin = require("html-webpack-plugin")
const {merge} = require('webpack-merge')
// 导入其他的配置信息
const prodConfig = require('./webpack.prod')
const devConfig = require('./webpack.dev')
// 定义对象保存 base 配置信息
const commonConfig = {
entry: "./src/index.js", /* 打包的入口地址,可以使用相对路径是相对context, 默认是看--config, 其对应的是config目录, 即就是工程根目录, 反而没有报错, 因为是相对路径 */
resolve:{
extensions:['.js', '.json', '.jsx', '.ts','.vue'],
mainFiles: ['index'],
alias: {
'@':resovleApp('./src')
},
},
output:{
filename: "js/main.js", /* 打包后的文件名字 */
path: resovleApp('./dist'),/* 绝对地址,使用path包来解决 */
},
module:{
rules:[
{
test: /\.css$/,
use:[
"style-loader",
{
loader:"css-loader",
options:{
importLoaders: 1, // 往前找一个,即postcss-loader
esModule:false,
}
},
"postcss-loader",
]
},
{
test: /\.less$/,
use:[
"style-loader",
{
loader:"css-loader",
options:{
importLoaders: 2, // 往前找2个,即postcss-loader和less-loader
}
},
"postcss-loader",
"less-loader"
]
},
{
test: /\.(png|svg|gif|jpe?g)$/,
type: "asset",
generator: {
filename: "img/[name].[hash:6][ext]",
},
parser: {
dataUrlCondition: {
maxSize: 1.6 * 1024 * 1024
}
}
},
{
test: /\.(ttf|woff2?)$/,
type: "asset/resource",
generator: {
filename: "font/[name].[hash:3][ext]"
}
},
{
test: /\.jsx?$/,
exclude:/node_modules/,
use:["babel-loader"]
},
{
test: /\.ts$/,
use:["babel-loader"]
},
{
test: /\.vue$/,
use:["vue-loader"]
}
]
},
plugins:[
new HtmlWebpackPlugin({
title:"merge_prod_env",
template: "./public/index.html"
})
]
}
module.exports = (env) => {
const isPrduction = env.production
// 依据当前的打包模式来合并配置
const config = isPrduction ? prodConfig : devConfig
const mergeConfig = merge(commonConfig, config)
return mergeConfig
}
webpack.dev.js
const ReactRefreshWebpackPlugin = require("@pmmmwh/react-refresh-webpack-plugin")
const VueLoaderPlugin = require("vue-loader/lib/plugin")
module.exports = {
mode: "development",
devtool:'cheap-module-source-map',
target:'web',
devServer:{
hot: true,
port: 3000,
open: false,
compress: true,
historyApiFallback: true,
proxy: {
'/api': {
target: "https://api.github.com",
pathRewrite: {"^/api": ""},
changeOrigin: true
}
}
},
plugins:[
new ReactRefreshWebpackPlugin(),
new VueLoaderPlugin()
]
}
webpack.prod.js
const CopyWebpackPlugin = require("copy-webpack-plugin")
const {CleanWebpackPlugin} = require("clean-webpack-plugin")
module.exports = {
mode: "production",
plugins:[
new CleanWebpackPlugin(),
new CopyWebpackPlugin({
patterns:[
{
from: 'public',
// to 不需要配置复制到的地址, 因为此插件可以根据webpack配置的output来配置。
globOptions: {
ignore: ['**/index.html']
}
}
]
})
]
}