本笔记为CoderWhy老师Webpack5教程的学习笔记,内容来自CoderWhy老师的PPT以及Webpack官方文档。
基本配置
webpack配置文件
- webpack默认的配置文件为 webpack.config.js
- 可以通过
webpack --config shirllyuan.config.js的指令将webpack的配置文件修改为当前终端根目录下的shirllyuan.config.js - 可以在package.json中script中添加如下配置,来使用npm run build进行构建,此时配置文件目录相对于package.json所在目录
"scripts": {
"build": "webpack --config shirllyuan.config.js",
"serve": "webpack serve --config shirllyuan.config.js",
},
- 入口和出口基本配置:
const path = require('path')
module.exports = {
mode: "development",
entry: "./src/service.js",
output: {
filename: "main.js",
path: path.resolve(__dirname,"./dist")
}
}
loader的使用
module.rules
-
test属性: 用于对 resource(资源)进行匹配的,通常会设置成正则表达式;
-
use属性:用来声明需要使用那些loader,loader的顺序按照从右到左,从下到上的顺序加载[useEntry]
-
useEntry一个对象,webpack支持使用字符串进行简写(当不存在options时可使用loader的字符串进行表示)
- loader:必须有一个 loader属性,对应的值是一个字符串;
- options: 可选的属性,值是一个字符串或者对象,值会被传入到loader中
-
-
loader属性:这个是在只使用一个loader时use属性的简写形式
CSS-loader
- 可以用来读取css文件
- 内联方式加载
import "css-loader!./css/index.css"
- 配置方式
module: {
rules;[
{
test: /.css$/, //使用正则表达式匹配资源
use: [
{
loader: 'css-laoder', //使用的loader名字
option: { //额外选项,此处未css-loader的配置
importLoaders: 1,
}
}
]
}
]
}
Style-loader
- 只使用css-loader只能使得webpack对css文件进行解析,无法将css注入我们的js代码中,可以使用style-loader将我们引入的css文件注入到代码中
- style-loader会生成一个style的dom节点,并将我们的css代码插入该dom中,最后将该dom节点挂载到html文档中
- style-loader配置:
use: [
"style-loader",
"css-loader"
]
//必须按照这个顺序进行执行,css-loader对文件进行解析,然后交给style-loader插入文档
Less-loader
- 开发过程中我们会用到less、sasa等css预处理器,加载对应的文件就需要对应的loader
- 配置信息:
{
test: /.less$/,
use:[
"style-loader",
"css-loader",
"less-loader"
]
}
//过程: less-loader调用less将less转为css,css-loader与style-loader进行解析代码和插入文档
Browserslist
-
一个用于查询浏览器市场占有率的node脚本,主要调用调用caniuse-lite工具,在caniuse.com这个网站上来查询市场占有率等信息。
-
可以使用命令行进行调用
npx browserslist “>1%, last 2version. not dead” -
配置规则
-
defaults:Browserslist的默认浏览器(> 0.5%, last 2 versions, Firefox ESR, not dead)。
-
5%:通过全局使用情况统计信息选择的浏览器版本。 >=,<和<=。
- 5% in US:使用美国使用情况统计信息。它接受两个字母的国家/地区代码
-
5% in alt-AS:使用亚洲地区使用情况统计信息。有关所有区域代码的列表,请参见caniuse-lite/data/regions
-
5% in browserslist-config-mycompany stats:使用 来自的自定义使用情况数据browserslist-config-mycompany/browserslist-stats.json。
- cover 99.5%:提供覆盖率的最受欢迎的浏览器。
- cover 99.5% in US:与上述相同,但国家/地区代码由两个字母组成。
- cover 99.5% in my stats:使用自定义用法数据
-
dead:24个月内没有官方支持或更新的浏览器。
-
last 2 versions:每个浏览器的最后2个版本。
- last 2 Chrome versions:最近2个版本的Chrome浏览器。
- last 2 major versions或last 2 iOS major versions:最近2个主要版本的所有次要/补丁版本。
-
node 10和node 10.4:选择最新的Node.js10.x.x 或10.4.x版本。
- current node:Browserslist现在使用的Node.js版本。
- maintained node versions:所有Node.js版本,仍由 Node.js Foundation维护。
-
iOS 7:直接使用iOS浏览器版本7。
- Firefox > 20:Firefox的版本高于20 >=,<并且<=也可以使用。它也可以与Node.js一起使用。
- ie 6-8:选择一个包含范围的版本。
- Firefox ESR:最新的[Firefox ESR]版本。
- PhantomJS 2.1和PhantomJS 1.9:选择类似于PhantomJS运行时的Safari版本。
-
extends browserslist-config-mycompany:从browserslist-config-mycompanynpm包中查询 。
-
supports es6-module:支持特定功能的浏览器。 es6-module这是“我可以使用” 页面feat的URL上的参数。有关所有可用功能的列表,请参见 。caniuse-lite/data/features
-
browserslist config:在Browserslist配置中定义的浏览器。在差异服务中很有用,可用于修改用户的配置,例如 browserslist config and supports es6-module。
-
since 2015或last 2 years:自2015年以来发布的所有版本(since 2015-03以及since 2015-03-10)。
-
unreleased versions或unreleased Chrome versions:Alpha和Beta版本。
-
not ie <= 8:排除先前查询选择的浏览器
-
-
配置文件为
.browserslistrc文件运行browserslist时会使用改文件的配置 -
也可以在package.json中进行配置
"browserslist": [
"last 2 version",
"not dead",
"> 0.5%"
]
- 多个条件之间的关系(来自browserslist文档)
PostCSS
- PostCSS可以借助node环境进行样式转换,如:使用autoprefixer插件添加浏览器前缀等。
- PostCSS可以使用Cli工具进行独立使用(postcss-cli)
npx postcss --use autoprefixer -o end.css ./src/css/index.css
- PostCSS配置文件为
postcss.config.js
module.exports = {
plugins: [
// require("autoprefixer"), //require("postcss-preset-env"),已经包含autoprefixer
require("postcss-preset-env"),
// require("postcss-preset-env")({
// //此处可添加参数
// }),
// 也可以不使用require
// "autoprefixer":{
// //此处添加配置
// }
]
}
- 也可以在webpack.config.js中进行配置对postcss-loader设置
//如果postcss.config.js未配置则必须在这里配置
{
loader: "postcss-loader",
options: {
postcssOptions: {
plugins: [
// require("autoprefixer"), //require("postcss-preset-env"),已经包含
require("postcss-preset-env"),
// require("postcss-preset-env")({
// //此处可添加参数
// }),
]
}
}
}
Autoprefixer
- 根据browserslist的结果来为我们的CSS添加浏览器前缀
postcss-preset-env
- postcss-preset-env是postcss的环境预设,该预设已经包含autoprefixer等插件,也会为我们的CSS环境添加polyfill
webpack对css文件完整配置
module: {
rules: [
{
// 规则使用正则表达式
test: /.css$/, // 匹配资源
use: [
// { loader: "css-loader" },
// 注意: 编写顺序(从下往上, 从右往做, 从后往前)
"style-loader",
{
loader: "css-loader",
options: {
// url: false,
importLoaders: 1,
}
},
//如果postcss.config.js未配置则必须在这里配置
// {
// loader: "postcss-loader",
// options: {
// postcssOptions: {
// plugins: [
// // require("autoprefixer"), //require("postcss-preset-env"),已经包含
// require("postcss-preset-env"),
// // require("postcss-preset-env")({
// // //此处可添加参数
// // }),
// ]
// }
// }
// }
// "css-loader",
//配置信息也配置在postcss.config.js中,此时只传入字符串即可
"postcss-loader"
],
// loader: "css-loader"
},
{
test: /.less$/,
use: [
"style-loader",
{
loader: "css-loader",
options: {
importLoaders: 2
}
},
"postcss-loader",
"less-loader"
]
}
]
},
图片、字体等文件的加载
file-loader
- file-loader的作用就是帮助我们处理import/require()方式引入的一个文件资源,并且会将它放到我们输出的 文件夹中;
- 基本使用方式:
{
test: /.(png|jpe?g|gif|svg)$/i,
use: {
loader: "file-loader"
}
}
-
为了保留原来文件的文件名、拓展名,以及防止文件重复,我们会采用占位符(PlaceHolder)来进行对文件命名
-
常用的PlaceHolder:
- [ext] : 处理文件拓展名
- [name] : 处理文件的名称
- [hash] : 文件的内容,使用MD4的散列函数处理,生成的一个128位的hash值(32个十六进制)
- [contentHash]:在file-loader中和[hash]结果是一致的(在webpack的一些其他地方不一样,在打包优化的chunk的地方会说到);
- [hash:]:截图hash的长度,默认32个字符太长了
- [path]:文件相对于webpack配置文件的路径
-
在file-loader中设置文件名称:
{
test: /.(png|jpe?g|gif|svg)$/i,
use: {
loader: "file-loader",
options: {
name: "img/[name].[hash:8][ext]", //存放路径为相对Output根目录下img文件夹
//name: "[name].[hash:8][ext]"
//outputPath: "img" //与上面一样路径
}
}
}
url-loader
- 使用方法与file-loader类似,file-loader会默认将体积较小的图片转为Base64编码插入js内,来减少文档加载阶段的请求次数。、
- 可以配置limit属性,用于设置转换的限制
{
test: /.(png|jpe?g|gif|svg)$/i,
use: {
loader: "url-loader",
options: {
limit: 100 * 1024, //则100kb以下的图片会被转为base64形式
name: "img/[name].[hash:8][ext]", //存放路径为相对Output根目录下img文件夹
}
}
}
asset module type
-
在webpack5之前,加载这些资源我们需要使用一些loader,比如raw-loader 、url-loader、file-loader;
-
在webpack5之后,我们可以直接使用资源模块类型(asset module type),来替代上面的这些loader;
-
资源模块类型(asset module type),通过添加 4 种新的模块类型,来替换所有这些 loader:
- asset/resource 发送一个单独的文件并导出 URL。之前通过使用 file-loader 实现;
- asset/inline 导出一个资源的 data URI。之前通过使用 url-loader 实现;
- asset/source 导出资源的源代码。之前通过使用 raw-loader 实现;
- asset 在导出一个 data URI 和发送一个单独的文件之间自动选择。之前通过使用 url-loader,并且配置资源 体积限制实现;
-
asset module type的基本使用:
output: {
filename: "main.js",
path: path.resolve(__dirname,"./dist"),
assetModuleFilename: "img/[name].[hash:6][ext]" //自定义文件输出路径和文件名方式二
}
//也可采用以上方式自定义asset输出路径与文件名
{
test: /.(png|jpe?g|gif|svg)$/i,
type: "asset",
generator: {
filename: "img/[name].[hash:6][ext]" //自定义文件输出路径和文件名方式一
},
parser: {
dataUrlCondition: {
maxSize: 100 * 1024 //url的limit的效果
}
}
}
加载字体文件
{
test: /.(woff2?|eot|ttf)$/i,
type: "asset/resource",
generator: {
filename: "font/[name].[hash:6][ext]" //自定义文件输出路径和文件名方式一
}
}
数据的加载
- 此外,可以加载的有用资源还有数据,如 JSON 文件,CSV、TSV 和 XML。类似于 NodeJS,JSON 支持实际上是内置的,也就是说
import Data from './data.json'默认将正常运行。要导入 CSV、TSV 和 XML,你可以使用 csv-loader 和 xml-loader。让我们处理加载这三类文件:
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),
},
module: {
rules: [
{
test: /.css$/i,
use: ['style-loader', 'css-loader'],
},
{
test: /.(png|svg|jpg|jpeg|gif)$/i,
type: 'asset/resource',
},
{
test: /.(woff|woff2|eot|ttf|otf)$/i,
type: 'asset/resource',
},
{
test: /.(csv|tsv)$/i,
use: ['csv-loader'],
},
{
test: /.xml$/i,
use: ['xml-loader'],
},
],
},
};
- 通过使用 自定义 parser 替代特定的 webpack loader,可以将任何
toml、yaml或json5文件作为 JSON 模块导入。
const path = require('path');
const toml = require('toml');
const yaml = require('yamljs');
const json5 = require('json5');
module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),
},
module: {
rules: [
{
test: /.css$/i,
use: ['style-loader', 'css-loader'],
},
{
test: /.(png|svg|jpg|jpeg|gif)$/i,
type: 'asset/resource',
},
{
test: /.(woff|woff2|eot|ttf|otf)$/i,
type: 'asset/resource',
},
{
test: /.(csv|tsv)$/i,
use: ['csv-loader'],
},
{
test: /.xml$/i,
use: ['xml-loader'],
},
{
test: /.toml$/i,
type: 'json',
parser: {
parse: toml.parse,
},
},
{
test: /.yaml$/i,
type: 'json',
parser: {
parse: yaml.parse,
},
},
{
test: /.json5$/i,
type: 'json',
parser: {
parse: json5.parse,
},
},
],
},
};
Plugin的使用
Plugin与Loader的区别
- 官方文档中:Loader是用来转换某些类型的模块;plugin是用来进行更广泛的认为,例如,打包优化,资源的管理,以及环境变量的注入等。
CleanWebpackPlugin
- webpack打包时并不会帮我们自动删除output所在文件夹,使用该插件可以在打包前帮我们自动删除上一次打包的残留
- Plugin都是一个构造函数
- module.plugin中进配置
const CleanWebpackPlugin = require('clean-webpack-plugin')
module.export = {
plugins: [
new CleanWebpackplugin()
]
}
HtmlWebpackPlugin
- 在进行项目部署的时,必然也是需要有对应的入口文件index.html
const HtmlWebpackPlugin = require('html-webpack-plugin')\
module.export = {
plugins: [
new HtmlWebpackPlugin({
title: 'THis title will be found in your html' //该注入参数,可以在HTML中被读取
template: "./public/index.html" //定义该index.html文件的模板位置(此处使用的ejs模板引擎)
})
]
}
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<!-- BASE_URL为一个常量,由DefinedPlugin插件进行注入 -->
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<!-- tmlWebpackPlugin.options.title变量为Plugin中所定义的title值 -->
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<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>
<script src="./test.js"></script>
</html>
DefinePlugin
- 可以使用该插件为我们的模板注入常量
const { DefinePlugin } = require('webpack')
module.export = {
plugins: [
new DefinePlugin({
BASE_URL : '"./"' //BASE_URL输出为"./"
})
]
}
CopyWebpackPlugin
- 可以将一些指定的文件复制到dist文件夹
const CopyWebpackPlugin = require("copy-webpack-plugin");
module.export = {
plugins: [
new CopyWebpackPlugin({
patterns: [ //可以设置多个目标
from: "public", //复制根目录下public下的文件
globOption: {
ignore: [ //设置那些文件被忽略
"**/index.html" //用**表示public文件夹(from所指定的文件夹),glob文件匹配模式
]
}
]
})
]
}
模块化与SourceMap
-
mode配置可以webpack使用响应模式的内置优化
- 可选值有:'none' | 'development' | 'production';
-
devtool:此选项控制是否生成,以及如何生成 source map
- 你可以直接使用
SourceMapDevToolPlugin/EvalSourceMapDevToolPlugin来替代使用devtool选项,因为它有更多的选项。切勿同时使用devtool选项和SourceMapDevToolPlugin/EvalSourceMapDevToolPlugin插件。devtool选项在内部添加过这些插件,所以你最终将应用两次插件。
- 你可以直接使用
-
常见的devtool组合规则
- inline- (不会生成单独的source文件)|hidden- (生成但隐藏sourcemap)|eval (使用eval):三个值时三选一;
- nosources (生成的sourcemap只有错误信息提示,没有生成源代码文件):可选值;
- cheap可选值,并且可以跟随module的值;(低开销无列映射)
- [ inline-|hidden-|eval- ][nosources- ][cheap-[module-]]source-map
Babel及其使用
- Babel是一个工具链,主要用于旧浏览器或者缓解中将ECMAScript 2015+代码转换为向后兼容版本的 JavaScript;、
- 包括:语法转换、源代码转换、Polyfill实现目标缓解缺少的功能等;
- babel可以使用@babel/cli独立使用
npx babel src --out-dir dist
#代表从src文件寻找源文件,输出到dist文件夹
-
babel实现一些转化功能需要用到babel相关的插件:
- 转化箭头函数用到的插件:@babel/plugin-transform-arrow-functions
- 转化let、const的插件: @babel/plugin-transform-block-scoping
npx babel src --out-dir dist --plugins=@babel/plugin-transform-block-scoping,@babel/plugin-transform-arrow-functions
-
我们可以使用预设来实现转化:@babel/preset-env 该预设包含以上插件的规则以及其他的一些转化规则
- 常见的预设: env 、 React 、 TypeScript
Babel的底层原理
- babel可以看成一个编译器,即将我们的源代码转换成另一段源代码
Babel的工作流程
- 解析阶段(Parsing)
- 转换阶段(Transformation)
- 代码生成阶段(Code Generation)
每个阶段会有自己的工作
- 词法分析(Lexical Analusis): 生成Token数组
- 语法分析(Syntactic Analysis): 生成AST(抽象语法树)
- 遍历(Traversal)
- 访问(Visitor)
- 应用插件(Plugin):生成新的AST
- 生成源代码
Babel-loader
rules: [
{
test: /.jsx?$/,
exclude: /node_modules/,
use: {
loader: "babel-loader",
options: {
presets: [
["@babel/preset-env", {
targets: ["chrome 88"] //设置browserslist的条件,根据需要兼容的目标浏览器生成代码
enmodules: true
}]
]
plugins: [
"@babel/plugin-transform-arrow-functions",
"@babel/plugin-transform-block-scoping"
]
}
}
}
]
Babel的配置文件
-
Babel的配置在babel.config.js(.json | .cjs | .mjs)或者.babelrc.json(.babelrc | .js | .cjs | .mjs)中进行配置
- .babelrc.json:早期使用较多的配置方式,但是对于配置Monorepos项目是比较麻烦的
- babel.config.json(babel7):可以直接作用于Monorepos项目的子包,更加推荐
module.exports = {
presets: [
["@babel/preset-env",{
useBuiltIns: "usage", // false usage entry
corejs: 3,
}],
["@babel/preset-react"],
["@babel/preset-typescript"]
],
plugins: [
// useBuiltIns是全局的,该插件是非全局的
["@babel/plugin-transform-runtime",{
corejs: 3,
}]
]
}
-
useBuiltIns:设置以什么样的方式来使用polyfill;
-
false: 不用任何的polyfill相关的代码
-
usage: 代码中需要哪些polyfill, 就引用相关的api
-
手动在入口文件中导入 core-js/regenerator-runtime, 根据目标浏览器引入所有对应的polyfill
- 如果我们依赖的某一个库本身使用了某些polyfill的特性,但是因为我们使用的是usage,所以之后用户浏览器 可能会报错
- 入口文件中添加 `import 'core-js/stable'; import 'regenerator-runtime/runtime';
-
-
corejs:设置corejs的版本,目前使用较多的是3.x的版本
- 另外corejs可以设置是否对提议阶段的特性进行支持
- 设置 proposals属性为true即可
Plugin-transform-runtime
-
在前面我们使用的polyfill,默认情况是添加的所有特性都是全局的
- 如果我们正在编写一个工具库,这个工具库需要使用polyfill
- 别人在使用我们工具时,工具库通过polyfill添加的特性,可能会污染它们的代码
- 所以,当编写工具时,babel更推荐我们使用一个插件: @babel/plugin-transform-runtime来完成polyfill 的功能
Babel对React的JSX的支持
-
对React的JSX代码进行处理需要:
- @babel/plugin-syntax-jsx
- @babel/plugin-transform-react-jsx
- @babel/plugin-transform-react-display-name
-
也可以使用预设: @babel/preset-react
TypeScript的相关支持
- TypeScript的配置文件为tsconfig.json: 可以使用tsc --init指令进行生成
- 在Webpack中使用TypeScript需要使用ts-loader进行处理
{
test: /.ts$/,
exclude: /node_modules/,
use: "ts-loader"
}
-
除了可以使用TypeScript Compiler来编译TypeScript之外,我们也可以使用Babel
- 我们可以使用插件: @babel/tranform-typescript;
- 更推荐直接使用preset:@babel/preset-typescript;
{
test: /.ts$/,
exclude: /node_modules/,
// 本质上是依赖于typescript(typescript compiler)
use: "babel-loader"
}
-
ts-loader与babel-loader选择
-
使用ts-loader(TypeScript Compiler)
- 来直接编译TypeScript,那么只能将ts转换成js;
- 如果我们还希望在这个过程中添加对应的polyfill,那么ts-loader是无能为力的;
-
使用babel-loader(Babel)
- 来直接编译TypeScript,也可以将ts转换成js,并且可以实现polyfill的功能;
- 但是babel-loader在编译的过程中,不会对类型错误进行检测;
-
Glob文件匹配模式
| 模式 | 说明 |
|---|---|
* | 匹配除了斜杠(/)之外的所有字符。 Windows上是斜杠(/)和反斜杠() |
** | 匹配零个或多个目录及子目录。不包含 . 以及 .. 开头的。 |
? | 匹配任意单个字符。 |
[seq] | 匹配 seq 中的其中一个字符。 |
[!seq] | 匹配不在 seq 中的任意一个字符。 |
| `` | 转义符。 |
! | 排除符。 |
?(pattern_list) | 匹配零个或一个在 pattern_list 中的字符串。 |
*(pattern_list) | 匹配零个或多个在 pattern_list 中的字符串。 |
+(pattern_list) | 匹配一个或多个在 pattern_list 中的字符串。 |
@(pattern_list) | 匹配至少一个在 pattern_list 中的字符串。 |
!(pattern_list) | 匹配不在 pattern_list 中的字符串. |
[...] | POSIX style character classes inside sequences. |
ESlint
-
ESLint是一个静态代码分析工具(Static program analysis,在没有任何程序执行的情况下,对代码进行分析);
-
ESLint可以帮助我们在项目中建立统一的团队代码规范,保持正确、统一的代码风格,提高代码的可读性、可维护性
-
并且ESLint的规则是可配置的,我们可以自定义属于自己的规则
-
可以通过
eslint --init生成配置文件 -
.eslintrc.js是用来配置Eslint规则的 -
.eslintignore配置那些文件需要被eslint忽略,规则语法与.gitignore类似 -
ESlint文件解析:
-
env:运行的环境,比如是浏览器,并且我们会使用es2021(对应的ecmaVersion是12)的语法;
-
extends:可以扩展当前的配置,让其继承自其他的配置信息,可以跟字符串或者数组(多个);
-
parserOptions:这里可以指定ESMAScript的版本、sourceType的类型
- parser:默认情况下是espree(也是一个JS Parser,用于ESLint),但是因为我们需要编译TypeScript,所 以需要指定对应的解释器;
-
plugins:指定我们用到的插件;
-
rules:自定义的一些规则;
-
module.exports = {
'env': {
'browser': true,
'es2021': true,
},
// 'extends': [
// 'google',
// ],
// 'parser': '@typescript-eslint/parser',
'parserOptions': {
'ecmaVersion': 'latest',
'sourceType': 'module',
},
// 'plugins': [
// '@typescript-eslint',
// ],
// 'rules': {
// },
};
- Eslint-loader:事实上,我们在编译代码的时候,也希望进行代码的eslint检测,这个时候我们就可以使用eslint-loader来完成了
{
test: /.jsx?$/,
exclude: /node_modules/,
use: ['babel-loader', 'eslint-loader'],
},
Prettier
- ESLint会帮助我们提示错误(或者警告),但是不会帮助我们自动修复,在开发中我们希望文件在保存时,可以自动修复这些问题,我们可以选择使用另外一个工具:prettier
- 配置文件为
.prettierrc,可以通过prettier官网进行自动生成
Vue3使用webpack搭建环境
Babel配置
module.exports = {
presets: [
"@babel/preset-env"
]
}
PostCSS配置
module.exports = {
plugins: [
'postcss-preset-env'
]
}
WebPack配置
const path = require('path')
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { DefinePlugin } = require("webpack");
const CopyWebpackPlugin = require('copy-webpack-plugin');
const { VueLoaderPlugin } = require('vue-loader/dist/index');
module.exports = {
mode: "development",
entry: "./src/main.js",
devtool: false,
output: {
filename: "main.js",
path: path.resolve(__dirname,"./dist")
},
module: {
rules: [
{
test: /.less$/,
use: [
"style-loader",
{
loader: "css-loader",
options: {
importLoaders: 2
}
},
"postcss-loader",
"less-loader"
]
},
{
test: /.css$/,
use: [
"style-loader",
{
loader: "css-loader",
options: {
importLoaders: 1
}
},
"postcss-loader"
],
},
{
test: /.(jpe?g|png|gif|svg)$/,
type: "asset",
generator: {
filename: "img/[name]_[hash:6][ext]",
},
parser: {
dataUrlCondition: {
maxSize: 10 * 1024,
},
},
},
{
test: /.(eot|ttf|woff2?)$/,
type: "asset/resource",
generator: {
filename: "font/[name]_[hash:6][ext]",
},
},
{
test: /.js$/,
loader: "babel-loader"
},
{
test: /.jsx$/,
exclude: /node_modules/,
use: {
loader: "babel-loader"
}
},
{
test: /.ts$/,
exclude: /node_modules/,
use: "babel-loader"
},
{
test: /.vue$/,
use: "vue-loader"
}
]
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
title: "ChromeManifest3",
template: "./public/index.html"
}),
new CopyWebpackPlugin({
patterns: [
{
from: path.resolve(__dirname,"./public/manifest.json"),
to: "./manifest/[name].[hash:8][ext]",
globOptions: {
ignore: [
"**/index.html"
]
}
}
]
}),
new DefinePlugin({
BASE_URL: "'./'",
__VUE_OPTIONS_API__: true,
__VUE_PROD_DEVTOOLS__: false
}),
new VueLoaderPlugin()
]
}
DevServer与HMR
Webpack自动编译方式
-
webpack watch mode: 不会自动刷新浏览器。
-
在该模式下,webpack依赖图中的所有文件,只要有一个发生了更新,那么代码将被重新编译
-
配置方式:
-
module.exports = { //... watch: true, watchOption: { aggregateTimeout: 600, //当第一个文件更改,会在重新构建前增加延迟。这个选项允许 webpack 将这段时间内进行的任何其他更改都聚合到一次重新构建里。以毫秒为单位 ignored: /node_modules/, //对于某些系统,监听大量文件会导致大量的 CPU 或内存占用。可以使用正则排除像 node_modules 如此庞大的文件夹 poll: 1000, // 通过传递 true 开启 polling,或者指定毫秒为单位进行轮询。 followSymlinks: true, //根据软链接查找文件。这通常是不需要的,因为 webpack 已经使用 resolve.symlinks 解析了软链接。 stdin: true, //当 stdin 流结束时停止监听。 } }; - 在启动webpack的命令中,添加 --watch的标识
-
-
-
webpack-dev-server
- 会将打包后的文件保存在内存中(memory-fs库进行实现)
- 可以通过Node API运行,查看相关的
webpack-dev-serverAPI 文档。 - 也可以通过CLI的方式进行调用
npx webpack serve
-
webpack-dev-middleware
- 可以使用webpack-dev-middleware,以达到更大灵活性的dev-server
-
//server.js const express = require('express'); const webpack = require('webpack'); const webpackDevMiddleware = require('webpack-dev-middleware'); const app = express(); //加载配置信息 const config = require("./webpack.config"); // 传入配置信息, webpack根据配置信息进行编译 const compiler = webpack(config); const middleware = webpackDevMiddleware(compiler); app.use(middleware); app.listen(3000, () => { console.log("服务已经开启在3000端口上~"); }); -
node server.js #使用node进行开启
Hot Module Replacement (HMR)
-
dev-server开启HMR:
-
devServer: { hot: true }, -
//需要指定那些模块,才能触发热模块替换 if (module.hot) { module.hot.accept("./math.js", () => { console.log("math模块发生了更新~"); }); }
-
React的HMR
- 这里安装@pmmmwh/react-refresh-webpack-plugin
- 修改webpack.config.js与babel.config.js的配置文件
//webpack.config.js
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
module.exports = {
plugins: [
new HtmlWebpackPlugin({
template: "./index.html"
}),
new ReactRefreshWebpackPlugin()
]
}
//babel.config.js
module.exports = {
presets: [
["@babel/preset-env"],
["@babel/preset-react"],
],
plugins: [
["react-refresh/babel"]
]
}
Vue的HMR
- Vue的加载我们需要使用vue-loader,而vue-loader加载的组件默认会帮助我们进行HMR的处理。
- 安装vue-loader与vue-template-compiler
const VueLoaderPlugin = require('vue-loader/lib/plugin');
module.exports = {
module: {
rules: [
{
test: /.vue$/i,
use: "vue-loader"
},
{
test: /.css/i,
use: [
"style-loader",
"css-loader"
]
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: "./index.html"
}),
new VueLoaderPlugin()
]
}
HMR的原理
-
webpack-dev-server会创建两个服务:提供静态资源的服务(express)和Socket服务(net.Socket);
-
express server负责直接提供静态资源的服务(打包后的资源直接被浏览器请求和解析);
-
HMR Socket Server,是一个socket的长连接:
- 长连接有一个最好的好处是建立连接后双方可以通信(服务器可以直接发送文件到客户端);
- 当服务器监听到对应的模块发生变化时,会生成两个文件.json(manifest文件)和.js文件(update chunk);
- 通过长连接,可以直接将这两个文件主动发送给客户端(浏览器);
- 浏览器拿到两个新的文件后,通过HMR runtime机制,加载这两个文件,并且针对修改的模块进行更新;
-
这里有一张CoderWhy老师绘制的原理图:
-
runtime
- runtime,以及伴随的 manifest 数据,主要是指:在浏览器运行过程中,webpack 用来连接模块化应用程序所需的所有代码。它包含:在模块交互时,连接模块所需的加载和解析逻辑。包括:已经加载到浏览器中的连接模块逻辑,以及尚未加载模块的延迟加载逻辑。
-
manifest
-
一旦你的应用在浏览器中以
index.html文件的形式被打开,一些 bundle 和应用需要的各种资源都需要用某种方式被加载与链接起来。在经过打包、压缩、为延迟加载而拆分为细小的 chunk 这些 webpack优化之后,你精心安排的/src目录的文件结构都已经不再存在。所以 webpack 如何管理所有所需模块之间的交互呢?这就是 manifest 数据用途的由来…… -
当 compiler 开始执行、解析和映射应用程序时,它会保留所有模块的详细要点。这个数据集合称为 "manifest",当完成打包并发送到浏览器时,runtime 会通过 manifest 来解析和加载模块。无论你选择哪种 模块语法,那些
import或require语句现在都已经转换为__webpack_require__方法,此方法指向模块标识符(module identifier)。通过使用 manifest 中的数据,runtime 将能够检索这些标识符,找出每个标识符背后对应的模块。
output与publicPath
-
-
output中的path的作用是告知webpack之后的输出目录:
- 比如静态资源的js、css等输出到哪里,常见的会设置为dist、build文件夹等;
-
output中还有一个publicPath属性,该属性是指定index.html文件打包引用的一个基本路径:
- 它的默认值是一个空字符串,所以我们打包后引入js文件时,路径是 bundle.js;
- 在开发中,我们也将其设置为 / ,路径是 /bundle.js,那么浏览器会根据所在的域名+路径去请求对应的资源;
- 如果我们希望在本地直接打开html文件来运行,会将其设置为 ./,路径时 ./bundle.js,可以根据相对路径去 查找资源;
-
devServer的publicPath
-
devServer中也有一个publicPath的属性,该属性是指定本地服务所在的文件夹:
-
它的默认值是 /,也就是我们直接访问端口即可访问其中的资源 http:// localhost:8080;
-
如果我们将其设置为了 /abc,那么我们需要通过 http:// localhost:8080/abc才能访问到对应的打包后的资源;
-
并且这个时候,我们其中的bundle.js通过 http:// localhost:8080/bundle.js也是无法访问的:
- 所以必须将output.publicPath也设置为 /abc;
- 官方其实有提到,建议 devServer.publicPath 与 output.publicPath相同;
-
-
-
devServer的contentBase
-
devServer中contentBase对于我们直接访问打包后的资源其实并没有太大的作用,它的主要作用是如果我们打包 后的资源,又依赖于其他的一些资源,那么就需要指定从哪里来查找这个内容
-
比如在index.html中,我们需要依赖一个 abc.js 文件,这个文件我们存放在 public文件 中;
-
在index.html中,我们应该如何去引入这个文件呢?
- 比如代码是这样的:< script src = "./public/abc.js">
- 但是这样打包后浏览器是无法通过相对路径去找到这个文件夹的;
- 所以代码是这样的:< script src = "./abc.js" >
- 但是我们如何让它去查找到这个文件的存在呢? 设置contentBase即可;
-
-
当然在devServer中还有一个可以监听contentBase发生变化后重新编译的一个属性:watchContentBase。
-
DevServer的一些其他配置
-
hotOnly是当代码编译失败时,是否刷新整个页面:
- 默认情况下当代码编译失败修复后,我们会重新刷新整个页面;
- 如果不希望重新刷新整个页面,可以设置hotOnly为true;
-
host设置主机地址:
- 默认值是localhost;
- 如果希望其他地方也可以访问,可以设置为 0.0.0.0;
-
port设置监听的端口,默认情况下是8080
-
open是否打开浏览器:
- 默认值是false,设置为true会打开浏览器;
- 也可以设置为类似于 Google Chrome等值;
-
compress是否为静态文件开启gzip compression:
- 默认值是false,可以设置为true;
Proxy代理
-
proxy是我们开发中非常常用的一个配置选项,它的目的设置代理来解决跨域访问的问题: (开发阶段)
- 我们可以将请求发送到一个代理服务器,由代理服务器向跨域服务器发起请求,类似于Nginx反向代理。
-
可以进行如下设置
-
target:表示的是代理到的目标地址,比如 /api-hy/moment会被代理到 http:// localhost:8888/api/why/moment
-
pathRewrite:默认情况下,我们的 /api-hy 也会被写入到URL中,如果希望删除,可以使用pathRewrite;
- Proxy可以根据请求路径的正则表达匹配需要转发的代理服务器,但会将用于匹配路径的部分也当做路径向服务器进行请求,此时可以通过该配置对请求路径进行改写
-
secure:默认情况下不接收转发到https的服务器上,如果希望支持,可以设置为false
-
changeOrigin:它表示是否更新代理后请求的headers中host地址;
- 假如我们真实的请求的地址是向http:// loacalhost:8080进行请求的
- 我们通过代理向8888端口进行请求,此时请求头依旧是未转发前的请求头,此时host值为8080
- 当changOrigin为true时就会修改这个请求host,显示为我们实际请求的地址host 8888
-
historyApiFallback
-
historyApiFallback是开发中一个非常常见的属性,它主要的作用是解决SPA页面在路由跳转之后,进行页面刷新 时,返回404的错误。
-
boolean值:默认是false
- 如果设置为true,那么在刷新时,返回404错误时,会自动返回 index.html 的内容
-
object类型的值,可以配置rewrites属性:
- 可以配置from来匹配路径,决定要跳转到哪一个页面;
devServer: {
hot: true,
hotOnly: true,
// host: "0.0.0.0",
// port: 7777,
// open: true,
compress: true,
contentBase: path.resolve(__dirname, "./why"),
watchContentBase: true,
// publicPath: "/abc",
proxy: {
// "/why": "http://localhost:8888"
"/why": {
target: "http://localhost:8888",
pathRewrite: {
"^/why": ""
},
secure: false,
changeOrigin: true
}
},
// historyApiFallback: true
historyApiFallback: {
rewrites: [
{from: /abc/, to: "/index.html"}
]
}
},
resolve解析
-
Webpack对三种路径的解析:
-
绝对路径
- 由于已经获得文件的绝对路径,因此不需要再做进一步解析。
-
相对路径
- 在这种情况下,使用 import 或 require 的资源文件所处的目录,被认为是上下文目录;
- 在 import/require 中给定的相对路径,会拼接此上下文路径,来生成模块的绝对路径;
-
模块路径
-
在 resolve.modules中指定的所有目录检索模块;
- 默认值是 ['node_modules'],所以默认会从node_modules中查找文件;
-
我们可以通过设置别名的方式来替换初识模块路径,具体后面讲解alias的配置;
-
-
-
确定文件还是文件夹
-
如果是一个文件:
- 如果文件具有扩展名,则直接打包文件;
- 否则,将使用 resolve.extensions选项作为文件扩展名解析;
-
如果是一个文件夹
-
会在文件夹中根据 resolve.mainFiles配置选项中指定的文件顺序查找;
- resolve.mainFiles的默认值是 ['index'];
- 再根据 resolve.extensions来解析扩展名;
-
-
-
extensions是解析到文件时自动添加扩展名
- 默认值是 ['.wasm', '.mjs', '.js', '.json']
- 所以如果我们代码中想要添加加载 .vue 或者 jsx 或者 ts 等文件时,我们必须自己写上扩展名;
-
另一个非常好用的功能是配置别名alias
- 特别是当我们项目的目录结构比较深的时候,或者一个文件的路径可能需要 ../../../这种路径片段;
- 我们可以给某些常见的路径起一个别名
resolve: {
extensions: ['.wasm', '.mjs', '.js', '.json', '.jsx', '.ts', '.vue'],
alias: {
"@": path.resolve(__dirname, "./src"),
"pages": path.resolve(__dirname, "./src/pages")
}
},