webpack是一个静态的模块化打包工具,为现代的JavaScript应用程序;
打包bundler:webpack可以将帮助我们进行打包,所以它是一个打包工具
静态的static:这样表述的原因是我们最终可以将代码打包成最终的静态资源(部署到静态服务器);
模块化module:webpack默认支持各种模块化开发,ES Module、CommonJS、AMD等;
现代的modern:我们前端说过,正是因为现代前端开发面临各种各样的问题,才催生了webpack的出现和发展;
我们可以在根目录下创建一个webpack.config.js文件,来作为webpack的配置文件:
module.exports = {
entry: " 指定入口路径",
output: {
filename:"bundle.js", // 出口名字
path: '出口路径'
}
}
loader是什么
loader 可以用于对模块的源代码进行转换;
我们可以将css文件也看成是一个模块,我们是通过import来加载这个模块的;
在加载这个模块时,webpack其实并不知道如何对其进行加载,我们必须制定对应的loader来完成这个功能;
module.rules的配置如下:
test属性:用于对resource(资源)进行匹配的,通常会设置成正则表达式;
use属性:对应的值时一个数组:[UseEntry]
UseEntry是一个对象,可以通过对象的属性来设置一些其他属性
loader:必须有一个loader属性,对应的值是一个字符串;
options:可选的属性,值是一个字符串或者对象,值会被传入到loader中;
query:目前已经使用options来替代;
传递字符串(如:use: [ 'style-loader' ])是loader 属性的简写方式(如:use: [ { loader: 'style-loader'} ])
loader属性:Rule.use: [ { loader } ] 的简写。
module.exports = {
entry: " 指定入口路径",
output: {
filename:"bundle.js", // 出口名字
path: '出口路径'
},
module: {
rules: [
{
test:/\.css$/,
// loader:"css-loader" // 写法一
// use:["css-loader"] //写法二
// 写法三
use:[
{loader:"css-loader"}
]
}
]
}
}
当我们通过css-loader来加载css文件时,代码没有生效。这是因为css-loader只是将.css文件进行解析,并不会将解析之后的css插入到页面中,而style-loader将完成插入style的操作
注意:因为loader的执行顺序是从右向左(或者说从下到上,或者说从后到前的),所以我们需要将styleloader写到css-loader的前面;
use:[
{loader:"style-loader"},
{loader:"css-loader"}
]
use:[
{loader:"style-loader"},
{loader:"css-loader"}
{loader:"less-loader"}
]
认识browserslist工具
Browserslist编写规则一:
defaults:Browserslist的默认浏览器(> 0.5%, last 2 versions, Firefox ESR, not dead)。
5%:通过全局使用情况统计信息选择的浏览器版本。>=,<和<=工作过。
dead:24个月内没有官方支持或更新的浏览器。现在是IE 10,IE_Mob11,BlackBerry 10,BlackBerry 7,Samsung 4和OperaMobile12.1。
last 2 versions:每个浏览器的最后2个版本。
配置browserslist
方案一:在package.json中配置;
"browserslist": [
"last 2 version",
"not dead",
"> 0.2%"
]
方案二:单独的一个配置文件.browserslistrc文件;
last 2 version
not dead
> 0.2%
PostCSS是一个通过JavaScript来转换样式的工具,这个工具可以帮助我们进行一些CSS的转换和适配,比如自动添加浏览器前缀、css样式的重置;
如何使用
安装工具:postcss、postcss-cli
npm install postcss postcss-cli -D
插件autoprefixer
添加浏览器前缀需要安装autoprefixer
npm install autoprefixer -D
直接使用使用postcss工具,并且制定使用autoprefixer
npx postcss --use autoprefixer -o end.css ./src/css/style.css
postcss-loader
借助构建工具进行css处理
npm install postcss-loader -D
{
loader:"postcss-loader",
options: {
postcssOptions: {
plugins: [
require('autoprefixer')
]
}
}
},
单独的postcss配置
在跟目录下创建postcss.config.js
module.exports = {
plugins: [
require('autoprefixer')
]
}
postcss-preset-env
在项目中配置postcss-loader时,我们一般不使用autoprefixer。而是使用另一插件postcss-preset-env
postcss-preset-env也是一个postcss的插件;
它可以帮助我们将一些现代的CSS特性,转成大多数浏览器认识的CSS,并且会根据目标浏览器或者运行时环境添加所需的polyfill;
也包括会自动帮助我们添加autoprefixer(所以相当于已经内置了autoprefixer);
安装
npm install postcss-preset-env -D
使用
module.exports = {
plugins: [
require('postcss-preset-env')
]
}
用来处理jpg、png等格式的图片
file-loader的作用就是帮助我们处理import/require()方式引入的一个文件资源,并且会将它放到我们输出的文件夹中;
安装
npm install file-loader -D
配置:
{
test:/\.(png|jpe?g|svg|gif)$/i,
use: {
loader: "file-loader"
}
}
1、文件名称规则
对处理后的文件名称按照一定的规则进行显示,一般使用PlaceHolders来完成,webpack给我们提供了大量的PlaceHolders来显示不同的内容:
介绍几个最常用的placeholder:
[ext]:处理文件的扩展名;
[name]:处理文件的名称;
[hash]:文件的内容,使用MD4的散列函数处理,生成的一个128位的hash值(32个十六进制);
[contentHash]:在file-loader中和[hash]结果是一致的(在webpack的一些其他地方不一样,后面会讲到);
[hash:
[path]:文件相对于webpack配置文件的路径;
2、设置文件名称和存放路径
{
test:/\.(png|jpe?g|svg|gif)$/i,
use: {
loader: "file-loader"
options: {
name: "img/[name].[hash:8].[ext]",
outputPath: "img"
}
}
}
将较小的文件转换为base64的URI
安装:
npm install url-loader -D
配置:
{
test:/\.(png|jpe?g|svg|gif)$/i,
use: {
loader: "url-loader"
options: {
name: "img/[name].[hash:8].[ext]",
outputPath: "img"
}
}
}
打包之后的显示结果跟file-loader一样,但是在打包好的dist文件夹中,看不到图片文件,而是转换为base64格式存储。
limit属性
限制转换base64格式的图片大小(比如小于100kb)
{
test:/\.(png|jpe?g|svg|gif)$/i,
use: {
loader: "url-loader"
options: {
limit:100 * 1024,
name: "img/[name].[hash:8].[ext]",
outputPath: "img"
}
}
}
1、介绍
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,并且配置资源体积限制实现;
2、使用
{
test: /\.(png|jpe?g|svg|gif)$/i,
type: "asset/resource",
},
如何自定义文件的输出路径和文件名
方式一:修改output,添加assetModuleFilename属性;
output: {
filename: "bundle.js", // 出口名字
path: "出口路径",
assetModuleFilename:"img/[name].[hash:6][ext]"
},
方式二:在Rule中,添加一个generator属性,并且设置filename;
{
test: /\.(png|jpe?g|svg|gif)$/i,
type: "asset/resource",
generator: {
filename: "img/[name].[hash:6][ext]",
},
},
url-loader中的limit限制图片大小效果
{
test: /\.(png|jpe?g|svg|gif)$/i,
type: "asset", // 注意
generator: {
filename: "img/[name].[hash:6][ext]",
},
parser: {
dataUrlCondition: {
maxSize: 100 *1024
}
}
},
处理特殊字体或者字体图标的使用
我们可以选择使用file-loader来处理,也可以选择直接使用webpack5的资源模块类型来处理;
{
test: /\.(woff2?|eot|ttf)$/,
type: "asset/resource",
generator: {
filename: "img/[name].[hash:6][ext]",
},
},
Loader是用于特定的模块类型进行转换;
Plugin可以用于执行更加广泛的任务,比如打包优化、资源管理、环境变量注入等;
每次修改了一些配置,重新打包时,都需要手动删除dist文件夹,CleanWebpackPlugin可以帮助我们完成这个功能。
安装:
npm install clean-webpack-plugin -D
配置:
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
module.exports = {
plugins : [
new CleanWebpackPlugin()
]
};
HtmlWebpackPlugin用来对HTML进行打包处理
安装:
npm install html-webpack-plugin -D
配置:
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
titile: "webpack案例",
}),
],
};
如果我们想在自己的模块中加入一些比较特别的内容:
添加一个noscript标签,在用户的JavaScript被关闭时,给予响应的提示;
比如在开发vue或者react项目时,我们需要一个可以挂载后续组件的根标签
;自定义模板数据填充
上面的代码中,会有一些类似这样的语法<%变量%>,这个是EJS模块填充数据的方式。
在配置HtmlWebpackPlugin时,我们可以添加如下配置:
template:指定我们要使用的模块所在的路径;
title:在进行htmlWebpackPlugin.options.title读取时,就会读到该信息;
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
titile: "webpack案例",
template: "./public/index.html"
}),
],
};
当在我们的模块中还使用到一个BASE_URL的常量,我们需要设置这个常量,这个时候我们可以使用DefinePlugin插件;
使用:
DefinePlugin允许在编译时创建配置的全局常量,是一个webpack内置的插件(不需要单独安装):
const { DefinePlugin } = require("webpack");
module.exports = {
plugins: [
new DefinePlugin({
BASE_URL:'"./"' // 注意需要多一层包裹
})
],
};
vue的打包过程中,如果我们将一些文件放到public的目录下,那么这个目录会被复制到dist文件夹中。这个复制的功能,我们可以使用CopyWebpackPlugin来完成;
安装:
npm install copy-webpack-plugin -D
配置:
from:设置从哪一个源中开始复制;
to:复制到的位置,可以省略,会默认复制到打包的目录下;
globOptions:设置一些额外的选项,其中可以编写需要忽略的文件:
.DS_Store:mac目录下回自动生成的一个文件;
index.html:也不需要复制,因为我们已经通过HtmlWebpackPlugin完成了index.html的生成;
new CopyWebpackPlugin({
patterns: [
{
from:"public",
globOptions: {
ignore: [
'**/.DS_Store',
'**/index.html'
]
}
}
]
})
source-map是从已转换的代码,映射到原始的源文件。使浏览器可以重构原始源并在调试器中显示重建的原始源
第一步:根据源文件,生成source-map文件,webpack在打包时,可以通过配置生成source-map;
第二步:在转换后的代码,最后添加一个注释,它指向sourcemap;
//# sourceMappingURL=common.bundle.js.map
浏览器会根据我们的注释,查找响应的source-map,并且根据source-map还原我们的代码,方便进行调试。
version:当前使用的版本,也就是最新的第三版;
sources:从哪些文件转换过来的source-map和打包的代码(最初始的文件);
names:转换前的变量和属性名称(因为我目前使用的是development模式,所以不需要保留转换前的名称);
mappings:source-map用来和源文件映射的信息(比如位置信息等),一串base64VLQ(veriablelengthquantity可变长度值)编码;
file:打包后的文件(浏览器加载的文件);
sourceContent:转换前的具体代码信息(和sources是对应的关系);
sourceRoot:所有的sources相对的根目录;
webpack为我们提供了非常多的选项,目前为止是26个,来处理source-map,选择不同的值,打包形成的代码会有性能的差异,可以根据不同情况进行选择
不会生成source-map的配置项
false:不使用source-map,也就是没有任何和source-map相关的内容。
nnone:production模式下的默认值,不生成source-map。
eval:development模式下的默认值,不生成source-map
但是它会在eval执行的代码中,添加//#sourceURL=;
它会被浏览器在执行时解析,并且在调试面板中生成对应的一些文件目录,方便我们调试代码;
生成一个独立的source-map文件,并且在bundle文件中有一个注释,指向source-map文件;
bundle文件中有如下的注释:
//# sourceMappingURL=bundle.js.map
eval-source-map:会生成sourcemap,但是source-map是以DataUrl添加到eval函数的后面
inline-source-map:会生成sourcemap,但是source-map是以DataUrl添加到bundle文件的后面
cheap-source-map:
会生成sourcemap,但是会更加高效一些(cheap低开销),因为它没有生成列映射(Column Mapping)
会生成sourcemap,类似于cheap-source-map,但是对源自loader的sourcemap处理会更好。
会生成sourcemap,但是不会对source-map文件进行引用;
相当于删除了打包文件中对sourcemap的引用注释;
// 被删除掉的
//# sourceMappingURL=bundle.js.map
会生成sourcemap,但是生成的sourcemap只有错误信息的提示,不会生成源代码文件;
事实上,webpack提供给我们的26个值,是可以进行多组合的。
组合的规则如下:
inline-|hidden-|eval:三个值时三选一;
nosources:可选值;
cheap可选值,并且可以跟随module的值;
[inline-|hidden-|eval-][nosources-][cheap-[module-]]source-map
在开发中,最佳的实践是什么呢?
开发阶段:推荐使用source-map或者cheap-module-source-map
测试阶段:推荐使用source-map或者cheap-module-source-map
发布阶段:false、缺省值(不写)
Babel是一个工具链,主要用于旧浏览器或者缓解中将ECMAScript 2015+代码转换为向后兼容版本的JavaScript;
语法转换、源代码转换、Polyfill实现目标缓解缺少的功能等;
Babel命令行使用
@babel/core:babel的核心代码,必须安装;
@babel/cli:可以让我们在命令行使用babel;
npm install @babel/cli @babel/core
使用babel来处理我们的源代码:
src:是源文件的目录;
--out-dir:指定要输出的文件夹dist;
npx babel src--out-dirdist
插件的使用
比如我们需要转换箭头函数,那么我们就可以使用箭头函数转换相关的插件:
npm install @babel/plugin-transform-arrow-functions -D
npx babel src--out-dirdist--plugins=@babel/plugin-transform-arrow-functions
查看转换后的结果,我们会发现const 并没有转成var,这是因为plugin-transform-arrow-functions,并没有提供这样的功能,我们需要使用plugin-transform-block-scoping 来完成这样的功能。
npm install @babel/plugin-transform-block-scoping -D
npx babel src--out-dirdist--plugins=@babel/plugin-transform-block-scoping @babel/plugin-transform-arrow-functions
Babel的预设preset
如果要转换的内容过多,一个个设置是比较麻烦的,我们可以使用预设(preset)
安装 :
npm install @babel/preset-env -D
执行:
npx babel src--out-dirdist--presets=@babel/preset-env
工作流程:
解析阶段(Parsing)
转换阶段(Transformation)
生成阶段(CodeGeneration)
流程图:
安装:
npm install babel-loader @babel/core
使用:
module.exports = {
module: {
rules: [
{
test:/\.js$/,
use: {
loader: "babel-loader"
}
}
]
}
}
指定使用的插件
我们必须指定使用的插件才会生效
module: {
rules: [
{
test:/\.js$/,
use: {
loader: "babel-loader",
options: {
plugins: [
"@babel/plugin-transform-block-scoping",
"@babel/plugin-transform-arrow-functions"
]
}
}
}
]
}
babel-preset
如果我们一个个去安装使用插件,那么需要手动来管理大量的babel插件,我们可以直接给webpack提供一个preset,webpack会根据我们的预设来加载对应的插件列表,并且将其传递给babel。
常见的预设:
env
react
TypeScript
安装preset-env:
npm install @babel/preset-env
{
test:/\.js$/,
use: {
loader: "babel-loader",
options: {
plugins: [
"@babel/plugin-transform-block-scoping",
"@babel/plugin-transform-arrow-functions"
]
}
}
}
Babel的Stage-X设置
在babel7之前(比如babel6中),我们会经常看到这种设置方式:
它表达的含义是使用对应的babel-preset-stage-x预设;
从babel7开始,已经不建议使用了,建议使用preset-env来设置;
我们可以将babel的配置信息放到一个独立的文件中,babel给我们提供了两种配置文件的编写:
babel.config.json(或者.js,.cjs,.mjs)文件;
.babelrc.json(或者.babelrc,.js,.cjs,.mjs)文件;
区别:
.babelrc.json:早期使用较多的配置方式,但是对于配置Monorepos项目是比较麻烦的;
babel.config.json(babel7):可以直接作用于Monorepos项目的子包,更加推荐;
更像是应该填充物(垫片),一个补丁,可以帮助我们更好的使用JavaScript;
使用场景:
比如我们使用了一些语法特性(例如:Promise,Generator,Symbol等以及实例方法例如Array.prototype.includes等),但是某些浏览器压根不认识这些特性,必然会报错,我们可以使用polyfill来填充或者说打一个补丁,那么就会包含该特性了;
可以通过单独引入core-js和regenerator-runtime来完成polyfill的使用:
npm install core-js regenerator-runtime --save
{
test:/\.m?js$/,
exclude:/node_modules/,
use:"babel-loader"
}
配置babel.config.js
我们需要在babel.config.js文件中进行配置,给preset-env配置一些属性:
useBuiltIns:设置以什么样的方式来使用polyfill;
corejs:设置corejs的版本
另外corejs可以设置是否对提议阶段的特性进行支持;
设置proposals属性为true即可;
useBuiltIns属性设置
useBuiltIns属性有三个常见的值
第一个值:false
打包后的文件不使用polyfill来进行适配;
并且这个时候是不需要设置corejs属性的;
第二个值:usage
会根据源代码中出现的语言特性,自动检测所需要的polyfill;
这样可以确保最终包里的polyfill数量的最小化,打包的包相对会小一些;
可以设置corejs属性来确定使用的corejs的版本;
第三个值:entry
如果我们依赖的某一个库本身使用了某些polyfill的特性,但是因为我们使用的是usage,所以之后用户浏览器可能会报错,果你担心出现这种情况,可以使用entry;
需要在入口文件中添加`import 'core-js/stable'; import 'regenerator-runtime/runtime';
这样做会根据browserslist目标导入所有的polyfill,但是对应的包也会变大;
在前面我们使用的polyfill,默认情况是添加的所有特性都是全局的,如果我们正在编写一个工具库,这个工具库需要使用polyfill,别人在使用我们工具时,工具库通过polyfill添加的特性,可能会污染它们的代码,所以,当编写工具时,babel更推荐我们使用一个插件:@babel/plugin-transform-runtime来完成polyfill的功能;
安装:
npm install @babel/preset-react -D
使用:
presets: [
["@babel/preset-env", {
useBuiltIns:"usage",
corejs: 3.8
}],
["@babel/preset-react"]
]
TypeScript通过compiler来转换成JavaScript
安装:
npm install typescript -D
TypeScript的编译配置信息我们通常会编写一个tsconfig.json文件:
tsc --init
之后我们可以运行npxtsc来编译自己的ts代码:
npx tsc
1、使用ts-loader编译TS
安装:
npm install ts-loader -D
配置:
{
test:'/\.ts$/',
exclude: /node_modules/,
use: [
"ts-loader"
]
}
2、使用babel-loader编译TS
Babel是有对TypeScript进行支持
我们可以使用插件:@babel/tranform-typescript;
但是更推荐直接使用preset:@babel/preset-typescript;
安装:
npm install @babel/preset-typescript -D
配置:
{
test:'/\.ts$/',
exclude: /node_modules/,
use: [
"babel-loader"
]
}
大数据中关于eslint的配置
/*
* @Description: eslint 配置
* @ 规则依赖于 @umijs/fabric,在此基础上,可自行添加自己的规则进行配置
* @Author: 贾永昌
* @Date: 2022-05-01 13:55:14
* @LastEditTime: 2022-05-02 17:35:45
*/
module.exports = {
extends: [require.resolve('@umijs/fabric/dist/eslint')],
// in antd-design-pro
globals: {
ANT_DESIGN_PRO_ONLY_DO_NOT_USE_IN_YOUR_PRODUCTION: true,
page: true,
},
rules: {
// 强制语句有分号结尾
semi: [2, 'always'],
// ? 操作符前后必须有空格 bad: 1||2 good: 1 || 2
'space-infix-ops': 2,
// ? 对象字面量中冒号前面禁止有空格,后面必须有空格 bad: {a :'a'} good:{a: 'a'}
'key-spacing': 2,
// ? 花括号首尾必须有空格
'object-curly-spacing': [2, 'always'],
// ? 语句块(if、function、class、try...catch等的大括号) 的前面必须要有空格
'space-before-blocks': 2,
// ? 箭头函数的箭头与后面的{}之间需要空格
'arrow-spacing': 2,
// ? 禁止多余的空格
'no-multi-spaces': 2,
// ? 禁止代码行结束后面有多余空格
'no-trailing-spaces': 2,
// ? 禁止多余空行
'no-multiple-empty-lines': ['error', { max: 1, maxBOF: 1, maxEOF: 1 }],
// ? 允许标识符中使用悬空下划线(标识符的开头或末尾的下划线)
'no-underscore-dangle': 0,
// ? 允许逻辑短路、三元运算符等表达式求值
'no-unused-expressions': 0,
// ? 禁止使用嵌套的三元表达式
'no-nested-ternary': 2,
// ? 禁止对函数参数再赋值(保证react函数式编程纯函数的概念)
'no-param-reassign': 2,
// ? 禁止使用 var 定义变量
'no-var': 2,
// ? 禁止修改const声明的变量
'no-const-assign': 2,
// ? 函数调用时 函数名与()之间不能有空格
'no-spaced-func': 2,
// ? jsx 属性中强制使用双引号
'jsx-quotes': [2, 'prefer-double'],
// ? 禁止 jsx 属性对象的引用括号里 两边加空格
'react/jsx-curly-spacing': [2, 'never'],
// ? JSX 中前标签传有属性换行展示的话,其后面的 > 也需换行对齐展示
'react/jsx-closing-bracket-location': 2,
// ? 校验 jsx 中所有换行属性值缩进
'react/jsx-indent-props': [2, 2],
// ? jsx 中传入属性值是Boolean值且为true时,省略传入
'react/jsx-boolean-value': 2,
// ? 在 JSX 属性中禁止等号前后存在空格
'react/jsx-equals-spacing': 2,
// ? 关闭此规则,允许 useEffect 的依赖为空数组
'react-hooks/exhaustive-deps': 0,
// ? 未使用的变量警告提醒
'@typescript-eslint/no-unused-vars': ['warn'],
// ? 禁用使用在前,保证 useEffct 使用在最前面,这时候里面如果使用了外部的函数就会报这错
'@typescript-eslint/no-use-before-define': 0,
// ? 允许空的 ts 接口定义 eg: interface IProps {}
'@typescript-eslint/no-empty-interface': 0,
},
};
大数据中 .prettierrc 配置
{
"printWidth": 80,
"tabWidth": 2,
"singleQuote": true,
"useTabs": false,
"semi": true,
"jsxSingleQuote": false,
"trailingComma": "all",
"bracketSpacing": true,
"jsxBracketSameLine": false,
"arrowParens": "always",
"requirePragma": false,
"insertPragma": false,
"proseWrap": "preserve",
"htmlWhitespaceSensitivity": "css",
"overrides": [
{
"files": ".prettierrc",
"options": { "parser": "json" }
}
]
}
为什么需要搭建本地服务器?
我们希望可以做到,当文件发生变化时,可以自动完成编译和展示
webpack watch mode
webpack-dev-server
webpack-dev-middleware
webpack给我们提供了watch模式:
在该模式下,webpack依赖图中的所有文件,只要有一个发生了更新,那么代码将被重新编译(损耗性能)
开启watch的两种方式
方式一:在导出的配置中,添加watch: true;
方式二:在启动webpack的命令中,添加--watch的标识;
"scripts" : {
"watch":"webpack --watch"
}
除了可以监听到文件的变化,还可以具备实时重新加载的功能
安装:
npm install --save-dev webpack-dev-server
配置:
"scripts" : {
"watch":"webpack --watch"
"serve":"webpack serve --config wk.config.js"
}
webpack-dev-server 在编译之后不会写入到任何输出文件。而是将bundle 文件保留在内存中:
事实上webpack-dev-server使用了一个库叫memfs(memory-fswebpack自己写的)
如果我们想要有更好的自由度,可以使用webpack-dev-middleware;
定义:
webpack-dev-middleware 是一个封装器(wrapper),它可以把webpack处理过的文件发送到一个server,webpack-dev-server 在内部使用了它,然而它也可以作为一个单独的package 来使用,以便根据需求进行更多自定义设置;
output中还有一个publicPath属性,该属性是指定index.html文件打包引用的一个基本路径
它的默认值是一个空字符串,所以我们打包后引入js文件时,路径是bundle.js;
在开发中,我们也将其设置为/,路径是/bundle.js,那么浏览器会根据所在的域名+路径去请求对应的资源;
如果我们希望在本地直接打开html文件来运行,会将其设置为./,路径时./bundle.js,可以根据相对路径去查找资源;
module.exports = {
entry: " 指定入口路径",
output: {
filename:"bundle.js", // 出口名字
path: '出口路径'
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相同;
主要作用是如果我们打包后的资源,又依赖于其他的一些资源,那么就需要指定从哪里来查找这个内容
比如在index.html中,我们需要依赖一个abc.js文件,这个文件我们存放在public文件中
在index.html中,我们应该如何去引入这个文件
比如代码是这样的:
但是这样打包后浏览器是无法通过相对路径去找到这个文件夹的;
所以代码是这样的:;
但是我们如何让它去查找到这个文件的存在呢?设置contentBase即可
devserver: {
contentBase: path.resolve(__dirname,"why"),
watchContentBase:true //监听contentBase发生变化后重新编译
}
hotOnly是当代码编译失败时,是否刷新整个页面
port设置监听的端口,默认情况下是8080
host设置主机地址
open是否打开浏览器
compress是否为静态文件开启gzip compression
devserver: {
contentBase: path.resolve(__dirname,"why"),
watchContentBase:true, //监听contentBase发生变化后重新编译
hotOnly:true,
host: 0.0.0.0,
port: 3000,
open: true,
compress:true
}
我们可以将请求先发送到一个代理服务器,代理服务器和API服务器没有跨域的问题,就可以解决我们的跨域问题了
配置:
target:表示的是代理到的目标地址,比如/api-hy/moment会被代理到http://localhost:8888/api-hy/moment;
pathRewrite:默认情况下,我们的/api-hy也会被写入到URL中,如果希望删除,可以使用pathRewrite;
secure:默认情况下不接收转发到https的服务器上,如果希望支持,可以设置为false;
changeOrigin:它表示是否更新代理后请求的headers中host地址;
devserver: {
'/api': {
// 标识需要进行转换的请求的url
target: 'http://172.16.188.188:8000', // 服务端域名
changeOrigin: true, // 允许域名进行转换
pathRewrite: {
// 将请求url里的ci去掉
'^/api': '',
},
logLevel: 'debug',
secure: false, // 将该属性设置为false,将允许在https上运行或者运行在证书无效的后端服务器
},
},
historyApiFallback是开发中一个非常常见的属性,它主要的作用是解决SPA页面在路由跳转之后,进行页面刷新时,返回404的错误。
boolean值:默认是false
如果设置为true,那么在刷新时,返回404错误时,会自动返回index.html的内容;
object类型的值,可以配置rewrites属性:
可以配置from来匹配路径,决定要跳转到哪一个页面;
devserver: {
'/api': {
// 标识需要进行转换的请求的url
target: 'http://172.16.188.188:8000', // 服务端域名
changeOrigin: true, // 允许域名进行转换
pathRewrite: {
// 将请求url里的ci去掉
'^/api': '',
},
logLevel: 'debug',
secure: false, // 将该属性设置为false,将允许在https上运行或者运行在证书无效的后端服务器
},
historyApiFallback: {
rewrites:[
{from: /abc/, to:"/index.html"}
]
}
},
什么是HMR
HMR的全称是Hot Module Replacement,翻译为模块热替换;
模块热替换是指在应用程序运行过程中,替换、添加、删除模块,而无需重新刷新整个页面;
如何使用HMR
默认情况下,webpack-dev-server已经支持HMR,我们只需要开启即可;
在不开启HMR的情况下,当我们修改了源代码之后,整个页面会自动刷新,使用的是live reloading;
// 在webpack.config.js 中添加以下配置
devserver: {
hot:true
}
同时还需要指定发生更新的模块
if(module.hot) {
module.hot.accept("./*文件路径",() => {
console.log()
})
}
项目中已经有非常成熟的方案,别操心了
vue开发中,我们使用vue-loader,此loader支持vue组件的HMR,提供开箱即用的体验
react开发中,有React HotLoader,实时调整react组件(目前React官方已经弃用了,改成使用reactrefresh);
HMR的原理是什么
webpack-dev-server会创建两个服务:提供静态资源的服务(express)和Socket服务(net.Socket)
express server负责直接提供静态资源的服务(打包后的资源直接被浏览器请求和解析);
HMR Socket Server,是一个socket的长连接:(想想webSocket,需要及时通信)
长连接有一个最好的好处是建立连接后双方可以通信(服务器可以直接发送文件到客户端)
当服务器监听到对应的模块发生变化时,会生成两个文件.json(manifest文件)和.js文件(update chunk);
通过长连接,可以直接将这两个文件主动发送给客户端(浏览器)
浏览器拿到两个新的文件后,通过HMR runtime机制,加载这两个文件,并且针对修改的模块进行更新;
resolve用于设置模块如何被解析:
resolve可以帮助webpack从每个require/import 语句中,找到需要引入到合适的模块代码;
webpack 使用enhanced-resolve来解析文件路径;
webpack能解析三种文件路径:
绝对路径
相对路径
模块路径:在resolve.modules中指定的所有目录检索模块,默认值是['node_modules'],所以默认会从node_modules中查找文件;
extensions是解析到文件时自动添加扩展名:
默认值是['.wasm','.mjs','.js','.json'];
配置:
module.exports = {
entry: " 指定入口路径",
output: {
filename:"bundle.js", // 出口名字
path: '出口路径'
},
resolve:{
extensions:['.wasm','.mjs','.js','.json','.jsx','.ts'],
}
}
我们可以使用alias给某些常见的路径起一个别名;
resolve:{
extensions:['.wasm','.mjs','.js','.json','.jsx','.ts'],
alias: {
"@":resolveApp('./src'),
pages:resolveApp('./src/pages')
}
}
context的作用是用于解析入口(entry point)和加载器(loader)
默认是webpack的启动目录
module.exports = {
context:path.resolve(__dirname,'./')
entry:"../src/index.js"
}
将原来的webpack.config.js划分为webpack.comm.conf.js(通用配置)、webpack.dev.conf.js(开发环境)、webpack.prod.conf.js(生产环境)三部分
利用mode配置项,区分开发环境和生产环境。用利用merge将用到的环境配置和通用配置合并
代码分离(CodeSplitting)主要的目的是将代码分离到不同的bundle中,之后我们可以按需加载,或者并行加载这些文件;
Webpack中常用的代码分离有三种
入口起点:使用entry配置手动分离代码;
防止重复:使用EntryDependencies或者SplitChunksPlugin去重和分离代码;
动态导入:通过模块的内联函数调用来分离代码;
1、多入口起点
entry: {
index:"./src/index.js",
main:"./src/main.js"
}
output: {
filename:"[name].bundle.js",
path:resolveApp("./build")
}
2、EntryDependencies(入口依赖)
假如我们的index.js和main.js都依赖两个库:lodash、dayjs
如果我们单纯的进行入口分离,那么打包后的两个bunlde都有会有一份lodash和dayjs;
entry: {
index: {import:"./src/index.js",dependOn:"shared"},
main:{import:"./src/main.js",dependOn:"shared"},
shared:['lodash','axios']
}
output: {
filename:"[name].bundle.js",
path:resolveApp("./build"),
publicPath: ""
}
3、SplitChunks
另外一种分包的模式是splitChunk,它是使用SplitChunksPlugin来实现的:
Webpack提供了SplitChunksPlugin默认的配置,我们也可以手动来修改它的配置:
比如默认配置中,chunks仅仅针对于异步(async)请求,我们可以设置为initial或者all;
optimization: {
splitChunks: {
chunks:'all'
}
}
4、SplitChunks自定义配置
SplitChunks: {
chunks:'all',
// 拆分包的大小,至少为minsize
// 如果一个包拆分出来不到minsize,那么将不会被拆分
minsize:100,
// 将大于maxMize的包,拆分成不小于minSize 的包
maxsize:1000,
// 至少包被引入的次数
minChunks:2,
// 最大异步请求数量
maxAsyncRequests:30,
// 最大的初始化请求数量
cacheGroups: {
venders: {
test:/[\\/]node_modules[\\/]/,
priority: -10,
filename: "[id]_[hash:6]_vendor.js"
},
foo: {
test:/foo/,
priority: -20,
filename: "foo_[id]_[name]_.js"
}
}
}
配置解析:
Chunks
默认值是async
另一个值是initial,表示对通过的代码进行处理
all表示对同步和异步代码都进行处理
minSize
拆分包的大小, 至少为minSize;
如果一个包拆分出来达不到minSize,那么这个包就不会拆分;
maxSize
将大于maxSize的包,拆分为不小于minSize的包;
minChunks
至少被引入的次数,默认是1;
如果我们写一个2,但是引入了一次,那么不会被单独拆分;
name:设置拆包的名称
可以设置一个名称,也可以设置为false;
设置为false后,需要在cacheGroups中设置名称;
cacheGroups
用于对拆分的包就行分组,比如一个lodash在拆分之后,并不会立即打包,而是会等到有没有其他符合规则的包一起来打包;
test属性:匹配符合规则的包;
name属性:拆分包的name属性;
filename属性:拆分包的名称,可以自己使用placeholder属性;
5、动态导入(dynamic import)
使用ECMAScript中的import()语法来完成,也是目前推荐的方式;
注意:
在webpack中,通过动态导入获取到一个对象;
真正导出的内容,在改对象的default属性中,所以我们需要做一个简单的解构;
动态导入的文件命名
它的命名我们通常会在output中,通过chunkFilename属性来命名
output: {
filename: "[name].bundle.js",
path:resolveApp("./build"),
chunkFilename: "chunk_[id]_[name].js"
}
默认情况下我们获取到的[name]是和id的名称保持一致的
我们希望修改name的值,可以通过magic comments(魔法注释)的方式
import(/* webpackChunkName:"bar"*/ "./bar").then(({default:bar}) => {
bar()
})
6、optimization.chunkIds配置
optimization.chunkIds配置用于告知webpack模块的id采用什么算法生成。
natural:按照数字的顺序使用id;
named:development下的默认值,一个可读的名称的id;
deterministic:确定性的,在不同的编译中不变的短数字id
最佳实践:
开发过程中,我们推荐使用named;
打包过程中,我们推荐使用deterministic;
7、optimization. runtimeChunk配置
配置runtime相关的代码是否抽取到一个单独的chunk中:
抽离出来后,有利于浏览器缓存的策略:
设置的值
true/multiple:针对每个入口打包一个runtime文件;
single:打包一个runtime文件;
对象:name属性决定runtimeChunk的名称;
optimization:{ chunkIds:"deterministic", runtimeChunk: { name:"runtime" } }
8、Prefetch和Preload
webpack v4.6.0+增加了对预获取和预加载的支持。
prefetch(预获取):将来某些导航下可能需要的资源
preload(预加载):当前导航下可能需要资源
import(/* webpackChunkName:"bar"*/
/* webpackpeload:true */
"./bar").then(({default:bar}) => {
bar()
})
区别
preload chunk 会在父chunk 加载时,以并行方式开始加载。prefetch chunk 会在父chunk 加载结束后开始加载。
preload chunk 具有中等优先级,并立即下载。prefetch chunk 在浏览器闲置时下载。
preload chunk 会在父chunk 中立即请求,用于当下时刻。prefetch chunk 会用于未来的某个时刻。
9、CDN
CDN称之为内容分发网络(ContentDeliveryNetwork或ContentDistributionNetwork,缩写:CDN)
开发中的应用方式:
方式一:打包的所有静态资源,放到CDN服务器,用户所有资源都是通过CDN服务器加载的;
方式二:一些第三方资源放到CDN服务器上;
方式一花钱,直接说方式二
一些比较出名的开源框架都会将打包后的源码放到一些比较出名的、免费的CDN服务器上
使用方法:
第一步,我们可以通过webpack配置,来排除一些库的打包:
externals: {
lodash: "_",
dayjs: "dayjs"
}
第二步,在html模块中,加入CDN服务器地址:
10、认识shimming
shimming是一个概念,是某一类功能的统称:
比如我们现在依赖一个第三方的库,这个第三方的库本身依赖lodash,但是默认没有对lodash进行导入(认为全局存在lodash),那么我们就可以通过ProvidePlugin来实现shimming的效果;
注意:webpack并不推荐随意的使用shimming
Webpack背后的整个理念是使前端开发更加模块化;
也就是说,需要编写具有封闭性的、不存在隐含依赖(比如全局变量)的彼此隔离的模块;
11、MiniCssExtractPlugin
MiniCssExtractPlugin可以帮助我们将css提取到一个独立的css文件中,该插件需要在webpack4+才可以使用。
安装:
npm install mini-css-extract-plugin -D
配置:
plugins: [
new MiniCssExtractPlugin({
filename:"css/[name].[contenthash:8].css",
chunkfilename: "css/[name].[contenthash:8].css"
})
],
module:{
rules: [
{
test:/\.css$/i,
use:[MiniCssExtractPlugin.loader,'css-loader']
}
]
}
12、Hash、ContentHash、ChunkHash
hash值的生成和整个项目有关系:
比如我们现在有两个入口index.js和main.js;
它们分别会输出到不同的bundle文件中,并且在文件名称中我们有使用hash;
这个时候,如果修改了index.js文件中的内容,那么hash会发生变化;
那就意味着两个文件的名称都会发生变化;
chunkhash可以有效的解决上面的问题,它会根据不同的入口进行借来解析来生成hash值:
比如我们修改了index.js,那么main.js的chunkhash是不会发生改变的;
contenthash表示生成的文件hash名称,只和内容有关系:
比如我们的index.js,引入了一个style.css,style.css有被抽取到一个独立的css文件中;
这个css文件在命名时,如果我们使用的是chunkhash;
那么当index.js文件的内容发生变化时,css文件的命名也会发生变化;
这个时候我们可以使用contenthash;
认识DLL库(了解一下)
DLL全程是动态链接库(Dynamic Link Library),是为软件在Windows中实现共享函数库的一种实现方式;
webpack中也有内置DLL的功能,它指的是我们可以将可以共享,并且不经常改变的代码,抽取成一个共享的库;
Terser是一个JavaScript的解释(Parser)、Mangler(绞肉机)/Compressor(压缩机)的工具集;
早期我们会使用uglify-js来压缩、丑化我们的JavaScript代码,但是目前已经不再维护,并且不支持ES6+的语法;
Terser可以帮助我们压缩、丑化我们的代码,让我们的bundle变得更小。
安装:
npm install terser -g // 可以进行局部安装,也可以全局安装
命令行使用
terser [input files] [options]
常见的配置项:
Compress option
arrows:class或者object中的函数,转换成箭头函数;
arguments:将函数中使用arguments[index]转成对应的形参名称;
dead_code:移除不可达的代码(tree shaking);
等等其他属性,详情看官方文档
Mangle option
toplevel:默认值是false,顶层作用域中的变量名称,进行丑化(转换)
keep_classnames:默认值是false,是否保持依赖的类名称;
keep_fnames:默认值是false,是否保持原来的函数名称;
Terser在webpack中配置使用
(注意)真实开发中,我们不需要手动的通过terser来处理我们的代码,我们可以直接通过webpack来处理:
在webpack中有一个minimizer属性,在production模式下,默认就是使用TerserPlugin来处理我们的代码的;
如果我们对默认的配置不满意,也可以自己来创建TerserPlugin的实例,并且覆盖相关的配置;(基本不会手动配置)
module.exports = {
optimization: {
minimize:true,
minimizer: [
new TerserPlugin({
parallel:true, // 使用多进程并发运行提高构建的速度,默认值是true,
extractComments:false, // 默认值为true,表示会将注释抽取到一个单独的文件中
terserOptions: { // 设置我们的terser相关的配置
compress: { // 设置压缩相关的选项;
arguments:true,
dead_code:true
},
mangle:true, // 设置丑化相关的选项,可以直接设置为true;
toplevel:true, // 底层变量是否进行转换
keep_classnames:false,// 保留类的名称
keep_fnames:false// 保留函数的名称;
}
})
]
}
}
CSS的压缩
安装:
npm install css-minimizer-webpack-plugin -D
在optimization.minimizer中配置:
minimizer: [
new CssMinimizerplugin({
parallel: true
})
]
提升作用域 Scope Hoisting
Scope Hoisting从webpack3开始增加的一个新功能,功能是对作用域进行提升,并且让webpack打包后的代码更小、运行更快;
默认情况下webpack打包会有很多的函数作用域,Scope Hoisting可以将函数合并到一个模块中来运行
使用:
在production模式下,默认这个模块就会启用;
在development模式下,我们需要自己来打开该模块;
new webpack.optimize.ModuleConcatenationPlugin()
定义:最早的想法起源于LISP,用于消除未调用的代码(纯函数无副作用,可以放心的消除,这也是为什么要求我们在进行函数式编程时,尽量使用纯函数的原因之一)
webpack实现TreeShaking
两种方法:
在optimization中配置usedExports为true,来帮助Terser进行优化;
在package.json中配置sideEffects,直接对模块进行优化;
usedExports
在usedExports设置为true时,会有一段注释:unused harmony export mul,这段注释告知Terser在优化时,可以删除掉这段代码
注意:
配置该属性时,需要将mode设置为development模式
usedExports实现tree Shaking是结合terse来完成的
sideEffects
sideEffects用于告知webpack compiler哪些模块时有副作用的(副作用的意思是这里面的代码有执行一些特殊的任务,不能仅仅通过export来判断这段代码的意义;)
在package.json中设置sideEffects的值:
false:告知webpack可以安全的删除未用到的exports;
如果有一些希望保留,可以设置数组
"sideEffects": [
"./src/util/format.js",
"*.css"
]
CSS实现TreeShaking
我们可以使用一个库来完成CSS的Tree Shaking:PurgeCSS,帮助我们删除未使用的CSS的工具
安装:
npm install purgecss-webpack-plugin -D
配置:
paths:表示要检测哪些目录下的内容需要被分析,这里我们可以使用glob;
默认情况下,Purgecss会将我们的html标签的样式移除掉,如果我们希望保留,可以添加一个safelist的属性;
purgecss也可以对less文件进行处理(所以它是对打包后的css进行tree shaking操作)
new PurgecssPlugin({
paths:glob.sync(`${resolveApp(./src)}/**/*`,{nodir:true}),
safelist: function() {
return {
standard:['html']
}
}
})
定义:HTTP压缩是一种内置在服务器和客户端之间的,以改进传输速度和带宽利用率的方式
流程:
第一步:HTTP数据在服务器发送前就已经被压缩了
第二步:兼容的浏览器在向服务器发送请求时,会告知服务器自己支持哪些压缩格式
第三步:服务器在浏览器支持的压缩格式下,直接返回对应的压缩后的文件,并且在响应头中告知浏览器;
目前的压缩格式
compress–UNIX的“compress”程序的方法(历史性原因,不推荐大多数应用使用,应该使用gzip或deflate);
deflate–基于deflate算法(定义于RFC1951)的压缩,使用zlib数据格式封装;
gzip–GNUzip格式(定义于RFC1952),是目前使用比较广泛的压缩算法;
br–一种新的开源压缩算法,专为HTTP内容的编码而设计;
Webpack对文件压缩
webpack中相当于是实现了HTTP压缩的第一步操作,我们可以使用CompressionPlugin。
安装:
npm install compression-webpack-plugin -D
配置:
new CompressionPlugin({
test:/\.(css|js)$/, // 匹配哪些文件需要压缩
threshold:500, // 设置文件多大开始压缩
minRatio: 0.7, // 至少采用的压缩比例
algorithm: "gzip" // 采用的压缩算法
})
HTML文件中代码的压缩
我们之前使用了HtmlWebpackPlugin插件来生成HTML的模板,事实上它还有一些其他的配置:
inject:设置打包的资源插入的位置
true、false、body、head
cache:设置为true,只有当文件改变时,才会生成新的文件(默认值也是true)
minify:默认会使用一个插件html-minifier-terser
InlineChunkHtmlPlugin
可以辅助将一些chunk出来的模块,内联到html中
安装
npm install react-dev-utils -D
在production的plugins中进行配置:
module.exports = {
plugin:[
new InlineChunkHtmlPlugin(HtmlWebpackPlugin,[/runtime.+\.js/])
]
}
speed-measure-webpack-plugin 可以帮助我们看到每一个loader、每一个plugin的打包时间
安装:
npm install speed-measure-webpack-plugin -D
配置:
const smp = new SpeedMeasurePlugin();
cpnst webpackConfig = smp.wrap({
plugins: [new MyPlugin(),new MyOtherPlugin()]
})
使用webpack-bundle-analyzer工具
安装:
npm install webpack-bundle-analyzer -D
配置
module.exports = {
plugins: [
new BundleAnalyzerPlugin()
]
}
Compiler中webpack构建的之初就会创建的一个对象, 并且在webpack的整个生命周期都会存在(before -run -beforeCompiler-compile -make -finishMake-afterCompiler-done)
只要是做webpack的编译, 都会先创建一个Compiler
Compilation是到准备编译模块(比如main.js), 才会创建Compilation对象
watch -> 源代码发生改变就需要重新编译模块
主要是存在于compile -make 阶段主要使用的对象
Compiler可以继续使用(如果我修改webpack的配置, 那么需要重新执行run run build)
Compilation需要创建一个新的Compilation对象