项目构建
网站搭建
nginx
https://juejin.cn/post/6887135998099062792
https://blog.csdn.net/Charissa2017/article/details/105886521
Nginx是一款轻量级的Web服务器,具有内存占用少,启动极快,高并发能力强的优势,采用事件驱动的异步非阻塞处理方式框架,IO性能好,时常用于服务端的反向代理和负载均衡。
正向代理与反向代理
反向代理服务器位于用户与目标服务器之间,但是对于用户而言,反向代理服务器就相当于目标服务器,即用户直接访问反向代理服务器就可以获得目标服务器的资源。同时,用户不需要知道目标服务器的地址,也无须在用户端作任何设定。反向代理服务器通常可用来作为Web加速,即使用反向代理作为Web服务器的前置机来降低网络和服务器的负载,提高访问效率
正向代理是什么东东?反向代理又是啥?
「「正向代理」」
「「举个栗子」」 因为防火墙的原因, 直接访问谷歌是无法访问的, 这时可以借助梯子,即vpn,那么这个vpn(代理)就是代替客户端去访问服务器,然后将数据返回给客户端。服务器并不知道目标客户端,只是与vpn建立联系。
「「反向代理」」
「「举个栗子」」 你(客户端)去租房时,真正的房东(服务器)将房租给二手房东(代理服务器),而你通过二手房东租到的房子,也就是你与二手房东建立联系, 租到房子,并非真正的房东。
一句话: 正向代理 “代理”客户端, 反向代理“代理”服务器
nginx文件
配置文件
ps aux|grep nginx
查看 配置文件conf目录
nginx配置文件的基本结构
1 | 「「main」」:nginx的全局配置,对全局生效。 |
server
1 | server { |
nginx默认监听的就是80端口, 也可以监听其他端口号
server_name就是你服务器的名称,可以精准匹配,也可以使用通配符或正则匹配,没有顺序,一般是先到先得。
root 你项目文件存放路径, 一般是放在html下面,也可放在其他地方, 如/var/local/marking-h5,n那么就改为 root /var/local/marking-h5
(build打包后的dist文件在服务器上的路径)
index 你的项目入口, 通常是index.html.
1 | #点击刷新后,页面就会显示(404),使用try_files(进行内部重定向) |
部署多个网站
1 | #配置代理 |
其他文件
1 | /run nginx.pid |
nginx命令
1 | nginx #打开 nginx |
tip:
nginx: [error] open() “/run/nginx.pid” failed (2: No such file or directory)
1
nginx -c /etc/nginx/nginx.conf
PM2
PM2是node进程管理工具,可以利用它来简化很多node应用管理的繁琐任务,如性能监控、自动重启、负载均衡等,而且使用非常简单。
1 | pm2 start ./bin/www --watch --name my-api |
1 | 用法 |
express
https://help.aliyun.com/document_detail/50775.html
https://www.jianshu.com/p/175558881f19
前端⼯程化
构建历史
https://mp.weixin.qq.com/s/vhkAhBJ2mok43lIlHpu8Gg
前端模块化/构建工具从最开始的基于浏览器运行时加载的 RequireJs/Sea.js
到将所有资源组装依赖打包 webpack
/rollup
/parcel
的bundle
类模块化构建工具,再到现在的bundleless
基于浏览器原生 ES 模块的 snowpack
/vite
,前端的模块化/构建工具发展到现在已经快 10 年了。
Grunt
browserify
browserify
致力于在浏览器端使用CommonJs
,他使用跟NodeJs
一样的模块化语法,然后将所有依赖文件编译到一个bundle
文件,在浏览器通过<script>
标签使用的,并且支持 npm 库。Gulp
webpack
webpack1
支持CommonJs
和AMD
模块化系统,优化依赖关系,支持分包,支持多种类型 script、image、file、css/less/sass/stylus、mocha/eslint/jshint 的打包,丰富的插件体系。webpack
的概念更偏向于工程化以上的 3 个库
Grunt/Gulp/browserify
都是偏向于工具,而webpack
将以上功能都集成到一起,相比于工具它的功能大而全。rollup
rollup
编译ES6
模块,提出了Tree-shaking
,根据ES module
静态语法特性,删除未被实际使用的代码,支持导出多种规范语法,并且导出的代码非常简洁,如果看过vue
的dist
目录代码就知道导出的vue
代码完全不影响阅读。rollup
的插件系统支持:babel
、CommonJs
、terser
、typescript
等功能。相比于
browserify
的CommonJs
,rollup
专注于ES module
。
相比于webpack
大而全的前端工程化,rollup
专注于纯javascript
,大多被用作打包tool
工具或library
库。react、vue 等库都使用
rollup
打包项目,并且下面说到的vite
也依赖rollup
用作生产环境打包 js。snowpack 和 vite
因为
snowpack
基于浏览器的模块化 和vite
比较类似,都是bundleless
所以一起拿来说。bundleless
类运行时打包工具的启动速度是毫秒级的,因为不需要打包任何内容,只需要起两个server
,一个用于页面加载,另一个用于HMR
的WebSocket
,当浏览器发出原生的ES module
请求,server
收到请求只需编译当前文件后返回给浏览器不需要管依赖。bundleless
工具在生产环境打包的时候依然bundle
构建所以依赖视图的方式,vite 是利用rollup
打包生产环境的 js 的。
基于浏览器的模块化
CommonJS 和 EsModule
https://es6.ruanyifeng.com/#docs/module-loader
CommonJS
一切的开始要从CommonJS规范说起。
CommonJS
本来叫ServerJs,其目标本来是为浏览器之外的javascript
代码制定规范,在那时NodeJs
还没有出生,有一些零散的应用于服务端的JavaScript
代码,但是没有完整的生态。之后就是
NodeJs
从CommonJS
社区的规范中吸取经验创建了本身的模块系统。
核心思想:允许模块通过 require
方法来同步加载所要依赖的其他模块,然后通过 exports
或 module.exports
来导出需要暴露的接口。
1 | // a.js |
优点:服务器端模块重用,NPM中模块包多,有将近20万个。
缺点:加载模块是同步的,只有加载完成后才能执行后面的操作,也就是当要用到该模块了,现加载现用,不仅加载速度慢,而且还会导致性能、可用性、调试和跨域访问等问题。Node.js主要用于服务器编程,加载的模块文件一般都存在本地硬盘,加载起来比较快,不用考虑异步加载的方式,因此,CommonJS规范比较适用。然而,这并不适合在浏览器环境,同步意味着阻塞加载,浏览器资源是异步加载的,因此有了AMD CMD解决方案。
require的加载过程是同步的,所以必须等到引入的文件(模块)加载完成之后,才会继续执行其它代码,会产生阻塞现象,因为引入一个文件,则该文件内部的所有代码都会被执行一次。
环境:
- 服务器端的 Node.js
- Browserify,浏览器端的 CommonJS 实现,可以使用 NPM 的模块,但是编译打包后的文件体积可能很大
- modules-webmake,类似Browserify,还不如 Browserify 灵活
- wreq,Browserify 的前身
ES Module
ECMAScript6 标准增加了 JavaScript 语言层面的模块体系定义。ES6 模块的设计思想,是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。CommonJS 和 AMD 模块,都只能在运行时确定这些东西。由于ES6目前无法在浏览器中执行,所以,我们只能通过babel将不被支持的import编译为当前受到广泛支持的 require。
命名导出
1
2
3
4
5
6
7
8
9
10// B.js
/*-------- 单个变量或函数导出 ----------*/
export function show() { console.log('show方法被调用') }
export let count = 3
/*-------- 批量导出 ----------*/
function show() { console.log('show方法被调用') }
let count = 3
export {show, count} // 解构赋值语法(as关键字在这里表示将newA作为a的数据接口暴露给外部,外部不能直接访问a)第一种是单个的变量或函数导出,只需要直接在开头使用
export
关键字即可;第二种情况是批量地把多个变量或函数导出,只需要把它们储存到一个对象中即可
默认导出
1
2
3
4
5// B.js
function show() { console.log('show方法被调用') }
// 默认导出函数show
export default show默认导出是在
export
关键词后面再跟上一个default
表示导出的该变量或函数是匿名的注意: 一个模块只能默认导出一次,否则就会报错,具体原因会在后面讲解
导入
ES6 Module 的导入用到的关键字是
import
,具体代码如下1
2
3
4
5
6// A.js
import {show, count} from './B.js'
show() // show方法被调用
console.log(count) // 3ES6 Module的导入需要用一对
{}
大括号来接收我们需要导入的方法或函数注意: 大括号中的变量或函数名必须与导出时的名称一模一样
那么如果我们想修改导入的变量或函数的名称,可以通过
as
关键词来命名,代码如下1
2
3
4
5
6// A.js
import {show as print, count as number} from './B.js'
print() // show方法被调用
console.log(number) // 3如果我们要想将所有的变量或函数都导入,可以通过
*
来整体导入,代码如下1
2
3
4
5import * as bModule from './B.js'
bModule.show() // show方法被调用
console.log(bModule.count) // 3*
表示全部的意思,我们将其全部导入,并赋值给bModule
,这样我们就可以通过bModule
获取想要的变量或对象了以上所说的都是针对命名导出的变量或函数,那么如何导入一个默认导出的变量或函数呢?
1
2
3
4// 将通过 export default 导出的变量导入
import print from './B.js'
print() // show方法被调用命名导出的变量都是通过
{}
来接收的,那么去掉{}
,接收的就是默认导出的变量了,因为导出的变量是匿名的,因此我们可以随意地起个变量名用于接收补充: 这里特别提一下,与CommonJS不同,ES6 Module 的导入文件路径是不支持表达式的
优点:
- 容易进行静态分析
- 面向未来的 ECMAScript 标准
缺点:
- 原生浏览器端还没有实现该标准
- 全新的命令字,新版的 Node.js才支持
加载规则
浏览器加载 ES6 模块,也使用<script>
标签,但是要加入type="module"
属性。
1 | <script type="module" src="./foo.js"></script> |
上面代码在网页中插入一个模块foo.js
,由于type
属性设为module
,所以浏览器知道这是一个 ES6 模块。
浏览器对于带有type="module"
的<script>
,都是异步加载,不会造成堵塞浏览器,即等到整个页面渲染完,再执行模块脚本,等同于打开了<script>
标签的defer
属性。
1 | <script type="module" src="./foo.js"></script> |
如果网页有多个<script type="module">
,它们会按照在页面出现的顺序依次执行。
1 | <script>标签的async属性也可以打开,这时只要加载完成,渲染引擎就会中断渲染立即执行。执行完成后,再恢复渲染。 |
1 | <script type="module" src="./foo.js" async></script> |
一旦使用了async
属性,<script type="module">
就不会按照在页面出现的顺序执行,而是只要该模块加载完成,就执行该模块。
CommonJS 与ES Module的区别
https://mp.weixin.qq.com/s/1wUU-i3W4RlR2hf86lZqEA
这两者的主要区别主要有以下两点:
对于模块的依赖,CommonJS是动态的,ES6 Module 是静态的
对于模块的依赖,何为动态?何为静态?
动态是指对于模块的依赖关系建立在代码执行阶段;静态是指对于模块的依赖关系建立在代码编译阶段;
CommonJS
CommonJS导入时,
require
的路径参数是支持表达式的1
2
3// A.js
let fileName = 'example.js'
const bModule = require('./' + fileName)因为该路径在代码执行时是可以动态改变的,所以如果在代码编译阶段就建立各个模块的依赖关系,那么一定是不准确的,只有在代码运行了以后,才可以真正确认模块的依赖关系,因此说CommonJS是动态的。
CommonJs
支持动态导入,可以在语句中,使用require
语法1
2
3
4
5
6let lists = ["./index.js", "./config.js"]
lists.forEach((url) => require(url)) // 动态导入
if (lists.length) {
require(lists[0]) // 动态导入
}
Es Module
只能声明在该文件的最顶部,不能动态加载语句
1
2
3if (true) {
import xxx from 'XXX' // 报错
}
CommonJS导入的是值的拷贝,ES6 Module导入的是值的引用
首先来验证CommonJS,代码如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25// B.js
let count = 3
function change() {
count ++ // 变量count + 1
console.log('原count值为:', count); // 打印B.js模块中count的值
}
module.exports = {
count,
change
}
// A.js
let count = require('./B.js').count
let change = require('./B.js').change
console.log('改变前:', count);
change() // 调用模块B.js中的change方法,将原来的count + 1
console.log('改变后:', count);
// 运行A.js文件的结果
改变前:3
原count值为:4
改变后:3在上述代码中我们可以看到,在
A.js
文件中导入了B.js
文件中的变量count
和 函数change
,因为导入的count
只是对原有值的一个拷贝,因此尽管我们调用了函数change
改变了B.js
文件中变量count
的值,也不会影响到A.js
文件中的变量count
根据这个结果得出结论:CommonJS导入的变量是对原值的拷贝
CommonJS 模块的
require()
是同步加载模块,ES6 模块的import
命令是异步加载,有一个独立的模块依赖的解析阶段。
RequireJs 和 AMD
CommonJs
是一套同步模块导入规范,但是在浏览器上还没法实现同步加载,这一套规范在浏览器上明显行不通,所以基于浏览器的异步模块AMD
规范诞生。AMD规范(Asynchronous Module Definition,异步模块定义)是RequireJS在推广模块化开发的过程中提出的一种规范。
其核心接口是:define(id, 『dependencies』, factory) ,它要在声明模块的时候指定所有的依赖 dependencies ,并且还要当做形参传到factory 中,对于依赖的模块提前执行,依赖前置。
AMD
规范采用依赖前置,先把需要用到的依赖提前写在 dependencies
数组里,在所有依赖下载完成后再调用factory
回调,通过传参来获取模块,同时也支持require("beta")
的方式来获取模块,但实际上这个require
只是语法糖,模块并非在require
的时候导入,而是跟前面说的一样在调用factory
回调之前就被执行,关于依赖前置和执行时机这点在当时有很大的争议,被 CommonJs
社区所不容。
1 | define("module", ["dep1", "dep2"], function(d1, d2) { |
优点:在浏览器环境中异步加载模块;并行加载多个模块;
缺点:开发成本高,代码的阅读和书写比较困难,模块定义方式的语义不顺畅;不符合通用的模块化思维方式,是一种妥协的实现;
Sea.js 和 CMD
在不断给
RequireJs
提建议,但不断不被采纳后,玉伯结合RequireJs
和module/2.0
规范写出了基于 CMD(Common Module Definition)规范的Sea.js
。
Common Module Definition 规范和 AMD 很相似,尽量保持简单,并与 CommonJS 和 Node.js 的 Modules 规范保持了很大的兼容性。CMD规范(Common Module Definition,通用模块定义)是SeaJS在推广模块化开发的过程中提出的一种规范。
1 | define(function(require, exports, module) { |
优点:依赖就近,延迟执行(对于依赖的模块延迟执行,即只在需要用到某个模块的时候再require) 可以很容易在 Node.js 中运行;
缺点:依赖 SPM 打包,模块的加载逻辑偏重;
实现:Sea.js ;coolie
rollup
概念
2015 年,前端的ES module
发布后,rollup
应声而出。
rollup
编译ES6
模块,提出了Tree-shaking
,根据ES module
静态语法特性,删除未被实际使用的代码,支持导出多种规范语法,并且导出的代码非常简洁。react、vue 等库都使用rollup
打包项目,并且下面说到的vite
也依赖rollup
用作生产环境打包 js。
相比于
browserify
的CommonJs
,rollup
专注于ES module
。相比于
webpack
大而全的前端工程化,rollup
专注于纯javascript
,可以生成轻量、快速以及低复杂度的library和应用程序。rollup
的插件系统支持:babel
、CommonJs
、terser
、typescript
等功能。
Rollup VS Webpack
源码
webpack打包后的文件
rollup打包后的文件
webpack致力于复杂SPA的模块化构建,优势在于:
- 通过loader处理各种各样的资源依赖
- HMR模块热替换
- 按需加载
- 提取公共模块
rollup致力于打造性能出众的类库,有如下优势:
- 编译出来的代码
可读性好
- rollup打包后生成的bundle内容十分
干净
,没有什么多余的代码,只是将各个模块按照依赖顺序拼接起来,所有模块构建在一个函数内(Scope Hoisting), 执行效率更高。相比webpack(webpack打包后会生成__webpack_require__等runtime代码),rollup拥有无可比拟的性能优势,这是由依赖处理方式决定的,编译时依赖处理(rollup)自然比运行时依赖处理(webpack)性能更好
- 对于ES模块依赖库,rollup会静态分析代码中的 import,并将排除任何未实际使用的代码:tree-shaking
- 支持程序流分析,能更加正确的判断项目本身的代码是否有副作用(配合tree-shaking)
- 支持导出
es
模块文件(webpack不支持导出es模块) 但是模块过于静态化,HMR很难实现
通过以上的对比可以得出,构建App应用
时,webpack比较合适,如果是类库(纯js项目)
,rollup更加适合。
webpack构建App的优势体现在以下几方面:
- 强大的
插件生态
,主流前端框架都有对应的loader - 面向App的特性支持,比如之前提到的
HMR
,按需加载
,公共模块
提取等都是开发App应用必要的特性 - 简化Web开发各个环节,包括
图片自动base64,[资源缓存](https://www.zhihu.com/search?q=%E8%B5%84%E6%BA%90%E7%BC%93%E5%AD%98&search_source=Entity&hybrid_search_source=Entity&hybrid_search_extra=%7B%22sourceType%22%3A%22article%22%2C%22sourceId%22%3A75717476%7D)(chunkId),按路由做代码拆分,懒加载
等 - 可靠的依赖模块处理,不像rollup那样仅面向ES module,面临cjs的问题(webpack通过
__webpack_require__
实现各种类型的模块依赖问题)
rollup的优势在于构建高性能的bundle
,这正是类库所需要的。
副作用
https://blog.csdn.net/zz_jesse/article/details/108289318
Tree-shaking
以上代码最终打包后 b 的声明就会被删除掉。
这依赖ES module
的静态语法,在编译阶段就可以确定模块的导入导出有哪些变量。
CommonJs
因为是基于运行时的模块导入,其导出的是一个整体,并且require(variable)
内容可以为变量,所以在ast
编译阶段没办法识别为被使用的依赖。
webpack4
中也开始支持tree-shaking
,但是因为历史原因,有太多的基于CommonJS
代码,需要额外的配置。
parcel
上面提到过webpack
的两个缺点,而parcel
的诞生就是为了解决这两个缺点,parcel 主打极速零配置。
打包工具 | 时间 |
---|---|
browserify | 22.98s |
webpack | 20.71s |
parcel | 9.98s |
parcel - with cache | 2.64s |
以上是 parcel
官方的一个数据,基于一个合理大小的应用,包含 1726 个模块,6.5M 未压缩大小。在一台有 4 个物理核心 CPU 的 2016 MacBook Pro 上构建。
parcel
使用 worker
进程去启用多核编译,并且使用文件缓存。
parcel
支持 0 配置,内置了 html、babel、typescript、less、sass、vue
等功能,无需配置,并且不同于webpack
只能将 js 文件作为入口,在 parcel
中万物皆资源,所以 html
文件 css
文件都可以作为入口来打包。
所以不需要webpack
的复杂配置,只需要一个parcel index.html
命令就可以直接起一个自带热更新的server
来开发vue/react
项目。
parcel 也有它的缺点:
- 0 配置的代价,0 配置是好,但是如果想要配置一些复杂的配置就很麻烦。
- 生态,相比于
webpack
比较小众,如果遇到错误查找解决方案比较麻烦。
原理
commander
获取命令- 启动
server
服务,启动watch
监听文件,启动WebSocket
服务用于 hmr,启动多线程 - 如果是第一次启动,针对入口文件开始编译
- 根据扩展名生成对应
asset
资源,例如jsAsset
、cssAsset
、vueAsset
,如果parcel
识别less
文件后项目内如果没有less
库会自动安装 - 读取缓存,如果有缓存跳到第 7 步
- 多线程编译文件,调用
asset
内方法parse -> ast -> 收集依赖 -> transform(转换代码) -> generate(生成代码)
,在这个过程中收集到依赖,编译完结果写入缓存 - 编译依赖文件,重复第 4 步开始
createBundleTree
创建依赖树,替换 hash 等,package
打包生成最终代码- 当
watch
文件发生变化,重复第 4 步,并将结果 7 通过WebSocket
发送到浏览器,进行热更新。
一个完整的模块化打包工具就以上功能和流程。
webpack
概念
webpack是一个前端模块化方案,更侧重模块打包,我们可以把开发中的所有资源(图片、js文件、css文件等)都看成模块,通过loader(加载器)和plugins(插件)对资源进行处理,打包成符合生产环境部署的前端资源。
网页中常见的静态资源
js–js,jax,coffee,ts(TypeScript,需要编译为js)
css– css,less,sass
image–jpg,png,gif,bmp,svg
字体文件(Fonts)–svg,ttf,eot,woff,woff2
模板文件–ejs,jade,vue(这是在webpack中定义的组件的方式)
特点:
模块化开发
在没有各个 webpack 搭建的脚手架(create-react-app、vue-cli 等等)之前,我们通过在 HTML5 文件里引入一个个 Javascript 文件来进行开发,这就可能导致并行请求数量过多、存在重复代码等问题。
而通过 webpack,我们可以使用 import、require 来进行模块化开发。
在 webpack 中一切皆模块,js、css、图片、字体都是模块,而且支持静态解析、按需打包、动态加载、代码分离等功能,帮助我们优化代码,提升性能。
新语法
Javascript、CSS 的语法规范在不断更新,但是浏览器的兼容性却不能同步的更新,开发者可以通过 webpack 预处理器进行编译,自由的使用 JS、CSS 等语言的新语法。
webpack 使用 loader 对文件进行预处理。你可以构建包括 JavaScript 在内的任何静态资源,如 Less、Sass、ES6、TypeScript。
主流框架脚手架
Vue 脚手架
vue-cli
、React 脚手架creact-react-app
、Taro 脚手架taro-cli
都是使用 webpack,开发者掌握 webpack 后,可以自由配置脚手架,根据项目需要,调整 webpack 配置,以提高项目性能。
文件搭建
https://juejin.cn/post/6844903968405979144
1 | npm init |
webpack.base.conf.js ==> webpack基本配置;
webpack.dev.conf.js ==> webpack开发环境配置;
webpack.prod.conf.js ==> webpack生产环境配置;
配置
entry
1 | entry: path.resolve(__dirname, "./src/main.js"), |
output
1 | output: { |
mode
webpack5 提供了模式选择,包括开发模式、生产模式、空模式,并对不同模式做了对应的内置优化。可通过配置模式让项目性能更优。
Sourcemap
https://mp.weixin.qq.com/s/g5GcZ10G89Xb9hZ1SibVrA
Sourcemap
本质上是一个信息文件(储存着代码转换前后的对应位置信息),关联编译后的代码和源码的,通过一个个行列号的映射。比如编译后代码的第 3 行第 4 列,对应着源码里的第 8 行第 5 列这种,这叫做一个mapping。简单说 Sourcemap
构建了处理前以及处理后的代码之间的一座桥梁,方便定位生产环境中出现 bug
的位置。
sourcemap 的格式如下:
1 | { |
version 是版本号,file 是文件名,sourceRoot 是源码根目录,names 是转换前的变量名,sources 是源码文件,sourcesContent 是每个 sources 对应的源码的内容,mappings 就是一个个位置映射了。
为什么 sources 可以有多个呢?
因为可能编译产物是多个源文件合并的,比如打包,一个 bundle.js 就对应了 n 个 sources 源文件。
为什么要把变量名单独摘出来到 names 里呢?
因为这样就可以通过下标来索引了,mapping 里面就不用保存变量名,只保留 names 的索引就行。
重点是 mappings 部分:
mappings 部分是通过分号;
和逗号 ,
分隔的:
1 | mappings:"AAAAA,BBBBB;CCCCC" |
一个分号就代表一行,这样就免去了行的映射。
然后每一行可能有多个位置的映射,用 ,
分隔
那具体的每一个 mapping 都是啥呢?
比如 AAAAA 一共五位,分别有不同的含义:
- 第一位:转换后代码的第几列(行数通过分号 ; 来确定)
- 第二位:对应转换前的哪个源码文件,保存在 sources 里的,这里通过下标索引
- 第三位:对应转换前的源码的第几行
- 第四位:对应转换前的源码的第几列
- 第五位:对应转换前的源码的哪个变量名,保存在 names 里的,这里通过下标索引
基础配置
- eval:浏览器 devtool 支持通过 sourceUrl 来把 eval 的内容单独生成文件,还可以进一步通过 sourceMappingUrl 来映射回源码,webpack 利用这个特性来简化了 sourcemap 的处理,可以直接从模块开始映射,不用从 bundle 级别。
- cheap:只映射到源代码的某一行,不精确到列,可以提升 sourcemap 生成速度
- source-map:生成 sourcemap 文件,可以配置 inline,会以 dataURL 的方式内联,可以配置 hidden,只生成 sourcemap,不和生成的文件关联。
- nosources:不生成 sourceContent 内容,可以减小 sourcemap 文件的大小
- module:sourcemap 生成时会关联每一步 loader 生成的 sourcemap,配合 sourcemap-loader 可以映射回最初的源码
理解了这些基础配置项,根据 ^(inline-|hidden-|eval-)?(nosources-)?(cheap-(module-)?)?source-map$ 的规律来进行组合,就可以实现各种需求下的 sourcemap 配置。
当然,这种 sourcemap 配置还不够细致,比如 sourcemap 的 url 怎么生成,文件名是什么。如果想对这些做配置,可以关掉 devtool,启用 SourceMapDevToolPlugin 来配置。
虽然 webapck 的 sourcemap 配置方式比较多,但最底层也就是浏览器支持的文件级别的 sourcemap 还有 eval 代码的 source 映射和 sourcemap 这两种机制。其余的方式都是基于这两种机制的封装。
devtool
Sourcemap
本质上是一个信息文件,关联编译后的代码和源码的,通过一个个行列号的映射。比如编译后代码的第 3 行第 4 列,对应着源码里的第 8 行第 5 列这种,这叫做一个mapping。
mapping里面储存着代码转换前后的对应位置信息。它记录了转换压缩后的代码所对应的转换前的源代码位置,是源代码和生产代码的映射。简单说 Sourcemap
构建了处理前以及处理后的代码之间的一座桥梁,方便定位生产环境中出现 bug
的位置。
externals
防止将某些 import
的包打包到 bundle 中,而是在运行时(runtime)再去从外部获取这些*扩展依赖(external dependencies)*。
例如,从 CDN 引入 jQuery,而不是把它打包
通过script方式引入后,在文件中直接使用。这里是没有问题的,如果你使用了eslint,它会提示你该变量未定义。但是如果你想在文件中使用import $ from ‘jquery’;不好意思不行,因为你没有npm install jquery,那么如何让我们像安装了jquery一样使用这个东西呢?这就需要配置externals配置
proxy
该配置是为了解决前后端联调时出现的跨域问题,将后端域名下的请求代理到本地,从而避免跨域请求;
1 | devServer: { |
接口是https://api.douban.com/v2/music/search?q=周杰伦
调用时就可以将接口写成这样/mgr/v2/music/search?q=周杰伦
那这个pathRewrite到底是干嘛用的,为什么有时候需要写,有时候不需要写
其实很简单,用代理, 首先你得有一个标识, 告诉node, 我接口只要是’/mgr’开头的才用代理.所以你的接口就要这么写 /mgr/xx/xx. 最后代理的路径就是 http://xxx.xx.com/mgr/xx/xx.
可是不对啊, 我正确的接口路径里面没有/mgr啊. 所以就需要 pathRewrite,用'^/mgr':''
, 把’/mgr’去掉, 这样既能有正确标识, 又能在请求接口的时候去掉mgr
Resolve
resolve用来解析模块路径,帮助 webpack 找到 bundle 中以require/import引入的模块代码
- extensions:extensions扩展名选项在resolve追踪到的文件如果没有扩展名时,会尝试在其提供的扩展名选项里进行匹配
- alias:通过
resolve.alias
来自定义模块路径的映射 - symlinks:用于配置 npm link 是否生效,禁用可提升编译速度。
1 | // webpack.config.js |
optimization
optimization 用于自定义 webpack 的内置优化配置,一般用于生产模式提升性能,常用配置项如下:
- minimize:是否需要压缩 bundle;
- minimizer:配置压缩工具,如 TerserPlugin、OptimizeCSSAssetsPlugin;
- splitChunks:拆分 bundle;
- runtimeChunk:是否需要将所有生成 chunk 之间共享的运行时文件拆分出来。
1 | module.exports = { |
loader
webpack默认只能打包处理 JS类型的文件,无法处理其他的非JS类型的文件。
使用 loader
在你的应用程序中,有两种使用 loader 的方式:
配置参数
test
属性,识别出哪些文件会被转换。use
属性,定义出在进行转换时,应该使用哪个 loadertype
,资源模块类型- generator
资源模块类型
https://blog.csdn.net/qq_41887214/article/details/121631683
资源模块(asset module)是一种模块类型,它允许使用资源文件(字体,图标等)而无需配置额外 loader。
asset/resource 发送一个单独的文件并导出 URL。之前通过使用 file-loader 实现。
asset/inline 导出一个资源的 data URI。之前通过使用 url-loader 实现。
asset/source 导出资源的源代码。之前通过使用 raw-loader 实现。
asset 在导出一个 data URI 和发送一个单独的文件之间自动选择。之前通过使用 url-loader,并且配置资源体积限制实现。
css
js
代码里如果使用import
导入一个样式文件style.less
(代码如下),webpack
碰到.less
后缀的文件不知所措.因为它默认只能处理以.js
和.json
结尾的文件.
1 | //js文件 |
有了loader
的赋能,webpack
便有能力处理.less
文件.
比如上面的配置代码,项目中一旦碰到导入以.less
为后缀的样式文件,webpack
会先将文件内容发送给less-loader
处理,less-loader
将所有less
语法的样式转变成普通的css
样式.
普通的css
样式继续发送给css-loader
处理,css-loader
最主要的功能是解析css
语法中的@import
和图片路径,处理完后导入的css
合并在了一起.
合并后的css
文件再继续传递,发送给html-loader
处理,它最终将样式内容插入到了html
头部的style
标签下,页面也因此添加了样式.
1 | //loader加载器模块配置 |
loader
在上面配置use
数组中的执行顺序是从后往前
1 | module.exports = { |
img
1 | { |
在css
加入img
在js
中加入img
font
1 | { |
加载数据
JSON 文件,CSV、TSV 和 XML,可以使用 csv-loader 和 xml-loader
自定义输出文件名
1.output.assetModuleFilename
1 | const path = require('path'); |
2.generator
1 | const path = require('path'); |
使用此配置,所有 png 文件都将被发送到输出目录中的 images 目录中。
Rule.generator.filename 与 output.assetModuleFilename 相同,并且仅适用于 asset 和 asset/resource 模块类型。
但Rule.generator.filename 优先级高于 output.assetModuleFilename
自定义loader
https://zhuanlan.zhihu.com/p/397174187
背景:项目团队要为每个项目部署监控系统,一旦生产环境下js
出现异常,要将报错信息及时上传到后台日志服务器.
在项目文件夹下创建一个文件error-loader.js
,编写下面的测试代码(代码如下).
loader
本质上是一个函数,参数content
是一段字符串,存储着文件的内容,最后将loader
函数导出就可以提供给webpack
使用了.
webpack
的配置文件在设置rules
时(代码如下),只需要将use
里的loader
指向上面导出的loader
函数的文件路径,这样webpack
就能顺利引用loader
了.另外我们还可以添加options
属性给loader
函数传参.
1 | //error-loader.js |
项目一旦启动打包,webpack
检测到.js
文件,它就会把文件的代码字符串传递给error-loader.js
导出的loader
函数执行.
我们上面编写的loader
函数并没有对代码字符串content
做任何操作,直接返回了结果.那么我们自定义loader
的目的就是为了对content
源代码做各种数据操作,再将操作完的结果返回.
比如我们可以使用正则表达式将content
中所有的console.log
语句全部去掉,那么最后我们生成的打包文件里就不会包含console.log
.
插件
webpack
一个模块打包器,根据entry指示webpack应该使用哪个模块,来作为构建其内部依赖图的开始。进入入口起点后,webpack 会找出有哪些模块和库是入口起点(直接和间接)依赖的。
每个依赖项随即被处理,最后输出到output字段指定的文件中
webpack-dev-server
webpack-dev-server:一个服务器插件,相当于webpack+apache,启动一个web服务并实时更新修改
启动webpack-dev-server后,在目标文件夹中是看不到编译后的文件的,实时编译后的文件都保存到了内存当中。
区别
- webpack不会实时更新修改,就只是一个打包工具,webpack-dev-server会实时自动更新修改
- webpack打包输出路径,output字段为path,webpack-dev-server打包输出路径,output字段为publicPath(此值为空时默认是项目根目录, contentBase:’src’,//指定托管的根目录)
- webpack打包输出的文件,是真的存在于物理地址path中,而webpack-dev-server打包输出的文件,是保存在内存中的,在项目目录中是找不到的。
模块热更新(Hot Module Replacement)是指在浏览器运行过程中,替换、添加或删除模块,而无需重新加载整个页面。
html-webpack-plugin
webpack-dev-server实现了自动编译刷新浏览器,让编译出来的bundle.js托关于服务器根路径(电脑内存)中去。
html-webpack-plugin会创建一个在内存中生成一个html的插件,帮我们自动引入在内存中打包好的bundle.js文件
1 | 属性 |
webpack-manifest-plugin
webpack 通过 manifest,可以追踪所有模块到输出 bundle 之间的映射。通过 WebpackManifestPlugin
插件,可以将 manifest 数据提取为一个 json 文件以供使用。
cross-env
1、什么是cross-env?
它是运行跨平台设置和使用环境变量的脚本。
2、为什么需要cross-env?
针对相同的语句和命令,我们希望这条语句能够同时在 Windows 和 Linux 上使用。
这个问题主要是因为不同的操作系统平台对 Shell 脚本的支持情况不一样导致的。
例如,如果你希望在 Windows 中使用命令NODE_ENV=production
来设置环境变量的话,大多数Windows 命令提示符都没有办法进行操作。
同样的,Windows 和 POSIX 命令使用环境变量的方式也有所不同。
对于POSIX,您可以使用:$ENV_VAR
,但是在 Windows 上需要使用 %ENV_VAR%
来设置环境变量。
上面的情况就是针对不同的操作系统平台,设置环境变量中使用的变量引用是不同的。
例如,我们常常用到的设置环境变量。
针对不同的操作系统环境
1 | windows |
process
process
在node中,有全局变量process表示的是当前的node进程。
process.env
process.env 属性返回的是一个包含用户环境信息的对象,它是区分开发环境或正式环境的依据。打开终端,输入node,输入process,就可以看到对应的描述信息
cross-env添加自定义变量
https://blog.csdn.net/qq_43277404/article/details/120902111
PWA
https://www.jianshu.com/p/7845a13a67d7
https://carljin.com/how-to-add-pwa-on-existed-project
PWA化主要解决了两大问题: 1)使web app有沉浸式体验,也就是更靠近原生体验。如:去掉浏览器的地址栏和底部工具栏;在桌面上生成图标,方便再次进入。 2)提供独立于浏览器的缓存,并且可以接收服务器的推送。如:在没有网络,或者网络状态较差的时候,仍可访问缓存在本地的数据.
编译优化
https://jelly.jd.com/article/61179aa26bea510187770aa3
https://www.psvmc.cn/article/2022-08-25-vue-cli-optimization.html
编译分析插件
webpack-bundle-analyzer
webpack-bundle-analyzer
可以生成代码分析报告,可以直观地分析打包出的文件有哪些,及它们的大小、占比情况、各文件 Gzipped 后的大小、模块包含关系、依赖项等
npm i -D webpackbar webpack-bundle-analyzer
1 | const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer'); |
新版的 vue-cli 也内置了webpack-bundle-analyzer
1 | "scripts": { |
配置:
analyzerMode:server / static / json / disabled
默认值:server。 在server 模式下,分析器将启动 HTTP 服务器以显示 bundle 报告。 在 static 模式下,将生成带有 bundle 报告的单个 HTML 文件。 在 json 模式下,将生成带有捆绑报告的单个 JSON 文件。 在 disable 模式下,您可以使用此插件通过将 generateStatsFile 设置为 true 来生成 Webpack Stats JSON 文件。
analyzerHost:默认值:127.0.0.1。 在 server 模式下用于启动 HTTP 服务器的主机。
analyzerPort:默认值:8888。在 server 模式下用于启动 HTTP 服务器的端口
reportFilename:默认值:report.html。 在 static 模式下生成的捆绑报告文件的路径。 它可以是绝对路径,也可以是相对于 bundle 文件输出目录的路径(在 webpack 配置中是 output.path)。
defaultSizes:stat / parsed / gzip
默认值:parsed。 默认情况下在报告中显示的模块大小。
stat:这是文件的“输入”大小,在进行任何转换(如缩小)之前。之所以称为“stat size”,是因为它是从 Webpack 的 stats 对象中获取的。
parsed:这是文件的“输出”大小。 如果你使用的是 Uglify 之类的 Webpack 插件,那么这个值将反映代码的缩小后的大小。
gzip:这是通过 gzip 压缩运行解析的包/模块的大小。
openAnalyzer:默认值:true。 在默认浏览器中自动打开报告。
genarateStatsFile:默认值:false。 如果为 true,将在 bundle 输出目录中生成 webpack stats JSON 文件
webpackbar
webpackbar
提供了友好的编译进度提示
1 | const WebpackBar = require('webpackbar'); |
speed-measure-webpack-plugin
优化 webpack 构建速度,首先需要知道是哪些插件、哪些 loader 耗时长,方便我们针对性的优化。通过 speed-measure-webpack-plugin 插件进行构建速度分析,可以看到各个 loader、plugin 的构建时长,后续可针对耗时 loader、plugin 进行优化。
1 | npm i -D speed-measure-webpack-plugin |
构建速度优化
缓存
Webpack
Webpack 中几种缓存方式:
cache-loader
hard-source-webpack-plugin
以上这些缓存方式都有首次启动时的开销,即它们会让 “冷启动” 时间会更长,但是二次启动能够节省很多时间
babel-loader
babel-loader的options设置中增加cacheDirectory属性,属性值为true。表示:开启babel缓存,第二次构建时会读取之前的缓存,构建速度会更快一点。
1 | { |
cache-loader
https://webpack.docschina.org/loaders/cache-loader/
在一些性能开销较大的 loader 之前添加 cache-loader,将结果缓存中磁盘中。默认保存在 node_modueles/.cache/cache-loader 目录下。
1 | module.exports = { |
如果你跟我一样,只打算给 babel-loader 配置 cache 的话,也可以不使用 cache-loader,给 babel-loader 增加选项 cacheDirectory。
持久化缓存
通过配置 webpack 持久化缓存 cache: filesystem
,来缓存生成的 webpack 模块和 chunk,改善构建速度。
简单来说,通过 cache: filesystem
可以将构建过程的 webpack 模板进行缓存,大幅提升二次构建速度、打包速度,当构建突然中断,二次进行构建时,可以直接从缓存中拉取,可提速 90% 左右。
1 | module.exports = { |
hard-source-webpack-plugin
HardSourceWebpackPlugin 和 speed-measure-webpack-plugin 不能一起使用
vue
cache-loader
Vue-Cli自带cache-loader ,会默认为 Vue/Babel/TypeScript
编译开启。文件会缓存在 node_modules/.cache
中。
cache-loader
进行以下两个的缓存了
- babel-loader 的 cacheDirectory 标志
- vue-loader 的 cacheDirectory 标志
hard-source-webpack-plugin
这个插件能正常使用的版本是
webpack5
以下的版本。
npm install --save-dev hard-source-webpack-plugin
为模块提供中间缓存,缓存路径是:node_modules/.cache/hard-source
1 | const HardSourceWebpackPlugin = require('hard-source-webpack-plugin') |
hash缓存
防止编译文件名字重复,部署版本的时候,浏览器使用缓存文件。同时,如果编译时文件未改动,不会改变文件名和文件的
hash、chunkhash、contenthash
hash是一整个项目,一次打包,只有一个hash值,是项目级的
chunhash是从入口entry出发,到它的依赖,以及依赖的依赖,依赖的依赖的依赖,等等,一直下去,所打包构成的代码块(模块的集合)叫做一个chunk,也就是说,入口文件和它的依赖的模块构成的一个代码块,被称为一个chunk。
contenthash是哈希只跟内容有关系,内容不变,哈希值不变。与chunkhash的区别可以举上面contenthash的例子,同时可以说明contenthash跟内容有关,但是chunkhash会考虑很多因素,比如模块路径、模块名称、模块大小、模块id等等。
1 | output: { |
在提取css时我们也可以这么命名文件名
1 | // css 提取 |
dll
将我们项目中的依赖使用dll插件进行动态链接,这样依赖就不会进行编译,从而极大地提高编译速度
webpack5 开箱即用的持久缓存是比 dll 更优的解决方案
将dll和缓存进行对比可以发现:
缓存 | DLL |
---|---|
把常用的文件存储到内存或硬盘中 | 把公共代码打包为dll文件放到硬盘中 |
再次打包时,直接取读取缓存 | 再次打包时,读取dll文件,不重新打包 |
加载时间减少 | 打包时间减少 |
autodll-webpack-plugin
多线程
将文件解析任务分解成多个子进程并发执行,发挥多核 CPU 电脑的威力。子进程处理完任务后再将结果发送给主进程。所以可以大大提升 Webpack 的项目构建速度
happypack
happypack 同样是用来设置多线程,但是在 webpack5 就不要再使用 happypack 了,官方也已经不再维护了,推荐使用 thread-loader。
npm install happypack -D
HappyPack 参数
id: String
用唯一的标识符 id 来代表当前的 HappyPack 是用来处理一类特定的文件.loaders: Array
用法和 webpack Loader 配置中一样.threads: Number
代表开启几个子进程去处理这一类型的文件,默认是3个,类型必须是整数。verbose: Boolean
是否允许 HappyPack 输出日志,默认是 true。threadPool: HappyThreadPool
代表共享进程池,即多个 HappyPack 实例都使用同一个共享进程池中的子进程去处理任务,以防止资源占用过多。verboseWhenProfiling: Boolean
开启webpack --profile
,仍然希望HappyPack产生输出。debug: Boolean
启用debug 用于故障排查。默认false
。
1 | //提升 Webpack 构建速度 |
vue
1 | //把对.js 的文件处理交给id为happyBabel 的HappyPack 的实例执行 |
thread-loader
Webpack
npm install –save-dev thread-loader
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15const path = require("path");
module.exports = {
module: {
rules: [
{
test: /\.js$/,
include: path.resolve('src'),
use: [
"thread-loader",
// 耗时的 loader (例如 babel-loader)
],
},
],
},
};Vue-Cli已经内置
thread-loader
,thread-loader 会在多核 CPU 的机器上为Babel/TypeScript
转译开启。1
2
3module.exports = {
parallel: true,
}Type:
boolean
Default:
require('os').cpus().length > 1
是否为 Babel 或 TypeScript 使用
thread-loader
。该选项在系统的 CPU 有多于一个内核时自动启用,仅作用于生产构建。
缩小文件检索解析范围
alias
- 为避免无用的检索与递归遍历,可以使用alias指定引用时候的模块
extensions
extensions 表示需要解析的文件类型列表。
根据项目中的文件类型,定义 extensions,以覆盖 webpack 默认的 extensions,加快解析速度。
由于 webpack 的解析顺序是从左到右,因此要将使用频率高的文件类型放在左侧,如下我将
tsx
放在最左侧1
2
3
4
5module.exports = {
resolve: {
extensions: ['.tsx', '.js'], // 因为我的项目只有这两种类型的文件,如果有其他类型,需要添加进去。
}
}
noParse
- noParse,对不依赖本地代码的第三方依赖不进行解析,比如CDN引用的第三方依赖。
include
- 为 loader 指定 include,减少 loader 应用范围,仅应用于最少数量的必要模块,。
import优化
运用这个插件能在代码使用了import语法的情况下,大大提高代码的编译速度。
安装 babel-plugin-dynamic-import-node
1 | npm install --save-dev babel-plugin-dynamic-import-node |
vue-cli3
修改babel.config.js文件
1 | module.exports = { |
vue.cli2
.babelrc文件
1 | "env": { |
打包/上线运行优化
某些 utility, plugin 和 loader 都只用于生产环境。例如,在开发环境下使用 TerserPlugin
来 minify(压缩) 和 mangle(混淆破坏) 代码是没有意义的。通常在开发环境下,应该排除以下这些工具:
TerserPlugin
[fullhash]
/[chunkhash]
/[contenthash]
代码分离
https://www.cnblogs.com/Mr-Hou88888/p/16217467.html
https://blog.csdn.net/qq_41887214/article/details/121646392
代码分离code splitting是 webpack 中最引人注目的特性之一。此特性能够把代码分离到不同的 bundle 中,然后可以按需加载或并行加载这些文件。代码分离可以用于获取更小的 bundle,加快打包速度,以及控制资源加载优先级,如果使用合理,会极大影响加载时间。
多入口起点
src/index.js
1 | console.log('Hello world!'); |
src/another-module.js
1 | import _ from 'lodash' |
这个模块依赖了 lodash ,需要安装一下:
1 | npm install lodash |
webpack.config.js
1 | module.exports = { |
执行webpack
命令,可以看到报错了 ̄□ ̄||
1 | module.exports = { |
执行webpack
命令,可以看到不报错了,并且dist
输出了两个js文件
文件another.bundle.js
来源于entry.another
,即src/another.js
,文件大小为554kb
,因为被lodash
被打包进去了
文件index.bundle.js
来源于entry.index
,即src/index.js
,文件大小为1.21kb
但是,如果我们的其他入口也需要使用lodash
呢?
1 | src/index.js |
lodash
在两个引用文件中都被打包了,我们期望lodash
应该是公用的
配置 dependOn option
选项,这样可以在多个 chunk 之间共享模块
1 | module.exports = { |
执行webpack
命令,可以看到打包结果
已经提取出来common_chunk.bundle.js
,即为提取打包了lodash
公用模块
index.bundle.js
another.bundle.js
体积也变小
分离 Vendor
chunk-vendors.js :顾名思义,chunk-vendors.js 是捆绑所有不是自己的模块,而是来自其他方的模块的捆绑包,它们称为第三方模块或供应商模块。
通常,它意味着(仅和)来自项目 /node_modules 目录的所有模块,会将所有 /node_modules 中的第三方包打包到 chunk-vendors.js 中。
将所有的第三方包集中到一个文件,自然也会出现文件过大的问题。
可以看到,当前只有一个 chunk 也就是 app.js ,他是一个 entry chunk 。因为我们的 webpack 配置是这样子的:
1 | // webpack.config.js |
app.js 包含了我们的第三方库 vue 和 axios ,以及我们的业务代码 src 。
分离 Vendor,最简单方法就是:加一个 entry ( File Changes ):
1 | // webpack.config.js |
虽然 vendor.js 这个 entry chunk 包含了我们想要的 vue 和 axios ,但是细心的同学会发现, app.js 也包含了他们!为什么!?
其实这是很正常的事情:每个 entry 都包含了他自己的依赖,这样他才能作为一个入口,独立地跑起来。
很难受,事实上我们并不想 app.js 还包含了 vue 和 axios 。如果可以把他们俩相同的依赖提取出来就好了,就像这样:
SplitChunksPlugin
SplitChunksPlugin 插件可以将公共的依赖模块提取到已有的 chunk 中,或者提取到一个新生成的 chunk。让我们使用这个插件,将之前的示例中重复的 lodash 模块去除:
webpack.config.js
1 | module.exports = { |
使用 optimization.splitChunks
配置选项之后,现在应该可以看出,index.bundle.js
和 another.bundle.js
中已经移除了重复的依赖模块。需要注意的是,插件将 lodash
分离到单独的 chunk,并且将其从 main bundle 中移除,减轻了大小
CommonsChunkPlugin(已废弃)
现在,修改我们的 webpack 配置文件( File Changes ):
1 | new webpack.optimize.CommonsChunkPlugin({ |
但是!随着业务的增长,我们依赖的第三方库代码很可能会越来越多,这时候我们的 webpack.config.js 就变成这样了:
1 | module.exports = { |
vendor entry 会变成很长很长,更糟糕的是,我们每次引入了新的第三方库,都需要在 vendor 手动增加对应的包名。
动态导入懒加载
- import() 为动态加载脚本,webpack 会生成类似以上动态创建
script
标签的代码 - import 里的注释为特殊含义的魔法注释,如果不设置 webpackChunkName,加载的脚本将被按数字次序命名
如果我们想「按需加载」路由组件的话,只要改几行代码就好了。
1 | //index.js |
动态import使用最多的一个场景是懒加载(比如路由懒加载)
- 封装一个component.js,返回一个component对象;
- 我们可以在一个按钮点击时,加载这个对象;
如果你用了 Babel ,就需要装上这个插件@babel/plugin-syntax-dynamic-import:babel plugin syntax dynamic import 来解析 import() 语法。
预加载
在声明 import 时,使用下面这些内置指令,可以让 webpack 输出 “resource hint(资源提示)”,来告知浏览器:
- prefetch(预获取):将来某些导航下可能需要的资源。只会缓存资源不会解析
- preload(预加载):当前导航下可能需要资源。会缓存资源并解析
prefetch
下面这个 prefetch 的简单示例中,有一个 HomePage
组件,其内部渲染一个 LoginButton
组件,然后在点击后按需加载 LoginModal
组件。
LoginButton.js
1 | //... |
这会生成 <link rel="prefetch" href="login-modal-chunk.js">
并追加到页面头部,指示着浏览器在闲置时间预取 login-modal-chunk.js
文件。
只要父 chunk 完成加载,webpack 就会添加 prefetch hint(预取提示)。
不同
与 prefetch 指令相比,preload 指令有许多不同之处:
- preload chunk 会在父 chunk 加载时,以并行方式开始加载。prefetch chunk 会在父 chunk 加载结束后开始加载。
- preload chunk 具有中等优先级,并立即下载。prefetch chunk 在浏览器闲置时下载。
- preload chunk 会在父 chunk 中立即请求,用于当下时刻。prefetch chunk 会用于未来的某个时刻。
- 浏览器支持程度不同。
babel
动态导入
如果你用了 Babel ,就需要装上这个插件@babel/plugin-syntax-dynamic-import:babel plugin syntax dynamic import 来解析 import() 语法。
1 | // .babelrc |
console移除
babel-plugin-transform-remove-console插件,配置在babel.config.js中,vue-cli5实测可行,vue-cli3,4也可行。(尝试后,谷歌浏览器控制台仅websocket的打印输出未清除,其他打印输出都是清除干净了的)
下载依赖
npm install babel-plugin-transform-remove-console -D
or
yarn add babel-plugin-transform-remove-console -D
babel.config.js中
1 | const proPlugins = []; |
辅助代码
Babel 为编译的每个文件都插入了辅助代码,使代码体积过大!默认情况下会被添加到每一个需要它的文件中。可以将这些辅助代码作为一个独立模块,来避免重复引入。
@babel/plugin-transform-runtiome:禁用了 Babel 自动对每个文件的 runtime 注入,而是引入 @babel/plugin-transform-runtiome 并且使所有辅助代码从这里引用
先下载包:
npm i @babel/plugin-transform-runtime -D
资源模块
使用 webpack 资源模块 (asset module) 代替旧的 assets loader(如 file-loader
/url-loader
/raw-loader
等),减少 loader 配置数量。
配置方式如下:
1 | module.exports = { |
SourceMap
最佳选择是 eval-cheap-module-source-map
详细区分可至 webpack devtool 查看。
css抽离
mini-css-extract-plugin插件会将 CSS 提取到单独的文件中,为每个包含 CSS 的 JS 文件创建一个 CSS 文件,并且支持 CSS 和 SourceMaps 的按需加载
1 | const MiniCssExtractPlugin = require("mini-css-extract-plugin"); |
css压缩
1 | const MiniCssExtractPlugin = require("mini-css-extract-plugin"); |
这将仅在mode: production
生产环境
开启 CSS 优化
如果还想在开发环境
下启用 CSS 优化,optimization.minimize
设置为 true
gzip压缩
前端将文件打包成 .gz
文件,然后通过 nginx
的配置,让浏览器直接解析 .gz
文件,可以大大提升文件加载的速度,浏览器可以直接解析 .gz
文件并解压。
1 | 启用gzip压缩(需要配置nginx,可以看出压缩后的文件大小明显变化) |
js压缩
terser-webpack-plugin
关于 source maps 说明
只对
devtool
选项的source-map
,inline-source-map
,hidden-source-map
和nosources-source-map
有效。
为何如此?
eval
会包裹 modules,通过eval("string")
,而 minimizer 不会处理字符串。cheap
不存在列信息,minimizer 只产生单行,只会留下一个映射。
使用支持的devtool
值可以生成 source dmap。
parallel
使用多进程并发运行以提高构建速度。 并发运行的默认数量: os.cpus().length - 1
。并发运行可以显著提高构建速度,因此强烈建议添加此配置。对于实际环境,可以使用is-wsl
插件来判断是否支持多进程。d
webpack
terser-webpack-plugin和uglifyjs-webpack-plugin(不推荐)
不再维护 uglify-es ,并且 uglify-js 不支持 ES6 +。
terser 是 uglify-es 的一个分支,主要保留了与 uglify-es 和 uglify-js@3 的 API 和 CLI 兼容性。
webpack5 自带最新的 terser-webpack-plugin
,无需手动安装。
terser-webpack-plugin
默认开启了 parallel: true
配置,并发运行的默认数量: os.cpus().length - 1
,使用多进程并发运行压缩以提高构建速度。
1 | const TerserPlugin = require('terser-webpack-plugin'); |
vue-cli
1 | config.optimization.minimize(true)// 开启压缩js代码 |
img压缩
image-minimizer-webpack-plugin:用来压缩图片的插件
npm i image-minimizer-webpack-plugin imagemin -D
还有剩下包需要下载,有两种模式:
无损压缩
npm install imagemin-gifsicle imagemin-jpegtran imagemin-optipng imagemin-svgo -D
有损压缩
npm install imagemin-gifsicle imagemin-mozjpeg imagemin-pngquant imagemin-svgo -D
配置CDN
线上使用 cdn ,如何库有问题,项目就会有问题,除非公司有自己的 cdn 库,不过这确实也是一种优化方案,效果也还不错。它的配置也很简单,在 externals 中配置,例子:
1 | module.exports = { |
然后在 public/index.html
模板文件中引入 cdn 地址:
1 | <!DOCTYPE html> |
我这里使用的是 bootcdn 的地址,需要注意版本问题。
也可以借助 HtmlWebpackPlugin 插件来方便插入 cdn 的引入。
使用 cdn 引入的方式虽然能极大改善网页加载速度,但我还是不会用这个功能,项目还不需要非得这样的优化,也怕 cdn 不稳定。
借助 HtmlWebpackPlugin 插件来方便插入 cdn 的引入
1 | //生产环境标记 |
index.html中添加
1 | <% for (var i in htmlWebpackPlugin.options.cdn&&htmlWebpackPlugin.options.cdn.js) { %> |
插件按需加载
1. lodash
类似 import { throttle } from 'lodash'
就属于有副作用的引用,会将整个 lodash 文件进行打包。
优化方式是使用 import { throttle } from 'lodash-es'
代替 import { throttle } from 'lodash'
, lodash-es 将 Lodash 库导出为 ES 模块,支持基于 ES modules 的 tree shaking,实现按需引入。
2. ant-design
ant-design 默认支持基于 ES modules 的 tree shaking,对于 js 部分,直接引入 import { Button } from 'antd'
就会有按需加载的效果。
假如项目中仅引入少部分组件,import { Button } from 'antd'
也属于有副作用,webpack不能把其他组件进行tree-shaking。这时可以缩小引用范围,将引入方式修改为 import { Button } from 'antd/lib/button'
来进一步优化。
未引用代码移除
Tree Shaking(已经配置)
tree shaking是一个术语,用于描述移除 JavaScript 上下文中的未引用代码(dead-code)。它依赖于 ES2015 模块语法的 静态结构 特性,例如
import和
export
Tree Shaking 只支持 ESM 的引入方式,不支持 Common JS 的引入方式。
- ESM: export + import
- Common JS: module.exports + require
sideEffects 有三种情况
sideEffects:true 所有文件都有副作用,全都不可 tree-shaking
sideEffects:false 有这些文件有副作用,所有其他文件都可以 tree-shaking,但会保留这些文件
sideEffects:[] 部分 tree-shaking , 除了数组外都 tree-shaking
“side effect(副作用)” 的定义是,在导入时会执行特殊行为的代码,而不是仅仅暴露一个 export 或多个 export。举例说明,例如 polyfill,它影响全局作用域,并且通常不提供 export。
如果你的代码确实有一些副作用,可以改为提供一个数组:
1 | { |
所有导入文件都会受到 tree shaking 的影响。这意味着,如果在项目中使用类似 css-loader
并 import 一个 CSS 文件,则需要将其添加到 side effect 列表中,以免在生产模式中无意中将它删除:
1 | { |
IgnorePlugin
- 这是webpack内置插件
- 这个插件的作用是:忽略第三方包指定目录,让这些指定目录不要被打包进去
1 | //虽然我设置了语言为中文,但是在打包的时候,是会将所有语言都打包进去的。这样就导致包很大,打包速度又慢 |
css
打包时把没有用的 CSS 代码摇走,可以大幅减少打包后的 CSS 文件大小。使用 purgecss-webpack-plugin 对 CSS Tree Shaking。
插件
autoprefixer兼容css
webpak 引入autoprefixer,自动加上各种前缀让不同的浏览器得以支持
tip
报错hardsource:a3a09f18: Could not freeze ./node_modules/mini-css-extract-plugin/dist/loader.js
https://blog.csdn.net/weixin_43840202/article/details/124006937
babel
https://zhuanlan.zhihu.com/p/498461706
https://juejin.cn/post/7051555571451265038#heading-10
https://mp.weixin.qq.com/s/qT0y0JlDP7bxESRKip-f5w
Babel 是一个 JavaScript 编译器
Babel 是一个工具链,主要用于将采用 ECMAScript 2015+ 语法编写的代码转换为向后兼容的 JavaScript 语法,以便能够运行在当前和旧版本的浏览器或其他环境中。
简单来说就是把 JavaScript 中 es2015+ 的新语法转化为 es5,让低端运行环境(如浏览器和 node )能够认识并执行。比如在代码中使用了 ES6 的箭头函数,但是这种写法会在 IE 浏览器中报错,为了让代码能在IE中运行,就需要将代码编译成IE支持的写法,这就是babel的主要工作。
babel 在转译的时候,会将源代码分成 syntax 和 api 两部分来处理:
- syntax:类似于展开对象、optional chain、let、const 等语法
- api:类似于 [1,2,3].includes 等函数、方法
工作原理
babel的主要工作流程分为三个阶段,解析(parse),转换(transform),生成(generate)
如下图所示:
解析
通过 @babel/parser
把源代码字符串转成抽象语法树(AST),在解析过程中主要是两个阶段:词法分析和语法分析
词法分析
词法分析阶段把字符串形式的代码解析成一个个具有实际意义的语法单元组成的数据,这种数据被称之为令牌(tokens)流。
语法分析
语法解析器把 Tokens 转换为抽象语法树 AST
被解析语法当中具备实际意义的最小单元,举个例子:’2008年奥运会在北京举行’,这句话不论词性主谓等关系,会把这句话拆分成: 2018年、奥运会、在、北京、举行,这就是分词,把整句话拆分成有意义的最小颗粒,这些最小颗粒不能再继续拆分,否则会失去表达意义。
JavaScript 中常见的语法单元如下:
- 空白:JS中连续的空格、换行、缩进等这些如果不在字符串里,就没有任何实际逻辑意义,所以把连续的空白符直接组合在一起作为一个语法单元。
- 注释:行注释或块注释,虽然对于人类来说有意义,但是对于计算机来说知道这是个“注释”就行了,并不关心内容,所以直接作为一个不可再拆的语法单元
- 字符串:对于机器而言,字符串的内容只是会参与计算或展示,里面再细分的内容也是没必要分析的
- 数字:JS语言里就有16、10、8进制以及科学表达法等数字表达语法,数字也是个具备含义的最小单元
- 标识符:没有被引号扩起来的连续字符,可包含字母、_、$、及数字(数字不能作为开头)。标识符可能代表一个变量,或者true、false这种内置常量、也可能是if、return、function这种关键字
- 运算符:+、-、*、/、>、<等等
- 括号:(…)可能表示运算优先级、也可能表示函数调用,分词阶段并不关注是哪种语义,只把“(”或“)”当做一种基本语法单元
- 还有其他:如中括号、大括号、分号、冒号、点等等不再一一列举
什么是 AST(Abstract Syntax Tree)?
抽象语法树 ,它是源代码语法结构的一种抽象表示。它以树状的形式表现编程语言的语法结构,树上的每个节点都表示源代码中的一种结构。
举例:
console.log(‘你好,babel’)解析成 AST 后的 json格式如下(转换可通过https://astexplorer.net/#/KJ8AjD6maa进行在线转换):
类似于
1 | { |
类似于这样的结构叫做节点,一个 AST 是由单个或者多个这样的节点构成的,节点内部还可以有子节点,构成一棵语法树。详细请见:https://juejin.cn/post/6844904035271573511
转换
通过@babel/traverse
遍历抽象语法树(AST),并调用Babel配置文件中的插件,对抽象语法树(AST)进行增删改
生成
通过@babel/generator
把转换后的抽象语法书(AST)生成目标代码
babel插件
babel/core
是 babel的核心,主要作用就是根据我们的配置文件转换代码,配置文件一般是.babelrc(静态文件)或 babel.config.js(可编程),主要作用如下:
- 加载和处理配置(config)
- 加载插件
- 调用
Parser
进行语法解析,生成AST
- 调用
Traverser
遍历AST,并使用访问者模式
应用’插件’对 AST 进行转换 - 生成代码,包括SourceMap转换和源代码生成
核心周边
Parser(
@babel/parser
) : 将源代码解析为 AST 。Traverser(
@babel/traverse
) : 对 AST 进行遍历,转换插件
会通过它获取感兴趣的AST节点,对节点继续操作Generator(
@babel/generator
) : 将 AST 转换为源代码babel-loader: 负责 es6 语法转化
babel-preset-env: 包含 es6、7 等版本的语法转化规则
babel的插件分类
语法插件:
语法插件仅允许 babel 解析语法,不做转换操作;当添加语法插件后,使得babel能够解析更多的语法
例如:当我们定义或者调用方法时,最后一个参数之后是不允许增加逗号的,如 func
(param1, param2,)
就是非法的。如果源码是这种写法,经过 babel 之后就会提示语法错误。但最近的 JS 提案中已经允许了这种新的写法(让代码 diff 更加清晰)。为了避免 babel 报错,就需要增加语法插件
babel-plugin-syntax-trailing-function-commas
转译插件:
当添加转译插件后,会将源代码进行转译输出。
例如:箭头函数
(a) => a
就会转化为function (a) {return a}
。完成这个工作的插件叫做babel-plugin-transform-es2015-arrow-functions
。
babel-loader
负责 es6 语法转化
babel/preset-env
包含 es6、7 等版本的语法转化规则
babel-polyfill
babel只负责语法转换,比如将ES6的语法转换成ES5。但如果有些对象、方法,浏览器本身不支持,比如:
- 全局对象:Promise、WeakMap 等。
- 全局静态函数:Array.from、Object.assign 等。
- 实例方法:比如 Array.prototype.includes 等。
此时,需要引入@babel/polyfill来模拟实现这些对象、方法。需要安装在生产依赖中
主要缺点:
- 使用 babel-polufill 导致打出来的包体积比较大,因为 babel-polyfill 是一个整体,把所有方法都会加到原型链上。比如使用了 Array.from,但它会把 Object.defineProperty也给加上,属于一种浪费,要解决这个问题,可以通过单独使用 core-js 的某个类库解决,core-js 是分开的
- babel-polyfill 会污染全局变量,给很多的原型链上作出修改,所以会比较倾向于使用 babel-plugin-transform-runtime
注意:如果代码中使用了较高版本的 js实例方法, 比如 [1,2,3].includes(1),此时还是需要使用 polyfill
babel-runtime
提供各种 helper 函数
babel-plugin-transform-runtime
https://blog.windstone.cc/es6/babel/@babel/plugin-transform-runtime.html
该插件会开启对 Babel 注入的helper code
(helper
可译为辅助函数)的复用,以节省代码体积。
Babel 使用了一些很小的helpers
作为通用函数比如_extend
。默认情况下,这些helpers
会被添加到需要的每一个文件里。这种代码重复有时是不需要的,尤其是你的应用分散在多个文件里。这也是@babel/plugin-transform-runtime
插件出现的原因:所有的helpers
将引用@babel/runtime
模块以避免在编译输出文件里的代码重复。@babel/runtime
将编译到构建输出文件里。
@babel/runtime: 提供各种 helper 函数
@babel/plugin-transform-runtime: 自动引入 helper 函数
预设
插件只对单个功能进行转换,当配置插件比较多时,就可以封装成预设(presets)以此来简化插件的使用,预设简单说就是一组原先设定好的插件,是一组插件的集合,比如 @babel/preset-react 包含以下插件:
- @babel/plugin-syntax-jsx
- @babel/plugin-transform-react-jsx
- @babel/plugin-transform-react-display-name
比如 es2015 是一套规范,包含很多转译插件。如果每次要开发者一个个添加并安装,配置文件很长不说,npm install
的时间也会很长,为了解决这个问题,babel 还提供了一组插件的集合。因为常用,所以不必重复定义 & 安装。
常见的preset 分为以下几种
官网常推荐的 详见:https://babel.docschina.org/docs/en/babel-preset-env/
@babel/preset-env。主要可以根据配置的目标浏览器或者运行环境来自动将ES2015+的代码转换为es5。
@babel/preset-react 。react框架需要的
@babel/preset-flow flow需要的。Flow 是一个静态类型检测工具,进行类型检查,类似于ts
@babel/preset-typescript typescript。需要的
执行顺序原则如下:
- Plugin 会运行在 Preset 之前。
- Plugin 会从前到后顺序执行。
- Preset 的顺序则从后向前。
preset 的逆向顺序主要是为了保证向后兼容,因为大多数用户的编写顺序是 ['es2015', 'stage-0']
。这样必须先执行 stage-0
才能确保 babel 不报错。因为低一级的 stage 会包含所有高级 stage 的内容
@babel/preset-env
可以根据配置的目标浏览器或者运行环境来自动将 ES5+的代码转换为 ES5
ESM
ESM:基于浏览器 ES 模块的构建工具
browserify
、webpack
、rollup
、parcel
这些工具的思想都是递归循环依赖,然后组装成依赖树,优化完依赖树后生成代码。
但是这样做的缺点就是慢,需要遍历完所有依赖,即使 parcel
利用了多核,webpack
也支持多线程,在打包大型项目的时候依然慢可能会用上几分钟,存在性能瓶颈。所以基于浏览器原生 ESM
的运行时打包工具出现:
仅打包屏幕中用到的资源,而不用打包整个项目,开发时的体验相比于 bundle
类的工具只能用极速来形容。(实际生产环境打包依然会构建依赖方式打包)
bundleless打包原理
bundleless
类运行时打包工具的启动速度是毫秒级的,因为不需要打包任何内容,只需要起两个server
,一个用于页面加载,另一个用于HMR
的WebSocket
,当浏览器发出原生的ES module
请求,server
收到请求只需编译当前文件后返回给浏览器不需要管依赖。
bundleless
工具在生产环境打包的时候依然bundle
构建所有依赖视图的方式,vite 是利用 rollup
打包生产环境的 js 的。
原理拿 vite
举例:
vite
在启动服务器后,会预先以所有 html 为入口,使用 esbuild
编译一遍,把所有的 node_modules
下的依赖编译并缓存起来,例如vue
缓存为单个文件。
当打开在浏览器中输入链接,渲染index.html
文件的时候,利用浏览器自带的ES module
来请求文件。
vite 收到一个src/main.js
的 http
文件请求,使用esbuild
开始编译main.js
,这里不进行main.js
里面的依赖编译。
浏览器获取到并编译main.js
后,再次发出 2 个请求,一个是 vue
的请求,因为前面已经说了 vue 被预先缓存下来,直接返回缓存给浏览器,另一个是App.vue
文件,这个需要@vitejs/plugin-vue
来编译,编译完成后返回结果给浏览器(@vitejs/plugin-vue
会在脚手架创建模板的时候自动配置)。
因为是基于浏览器的ES module
,所以编译过程中需要把一些 CommonJs
、UMD
的模块都转成 ESM
。
Vite
同时利用 HTTP
头来加速整个页面的重新加载(再次让浏览器为我们做更多事情):源码模块的请求会根据 304 Not Modified
进行协商缓存,而依赖模块请求则会通过 Cache-Control: max-age=31536000,immutable
进行强缓存,因此一旦被缓存它们将不需要再次请求,即使缓存失效只要服务没有被杀死,编译结果依然保存在程序内存中也会很快返回。
上面多次提到了esbuild
,esbuild
使用 go
语言编写,所以在 i/o
和运算运行速度上比解释性语言 NodeJs
快得多,esbuild
号称速度是 node
写的其他工具的 10~100 倍。
ES module
依赖运行时编译的概念 + esbuild
+ 缓存 让 vite
的速度远远甩开其他构建工具。
snowpack
vite
https://juejin.cn/post/7004784113706090526
vite配置多入口,多页面应用:https://juejin.cn/post/6985500915969032222
@vitejs/plugin-vue:
通过@vitejs/plugin-vue这个插件来支持Vue
vite-plugin-node-polyfills:https://juejin.cn/post/7145383283038093319
项目使用的两个第三方库中有使用到
node
环境的api
,vite
本身并不会自动引入这些polyfills,所以需要自己添加他们
比较
Webpack与Vite对⽐
Vite特点: 轻量、按需打包rollup 、HMR (热渲染依赖)
Webpack:由于需要预先编译打包所以启动速度慢,但⽣态成熟。
对⽐
webpack :强调对web开发的⽀持,尤其是内置了HMR的⽀持,插件系统⽐较强⼤,对各种模块系统兼容性最佳
(amd,cjs,umd,esm等,兼容性好的有点过分了,这实际上有利有弊,导致⾯向webpack编程),有丰富的⽣态,缺
点是产物不够⼲净,产物不⽀持⽣成esm格式, 插件开发上⼿较难,不太适合库的开发。
rollup: 强调对库开发的⽀持,基于ESM模块系统,对tree shaking有着良好的⽀持,产物⾮常⼲净,⽀持多种输出
格式,适合做库的开发,插件api⽐较友好,缺点是对cjs⽀持需要依赖插件,且⽀持效果不佳需要较多的hack,不
⽀持HMR,做应⽤开发时需要依赖各种插件。
esbuild: 强调性能,内置了对css、图⽚、react、typescript等内置⽀持,编译速度特别快(是webpack和rollup速
度的100倍+),缺点是⽬前插件系统较为简单,⽣态不如webpack和rollup成熟。
微前端
single-spa
概念
Single-spa 从现代框架组件生命周期中获得灵感,将生命周期应用于整个应用程序。 它脱胎于 Canopy 使用 React + React-router 替换 AngularJS + ui-router 的思考,避免应用程序被束缚。
Single-spa 包括以下内容:
Applications,每个应用程序本身就是一个完整的 SPA (某种程度上)。 每个应用程序都可以响应 url 路由事件,并且必须知道如何从 DOM 中初始化、挂载和卸载自己。 传统 SPA 应用程序和 Single SPA 应用程序的主要区别在于,它们必须能够与其他应用程序共存,而且它们没有各自的 html 页面。
例如,React 或 Angular spa 就是应用程序。 当激活时,它们监听 url 路由事件并将内容放在 DOM上。 当它们处于非活动状态时,它们不侦听 url 路由事件,并且完全从 DOM 中删除。
一个 single-spa-config配置, 这是html页面和向Single SPA注册应用程序的JavaScript。每个应用程序都注册了三件东西
- A name
- A function (加载应用程序的代码)
- A function (确定应用程序何时处于活动状态/非活动状态)
主题 | 应用程序 | 沙箱 | 公共模块 |
---|---|---|---|
路由 | 有多个路由 | 无路由 | 无路由 |
API | 声明API | 必要的API | 没有single-spa API |
渲染UI | 渲染UI | 渲染UI | 不直接渲染UI |
生命周期 | single-spa管理生命周期 | 自定义管理生命周期 | 没有生命周期 |
什么时候使用 | 核心构建模块 | 仅在多个框架中需要 | 共享通用逻辑时使用 |
创建
创建类型
https://zh-hans.single-spa.js.org/docs/separating-applications
初始化
- 创建一个 html 文件:
1 | <html> |
- 创建一个single-spa-config。查看文档以获取更多详细信息。
1 | //main.js |
- 创建一个应用程序。查看文档以获取更多详细信息。
1 | //app1.js |
注册应用的生命周期
在一个 single-spa 页面,注册的应用会经过下载(loaded)、初始化(initialized)、被挂载(mounted)、卸载(unmounted)和unloaded(被移除)等过程。single-spa会通过“生命周期”为这些过程提供钩子函数。
生命周期函数是 single-spa 在注册的应用上调用的一系列函数,single-spa 会在各应用的主文件中,查找对应的函数名并进行调用。
注:
bootstrap
,mount
, andunmount
的实现是必须的,unload
则是可选的- 生命周期函数必须有返回值,可以是Promise或者
async
函数 - 如果导出的是函数数组而不是单个函数,这些函数会被依次调用,对于promise函数,会等到resolve之后再调用下一个函数
- 如果 single-spa 未启动,各个应用会被下载,但不会被初始化、挂载或卸载。
注
在single-spa 生态中有各个主流框架对于生命周期函数的实现,这些文档有助于理解这些helper执行的操作,也有助于你自己实现生命周期函数。
生命周期参数
生命周期函数使用”props” 传参,这个对象包含single-spa相关信息和其他的自定义属性。
1 | function bootstrap(props) { |
内置参数
每个生命周期函数的入参都会保证有如下参数:
name
: 注册到 single-spa 的应用名称singleSpa
: 对singleSpa 实例的引用, 方便各应用和类库调用singleSpa提供的API时不再导入它。 可以解决有多个webpack配置文件构建时无法保证只引用一个singleSpa实例的问题。mountParcel
: mountParcel 函数.
自定义参数
除single-spa提供的内置参数外,还可以指定自定义参数,在调用各个生命周期函数时传入。指定方法是在调用registerApplication
时,传入第4个参数。
root.application.js
1 | singleSpa.registerApplication({ |
app1.js
1 | export function mount(props) { |
可能使用到的场景:
- 各个应用共享一个公共的 access token
- 下发初始化信息,如渲染目标
- 传递对事件总线(event bus)的引用,方便各应用之间进行通信
注意如果没有提供自定义参数,则props.customProps
默认会返回一个空对象。
下载(load)
注册的应用会被懒加载,这指的是该应用的代码会从服务器端下载并执行。注册的应用在activity function 第一次返回真值(truthy value)时,下载动作会发生。在下载过程中,建议尽可能执行少的操作,可以在bootstrap
生命周期之后再执行各项操作。若确实有在下载时需要执行的操作,可将代码放入子应用入口文件中,但要放在各导出函数的外部。例如:
1 | console.log("The registered application has been loaded!"); |
初始化
这个生命周期函数会在应用第一次挂载前执行一次。
1 | export function bootstrap(props) { |
挂载
每当应用的activity function返回真值,但该应用处于未挂载状态时,挂载的生命周期函数就会被调用。调用时,函数会根据URL来确定当前被激活的路由,创建DOM元素、监听DOM事件等以向用户呈现渲染的内容。任何子路由的改变(如hashchange
或popstate
等)不会再次触发mount
,需要各应用自行处理。
1 | export function mount(props) { |
卸载
每当应用的activity function返回假值,但该应用已挂载时,卸载的生命周期函数就会被调用。卸载函数被调用时,会清理在挂载应用时被创建的DOM元素、事件监听、内存、全局变量和消息订阅等。
1 | export function unmount(props) { |
移除
“移除”生命周期函数的实现是可选的,它只有在unloadApplication被调用时才会触发。如果一个已注册的应用没有实现这个生命周期函数,则假设这个应用无需被移除。
移除的目的是各应用在移除之前执行部分逻辑,一旦应用被移除,它的状态将会变成NOT_LOADED,下次激活时会被重新初始化。
移除函数的设计动机是对所有注册的应用实现“热下载”,不过在其他场景中也非常有用,比如想要重新初始化一个应用,且在重新初始化之前执行一些逻辑操作时。
1 | export function unload(props) { |
超时
默认情况下,所有注册的应用遵循全局超时配置,但对于每个应用,也可以通过在主入口文件导出一个timeouts
对象来重新定义超时时间。如:
app-1.main-entry.js
1 | export function bootstrap(props) {...} |
注意millis
指的是最终控制台输出警告的毫秒数,warningMillis
指的是将警告打印到控制台(间隔)的毫秒数。
切换应用时过渡
如果你想为应用在挂载和卸载时加一些过渡效果(动画效果等),则需要将其和bootstrap
, mount
, 和 unmount
等生命周期函数关联。这个single-spa 过渡仓库是个小demo,展示了生命周期之间切换时如何过渡。
对于已经挂载的应用,各个页面之间的过渡效果可由应用本身自行处理,如基于React创建的项目可使用using react-transition-group实现过渡效果。
API
https://zh-hans.single-spa.js.org/docs/api
registerApplication注册
1 | singleSpa.registerApplication({ |
name
registerApplication
的第一个参数表示应用名称,name
必须是string类型
Loading Function or Application
registerApplication
的第二个参数可以是一个Promise类型的 加载函数,也可以是一个已经被解析的应用。
加载函数
registerApplication的第二个参数必须是返回promise的函数((或“async function”方法)。这个函数没有入参,会在应用第一次被下载时调用。返回的Promise resolve之后的结果必须是一个可以被解析的应用。常见的实现方法是使用import加载:() => import(‘/path/to/application.js’)
Application
你可以选择将一个已经被解析过的应用作为registerApplication
的第二个参数,这个应用其实是一个包含各个生命周期函数的对象。我们既可以从另外一个文件中引入该对象,也可以在single-spa的配置文件中定义这个对象。
1 | const application = { |
激活函数
registerApplication
的第三个参数需要是一个纯函数,window.location
会作为第一个参数被调用,当函数返回的值为真(true)值时,应用会被激活。通常情况下,Activity function会根据window.location
/后面的path来决定该应用是否需要被激活。
另外一种场景是single-spa根据顶级路由查找应用,而每个应用会处理自身的子路由。 在以下场景,single-spa会调用应用的activity function
在以下情况下,single-spa将调用每个应用的活动函数:
hashchange
orpopstate
事件触发时pushState
orreplaceState
被调用时- 在single-spa上手动调用[
triggerAppChange
] 方法 checkActivityFunctions
方法被调用时
路径前缀会匹配url,允许以下每一种前缀:
‘/app1’
‘/users/:userId/profile’
✅ https://app.com/users/123/profile
✅ https://app.com/users/123/profile/sub-profile/
‘/pathname/#/hash’
✅ https://app.com/pathname/#/hash
✅ https://app.com/pathname/#/hash/route/nested
[‘/pathname/#/hash’, ‘/app1’]
✅ https://app.com/pathname/#/hash/route/nested
✅ https://app.com/app1/anything/everything
自定义属性
registerApplication
函数可选的第四个参数是 custom props。这个参数会传递给 single-spa 的 lifecycle
函数。自定义属性可以是一个对象,也可以是一个返回Object的函数。如果自定属性是一个函数,函数的参数是应用的名字(application name)和当前window.location
。
start
start()方法
必须被single-spa配置文件的js调用,这时应用才会被真正挂载。在start
被调用之前,应用先被下载,但不会初始化/挂载/卸载。start
方法可以协助我们更好提升应用的性能。举个例子,我们可能会马上注册一个应用(为了立刻下载代码),但不能马上就在DOM节点上挂载该应用,而是需要等一个AJAX请求(可能会获取用户的登录信息)完成后,再根据结果进行挂载。这种情况下,最佳实践是先调用registerApplication
,等AJAX请求完成后再调用start
1 | //single-spa-config.js |
tip
公共模块
https://zh-hans.single-spa.js.org/docs/recommended-setup#%E6%A6%82%E8%BF%B0
同时注册两个路由??
emm…也是可以的。 如果你能用正确的方式来实现,这件事并不可怕。一旦你正确实现了,这将非常非常厉害。一种实现方式是为每个app创建一个<div id="app name"></div>
,这样这两个应用就不会同时修改相同的DOM节点了。[考虑一个path变动,同时有两个应用被激活的场景,译者注]
一个多应用的的例子看着就像这样:
1 | <div id="single-spa-application:app-name"></div> |
项目构建
husky + lint-staged
使用husky
+ lint-staged
助力团队编码规范, husky&lint-staged 安装推荐使用 mrm
, 它将根据 package.json
依赖项中的代码质量工具来安装和配置 husky 和 lint-staged,因此请确保在此之前安装并配置所有代码质量工具,如 Prettier 和 ESlint
首先安装 mrm
1 | npm i mrm -D --registry=https://registry.npm.taobao.org |
husky
是一个为 git 客户端增加 hook
的工具。安装后,它会自动在仓库中的 .git/
目录下增加相应的钩子;比如 pre-commit
钩子就会在你执行 git commit
的触发。
那么我们可以在 pre-commit
中实现一些比如 lint 检查
、单元测试
、代码美化
等操作。当然,pre-commit
阶段执行的命令当然要保证其速度不要太慢,每次 commit 都等很久也不是什么好的体验。
lint-staged
,一个仅仅过滤出 Git 代码暂存区文件(被 git add
的文件)的工具;这个很实用,因为我们如果对整个项目的代码做一个检查,可能耗时很长,如果是老项目,要对之前的代码做一个代码规范检查并修改的话,这可能就麻烦了呀,可能导致项目改动很大。
所以这个 lint-staged
,对团队项目和开源项目来说,是一个很好的工具,它是对个人要提交的代码的一个规范和约束
安装 lint-staged
mrm
安装lint-staged
会自动
把husky
一起安装下来
1 | npx mrm lint-staged |
TS
配置
https://blog.csdn.net/weixin_50763257/article/details/125755802
https://zhuanlan.zhihu.com/p/285270177
npm install -g typescript
tsc –init //生成 tsconfig.json
1
2
3
4
5
6
7
8
9
10
11
12{
"compilerOptions": {
"outDir": "./built/",
"sourceMap": true,
"strict": true,
"noImplicitReturns": true,
"module": "es2015",
"moduleResolution": "node",
"target": "es5",
},
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"]
}
alias
1 | //tsconfig.json |
.d.ts声明文件
声明文件也叫做描述文件,以d.ts结尾的文件名,比如xxx.d.ts。声明文件主要给ts编译器用的。
开发中不可避免要引用其它第三方的 js库。这时TS就对引入变量的具体类型不明确了,为了告诉TS变量的类型,因此就有了.d.ts (d即declare),ts的声明文件。
顶级声明declare
.d.ts
的顶级声明必须以declare
开头以declare声明的变量和模块后,其他地方不需要引入,就可以直接使用了
注意我们需要在配置文件下,引入声明文件
1
2
3
4
5
6{
"compilerOptions": {
...
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"]
...
}
普通类型声明
1 | declare let age: number; |
声明类型
1 | declare type Asd { |
在include包含的文件范围内可以直接使用Asd这个type
外部枚举
1 | declare enum Seasons { |
命名空间
- 如果一个全局变量有很多子属性,就可以使用namespace。
- 声明文件里的namespace表示一个全局变量,包含很多个子属性
- 在命名空间内部不需要再使用declare
1 | // 模拟 jQuery 的 $ |
declare声明模块
1 | declare module '*.css'; |
这样,我们可以在ts中引入相关的文件而不报错了
注意
declare
与export
不要同级使用,不然的话,声明文件就需要导入了- 在声明文件中
type
与interface
也可以不用加declare
,效果相同
给Window增加自定义属性
给Window增加一个简单的类型声明
创建一个 xxx.d.ts
文件, 使用 declare
声明类型 <注意:**此文件中不可以具有 import
等导入方法**>
1 | <env.d.ts> |
给Window增加一个复杂的类型声明
因为在声明文件中使用 import
会导致被当作一个模块导致类型声明失效,如果我们要给Window增加一个已经声明好的类型就需要先创建一个文件用于定义全局命名空间,我们可以在命名空间中引入类型
创建 xxx.d.ts
文件 -> 创建命名空间 -> 在Window声明文件中使用命名空间定义的类型
1 | <window.d.ts> |
tip
在script 标签中引入ts后,会产生JSX语法错误
“jsx”: “preserve”,
找不到模块“XXX.vue”或其相应的类型声明
在根目录中创建 shims.d.ts文件
1
2
3
4
5
6declare module '*.vue' {
import { ComponentOptions } from 'vue'
const componentOptions: ComponentOptions
export default componentOptions
}
declare module '*'也可以修改tsconfig.json 中替换 declare module ‘*’
1
2"noImplicitAny": false,
"allowJs": true,
路由文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26// 当设置 true 的时候该路由不会在侧边栏出现 如401,login等页面,或者如一些编辑页面/edit/1
hidden: true // (默认 false)
//当设置 noRedirect 的时候该路由在面包屑导航中不可被点击
redirect: 'noRedirect'
// 当你一个路由下面的 children 声明的路由大于1个时,自动会变成嵌套的模式--如组件页面
// 只有一个时,会将那个子路由当做根路由显示在侧边栏--如引导页面
// 若你想不管路由下面的 children 声明的个数都显示你的根路由
// 你可以设置 alwaysShow: true,这样它就会忽略之前定义的规则,一直显示根路由
alwaysShow: true
name: 'router-name' // 设定路由的名字,一定要填写不然使用<keep-alive>时会出现各种问题
meta: {
roles: ['admin', 'editor'] // 设置该路由进入的权限,支持多个权限叠加
title: 'title' // 设置该路由在侧边栏和面包屑中展示的名字
icon: 'svg-name' // 设置该路由的图标,支持 svg-class,也支持 el-icon-x element-ui 的 icon
noCache: true // 如果设置为true,则不会被 <keep-alive> 缓存(默认 false)
breadcrumb: false // 如果设置为false,则不会在breadcrumb面包屑中显示(默认 true)
affix: true // 如果设置为true,它则会固定在tags-view中(默认 false)
// 当路由设置了该属性,则会高亮相对应的侧边栏。
// 这在某些场景非常有用,比如:一个文章的列表页路由为:/article/list
// 点击文章进入文章详情页,这时候路由为/article/1,但你想在侧边栏高亮文章列表的路由,就可以进行如下设置
activeMenu: '/article/list'
}
scp2自动化部署
https://blog.csdn.net/weixin_41305441/article/details/107108429
1 | const client = require('scp2'); |
axios构建
概念
增post 删delete 改put 查get
OPTIONS请求方法的主要用途有两个:
1、获取服务器支持的HTTP请求方法;也是黑客经常使用的方法。
2、当发起跨域请求时,由于安全原因,触发一定条件时浏览器会在正式请求之前自动先发起OPTIONS请求,即CORS预检请求,服务器若接受该跨域请求,浏览器才继续发起正式请求。
重复调用
https://www.jb51.net/article/221311.htm
搭建
vue
1 | import Vue from 'vue' |
react
1 | import axios from "axios"; |
国际化 i18n
https://mp.weixin.qq.com/s/NcKVhpKNTnX6E8Ei3Y5LAw
换肤
css
使用var,var() 函数用于插入自定义的属性值
:root是一个伪类,表示文档根元素(html),所有主流浏览器均支持 :root 选择器,除了 IE8 及更早的版本。
1 |
|
less
lessOptions配置modifyVars,globalVars
scss
注入全局样式
1 | sass-loader v8-,这个选项名是 "data" |
图片
https://shengchangwei.github.io/optimizing-images/
概念
- 有损(
Lossless
)和无损(Lossy
):一般我们聊到图片的时候经常会听到别人提到有损或者无损的字眼,那么什么是有损和无损呢?无损就是图片在压缩保存后虽然占用的存储更小了,但是图像的呈现质量依然如旧;而有损则是相反,图像随着一次又一次的压缩后,质量会变得越来越差。 - 索引色(
Indexed color
)和直接色(Direct color
):按照不同的颜色深度可以将颜色分为索引色和直接色。索引色就是图像作者指定图片的用色总共不超过256种;而直接色则是对颜色使用没有限制,可以存在成千上万的颜色值。 - 光栅格式(
raster
)和矢量格式(vector
):图像根据信息的表示方式可以分为光栅图和矢量图。光栅图也叫作位图,点阵图或者像素图,图的最小单位是由一个个带颜色的像素组合而成,在Photoshop
里把图片放到最大,将看到许许多多的像素方块,所以光栅图在伸缩的时候图像可能会失真;而矢量图则是以由点、线和一些几何图形为基础,通过数学计算来排列组合而成,这种图在伸缩的时候能完好的保护质量。
JPEG/JPG
关键字
有损压缩、体积小、加载快、不支持透明
优点
优点:JPG 最大的特点是有损压缩。这种高效的压缩算法使它成为了一种非常轻巧的图片格式。另一方面,即使被称为“有损”压缩,JPG的压缩方式仍然是一种高质量的压缩方式:当我们把图片体积压缩至原有体积的 50% 以下时,JPG 仍然可以保持住 60% 的品质。此外,JPG 格式以 24 位存储单个图,可以呈现多达 1600 万种颜色,足以应对大多数场景下对色彩的要求,这一点决定了它压缩前后的质量损耗并不容易被我们人类的肉眼所察觉——前提是你用对了业务场景。
缺点
有损压缩在在大图,如背景图、轮播图确实很难露出马脚,但当它处理矢量图形和 Logo 等线条感较强、颜色对比强烈的图像时,人为压缩导致的图片模糊会相当明显。
此外,JPEG 图像不支持透明度处理,透明图片需要召唤 PNG 来呈现。
应用场景
JPG 适用于呈现色彩丰富的图片,在我们日常开发中,JPG 图片经常作为大的背景图、轮播图或 Banner 图出现。使用 JPG 呈现大图,既可以保住图片的质量,又不会带来令人头疼的图片体积,是当下比较推崇的一种方案。
PNG
关键字
无损压缩、质量高、体积大、支持透明
优点
PNG(可移植网络图形格式)是一种无损压缩的高保真的图片格式。
PNG 图片具有比 JPG 更强的色彩表现力,对线条的处理更加细腻,对透明度有良好的支持。它弥补了上文我们提到的 JPG 的局限性,唯一的 BUG 就是体积太大。
缺点
PNG 体积较大,较旧的浏览器和程序可能不支持 PNG 文件。
应用场景
复杂的、色彩层次丰富的图片,用 PNG 来处理的话,成本会比较高,我们一般会交给 JPG 去存储。考虑到 PNG 在处理线条和颜色对比度方面的优势,我们主要用它来呈现小的 Logo、颜色简单且对比强烈的图片或背景等。
扩展
PNG-8 与 PNG-24 的选择题
什么时候用 PNG-8,什么时候用 PNG-24,这是一个问题。
理论上来说,当你追求最佳的显示效果、并且不在意文件体积大小时,是推荐使用 PNG-24 的。
但实践当中,为了规避体积的问题,我们一般不用PNG去处理较复杂的图像。当我们遇到适合 PNG 的场景时,也会优先选择更为小巧的 PNG-8。
如何确定一张图片是该用 PNG-8 还是 PNG-24 去呈现呢?好的做法是把图片先按照这两种格式分别输出,看 PNG-8 输出的结果是否会带来肉眼可见的质量损耗,并且确认这种损耗是否在我们(尤其是你的 UI 设计师)可接受的范围内,基于对比的结果去做判断。
SVG
关键字
文本文件、体积小、不失真、兼容性好
优点
和性能关系最密切的一点就是:SVG 与 PNG 和 JPG 相比,文件体积更小,可压缩性更强。 当然,作为矢量图,它最显著的优势还是在于图片可无限放大而不失真这一点上。这使得 SVG 即使是被放到视网膜屏幕上,也可以一如既往地展现出较好的成像品质——1 张 SVG 足以适配 n 种分辨率。此外,SVG 是文本文件。我们既可以像写代码一样定义 SVG,把它写在 HTML 里、成为 DOM 的一部分,也可以把对图形的描述写入以 .svg 为后缀的独立文件(SVG 文件在使用上与普通图片文件无异)。这使得 SVG 文件可以被非常多的工具读取和修改,具有较强的灵活性。
缺点
一方面是它的渲染成本比较高,这点对性能来说是很不利的。另一方面,SVG 存在着其它图片格式所没有的学习成本(它是可编程的)。
应用场景
SVG 是可编程的,因此可以在WEB项目中的平面图绘制,如需要绘制线,多边形,图片等。 SVG也可作为iocn图标,个人认为作为图标使用的话, 在线矢量图形库已经满足大部分的业务需求了。
svg改颜色
Base64
关键字
文本文件、依赖编码、小图标解决方案
优点
base64就是一串字符串码表示的图片,在加载页面和js时一块加载出来,减少了加载图片时的http请求。加载一张图片时会发起一次http请求,http请求每次建立都会需要一定的时间,对于加载一张小图来说,下载图片所需的时间会比建立http请求的时间要短。
缺点
既然 Base64 这么棒,我们何不把大图也换成 Base64 呢?
这是因为,Base64 编码后,图片大小会膨胀为原文件的 4/3(这是由 Base64 的编码原理决定的)。如果我们把大图也编码到 HTML 或 CSS 文件中,后者的体积会明显增加,即便我们减少了 HTTP 请求,也无法弥补这庞大的体积带来的性能开销,得不偿失。 在传输非常小的图片的时候,Base64 带来的文件体积膨胀、以及浏览器解析 Base64 的时间开销,与它节省掉的 HTTP 请求开销相比,可以忽略不计,这时候才能真正体现出它在性能方面的优势。 因此,Base64 并非万全之策,我们往往在一张图片满足以下条件时会对它应用 Base64 编码:
图片的实际尺寸很小(大家可以观察一下掘金页面的 Base64 图,几乎没有超过 2kb 的)
图片无法以雪碧图的形式与其它小图结合(合成雪碧图仍是主要的减少 HTTP 请求的途径,Base64 是雪碧图的补充)
图片的更新频率非常低(不需我们重复编码和修改文件内容,维护成本较低)
应用场景
Base64应用场景往往是比较小的图片,如:页面的Logo。对于一些体积较大的图片,由于Base64的缺点不建议转成Base64的格式。
如果你使用vue框架,就应该知道, vue-cli创建的webpack模板默认会将10K以下的图片和字体文件转为base64。好处带来了更快的渲染,不会因为页面切换时还有加载图片的延迟感。
WebP
关键字
年轻的全能型选手
WebP 是今天在座各类图片格式中最年轻的一位,它于 2010 年被提出, 是 Google 专为 Web 开发的一种旨在加快图片加载速度的图片格式,它支持有损压缩和无损压缩。
优点
WebP 像 JPEG 一样对细节丰富的图片信手拈来,像 PNG 一样支持透明,像 GIF 一样可以显示动态图片——它集多种图片文件格式的优点于一身。WebP 的官方介绍:
与 PNG 相比,WebP 无损图像的尺寸缩小了 26%。在等效的 SSIM 质量指数下,WebP 有损图像比同类
JPEG 图像小 25-34%。 无损 WebP 支持透明度(也称为 alpha 通道),仅需 22% 的额外字节。
对于有损 RGB 压缩可接受的情况,有损 WebP 也支持透明度,与 PNG 相比,通常提供 3 倍的文件大小。图片优化是质量与性能的博弈,从这个角度看,WebP 无疑是真正的赢家。缺点
WebP 纵有千般好,但它毕竟太年轻。我们知道,任何新生事物,都逃不开兼容性的大坑。此外,WebP 还会增加服务器的负担——和编码 JPG 文件相比,编码同样质量的 WebP 文件会占用更多的计算资源。
应用场景
现在限制我们使用 WebP 的最大问题不是“这个图片是否适合用 WebP 呈现”的问题,而是“浏览器是否允许 WebP”的问题,即我们上文谈到的兼容性问题。具体来说,一旦我们选择了 WebP,就要考虑在 Safari 等浏览器下它无法显示的问题,也就是说我们需要准备方案B,准备降级方案。
svg
1 | <img |
多页面
配置文件
package.json
https://es6.ruanyifeng.com/#docs/module-loader#package-json-%E7%9A%84-exports-%E5%AD%97%E6%AE%B5
https://nodejs.cn/api/packages.html
main
项目发布时,默认会包括 package.json,license,README 和main 字段里指定的文件,因为 main 字段里指定的是项目的入口文件,在 browser 和 Node 环境中都可以使用。
如果不设置 main 字段,那么入口文件就是根目录下的 index.js。
比如 packageA 的 main 字段指定为 index.js。
1 | "main": "./index.js" |
我们引入 packageA 时,实际上引入的就是 node_modules/packageA/index.js。
name
项目的名称,如果是第三方包的话,其他人可以通过该名称使用 npm install 进行安装。
1 | "name": "react" |
keywords
一组项目的技术关键词,比如 Ant Design 组件库的 keywords 如下:
1 | "keywords": [ |
repository/homepage
repository项目的仓库地址以及版本控制信息。
homepage项目主页的链接,通常是项目 github 链接,项目官网或文档首页。
1 | { |
bugs
项目 bug 反馈地址,通常是 github issue 页面的链接。
1 | "bugs": "http://github.com/vuejs/core/issues" |
license
项目的开源许可证。项目的版权拥有人可以使用开源许可证来限制源码的使用、复制、修改和再发布等行为。常见的开源许可证有 BSD、MIT、Apache 等,它们的区别可以参考:如何选择开源许可证?
type
在 node 支持 ES 模块后,要求 ES 模块采用 .mjs 后缀文件名。只要遇到 .mjs 文件,就认为它是 ES 模块。如果不想修改文件后缀,就可以在 package.json文件中,指定 type 字段为 module。
“type”: “module”
这样所有 .js 后缀的文件,node 都会用 ES 模块解释。
使用 ES 模块规范
$ node index.js
exports
使用
"exports"
字段可以防止包的使用者使用包中其他未定义的入口点,指定只能引用包文件的范围exports定义了自定义导出规则,可以理解为路径映射,防止其他未被定义的内容被访问
若export不起作用:https://stackoverflow.com/questions/58990498/package-json-exports-field-not-working-with-typescript
子目录别名
package.json
文件的exports
字段可以指定脚本或子目录的别名。
1 | // ./node_modules/es-module-package/package.json |
上面的代码指定src/submodule.js
别名为submodule
,然后就可以从别名加载这个文件。
1 | import submodule from 'es-module-package/submodule'; |
如果没有指定别名,就不能用“模块+脚本名”这种形式加载脚本。
1 | // 报错 |
main 的别名
exports
字段的别名如果是.
,就代表模块的主入口,优先级高于main
字段,并且可以直接简写成exports
字段的值。
1 | { |
由于exports
字段只有支持 ES6 的 Node.js 才认识,所以可以用来兼容旧版本的 Node.js。
1 | { |
上面代码中,老版本的 Node.js (不支持 ES6 模块)的入口文件是main-legacy.cjs
,新版本的 Node.js 的入口文件是main-modern.cjs
。
条件加载
利用.
这个别名,可以为 ES6 模块和 CommonJS 指定不同的入口。
1 | { |
上面代码中,别名.
的require
条件指定require()
命令的入口文件(即 CommonJS 的入口),default
条件指定其他情况的入口(即 ES6 的入口)
tsconfig.json
1 | { |
files
配置项值是一个数组,用来指定了待编译文件,即入口文件。
当入口文件依赖其他文件时,不需要将被依赖文件也指定到 files
中,因为编译器会自动将所有的依赖文件归纳为编译对象,即 index.ts
依赖 user.ts
时,不需要在 files
中指定 user.ts
, user.ts
会自动纳入待编译文件。
.browserslistrc
.commitlintrc.json
.editorconfig
EditorConfig 主要用于统一不同 IDE 编辑器的编码风格。
在项目根目录下添加 .editorconfig
文件:
1 | # 表示是最顶层的 EditorConfig 配置文件 |
.eslintignore
.eslintrc.js
.gitignore
.npmrc
.prettierrc
.stylelintrc.json
stylelint.config.js
babel.config.js
vue项目搭建
项目搭建
vue脚手架
https://cli.vuejs.org/zh/guide/
CLI
CLI (
@vue/cli
) 是一个全局安装的 npm 包,提供了终端里的vue
命令。它可以通过vue create
快速搭建一个新项目,也可以通过vue ui
通过一套图形化界面管理你的所有项目CLI 服务
CLI 服务 (
@vue/cli-service
) 是一个开发环境依赖。它是一个 npm 包,局部安装在每个@vue/cli
创建的项目中。CLI 服务是构建于 webpack 和 webpack-dev-server 之上的。它包含了:
- 加载其它 CLI 插件的核心服务;
- 一个针对绝大部分应用优化过的内部的 webpack 配置;
- 项目内部的
vue-cli-service
命令,提供serve
、build
和inspect
命令。
vue2
https://blog.csdn.net/weixin_44882488/article/details/124220864
安装脚手架:
npm install -g @vue/cli
,npm i -g webpack webpack-cli
生成项目模板-可以自定义
vue create 文件夹名称
vue3
https://juejin.cn/post/7156957907890733063
1.使用 vue-cli 创建
官方文档:https://cli.vuejs.org/zh/guide/creating-a-project.html#vue-create
1 | ## 查看@vue/cli版本,确保@vue/cli版本在4.5.0以上 |
2.使用 vite 创建
官方文档:https://v3.cn.vuejs.org/guide/installation.html#vite
vite官网:https://vitejs.cn
https://zhuanlan.zhihu.com/p/654327710
- 什么是vite?—— 新一代前端构建工具。
- 优势如下:
- 开发环境中,无需打包操作,可快速的冷启动。
- 轻量快速的热重载(HMR)。
- 真正的按需编译,不再等待整个应用编译完成。
- 传统构建 与 vite构建对比图
1 | ## 创建工程 |
配置
https://www.jianshu.com/p/d8d819cc607f
1 | 项目根目录控制台执行 vue inspect > webapck.config.js |
环境配置
1 | 用法:vue-cli-service build [options] [entry|pattern] |
模式
可以通过传递
--mode
选项参数为命令行覆写默认的模式
development
模式用于vue-cli-service serve
test
模式用于vue-cli-service test:unit
production
模式用于vue-cli-service build
和vue-cli-service test:e2e
如果你想要在构建命令中使用开发环境变量:
1 | vue-cli-service build --mode development |
当运行 vue-cli-service
命令时,所有的环境变量都从对应的环境文件中载入
环境变量
项目根目录中放置下列环境文件来指定环境变量:
1 | .env # 在所有的环境中被载入 |
只有 NODE_ENV
,BASE_URL
和以 VUE_APP_
开头的变量将通过 webpack.DefinePlugin
静态地嵌入到客户端侧的代码中
使用环境变量:console.log(process.env.VUE_APP_SECRET)
插件
vscode插件
Volar:https://zhuanlan.zhihu.com/p/401160130
高亮、语法提示
不再需要唯一根标签
编辑器快捷分割
ref sugar
语法快捷改动支持
业务工具类
循环滚动列表
- vue-seamless-scroll
图片资源懒加载
对于图片过多的页面,为了加速页面加载速度,所以很多时候我们需要将页面内未出现在可视区域内的图片先不做加载, 等到滚动到可视区域后再去加载。这样对于页面加载性能上会有很大的提升,也提高了用户体验。我们在项目中使用 Vue 的 vue-lazyload 插件:
(1)安装插件
1 | npm install vue-lazyload --save-dev |
(2)在入口文件 man.js 中引入并使用
1 | import VueLazyload from 'vue-lazyload' |
然后再 vue 中直接使用
1 | Vue.use(VueLazyload) |
或者添加自定义选项
1 | Vue.use(VueLazyload, { |
(3)在 vue 文件中将 img 标签的 src 属性直接改为 v-lazy ,从而将图片显示方式更改为懒加载显示:
以上为 vue-lazyload 插件的简单使用,如果要看插件的更多参数选项,可以查看 vue-lazyload 的 github 地址。
展示动态图片
PWA
- vue/cli-plugin-pwa只能在https,或者本地生产环境使用
框架功能类
- ant-design-vue:vue2.0使用版本1,vue3.0使用版本2
- Muse https://www.cnblogs.com/randomlee/p/10782710.html
- vue-class-component: 用类的方式编写组件 , vue2.x 对 TS 的支持并不友好,所以 vue2.x 跟 TS 的整合,通常需要基于 vue-class-component 来用基于 class(类) 的组件书写方式。
存储
- vue-ls:用于从Vue上下文中使用本地Storage,会话Storage和内存Storage
tip
组件内部引入第三方的js文件只在当前组件生效的办法
缓存导致编译问题
cache-loader
会默认为 Vue/Babel/TypeScript 编译开启。文件会缓存在node_modules/.cache
中——如果你遇到了编译方面的问题,记得先删掉缓存目录之后再试试看。
react项目搭建
脚⼿架create-react-app
全局安装create-react-app
1 | $ npm install -g create-react-app |
创建⼀个项⽬
1 | $ create-react-app your-app 注意命名⽅式 |
插件
底层编译类
react
react 这个包,是专门用来创建React组件、组件生命周期等这些东西的;
react-dom 里面主要封装了和 DOM 操作相关的包,要把组件渲染到页面上
1 | import React from 'react' |
要使用 JSX 语法,必须先运行 cnpm i babel-preset-react -D
,然后再 .babelrc
中添加 语法配置;
框架功能类
- pubsub利用JavaScript进行发布/订阅的库
- React 中保存页面状态vue/react->keep-alive/React Activation
样式动画类
CSS-in-JS
CSS-in-JS就是将应用的CSS样式写在JavaScript文件里面,而不是独立为一些.css
,.scss
或者less
之类的文件,这样你就可以在CSS中使用一些属于JS的诸如模块声明,变量定义,函数调用和条件判断等语言特性来提供灵活的可扩展的样式定义。
1 | // 用 npm 安装 |
1 | import React from 'react'; |
1 | import React from 'react'; |
1 | const useStyles = makeStyles({ |
样式动画库
https://zhuanlan.zhihu.com/p/361065034
- https://react-spring.io/basics
- https://reactcommunity.org/react-transition-group/
- https://motion.ant.design/components/tween-one-cn
- http://textillate.js.org/
- http://react-animations.herokuapp.com/
- https://www.framer.com/docs/
工具类
拖拽react-sortable-hoc
高阶函数(Higher Order Function)=> 参数或返回值为函数组件
react-sortable-hoc
提供了两个特别重要的 API ,分别是SortableContainer 和 SortableElement
看英文的意思也知道,SortableElement
用来装饰每个要拖拽的组件,相对于SortableContainer
要装饰的组件,SortableElement
装饰的组件是子组件。
1 | const SortableItem = SortableElement((props: any) => <div>{props.children}</div>); |
array-move
就一个 API,它的主要作用就是用来交换数组中元素的位置。我们用 node 进行调试
1 | // 引包 |
优化
- react:usememo减少渲染
- react,lazy路由懒加载,代码的分片
tip
打包慢 electron winCodeSign-2.6.0.7z 离线下载
Theia
介绍
Eclipse Theia 不是一个 IDE,而是一个用来开发 IDE 的框架。是Eclipse 基金会打造的云端及桌面IDE框架,该产品旨在替代微软的 Visual Studio Code
Theia 能够是一个桌面应用,也能够在浏览器和远程服务中运行。为了用同一套代码,支持桌面应用和webIDE,Theia 运行在两个独立的进程中。这个进程分别被称为前端和后端,它们经过WebSockets上的JSON-RPC消息或HTTP上的REST api进行通讯。在桌面应用中,后端和前端都在本地运行,而在远程上下文中后端将在远程主机上运行。web
前端和后端流程都有它们的依赖注入容器,能够贡献扩展。express
- 前端
前端进程表明客户端而且担任呈现UI的职责。在浏览器中,它简单的在渲染循环中运行,而在Electron中,它在Electron 的窗口中运行,Electron 窗口是一个基本的带有附加Electron和Node.js api的浏览器。而任何前端代码均可以假设浏览器是一个平台,而不用关联Node.js。后端
- 后端
后端进程运行在Node.js上。咱们使用express做为HTTP服务器。它可能不须要使用任何依赖浏览器做为平台的代码(DOM api)。后端应用程序的启动将首先加载全部贡献扩展的DI模块,而后得到BackendApplication的实例并在其上调用start(portNumber)。默认状况下,后台的express服务器也为前台提供代码。api
- 平台分离
在扩展程序的顶层文件夹中,咱们还有一个附加的文件夹层,能够按平台分开:浏览器
- common文件夹包含不依赖于任何运行时的代码。服务器
- 浏览器文件夹包含要求使用现代浏览器做为平台(DOM API)的代码。架构
- 电子浏览器文件夹包含须要DOM API以及Electron渲染器过程特定的API的前端代码。eclipse
- 节点文件夹包含(后端)代码,须要Node.js做为平台。
- node-electron文件夹包含特定于Electron的(后端)代码。
Cloud IDE 架构
Cloud IDE 主要包含 Client、Server、Container Pool 三部分。
- Client: 客户端也是最重要的端,将代码编辑等本地功能移植到浏览器中。
- Server: 服务端也是控制端,包括管理数据交互及资源调度。
- Container Pool: 运行时,用户代码真正运行的容器环境。
三者之间最典型的架构如下图所示:
环境配置
https://www.cnblogs.com/fanqisoft/p/13171657.html
https://github.com/eclipse-theia/theia/blob/master/doc/Developing.md
1 | win11 - 从磁盘到项目文件的路径中不能有中文。因为一些引用路径是用的绝对路径,如果用中文,一些方法是无法识别文件路径的(PS:我猜主要还是国外的人写的,并没有适配语言) |
下载node-gyp
node-gyp查找VS安装路径简单解析:https://juejin.cn/post/6949529951284494344(**PS:通过find-visualstudio.js文件,需要在配置环境用户变量VSINSTALLDIR。这个能让node-gyp找到vs的位置**)
1 | 环境变量-》用户变量 |
github: https://github.com/nodejs/node-gyp#on-windows
1 | npm i -g node-gyp@last |
如果项目文件中依赖的node-gyp不是最新版本,node_modules/node-gyp/lib/find-visualstudio.js文件中可能没有针对2022版本的处理。
下载pyhon
配置pyenv:https://blog.csdn.net/weixin_42289080/article/details/127997003
1 | where python |
1 | 两种方法: |
安装C/C++编译工具
安装Visual Studio软件以及 C++ 相应的编译工具
1
2
3
4
5
6
72022
npm config set msbuild_path "C:\Program Files\Microsoft Visual Studio\2022\Community\MSBuild\Current\Bin\MSBuild.exe"
2017
npm config set msbuild_path "C:\Program Files (x86)\Microsoft Visual Studio\2017\BuildTools\MSBuild\15.0\Bin\MSBuild.exe"
npm config set msvs_version 2022 --globalwin11系统,如果SDK报错,需要安装win10SDK
纯前端开发者,只需要安装C/C++编译器工具即可
1
以管理员身份执行 npm install -global -production windows-build-tools
配置环境变量或者npm配置
配置npm
1 | npm config set VCINSTALLDIR "C:\Program Files\Microsoft Visual Studio\2022\Community\VC" --global |
1 | //.npmrc |
依赖下载启动问题
下载慢,将资源地址设置为淘宝镜像:yarn config set registry https://registry.npm.taobao.org/
每次更改依赖后下载或者启动有问题,最好删除node_module,以及yarn的缓存(PS:我就是没有去删除yarn缓存,启动服务启动失败)
从磁盘到项目文件的路径中不能有中文。因为一些引用路径是用的绝对路径,如果用中文,一些方法是无法识别文件路径的(PS:我猜主要还是国外的人写的,并没有适配语言)
THEIA_ELECTRON_SKIP_REPLACE_FFMPEG=1 yarn
,忽略FFMPEG去下载,我的win11找不到FFMPEG的共享库Module did not self-register drivelist
cd ./node_modules/drivelist && ../.bin/electron-rebuild
启动项目
由于 Theia 及其扩展是 Node.js 包,而 Theia 应用程序是包的集合,因此启动 Theia 及所选扩展的一个非常简单的方法就是创建一个 package.json。
参考文档:Build your own IDE
theia-blueprint
整个工程使用 Lerna 配置 mono-repo构建
目录
1 | resources:electron打包常用一些资源,像应用的图标。 |
安装插件
1 | { |
添加了 theiaPluginsDir
和 theiaPlugins
这两个属性。theiaPluginsDir
是用来设置我们的插件存放地址的,theiaPlugins
就是我们要安装的插件了
运行项目之前,我们要先运行 yarn prepare
来准备环境,我们会在日志中看到插件的下载情况:
这些插件都会放在当前目录下的 plugins
文件夹下
命令
theia这个命令是来自devDependencies配置的@theia/cli
yarn
构建项目
yarn theia build
这个命令主要是用来生成项目代码的,包含源码,webpack 配置文件以及 webpack 打包后的文件。运行成功的结果如下:
- lib:构建生成的Bundle包
- plugins:执行download:plugins时下载的插件包
- src-gen:theia命令自动生成的工程文件
- gen-webpack.config.js:theia自动生成的webpack配置文件,由webpack.config.js引入
运行 Theia IDE
yarn theia start
theia rebuild:browser
:构建环境切换到browser
theia rebuild:electron
:构建环境切换到electron
theia包
- application-manager:应用工程管理器,提供 Frontend、Backend、Webpack 代码生成
- application-package:应用 package.json 配置解析,管理 Application、Extensions
- cli:是一个命令行工具,用于管理基于 Theia 的应用程序,为扩展和应用程序开发提供了有用的脚本和命令
- ffmpeg:是一个Node Native 插件,用于动态链接到 Electronffmpeg.dll 并获取包含的编解码器列表
- localization-manager:用于为不同语言创建 Theia 和 Theia 扩展的本地化
- ovsx-client:该包用于通过其 REST API@theia/ovsx-client 进行交互。open-vsx 该包允许客户端获取扩展及其元数据、搜索注册表,并包含必要的逻辑来根据提供的支持的 API 版本确定兼容性
- private-eslint-plugin:对 Eclipse Theia 开发有用的 @theia/eslint-plugin 贡献规则。该插件通过静态分析帮助识别开发过程中的问题,包括代码质量、潜在问题和代码异常。
- private-ext-scripts:是一个命令行工具,用于在 Theia 包中运行共享的 npm 脚本
- private-re-exports:用于重新导出依赖项
- request:发送代理请求的库
- search-in-workspace` 负责搜索模块
- terminal` 负责终端模块等
Monorepo
什么是 Monorepo ?
Monorepo
是管理项目代码的方式之一,指在一个大的项目仓库(repo)中 管理多个模块/包(package),这种类型的项目大都在项目根目录下有一个packages文件夹,分多个项目管理。大概结构如下:
1 | ├── packages |
目前很很多大型项目采用这样的结构,比如:Babel
、vue3
和vite
等。
Monorepo 的好处在哪里嘞?
- 统一管理。比如微前端项目,多个子应用可以放在同一个
monorepo
中方便管理;后端用node.js
的项目放在monorepo
中也可以使用同一套技术栈管理。在CI/CD等流水线过程中,方便统一迭代或升级版本,也方便做通用化的配置,适用到多个子项目当中。 - 依赖提升。如果多个项目都依赖了诸如
react
、vue
或TypeScript
等常用库,那可以通过lerna
或者yarn workspace
将依赖提升到最外层,多个子模块/包复用同一个依赖,减小项目体积。
lerna
命令
https://gitcode.net/mirrors/lerna/lerna?utm_source=csdn_github_accelerator
https://blog.csdn.net/a357951314/article/details/111314875
lerna init
1 | lerna.json |
1 | //package.json |
lerna create
lerna create
添加package
lerna add
执行 lerna add
为特定package或这所有package添加依赖
注意默认lerna add会在每个子package中添加依赖,执行 lerna bootstrap --hoist
重新提取公共依赖
lerna run
我们可以使用lerna run
指令,去运行每个包中包含有相关package script的相关命令
lerna run –parallel start
--parallel
参数就是指为项目的中需要一直的进程,打印所有子进程的输出。说得有点绕,理解为需要一直运行的就加上这个参数就可以了
lerna bootstrap
使用下面命令,可以安装所有包的package.json
中的dependencies
lerna clean
删除已经安装的子项目node_modules
公共的包
我们可以将公共的包安装在根目录下的package.json
下,例如,你是前端微服务的项目而且是使用react作为主要技术栈的,可以把react
、react-dom
等安装在根目录下,packages都是可以直接引用的
更常用的是一些项目规范的配置,例如eslint
、prettier
、tsconfig
之类的,也可以直接安装在根目录下
把公共包提取出来的好处有:
- 所有包用的依赖包版本都是一致的
- 做一些包的升级和像github进行包检查时,更方便地进行升级
- 安装依赖包的时间可以更少
- 需要更少的存储空间
项目中包与包之间的引用
如果我们packages里面需要进行互相引用
我们使用lerna add
命令,可以为指定的包安装第三方的或者本地的包,这个指令和yarn add
或者npm install
实质上是类似的,下面是一些来自文档的一些例子
构建
https://blog.csdn.net/dennis_jiang/article/details/112181925
1 | . |
lerna init
packages目录下创建子项目模块
按照
mono-repo
的惯例,这几个子项目的名称最好命名为@<主项目名称>/<子项目名称>
,这样当别人引用你的时候,你的这几个项目都可以在node_modules
的同一个目录下面,目录名字就是@<主项目名称>
,所以我们手动改下三个子项目package.json
里面的name
这里我按照vue项目流程创建
lerna bootstrap
packages/下面的每个子项目有自己的node_modules,如果将它打开,会发现很多重复的依赖包,这会占用我们大量的硬盘空间。lerna提供了另一个强大的功能:将子项目的依赖包都提取到最顶层,我们只需要先删除子项目的node_modules再跑下面这行命令就行了:lerna bootstrap –hoist
lerna bootstrap –hoist虽然可以将子项目的依赖提升到顶层,但是他的方式比较粗暴:先在每个子项目运行npm install,等所有依赖都安装好后,将他们移动到顶层的node_modules。这会导致一个问题,如果多个子项目依赖同一个第三方库,但是需求的版本不同怎么办?比如我们三个子项目都依赖antd,但是他们的版本不完全一样
vue1和vue2项目需要的element版本都是3.1.0,但是common需要的版本却是4.9.4,如果使用lerna bootstrap –hoist来进行提升,lerna会提升用的最多的版本,也就是3.1.0到顶层,然后把子项目的node_modules里面的antd都删了。也就是说common去访问element的话,也会拿到3.1.0的版本,这可能会导致common项目工作不正常
yarn workspace可以解决前面说的版本不一致的问题。
lerna bootstrap --hoist
会把所有子项目用的最多的版本移动到顶层,而yarn workspace
则会检查每个子项目里面依赖及其版本,如果版本不一样则会留在子项目自己的node_modules
里面,只有完全一样的依赖才会提升到顶层。1
2
3
4
5
6// 顶层package.json
{
"workspaces": [
"packages/*"
]
}1
2
3
4
5// lerna.json
{
"npmClient": "yarn",
"useWorkspaces": true
}使用了
yarn workspace
,我们就不用lerna bootstrap
来安装依赖了,而是像以前一样yarn install
就行了,他会自动帮我们提升依赖,这里的yarn install
无论在顶层运行还是在任意一个子项目运行效果都是一样的。在顶层运行了
lerna run start
,这相当于去每个子项目下面都去执行yarn run start
或者npm run start
,具体是yarn
还是npm
,取决于你在lerna.json
里面的这个设置:1
"npmClient": "yarn"
只想在其中一个子项目运行命令
1
2
3
4
5
6// 顶层package.json
{
"scripts": {
"start:vue": "lerna --scope @mono-repo-demo/vue run start"//--scope来指定在管理员子项目下运行
}
}引入公共组件:lerna add @mono-repo-demo/common –scope @mono-repo-demo/vue1
common 组件写好了,我们就在vue1里面引用下他,要引用上面的组件,我们需要先在vue1的package.json里面将这个依赖加上,我们可以去手动修改他,也可以使用lerna命令:
1 | //packages\vue\package |
mono-repo管理electron和web
electron和web一致
编码规范
规范的意义
每个程序员都有自己的编码习惯,最常见的莫过于:
- 有的人写代码一行代码结尾必须加分号
;
,有的人觉得不加分号;
更好看; - 有的人写代码一行代码不会超过 80 个字符,认为这样看起来简洁明了,有的人喜欢把所有逻辑都写在一行代码上,觉得别人看不懂的代码很牛逼;
- 有的人使用变量必然会先定义
var a = 10;
,而粗心的人写变量可能没有定义过就直接使用b = 10;
;
如果你写自己的项目怎么折腾都没关系,但是在公司中老板希望每个人写出的代码都要符合一个统一的规则,这样别人看源码就能够看得懂,因为源码是符合统一的编码规范制定的。
那么问题来了,总不能每个人写的代码老板都要一行行代码去检查吧,这是一件很蠢的事情。凡是重复性的工作,都应该被制作成工具来节约成本。这个工具应该做两件事情:
- 提供编码规范;
- 提供自动检验代码的程序,并打印检验结果:告诉你哪一个文件哪一行代码不符合哪一条编码规范,方便你去修改代码。
Lint 因此而诞生。Lint 是检验代码格式工具的一个统称,具体的工具有 Jslint
、 Eslint
等等
检测方式
检测可分为两种方式
使用编辑器的扩展
- 项目中的
.vscode/settings.json
- 本地环境中的
settings.json
- 项目中的
使用脚本的方式
- prettier
- eslint
上面两种方式如果同时存在的话,会有优先级的问题。**.prettierrc 的优先级会高于在vscode全局配置settings.json中格式化配置的优先级(同理eslint)**
也就是说,如果你在一个项目中有 .prettierrc 配置文件,然后你又在settings.json也配置了格式化规则,那么当你在vscode编辑器中对一个文件点击鼠标右键[格式化文档]的时候,格式化规则会以 .prettierrc 为准
Eslint(代码质量)
【eslint英文文档】https://eslint.org/docs/user-guide/configuring
【eslint中文文档】http://eslint.cn/docs/rules/ https://zh-hans.eslint.org/
含义
ESLint 是什么呢?
是一个开源的 JavaScript 的 linting 工具,使用 espree 将 JavaScript 代码解析成抽象语法树 (AST),然后通过AST 来分析我们代码,从而给予我们两种提示:
- 代码质量问题
- 代码风格问题(ESLint 之类的 Linters 对于代码格式化的能力是有限的,不如 Prettier 那么专业。通过 Prettier 执行格式化代码的工作,而代码质量的控制由 ESLint 处理)
获得如下收益:
- 在执行代码之前发现并修复语法错误,减少调试耗时和潜在 bug
- 保证项目的编码风格统一,提高可维护性
- 督促团队成员在编码时遵守约定的最佳实践,提高代码质量
ESLint 可以让程序员在编码的过程中发现问题而不是在执行的过程中,还可以让让程序员可以创建自己的检测规则。ESLint 的所有规则都被设计成可插拔的。为了便于人们使用,ESLint 内置了一些规则,当然,你可以在使用过程中自定义规则。所有的规则默认都是禁用的。
安装
生成流程:https://blog.csdn.net/shenxianhui1995/article/details/103035013
如果你想你所有项目都使用eslint,请全局安装;如果你想当前项目使用,请局部安装。
全局安装:
-
npm install -g eslint
-
eslint --init
-
项目安装:
-
npm install eslint --save-dev
-
./node_modules/.bin/eslint --init
-
1 | ? How would you like to use ESLint? (Use arrow keys) // 你想怎样使用eslint |
命令
“lint”: “eslint . –ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts –fix”
原理
ESLint 的原理,是遍历语法树然后检验,其核心的 Linter,是不依赖 node 环境的,并且官方也进行了单独的打包输出,具体可以通过 clone官方代码 后,执行 npm run webpack 拿到核心的打包后的 ESLint.js。其本质是对 linter.js 文件的打包。
同时官方也基于该打包产物,提供了 ESLint 的官方 demo。
eslint配置
https://www.cnblogs.com/jiaoshou/p/11218526.html
eslint文件搜索
- 一般都采用.eslintrc.的配置文件进行配置, 如果放在项目的根目录中,则会作用于整个项目。如果在项目的子目录中也包含着.eslintrc文件,则对于子目录中文件的检查会忽略掉根目录中的配置,而直接采用子目录中的配置,这就能够在不同的目录范围内应用不同的检查规则,显得比较灵活。ESLint采用逐级向上查找的方式查找.eslintrc.文件,当找到带有”root”: true配置项的.eslintrc.文件时,将会停止向上查找。
- 在 package.json文件里的 eslintConfig 字段进行配置。
跳过检测
eslint可以具体文件中设置特定代码的规则,常用于跳过某条语句的检测。
1 | 注销全部规则,在代码前新建一行,添加注销 /* eslint-disable *\/ 。如果没有恢复设置的语句,则下列全部代码都会跳过检测。 |
root
root:是否以当前目录为根目录。 告诉 ESLint 不要再往上级目录查找,利用此属性配置,项目级和目录级的配置都可以不受上级目录以及祖先目录的配置影响,通常项目根目录应该设置为 true
举个例子,比如这个项目 vue-project1,默认情况下 root 为 false,而且该项目上层目录下还有 eslint 配置文件的话,这个更上一层的配置就会对你的项目文件的代码产生作用,直到到达根目录才会停止。这是我们不愿意看到的,所以就需要我们在当前项目目录下设置 root: true,告诉 ESLint 这里就是根目录了,别再往上查找其他的配置文件了!
env运行环境
env
,这是对环境定义的一组全局变量的预设。
使用 env
属性来指定要启用的环境,将其设置为 true
,以保证在进行代码检测时不会把这些环境预定义的全局变量识别成未定义的变量而报错
http://eslint.cn/docs/user-guide/configuring#specifying-environments
常见的运行环境
- browser - 浏览器环境中的全局变量。
- node - Node.js 全局变量和 Node.js 作用域。
- es6 - 启用除了 modules 以外的所有 ECMAScript 6 特性(该选项会自动设置 ecmaVersion 解析器选项为 6)。
- amd - 将 require() 和 define() 定义为像 amd 一样的全局变量。
- commonjs - CommonJS 全局变量和 CommonJS 作用域 (用于 Browserify/WebPack 打包的只在浏览器中运行的代码)。
- jquery - jQuery 全局变量。
- mongo - MongoDB 全局变量。
- worker - Web Workers 全局变量。
- serviceworker - Service Worker 全局变量。
1 | "env": { |
globals全局变量
ESLint会检测未声明的额外的全局变量,并发出报错,比如node环境中的process,浏览器环境下的全局变量console,以及我们通过cdn引入的jQuery定义的$等;我们可以在globals
中进行变量声明:
http://eslint.cn/docs/user-guide/configuring#specifying-globals
- 定义额外的全局,开发者自定义的全局变量,让其跳过no-undef 规则
- key值就是额外添加的全局变量
- value值用于标识该变量能否被重写,类似于const的作用。true为允许变量被重写
- 注意:要启用no-global-assign规则来禁止对只读的全局变量进行修改。
1 | { |
但是node或者浏览器中的全局变量很多,如果我们一个个进行声明显得繁琐,因此就需要用到我们的**env
,这是对环境定义的一组全局变量的预设。**
parserOptions解析器
http://eslint.cn/docs/user-guide/configuring#specifying-parser
ESLint 解析器将代码转换为 ESLint 可以评估的抽象语法树(AST, abstract syntax tree)。默认情况下,ESLint 使用内置的与标准 JavaScript运行时和版本兼容的 Espree 解析器。
自定义解析器让 ESLint 可以解析非标准的 JavaScript 语法。通常自定义解析器会被包含在可共享配置或插件中,这样你就不需要直接使用它们了。
比如用于让 ESLint 可以解析 TypeScript 代码的 @typescript-eslint/parser 解析器就被包含在 typescript-eslint 项目中。
要指定 npm 模块作为解析,需要在 .eslintrc
文件中使用 parser
选项指定。比如,下方就指定使用 babel-eslint 代替 Espree:
1 | parserOptions: { |
下列解析器与 ESLint 兼容:
- Esprima
- @babel/eslint-parser - Babel 解析器的包装以便与 ESLint 兼容。
- @typescript-eslint/parser - 将 TypeScript 转换为与 ESTree 格式兼容的解析器,好可以在 ESLint 中使用。
babel-eslint是一个与 Babel 和 ESLint 配合使用的插件。它允许 ESLint 在语法转换器(Babel)处理代码之前对代码进行检查,从而使 ESLint 能够正确识别 Babel 转换后的语法。
通俗地说,如果您使用 Babel 转换 JavaScript 代码,并且希望使用 ESLint 检查代码质量,则可以使用 babel-eslint 插件来确保 ESLint 正确识别转换后的语法。
- @typescript-eslint/parser(没有它,vue中ts不会检测报错):ESLint 的解析器,用于解析 typescript,从而检查和规范 Typescript 代码。
- @typescript-eslint/eslint-plugin:包含了各类定义好的检测 Typescript 代码的规范。
Vue
- vue-eslint-parser
- eslint-plugin-vue包含常用的 vue 规范
- Configurations for using Vue.js 3.x.
"plugin:vue/vue3-essential"
…base
, plus rules to prevent errors or unintended behavior."plugin:vue/vue3-strongly-recommended"
… Above, plus rules to considerably improve code readability and/or dev experience."plugin:vue/vue3-recommended"
… Above, plus rules to enforce subjective community defaults to ensure consistency.
- Configurations for using Vue.js 2.x.
"plugin:vue/essential"
…base
, plus rules to prevent errors or unintended behavior."plugin:vue/strongly-recommended"
… Above, plus rules to considerably improve code readability and/or dev experience."plugin:vue/recommended"
… Above, plus rules to enforce subjective community defaults to ensure consistency
- Configurations for using Vue.js 3.x.
overrides
overrides:用指定配置覆盖指定后缀的文件的规则配置
1 | overrides: [ |
extend规则继承
http://eslint.cn/docs/user-guide/configuring#extending-configuration-files
一个配置文件可以被基础配置中的已启用的规则继承。
extends
属性值可以是:
- 指定配置的字符串(配置文件的路径、可共享配置的名称、
eslint:recommended
或eslint:all
) - 字符串数组:每个配置继承它前面的配置
ESLint递归地扩展配置,因此基本配置也可以具有 extends
属性。extends
属性中的相对路径和可共享配置名从配置文件中出现的位置解析。
rules
属性可以做下面的任何事情以扩展(或覆盖)规则:
- 启用额外的规则
- 改变继承的规则级别而不改变它的选项:
- 基础配置:
"eqeqeq": ["error", "allow-null"]
- 派生的配置:
"eqeqeq": "warn"
- 最后生成的配置:
"eqeqeq": ["warn", "allow-null"]
- 基础配置:
- 覆盖基础配置中的规则的选项
- 基础配置:
"quotes": ["error", "single", "avoid-escape"]
- 派生的配置:
"quotes": ["error", "single"]
- 最后生成的配置:
"quotes": ["error", "single"]
- 基础配置:
plugins插件
http://eslint.cn/docs/user-guide/configuring#configuring-plugins
插件同样需要在node_module中下载
注意插件名忽略了「eslint-plugin-」前缀,所以在package.json中,对应的项目名是「eslint-plugin-vue」
插件的作用类似于解析器,用以扩展解析器的功能,用于检测非常规的js代码。也可能会新增一些特定的规则。
如 eslint-plugin-vue,是为了帮助我们检测.vue文件中 和