前端模块化

发布于:2025-06-08 ⋅ 阅读:(17) ⋅ 点赞:(0)

ES Module

在这里插入图片描述

es module是ECMA2015年提出的模块规范化

使用的前提:浏览器本身支持es module模块化

采用ES Module将默认采用严格模式:use strict

ES Module和CommonJS的模块化不同

  • 关键字差异:
    • CommonJS使用exports、module.exports导出,require导入
    • ES Module使用export导出,import导入
  • 底层原理:
    • 采用编译期静态分析
    • 支持动态引用方式
  • 语法结构:
    • export负责模块内容导出
    • import负责从其他模块导入

ES Module的基本导入和导出

要实现模块化就要实现基本的导入和导出

有名导出

基本导入导出

单独的

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <!-- 非模块化引入 -->
    <!-- <script src="./main.js" ></script> -->
    <!-- 模块化引入 -->
    <script src="./main.js" type='module'></script>
</body>
</html>

成为模块类型可以就有了自己的作用域;但是此处有导入就要有导出,导出在需要导出的文件使用export

foo.js

const name='foo';
const age=18;
function sayhello() {
    console.log("hello");
}
export { sayhello, name, age };//此处的大括号不是对象,不能写成键值对

导出的格式是export {标识符1,标识符2…}

然后导入进main.js

main.js:

//导入,import
import { name, age, sayHello } from './foo.js'//后面一定要加后缀名,
//如果用webpack就不用加
console.log(name)
console.log(age)
sayHello()

在浏览器直接使用esmoudle时,必须在文件后加上后缀名,删除后缀后浏览器会报404错误,无法找到对应模块

就可以正常显示了:

foo
18
hello

注意必须通过HTTP服务(如Live Server)打开,必须通过一个服务器,不能直接通过file协议访问,仅支持http/https/data等协议,不支持本地file协议,直接本地打开会遇到CORS错误(本地文件可能不安全)

起别名的导入导出

在foo.js的导出加上as关键字导出:

export {
    sayHello,
    name as fooName,//as关键字
    age
};

导入的时候按别名导入:

import { fooName, age, sayHello } from './foo.js'
const name='main'
console.log(fooName)
定义的同时导出

这样子导出没法起别名

export const name = 'foo'
export const age = 18
export function sayHello() {
  console.log('hello')
}

但是导入可以起别名,一般都是在导入的时候起别名:

import { name as fooName, age, sayHello } from './foo.js'
const name='main'
console.log(fooName)
console.log(age)
sayHello()

导入时可以给整个模块起别名(用的比较少):

import * as foo from './foo.js'//给整个模块起别名
const name='main'
console.log(foo.name)//这样使用
console.log(name)
console.log(foo.age)
sayHello()

一些比较优秀的代码结构会专门在目录下设置一个index.js来导入其他模块的代码,并且为其他代码做导出的仓库:

import { formatCount, formatDate } from './utils/format'
import { parseCount } from './utils/parse'
export {
    formatCount,
    formatDate,
    parseCount
}

这样main.js就不需要分别从其他模块里导入,直接从utils/index.js这个文件;里导入就好了:

import { formatCount,formatDate,parseCount} from './utils/index.js'//浏览器不支持默认找utils下的index.js文件,但是webpack支持
console.log(formatCount(1000))
console.log(formatDate(new Date()))
console.log(parseCount('1000'))

import和export的结合使用

省去/utils/index.js里的导入和导出,直接用export关键字表示同时进行将这些模块导入然后导出的工作:

export { formatCount, formatDate } from './format.js'
export { parseCount } from './parse.js'
//等效于
import { formatCount, formatDate } from './utils/format'
import { parseCount } from './utils/parse'
export {
    formatCount,
    formatDate,
    parseCount
}

如果这样写,我们也可以用通配符全部导入再导出:

export * from './format.js'
export * from './parse.js'

还是更推荐上面的写法,如果公司有对应导入导出的模块有文档的话,就可以用这种方式

默认导出

导出分为有名导出和默认导出

有名导出(named exports):

  • 在导出的时候指定了名字
  • 在导入的时候需要知道名字

默认导出(default export):

  • 不需要指定名字
  • 导入时不需要使用{},并且可以自己来指定名字
  • 方便和现有的CommonJS等规范相互操作

默认导出适合模块只提供单一主要功能的情况,例如这个js里我们只导出一个函数:

function parseLyric() {
    return ['歌词']
}
export default parseLyric//默认导出
// export {parseLyric} //命名导出

在main.js里导入,把导入的模块赋给前面的变量:

import parse_lyric from './parse_lyric.js'
console.log(parse_lyric())

也可以在定义的时候进行默认导出:

export default function() {
  return ['歌词']
}

导入的时候还是用同一种方法在导入的时候命名:

import parse_lyric from './parse_lyric.js'
console.log(parse_lyric())

注意事项:

  • 同一个模块里只能有一个默认导出

import函数

如果想根据条件导入模块,这么写是错误的:

let flag = false
if (flag) {
   import {name,age,sayHello} from './foo.js'
}

因为导入声明只能在模块顶层使用,不能写在逻辑里,并且会产生

  • ES Module需在解析阶段确定依赖关系
  • JS代码未执行时无法判断条件语句
  • 路径必须在解析阶段确定,不能运行时拼接,如:(‘./foo’+‘.js’)

的问题

所以引入了import函数的概念

// import {name,age,sayHello} from './foo.js'
let flag = false
if (flag) {
  //import {name,age,sayHello} from './foo.js'
    import('./foo.js').then((module) => {
      console.log(module.name)
      console.log(module.age)
      module.sayHello()
    })
}

import返回一个promise,promise的结果在.then里查看,实现动态加载模块的路径

import meta

ES11(ES2020)新增特性,包含模块的元数据信息,主要属性为模块URL

foo.js

const name = 'foo'
const age = 18
function sayHello() {
  console.log('hello')
}
console.log(import.meta.url) //打印当前模块的url
console.log(import.meta) //打印当前模块的元数据
//导出,export
export { sayHello, name, age }

main.js

let flag = true
if (flag) {
    import('./foo.js').then((module) => {
      console.log(module.name)
      console.log(module.age)
      module.sayHello()
    })

打印结果:

http://localhost:53139/01_%E6%A8%A1%E5%9D%97%E5%8C%96%E5%BC%80%E5%8F%91/05_ESModule/foo.js
{url:'http://localhost:53139/01_%E6%A8%A1%E5%9D%97%E5%8C%96%E5%BC%80%E5%8F%91/05_ESModule/foo.js', resolve: ƒ}
foo
18
hello

实际开发中使用频率较低,主要用于获取模块自身信息

不加from的import

单独的 import 通常用于 直接执行模块代码动态导入,只导入不导出的模块代码可以用单独的import;在入口文件导入相应的依赖,webpack可以根据入口文件的依赖进行相应的打包

ES Module的解析流程

ES modules的解析流程链接

解析流程分为三个阶段

  • 构建阶段(Construction): 根据地址查找js文件并下载,解析成模块记录(Module Record)
  • 实例化阶段(Instantiation): 对模块记录实例化,分配内存空间,解析导入导出语句,建立内存地址映射
  • 运行阶段(Evaluation): 执行代码计算值,并将值填充到内存地址中

前两个阶段不执行任何一段代码,这也是不把import放到代码里的原因之一

具体在我们刚才的例子中,在构建阶段,每个js文件下载下来以后对应一个Module Record,实例化对这个Module Record做解析,解析出里面具体有哪些导入和导出,如果导入了新的js文件浏览器就要下载对应的js文件,也会有其对应的Moudule Record;除了对导入进行解析,也会对导出解析,对导出的实例化会实例出一个Module Environment Record,只记录“谁被导出了”,没有具体的值,在第三阶段运行的时候,计算值,将值填到对应的内存地址去

下载,只有type='module’时才当做模块下载,下载时会把export出的内容(导出的内容本身不是对象)包在对象内,也就是Module Record

映射关系

实例化

包管理工具

npm包管理工具

代码共享的方法

  • 官网/github

    • 缺点是必须要知道对应github地址,并且手动下载
    • 需要在自己的项目中手动的引用,并且管理相关的依赖
    • 不需要使用的时候,需要手动来删除相关的依赖、
    • 当遇到版本升级或者切换时,需要重复上面的操作

    总结:传统,易出错

  • npm registry

    • 我们通过工具将代码发布到特定的位置
    • 其他程序员直接通过工具来安装、升级、删除我们的工具代码

​ 总结:方便管理,方便使用

npm

全称:Node Package Manager,也就是Node包管理器,也可以在前端项目中使用管理依赖的包,比如vue、vue-router、vuex、express、koa、react、react-dom、axios、babel、webpack等等

如何下载和安装npm工具?

npm属于node的一个管理工具,所以我们需要先安装Node:

node管理工具: https://nodejs.org/en/ ,安装Node的过程会自动安装npm工具

npm管理的包可以在哪里查看、搜索?

  • npm install dayjs/dayjs官网:Day.js中文网
  • npm官网:https://www.npmjs.org/

npm管理的包存放在哪里?

发布到registry上面,安装一个包时其实是从registry上面下载的

如果下载了dayjs,可以clg看看基本信息

const dayjs = require("dayjs");
console.log(dayjs())
//相关信息
// M {
//     '$L': 'en',
//     '$d': 2025-05-13T14:37:40.887Z,
//     '$y': 2025,
//     '$M': 4,
//     '$D': 13,
//     '$W': 2,
//     '$H': 22,
//     '$m': 37,
//     '$s': 40,
//     '$ms': 887,
//     '$x': {},
//     '$isDayjsObject': true
//   }

执行npm install时会自动创建两个核心文件:

  • package.json:记录项目依赖配置
  • node_modules:存储实际安装的依赖包

除必要文件外还会生成其他辅助文件

package配置文件和属性

package.json是Node.js项目的核心配置文件,用于记录项目的各种元信息

  • 文件格式: 采用JSON格式编写,具有严格的语法规范

  • 生成方式:

    • 方式一: 手动从零创建项目, npm init -y, npm init 需要自己手动配置,

    • 方式二: 通过脚手架创建项目, 脚手架会帮助我们生成package.json,并且里面有相关的配置

  • 必须写的属性:

    • name:项目名称,遵循npm命名规范

    • version:项目版本号,遵循语义化版本规范(x.y.z)

  • 描述性属性:

    • description:项目描述
    • author:作者信息
    • license:开源许可证
  • 特殊属性:

    • private:保护内部资源;

      • 设为true时,npm是不能发布它的,防止私有模块或私有项目发布出去的方式
    • main:用于设置程序的入口文件,Node.js会通过main属性查找对应的入口文件,默认文件是index.js,也可以自己设置

    • scripts:定义项目脚本命令;

      • script对应一个对象,对象里放键值对,里面是具体的命令和对应的键;
      • 常用的start、test、stop、restart用的时候可以省去前面的run,举个例子就是npm run start===npm start
    • dependencies:存放开发环境和生产环境都需要依赖的包;

      • 通常存放vue、axios、dayjs等这些在开发环境和生产环境都需要使用的库
      • 通过npm install 命令安装,安装后会在package.json的dependencies字段中记录依赖信息
      • 依赖传递:安装的库可能还会依赖其他库,这些依赖关系会自动处理并存储在node_modules文件夹中
      • 在文件传递的时候可以直接删掉node_modules,后续也可以通过npm install命令,根据dependencies字段自动安装所有记录的依赖库。
    • devDependencies:项目开发环境依赖

      • 生产环境依赖必须打包到生产环境,因为运行时仍需其功能;开发依赖不会出现在最终代码中,仅作为构建工具使用
      • 仅在开发阶段使用的 包放在这里,例如webpack(打包工具)、jest(测试框架)、eslint(代码检查工具)
    • peerDependencies:对等依赖

      • 依赖的一个包,它必须是以另外一个宿主包为前提的(要想用这个库必须也要有另一个库);比如element-plus是依赖于vue3的, ant design依赖于react、react-dom;
    • browserslist:指定Node和npm的版本号

      • 安装过程中先检查对应引擎版本,不符合就报错(也可以指定运行的操作系统,很少用到)
    • engines:指定项目运行所需的Node/npm最低版本

      • 确保开发环境一致性,版本不匹配时会发出警告

开发依赖vs生产依赖 vs对等依赖

生产依赖的安装:npm install 或npm install --save

开发依赖的安装:npm install --save-dev或npm i <包名> -D

不加任何参数时默认安装到生产依赖

对等依赖与生产环境依赖

  • 当用户安装你的包时,peerDependencies 不会自动安装,而是提示用户自行安装(否则报警告)。
  • 生产依赖(dependencies)则会自动安装。

依赖的版本规范

npm的包通常需要遵从semver版本规范:

  • semver: https://semver.org/lang/zh-CN/
  • npm semver: https://docs.npmjs.com/misc/semver

semver版本规范是X.Y.Z:

  • X主版本号(major):当你做了不兼容的API修改(可能不兼容之前的版本);
  • Y次版本号(minor):当你做了向下兼容的功能性新增(新功能增加,但是兼容之前的版本);
  • Z修订号(patch):当你做了向下兼容的问题修正(没有新功能,修复了之前版本的bug);

版本符号说明

  • 精确版本(如2.2.6):
    • 强制安装指定版本
    • 即使有更新版本也不会自动升级
  • 插入符(^):
    • 允许自动更新次版本号和修订号,y和z永远最新
    • 示例:^2.2.6 可升级到2.x.x最新版
  • 波浪号(~):
    • 仅允许自动更新修订号,z永远最新
    • 示例:~2.2.6 可升级到2.2.x最新版

npm install

npm install命令:

  • 根据 package.jsonpackage-lock.json 自动下载并安装所有依赖项。
  • 将包及其依赖项保存到项目的 node_modules 目录中。

局部安装vs全局安装

全局安装

  • 命令:npm install <包名> -g/npm install <包名> --location=global

  • 特点:

    • 安装到Node.js管理的特定全局目录

    • 自动添加到系统环境变量

    • 适用于工具类包(如webpack、yarn、npm、node 等,axios这种一般采用局部安装)

局部安装

  • 命令:npm install <包名>/npm i <包名>,默认把依赖安装到开发和生产依赖里;如果要安装到开发依赖使用npm install <包名> --save-dev或npm i <包名> -D

缓存机制

  • 缓存位置:本地电脑有专门的缓存目录存储已下载的包,默认存储在用户目录下的.npm缓存文件夹
  • 使用流程:安装前先检查缓存是否存在该包,如果存在则直接从缓存解压到node_modules,后续安装优先使用缓存版本;不存在时才从远程仓库下载
  • 版本检查:通过校验和(checksum)验证缓存包的完整性

包标识机制

  • 唯一标识:通过特定算法生成包的唯一标识符
  • 存储位置:标识符记录在package-lock.json文件中
  • 查找依据:根据标识符在缓存中精确查找对应包

package-lock.json

一般自动生成、维护,不用自己维护

作用

  • 版本锁定:精确记录每个依赖包及其子依赖的版本号,防止不同环境下安装不同版本
  • 依赖关系:保存完整的依赖树结构,包括间接依赖的版本信息
  • 多人协作:确保团队成员安装完全相同的依赖版本
  • 缓存标识:存储包的缓存标识符用于快速查找,避免重复下载相同版本的包

结构

  • 基础字段:
    • name:项目名称
    • version:项目版本
    • lockfileVersion:lock文件版本号
  • 依赖记录:
    • requires:标记是否使用requires字段跟踪依赖;
    • dependencies:详细记录所有依赖包及其版本
      • 二者区别:require是代码层面的运行时依赖解析机制,关注“如何找到模块”。dependencies是项目层面静态解析的包管理声明,关注“需要安装哪些模块及版本”。
  • 包信息:
    • resolved:包在远程仓库的具体地址
    • integrity:包的完整性校验值(SHA-512算法生成)

对于安装的优化:

  • 压缩包处理:远程下载的是压缩包(TGZ格式),本地解压使用
  • 缓存优先:下载的包会先存入缓存,后续安装优先使用缓存
  • 自动管理:package-lock.json由npm自动维护,开发者不应手动修改

install过程

基本安装流程

无lock文件时:

  • 解析依赖:解析package.json中的依赖声明

  • 依赖关系树:构建完整的依赖关系树

  • 注册表查询:执行npm install时,会根据包名在registry仓库中查找对应的包

  • 下载机制:找到包后会从远程仓库下载并解压到本地项目的node_modules文件夹

  • 依赖递归:安装过程中会检查包的依赖关系,递归下载所有依赖的包

  • 生成package-lock.json

有lock文件时:

  • 检查lock文件记录的依赖是否与当前环境一致(按照semver版本规范检测,若依赖关系不一致则重新构建依赖树走第一行的流程)

  • 若一致则优先查找本地缓存;若缓存查找到直接使用缓存里的压缩文件,并将压缩文件解压到node_modules里;若缓存未查找到则从registry下载走第一行的流程

其他命令

  • 卸载某个依赖包:
    • npm uninstall <包名>
    • npm uninstall <包名> --save-dev
    • npm uninstall <包名> -D
  • 强制重新build:
    • npm rebulid
  • 清除缓存:
    • npm cache clean
  • npm的命令官方文档
    • https://docs.npmjs.com/cli-documentation/cli
    • 更多的命令,可以根据需要查阅官方文档

yarn、cnpm、npx

yarn

  • yarn是由Facebook、Google、Exponent 和 Tilde 联合推出了一个新的 JS 包管理工具;
  • yarn 是为了弥补 早期npm 的一些缺陷而出现的;早期的npm存在很多的缺陷,比如安装依赖速度很慢、版本依赖混乱等等一系列的问题;但npm5版本后,npm进行了很多的升级和改进

npm和yarn的命令对比

功能 npm命令 yarn命令
初始化项目 npm init yarn init
安装依赖 npm install yarn/yarn install
添加依赖 npm install <包名> yarn add <包名>
添加开发依赖 npm install -D <包名> yarn add -D <包名>
删除依赖 npm uninstall <包名> yarn remove <包名>
运行脚本 npm run <脚本名> yarn run <脚本名>
强制重建 npm rebuild yarn install --force
清除缓存 npm cache clean yarn cache clean
  • npm的锁定文件: package-lock.json
  • yarn的锁定文件: yarn.lock

cnpm

China npm(中国版 npm)

  • 目的:解决国内开发者访问官方 npm registry (https://registry.npmjs.org) 时的网络延迟和稳定性问题。

  • 镜像源:默认使用淘宝 NPM 镜像源 (https://registry.npmmirror.com),服务器位于国内,下载速度更快

    • 每10分钟从npm官方仓库全量同步一次所有包

    • 部署在国内,最初为淘宝内部使用,后开放给所有前端开发者

    • 配置自己的registry仓库为镜像仓库:

      npm config set registry =https://registry.npmmirror.com
      
    • 或者安装cnpm:

      npm install -g cnpm --registry=https://registry.npmmirror.com
      cnpm cpnfig get registry # https://registry.npmmirror.com
      
  • 优点:

    • 解决国内访问npm仓库不稳定的问题
    • 下载速度通常更快
    • 对国内开发者贡献显著
  • 缺点:

    • 同步有10分钟延迟,可能不是最新版本
    • 非官方维护,存在服务可持续性风险
    • 镜像服务器依赖企业运营状况(如淘宝公司)

npx

操作系统执行命令优先级:当前目录下对应的程序=>找不到去环境变量里找

npx是npm5.2之后自带的命令,主要用于调用项目中安装的模块指令

当需要执行node_modules/.bin目录下的可执行文件时,使用npx可以避免全局安装带来的版本冲突问题(如全局webpack5.1.3与项目webpack3.6.0并存时)

执行webpack命令(Webpack 默认查找 ./src/index.js 作为入口文件)以后会生成一个dist目录,这个目录里是打包好的项目,dist目录内部的main.js里是被压缩好的代码,进行了简化,但不改变逻辑

如果想要用当前目录下的局部webpack来打包,可以在package.json的script里配置命令:

"scripts": {
    "build": "npx webpack"
  },

这样每次使用npm build ===npx webpack

但是现代版本优化后可直接执行本地模块命令,所以可以写成:

"scripts": {
    "build": "webpack"
  },

发布自己的开发包

如何开源自己的包?

1、发布到github上

2、发布到npm registry

  • 这个包一定要包含package.json

发布流程:注册npm账号=>在终端登录使用:npm login=>跳转到网页登录,终端显示Logged in on https://registry.npmjs.org/. =>发布前用npm view <新包名>检查包名是否重复、包名是否触发npm垃圾检测机制=>npm publish发布/npm publish --access=public公共发布

这是我的包:

npm i @august7488/my-react-table

下载以后写一个main.js文件导入一下包里的内容,然后执行node +main文件路径

更新仓库需要修改你更新的部分+修改版本号+重新发布

pnpm使用和原理

其他包管理器的问题:局部安装将依赖安装在项目node_modules目录下,仅当前项目可用的;全局安装将工具安装在系统目录,所有项目共享,但版本管理困难;包和包互相依赖占用磁盘空间;删除需要逐个删除node_modules,很麻烦

pnpm解决了这些问题

pnpm是"performant npm"的缩写,意为高性能的npm包管理工具。

  • 速度快

  • 使用硬链接和软链接机制

  • 支持monorepo项目结构

  • 采用内容寻址存储方式

拷贝、硬链接、软连接

软硬链接:硬连接直接指向磁盘存储单元,软连接仅保存目标文件的路径引用(快捷方式)

硬链接:

  • 多个文件名平等共享同一磁盘存储单元,修改任一硬连接都会影响原始数据

  • 删除一个文件名不影响其他硬连接访问数据,直到所有硬连接被删除才会释放存储空间

软连接(符号链接,特殊的文件):

  • 特殊文件类型(Windows快捷方式),仅包含目标文件的绝对/相对路径
  • 通过路径间接访问目标文件,若原始文件被删除则连接失效

文件系统是操作系统对物理磁盘的抽象层,通过文件名映射到实际存储数据

特性 拷贝 硬链接 软连接
存储方式 独立副本 共享数据块 路径跳转
修改影响 互不影响 同步更新 同步更新
原文件删除 无影响 仍可访问 失效
跨文件系统 ✅ 支持 ❌ 不支持 ✅ 支持
适用场景 备份、独立修改 节省空间、多入口访问 快捷方式、跨目录引用

pnpm做了什么

如果用npm或yarn开发了100个项目,这100个项目会在磁盘存储100份副本,例如每个项目的node_modules都包含完整axios副本

pnpm解决方案:

  • 统一存储:所有依赖包存放在全局统一位置(如~/.pnpm-store)

  • 硬链接机制:项目中的依赖通过硬链接指向全局存储,磁盘仅保留一份实体文件

  • 版本差异处理:不同版本依赖包仅存储差异文件,相同文件仍共享硬链接

扁平化和非扁平化

扁平化:

  • npm和yarn安装依赖时,将所有依赖包提升到node_modules根目录下统一管理、直接依赖和间接依赖都平铺在同一层级
  • 例如当A包依赖B包时,B包会被提升到顶层node_modules,B本来是下一级的依赖包被提升到根目录了
  • 查找规则:require查找依赖时,会向上层目录逐级查找node_modules
  • 优点:
    • 避免重复安装:多个包依赖相同子包时,只需安装一次
    • 节省空间:相比早期嵌套式管理,显著减少磁盘空间占用
    • 提升安装效率:减少重复下载和安装相同依赖包的时间
  • 缺点:
    • 隐式依赖问题:代码可以引用未在package.json声明的间接依赖(因为都提升到根目录了)
    • 版本冲突风险:当不同包需要不同版本依赖时,可能导致难以解决的版本冲突
    • 管理混乱:难以区分哪些是直接依赖,哪些是间接依赖

非扁平化:

  • 严格遵循依赖隔离原则,形成嵌套的树状结构,也会各自安装一份。

pnpm:

硬链接和符号链接:既解决了传统扁平化结构的缺陷(如幽灵依赖),又避免了嵌套结构的冗余。

如何实现:

  • 软连接=>硬链接=>全局安装的真实文件

假设安装 lodash@4.17.21

  • 全局存储
    ~/.pnpm-store/v3/files/00/xxxx...(真实文件,硬链接共享)。
  • 项目内 node_modules
node_modules/
├─ lodash -> .pnpm/lodash@4.17.21/node_modules/lodash  # 符号链接
├─ .pnpm/
   ├─ lodash@4.17.21/
      └─ node_modules/lodash  # 硬链接到全局文件

符号链接只是指针:它不存储文件内容,只是告诉系统去哪里找真实文件,真实文件在全局;而pnpm install后,项目的 node_modules 看起来和 npm/yarn 几乎一样,不影响开发且磁盘空间和安装速度更优

pnpm的使用

npm和pnpm对照

npm install → pnpm install(安装package.json中所有依赖)
npm install <pkg> → pnpm add <pkg>(安装特定包)
npm uninstall <pkg> → pnpm remove <pkg>(卸载包)
npm run <cmd> → pnpm <cmd>(运行脚本)
注意事项:直接使用pnpm <cmd>运行脚本时,需确保脚本名不与pnpm内置命令冲突

pnpm命令:

常用命令
pnpm link:将本地包链接到全局或其他位置
pnpm why:显示依赖指定包的所有包
pnpm rebuild:重新构建所有包
pnpm prune:移除未使用的包

高级命令:
pnpm patch <pkg>:对包打补丁
pnpm dlx:临时下载并运行包(类似npx)
pnpm create:创建新项目

脚本运行:
pnpm run <设置的命令>或简写为pnpm <设置的命令>

支持--if-present参数:脚本不存在时不报错
支持--parallel参数:并行运行脚本

pnpm store的存储位置

pnpm store

  • 7.0之前:统一存储在用户根目录下的.pnpm-store文件夹中
  • 7.0之后:
    • pnpm store path可获取当前项目使用的store目录位置
      • Linux默认位置:~/.local/share/pnpm/store
      • macOS默认位置:~/Library/pnpm/store
      • Windows默认位置:%LOCALAPPDATA%/pnpm/store
  • 跨磁盘限制:硬链接不能跨不同物理磁盘建立,pnpm会根据项目所在磁盘自动调整存储位置

pnpm store prune(裁剪)命令:当项目删除或依赖变更后,定期执行清理未被引用的旧版本,删除store中未被任何项目引用的包,释放磁盘空间;不建议经常使用

webpack和打包过程

认识webpack工具

写一个前端项目的过程:

挂载根节点
Babel转译
Webpack处理
Webpack打包
Webpack打包
HTML引入
安装依赖
安装依赖
安装依赖
HTML
React组件
JS/JSX
ES5 JS
CSS
优化后的CSS
bundle.js
浏览器运行
npm

打包工具:打包所有资源,优化生产环境代码,转为浏览器可以看懂的语言(html、css、js)

Webpack的作用: Webpack是一个现代JavaScript应用程序的静态模块打包器,它将项目中的各种资源(模块)打包成浏览器可以识别的格式。

前端开发的三大框架的创建过程是借助于脚手架(CLI)的,而这些脚手架都是基于webpack来支持模块化、less、ts、打包优化等的

webpack官网:https://webpack.js.org

使用安装:

npm install webpack webpack-cli -g # 全局安装

npm install webpack webpack-cli -D # 局部安装(更多)

  • webpack: 核心引擎

  • webpack-cli: 命令行接口工具

直接打包:

  • 用npx webpack执行局部安装的webpack
  • 打包结果:默认生成dist/main.js文件,包含所有依赖的打包代码
  • 部署文件:实际部署时只需使用打包后的dist/main.js文件
  • 运行:HTML引用,创建index.html并通过

输入配置:

  • 默认必须使用src/index.js作为入口文件,但是可以通过–entry ./src/main.js指定自定义入口文件

输出配置:

  • –output-filename bundle.js修改输出文件名
  • –output-path ./build修改输出目录

缺点:命令行参数方式配置冗长且不易维护

另一种方法:配置文件,webpack.config.js

  • 初始没有该文件可以手动创建

  • 配置格式:使用CommonJS模块化语法导出配置对象

  • //我的配置如下
    const path = require('path');
    module.exports = {
        
        entry:'./src/index.js',
        output: {//output的path一定是绝对路径
            filename: 'bundle.js',
            path: path.resolve(__dirname, './bundle'),
        }
    }
    
  • 也可以给配置文件重命名:使用npx webpack 的时候加上–config wk.config.js,指定自定义配置文件;也可以把这个命令写进package.json的script字段里

内置模块path

根据不同的操作系统, POSIX 标准(可移植操作系统接口),使软件可以跨平台移植(如 Linux 和 macOS 共享大部分命令行工具

path常见的API :

  • 获取文件后缀名:使用path.extname()获取文件扩展名

  • 获取文件名:

    • path.basename():获取完整文件名(含扩展名)
    • path.dirname():获取所在目录路径q
  • 路径的拼接:直接使用+会导致路径不规范,无法正确处理…/等相对路径

    • path.join方法:自动处理不同操作系统的分隔符差异

    • path.resolve方法

      • 始终返回绝对路径,从右往左拼接,遇到第一个绝对路径时停止拼接

      • 若全部片段处理后仍未得到绝对路径,则使用当前工作目录

      • 自动规范化路径(删除尾部斜杠/忽略空路径段)

      • 特殊:无参数时返回当前工作目录绝对路径

webpack基本打包

打包过程:根据命令或者配置文件找到入口文件,生成一个依赖关系图,包含应用程序中所需的所有模块(比如.js文件、css文件、图片、字体等),遍历图结构,打包一个个模块(根据文件的不同使用不同的loader来解析,例如对图片和js的loader是不一样的)

因为webpack默认只打包js文件,其他格式的文件肯在打包时报错,loader是来适配不同类型的文件打包的。

loader的核心功能:用于对模块的源代码进行转换,使webpack能够处理非JavaScript文件

webpack配置文件

使用方法:在webpack.config.js文件的module.exports中添加module配置项,在module中配置rules数组

  • rules属性对应的值是一个数组: [Rule]

  • 数组中存放的是一个个的Rule, Rule是一个对象, 对象中可以设置多个属性:

    • test属性:匹配文件后缀,通常会设置成正则表达式;

    • use属性:指定使用的loader(可以是字符串或数组

      • UseEntry是一个对象,可以通过对象的属性来设置一些其他属性

      • loader: 必须有一个 loader属性, 对应的值是一个字符串;

      • options: 可选的属性, 值是一个字符串或者对象, 值会被传入到loader中;

        query: 目前已经使用options来替代;

      • 传递字符串(如: use: [‘style-loader’]) 是 loader 属性的简写方式 (如: use: [{ loader: ‘style-loader’}]);

  • 格式如下:

    const path = require('path');
    module.exports = {
        
        entry:'./webpacktest/src/index.js',
        output: {
            filename: 'bundle.js',
            path: path.resolve(__dirname, './bundle'),
        },
        //下面是配置loader
        module: {
            rules: [ {
                test: /\.css$/,
                use: [
                    'style-loader',
                    'css-loader'
                ]
            }, {
                test: /\.(png|jpg|gif)$/,
                use: [
                    {
                        loader: 'file-loader',
                        options: {
                            name: '[path][name].[ext]',
                            outputPath: 'images/'
                        }
                    }
                ]
            }]
        }
    }
    

编写和打包CSS文件

loader使用也要安装,例css-loader:npm install caa-loader -D

此时完成了css的解析,要想将css插入页面里,需要使用style-loader,安装:npm install style-loader -D

style-loader和css-loader是并列关系,所以应该在use数组里继续引用:

  use: [
                'style-loader',
                'css-loader'
            ]
        }

因为loader的执行顺序是从下往上,所以应该把style-loader写在前面

如果loader只有一个,可以用简写1:

//简写1
rules:[
	test:/\.css$/,
	loader:'css-loader'
]

多个loader但是没有特别的属性,可以用简写2:

//简写2
use:['style-loader',"style-loader"]

编写和打包LESS文件

同理,处理less文件要用less-loader:

{
test: /.less$/,
use: ["style-loader", "css-loader", "less-loader"]
}

postcss工具处理CSS

在写一些有兼容性问题的(例如css的user selector:元素内容无法被鼠标选中)需要添加浏览器前缀,但是不同的浏览器有不同的前缀,需要为每个属性手动添加所有可能的前缀。另一个解决方法是使用PostCSS

PostCSS:一个通过JavaScript来转换样式的工具,帮助我们进行一些CSS的转换和适配,比如自动添加浏览器前缀、css样式重置,但是实现这些功能,我们需要借助于PostCSS对应的插件:autoprefixer

处理方法:下载好 npm install postcss-loader -D 和npm install autoprefixer -D插件

 rules: [ {
            test: /\.css$/,
            use: [
                'style-loader',
                'css-loader', {
                    loader: 'postcss-loader',
                    options: {
                        postcssOptions: {
                            plugins: [
                                require('autoprefixer')
                            ]
                        }
                    }
                }
            ]
        }
    }]

也可以把配置单独抽出来创建postcss.config.js文件,在里面配置,然后导出,在webpack.config.js里导入

//单独抽出
module.exports = {
        plugin: [
            'autoprefixer'
        ]
}
use: [
                'style-loader',
                'css-loader', 
                'postcssloader'
            ]

postcss-preset-env

在配置postcss-loader时,我们配置插件并不使用autoprefixer,用postcss-preset-env

核心功能:

  • postcss-preset-env也是一个postcss的插件
  • 它可以帮助我们将一些现代的CSS特性,转成大多数浏览器认识的CSS,并且会根据目标浏览器或者运行时环境添加所需的polyfill并且支持CSS新特性转换
  • 也包括会自动帮助我们添加autoprefixer(所以相当于已经内置了autoprefixer)
  • 可配置px转rem/vw等适配方案
  • 提供更全面的浏览器兼容方案

webpack打包其他资源

Webpack打包图片

webpack打包图片的流程:对图片资源进行重命名,通过内置的哈希算法生成唯一的哈希值作为新文件名(避免文件名冲突,同时便于缓存管理)=>原始图片资源复制到输出目录=>将URL设置到img元素或background-image属性中

在webpack5之前, 加载这些资源我们需要使用一些loader, 比如raw-loader、url-loader、file-loader;在webpack5开始, 我们可以直接使用资源模块类型(asset module type), 来替代上面的这些loader;

配置打包图片:

 {
             test: /\.(png|jpe?g|gif)$/,
             type: 'asset/resource',
             generator: {
                    filename: 'images/[hash][ext][query]'
                },  
                use: [
                    {
                        loader: 'file-loader',
                        options: {
                            name: '[path][name].[ext]',
                            outputPath: 'images/'
                        }
                    }
                ]   
            }

资源模块类型(asset module type),通过添加4种新的模块类型, 来替换所有这些loader:

  • asset/resource :发送一个单独的文件并导出URL;✓ 之前通过使用 file-loader 实现;

  • asset/inline :导出一个资源的data URI; ✓之前通过使用 url-loader 实现;

  • asset/source: 导出资源的源代码;✓之前通过使用 raw-loader 实现(用的少)

  • asset: 在导出一个 data URI 和发送一个单独的文件之间自动选择;✓ 之前通过使用 url-loader,并且配置资源体积限制实现;

对于图片文件:

  • asset/resource会生成独立文件,通过url引用图片资源,适用于大尺寸图片,需要两次请求
  • asset/inline将图片转换为base64码,直接嵌入到打包文件中,不生成独立图片文件,适用于小尺寸图片,只要一次请求

如果想实现"大的图片用asset/resource,小的图片用asset/inline"可以将type:设置为asset,添加一个parser属性,并且制定dataUrl的条件, 添加maxSize属性

  {
                test: /\.(png|jpe?g|gif)$/,
                type: 'asset',
                generator: {
                    filename: 'images/[hash][ext][query]'
                }, 
                parser: {
                    dataUrlCondition: {
                        maxSiaze: 8 * 1024, // 8kb
                        // 8kb以下的图片会被转成base64格式
                    }
                }
            }

如果想对生成的图片文件重命名,可以在generator里用filename:

 generator: {
                    //name: '[hash][ext][query]',
                    // 生成的图片名称
                    // 这里的hash是根据文件内容生成的hash值
                    // ext是文件的扩展名
                    // query是文件的查询参数
     				//images/的作用是自动生成一个image文件夹存放图片
                    filename: 'images/[hash][ext][query]'
                }, 

Webpack打包JS代码

webpack默认把js打包为es6,如果要打包为es5,需要babel工具

Babel和babel-loader

Babel是一个工具链,主要用于将ECMAScript 2015+代码转换为向后兼容的JavaScript版本,包括语法转换和源代码转换
babel本身可以作为一个独立的工具(和postcss一样),不和webpack等构建工具配置来单独使用。

安装:

npm install @babel/cli @babel/core -D

  • @babel/core: babel的核心代码,必须安装;

  • @babel/cli:可以让我们在命令行使用babel;

使用babel来处理我们的源代码:

  • src: 是源文件的目录;
  • –out-dir: 指定要输出的文件夹dist;

插件使用:

  • 转换箭头函数:

    • npm install @babel/plugin-transform-arrow-functions -D
    • npx babel src --out-dir dist --plugins=@babel/plugin-transform-arrow-functions
  • 如果想把const转为var:

    • npm install @babel/plugin-transform-block-scoping -D

    • npx babel src --out-dir dist --plugins=@babel/plugin-transform-block-scoping,@babel/plugin-transform-arrow-functions

    {
            test: /\.js$/,
            exclude: /node_modules/,
            use: {
              loader: 'babel-loader',
              options: {
                  //预设
                presets: ['@babel/preset-env'],
                plugins: ['@babel/plugin-transform-arrow-functions','@babel/plugin-transform-block-scoping'],
              },
            },
          },
    

    这个loader的配置也可以提取出来单独做个文件

真正开发的时候不是每次都要用这些插件的,太麻烦,可以使用预设,presets就是给babel设置预设,安装预设:npm install @babel/preset-env (-D)

常见的预设有三个:env、react、TypeScript

使用-npx babel <要处理的源码目录> --out-dir <转译后的文件输出的目录> --preset=@babel/preset-env

babel只转译代码,webpack会加入一些前端模块化的,可以在weboack的rules下的loader配置babel的loader

Webpack打包Vue

vue的安装

  • 安装Vue核心库:npm install vue

  • 安装Vue加载器:npm install vue-loader -D

安装vue-loader和@vue/compiler-sfc

vue在打包的时候要额外使用一个插件,在:vue-loader/dist/index下

const path = require('path');
const { VueLoaderPlugin } = require('vue-loader/dist/index')
module.exports = {
  entry: './webpacktest/src/index.js',
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, './bundle'),
  },
  module: {
      //省略...
    },
    plugins: [
      //做了一些css的内容,使用该插件
    new VueLoaderPlugin(),
  ],
}
    },

resolve模块解析

在开发中我们会有各种各样的模块依赖,这些模块可能来自于自己编写的代码,也可能来自第三方库;

resolve可以帮助webpack从每个 require/import 语句中,找到需要引入的合适的模块代码;

webpack使用 **enhanced-resolve **来解析文件路径

  • webpack能解析三种文件路径:
    • 绝对路径
      • 由于已经获得文件的绝对路径,因此不需要再做进一步解析。
    • 相对路径
      • 在这种情况下,使用 import 或 require 的资源文件所处的目录,被认为是上下文目录;
      • 在 import/require 中给定的相对路径,会拼接此上下文路径,来生成模块的绝对路径;
    • 模块路径
      • 在 resolve.modules中指定的所有目录检索模块;
        • 默认值是 [‘node_modules’],所以默认会从 node_modules 中查找文件;
      • 我们可以通过设置别名的方式(alias)来替换初识模块路径
  • 在解析文件路径的时候,要确认是文件还是文件夹:
    • 如果是一个文件:
      • 如果文件具有扩展名,则直接打包文件;
      • 否则, 将使用 resolve.extensions选项作为文件扩展名解析;
    • 如果是一个文件夹:
      • 会在文件夹中根据 resolve.mainFiles配置选项中指定的文件顺序查找;
        • resolve.mainFiles的默认值是 [‘index’];
        • 再根据 resolve.extensions来解析扩展名;
resolve: {
        extensions: ['.js', '.vue', '.json'],
            //起别名,给路径很复杂的模块起别名
        alias: {
            utils: path.resolve(__dirname, './src/utils'),
            vue$: 'vue/dist/vue.esm.js',
            '@': path.resolve(__dirname, './src'),
            'vue$': 'vue/dist/vue.esm.js',
        },
    },

webpack常见的插件和模式

认识插件Plugin

Loader是用于特定的模块类型进行转换;

Plugin可以用于执行更加广泛的任务, 比如打包优化、资源管理、环境变量注入等,在基础上更优化的一些东西;

CleanWebpackPlugin

借助CleanWebpackPlugin插件自动化清理构建输出目录,插件会清除 output.path 目录(如 dist/)下的所有文件,但保留目录本身,防止旧文件干扰新构建结果

npm install clean-webpack-plugin -D

const {ClearWebpackPlugin} = require('clean-webpack-plugin')
...(省略一些代码)
plugins: [
        //根据output找到打包后的目录,自动清除该目录后再打包生成目录里的新内容
        new ClearWebpackPlugin(),
  ],

除了使用这个插件,也可以在output下的clean的值设置为true,意思是在生成新文件夹清空output目录

...(省略一些代码)
output: {
  clean:true//生成新文件夹清空output目录
  },

HtmlWebpackPlugin

Webpack 生态中一个核心插件,用于简化 HTML 文件的创建和管理,自动生成html文件

npm install html-webpack-plugin -D

通过defer的方式将js引入html

defer 属性告诉浏览器:

  1. 异步下载:立即开始下载脚本,但不阻塞 HTML 解析
  2. 延迟执行:脚本会在 DOM 解析完成后DOMContentLoaded 事件前),按照声明顺序执行
const HtmlWebpackPlugin = require('html-webpack-plugin');
...(省略一些代码)
plugins: [
      //自动生成html文件
        new HtmlWebpackPlugin(),
  ],

自定义模板数据填充

形如<%= 变量 %>的内容是EJS语法,是EJS模块填充数据的方式

在配置HtmlWebpackPlugin时, 我们可以添加如下配置

  • template: 指定我们要使用的模块所在的路径;

  • title: 在进行htmlWebpackPlugin.options.title读取时, 就会读到该信息;

  • const HtmlWebpackPlugin = require('html-webpack-plugin');
    module.exports = {// 其他省略
    	plugins: [
            new HtmlWebpackPlugin({
    			title: "webpack项目",
    			template: "./public/index.html"
    		})
    	]
    }
    

一些引入的东西没有常量与之对应,例如baseURL,这时候就要用到下一个插件设置全局常量

DefinePlugin

DefinePlugin允许在编译时创建配置的全局常量,是一个webpack内置的插件(不需要单独安装):

const DefinePlugin = require('webpack').DefinePlugin;
module.exports = {// 其他省略
	plugins: [
    	//进行一些全局常量的定义
      	new DefinePlugin({
      	//这个定义会被放进代码里执行,所以在这里写成字符串
        	"BASE_URL": JSON.stringify("http://localhost:8080"),
    })
	]
}

mode模式配置

mode:设置构建模式,它会自动启用对应环境下的优化策略,三种可选:

  • development,开发模式,优化构建速度和调试体验,本地开发环境
  • production,生产模式,优化代码体积和运行性能,线上生产环境(什么都不设置默认为production)
  • none,不启用任何默认优化,特殊需求/自定义配置

source-map

调试困境:浏览器运行的是打包压缩后的代码,错误信息指向的是转换后的代码位置;难以直接对应到原始源代码,复杂项目调试效率低下

source-map:即使代码经过webpack打包后,控制台仍能准确定位到源文件中的错误行数,开发者无需查看打包后的bundle.js,可直接定位到源代码错误位置

使用source-map:在webpack.config.js的devtool配置项设置:‘source-map’,打包后会自动生成.map文件;在打包后的文件里最后一行的注释是“//# sourceMappingURL=bundle.js.map”,这个注释可以与source.map文件建立关联,浏览器开发者工具利用这个信息反向映射

一般浏览器都默认开启了,在这里设置,css也可以开启源码映射

source-map的配置项:

  • version:当前使用的版本,最新是第三版
  • sources:被转换的源文件
  • names:转换前的变量和属性名称
  • mappings:source-map用来和源文件映射的信息,比如位置信息,一串base64VLQ(veriable-length quantity可变长度值)
  • file:打包后的文件
  • sourceContent:转换前的具体代码信息,和sources对应
  • sourceRoot:所有的sources相对的根目录,控制源代码还原时的基础路径

devtool的配置项:

  • Webpack 提供了 多达 26 种 devtool 配置选项,用于控制 source-map 的生成。
  • 不同的 devtool 取值会影响:
    • 生成的 source-map 类型和精度
    • 打包速度与性能
  • 选择合适的值需要根据开发 or 生产环境进行权衡。

🔗 官方文档链接:https://webpack.docschina.org/configuration/devtool/

不会生成 Source Map 的几种取值

  1. false

    • 完全不使用 source-map。

    • 打包结果中不包含任何 source-map 相关的内容。

  2. none

    • production 模式下的默认值。

    • false 类似,不生成 source-map。

  3. eval

    • development 模式下的默认值。

    • 不会生成独立的 source-map 文件,但会在 eval 包裹的代码中加入:

      //# sourceURL=...
      
    • 可以在浏览器开发者工具中看到源码结构,调试友好,生成 sourceURL 注释

  4. eval-source-map

    • 会生成source map,但以DataUrl形式添加到eval函数后面。

    • 使用场景: 这种模式使用较少,主要用于特定调试场景,了解即可。

      1. cheap-source-map
        • 低开销,没有生成列映射,因为在开发中我们只要通过行信息就可以定位到错误了
  5. inline-source-map

    • 将source map以data URL的形式直接添加到打包后的bundle.js文件末尾,而不是生成单独的.map文件;

    • 会导致打包后的JS文件体积增大

  6. cheap-module-source-map和cheap-source-map的区别

    • 格式保留:
      • cheap-source-map:会删除源码中的空格等格式信息
      • cheap-module-source-map:完整保留源码格式
    • 错误定位:
      • cheap-source-map:经过loader处理的代码可能显示错误行号不准确
      • cheap-module-source-map:能准确定位到loader处理前的原始代码位置
    • 适用场景:
      • 当项目中使用babel等loader转换代码时,推荐使用cheap-module-source-map
      • 简单项目没有使用loader时,两者差异不大
  7. hidden-source-map

    • 一种特殊的source map配置方式,会生成source map文件但不会在打包文件中引用;需要时可通过手动添加引用注释来启用
    • 主要用于production生产环境
  8. nosources-source-map

    • 会生成source map文件,但仅保留错误信息的提示功能
    • 源代码缺失:不会生成对应的源代码文件,无法查看原始文件内容

Babel

Babel的底层原理

从一种源代码(原生语言)转换成另一种源代码(目标语言),这是编译器的工作。事实上我们可以将babel看成就一个编译器。

Babel编译器的作用就是将我们的源代码,转换成浏览器可以直接识别的另外一段源代码

Babel也拥有编译器的工作流程:

  • 解析阶段 (Parsing)

  • 转换阶段 (Transformation)

  • 生成阶段 (Code Generation)

babel的配置文件

配置文件:

babel.config.json/.js/.cjs.mjs,babel7以后,可以直接做Monorepos项目的子包
早期:.babelrc.json/.js/.cjs.mjs,对于配置Monorepos项目比较麻烦

babel和polyfill

polyfill直译为垫片、填充材料

polyfill 是一段用旧技术(比如 ES5)实现的新功能(比如 ES6 或更高版本)的代码,使得不支持新特性的浏览器或环境也能运行这些新特性。

eg:问题:IE 浏览器不支持Promise, 解决:引入 Promise 的 polyfill

如何使用:

  • 早期可以使用@babel/polyfill的包,现不推荐

  • 7.4.0以后可以通过单独引入core-js和regenerator-runtime来使用:

    • npm install core-js regenerator-runtime --save

    • import '@babel/polyfill'; // 引入polyfill
      // 该配置文件用于Babel的配置,主要是为了转换ES6+和JSX语法
      import 'core-js/stable'; // 如果useBuiltIns为'usage'或'entry',需要引入core-js
      import 'regenerator-runtime/runtime'; // 如果使用了async/await,需要引入regenerator-runtime
      module.exports = {
          presets: [
              '@babel/preset-env',{
                  corejs: 3,//指定core-js版本
                  useBuiltIns: 'usage',//usage表示按需加载polyfill,false表示不加载polyfill,entey表示根据browserslist目标导入所有的polyfill
              },
              '@babel/preset-react'
          ],
          plugins: [
              '@babel/plugin-proposal-class-properties'
          ]
      }
      

使用注意:

  • polyfill 只是模拟功能,不是语法转换(语法转换是由 Babel 等工具完成的)。
  • polyfill 会增加代码体积,要根据项目需要选择引入。
  • 对性能要求高的项目要注意 polyfill 的体积和性能影响。

React的jsx支持和TS解析

babel把jsx转为js:

  • 在我们编写react代码时, react使用的语法是jsx, jsx是可以直接使用babel来转换的。

  • 对react jsx代码进行处理需要如下插件:

    • @babel/plugin-syntax-jsx
    • @babel/plugin-transform-react-jsx
    • @babel/plugin-transform-react-display-name
  • 不想安装可以用preset:

    • npm install @babel/preset-react -D

TS的解析使用ts-loader:

  • npm install ts-loader -D

  • test: /\.ts$/,
       use: {
         loader: 'ts-loader',
         options: {
           transpileOnly: true // 只进行转译,不进行类型检查
         }
       }
    

    真正开发一般使用babel来处理,而不是ts-loader,一是需要单独下载,二是和polyfill不兼容。babel怎么处理?

    • 使用针对ts的预设:@babel/preset-typescript

    • '@babel/preset-typescript', // 如果需要支持TypeScript
             {
                 corejs: 3, // 指定core-js版本
                 useBuiltIns: 'usage', // usage表示按需加载polyfill, false表示不加载polyfill, entry表示根据browserslist目标导入所有的polyfill
             },
      
      • 使用ts-loader (TypeScript Compiler)

        • 来直接编译TypeScript,那么只能将ts转换成js;
        • 不能和polyfill兼容
        • 我们需要借助于babel来完成polyfill的填充功能;
      • 使用babel-loader (Babel)

        • 来直接编译TypeScript,也可以将ts转换成js,并且可以实现polyfill的功能;

        • 但是babel-loader在编译的过程中,不会对类型错误进行检测;

        • 如何用babel转译ts并且进行校验:使用tsc进行类型校验

          • 当输出与源文件基本相同时使用tsc;当需要构建管道和多种输出时使用babel转换+tsc类型检查

          • "scripts": {
              "build": "webpack --config ./webpacktest/webpack.config.js",
              "server": "webpack serve --config ./webpacktest/webpack.config.js",
              "ts-check": "tsc --noEmit",// 不生成输出
              "ts-check-watch": "tsc --noEmit --watch" // 实时监听文件变化
            }
            

Webpack搭建本地服务器

开启本地服务器

每次使用liveserver的本质就是搭建了一个服务器,每次更新页面都需要再打包,再跑一次liverserver,很麻烦;所以我们用webpack搭建本地服务器

这个过程经常操作会影响我们的开发效率,我们希望可以做到,当文件发生变化时,可以自动的完成编译和展示;

为了完成自动编译,webpack提供了几种可选的方式:

  • webpack watch mode;
  • webpack-dev-server (常用);
  • webpack-dev-middleware;

安装:install webpack-dev-server -D

搭建完服务器会自动进行打包,打包好以后不会生成本地文件,因为放到磁盘里再从磁盘里读效率太低;而是放到内存里,服务器从内存里读取,浏览器只做一个请求就可以了

在这里可以修改package.json里的scripts里的配置项:

"scripts": {
    "build": "webpack --config ./webpacktest/webpack.config.js",
    "server":"webpack serve --config ./webpacktest/webpack.config.js"
  }

HMR热模块替换

如果按照上面的方法,我们每次修改一个地方,整个服务器会全部重新刷新;如果只是一个模块改变了,只想更新这一个模块,可以使用热模块更新(HMR)

HMR的全称是Hot Module Replacement, 翻译为模块热替换,模块热替换是指在应用程序运行过程中,替换、添加、删除模块,而无需重新刷新整个页面;

HMR通过如下几种方式,来提高开发的速度:

  • 不重新加载整个页面,这样可以保留某些应用程序的状态不丢失;
  • 只更新需要变化的内容,节省开发的时间;
  • 修改了css、js源代码,会立即在浏览器更新,相当于直接在浏览器的devtools中直接修改样式;

如何使用HMR呢?

  • 默认情况下,webpack-dev-server已经支持HMR,我们只需要开启即可(默认已经开启);

  • 在不开启HMR的情况下,当我们修改了源代码之后,整个页面会自动刷新,使用的是live reloading;

  • module: {
     ...省略
      },
    devServer: {
          hot: true,//热更新,默认为true
      }
    

    但是会发现, 当我们修改了某一个模块的代码时, 依然是刷新的整个页面:

    • 这是因为我们需要去指定哪些模块发生更新时, 进行HMR;
    //写在入口文件
    if (module.hot) {
        //当util更新以后进行热模块替换
        module.hot.accept("./util.js", () => {
            console.log("util更新了");
        })
    }
    

框架的HMR

开发Vue、React项目,我们修改了组件,希望进行热更新,这个时候应该如何去操作呢?

事实上社区已经针对这些有很成熟的解决方案了:

  • 比如vue开发中,我们使用vue-loader,此loader支持vue组件的HMR,提供开箱即用的体验;
  • 比如react开发中,有React Hot Loader,实时调整react组件(目前React官方已经弃用了,改成使用react-refresh);

devServer配置

除了对hot进行配置,我们还可以配置很多其他的服务器配置项

静态文件是指在前端项目中不需要经过构建工具编译或处理,可以直接被浏览器加载和使用的固定资源文件。这些文件在服务器上以原始形式存储,每次请求时内容不变(除非手动更新)一般有图片,图标,字体,音视频,js库,css库等

 devServer: {
        hot: true,//热更新,默认为true
        open: true,//自动打开浏览器
        port: 8080,//端口号
        compress: true,//是否为静态文件启用gzip压缩,默认值是false
      	host:localhost//配置主机,如果希望其他地方也可以访问,设置为0.0.0.0
      	static:[public],//直接给浏览器看的文件目录,不需要打包
        //hotOnly:该配置项被删除
        liveReload:false,//默认为true,如果想完全禁用更新后刷新页面的功能,需要liveReload设置为false,把hot设置为false
       
  }
  • localhost 和 0.0.0.0 的区别:
    • localhost: 本质上是一个域名,通常情况下会被解析成127.0.0.1;
    • 127.0.0.1: 回环地址(Loop Back Address),表达的意思其实是我们主机自己发出去的包,直接被自己接收;
      • ✓ 正常的数据库包经常应用层 -传输层 -网络层 -数据链路层 -物理层;
      • ✓ 而回环地址,是在网络层直接就被获取到了,是不会经常数据链路层和物理层的;
      • ✓ 比如我们监听127.0.0.1时,在同一个网段下的主机中,通过ip地址是不能访问的;
    • 0.0.0.0: 监听IPV4上所有的地址,再根据端口找到不同的应用程序;
      • ✓ 比如我们监听0.0.0.0时,在同一个网段下的主机中,通过ip地址是可以访问的;

在真实的开发里要区分开发环境和生产环境的配置,不同的环境进行不同的配置,在mode配置项里,选择相应的production或development的时候webpack就以及针对这两个进行相应的配置了

  • 命令映射:
    • npm run serve -> 加载开发配置
    • npm run build -> 加载生产配置
  • 实现方式:在package.json中通过–config参数指定不同配置文件
  • 框架实践:Vue和React也采用类似的环境区分方案

当配置文件的路径改变时,入口文件不用修改,需要修改的是context属性(保持entry为./src/main.js,因为默认context是项目根目录)

context的默认配置路径是webpack启动的路径

有时候开发环境配置(dev)和生产环境配置(prod)存在大量相似配置项,可以将公共配置项抽离到单独文件(webpack.comm.config.js),抽离出来以后再合并,就需要使用webpack-merge

const { merge } = require("webpack-merge")//下载对应插件
const commonConfig = require("./webpack.comm.config")//导入公共配置
module.exports = merge(commonConfig, {mode: "development",//合并
	devServer: {
	hot: true,
	}
})

必须使用CommonJS规范(module.exportsmodule.exportsmodule.exports),因为webpack运行在Node环境

server的proxy代理

有一个后端服务器,后端服务器和前端不在同一个端口下,就会发生跨域

可以开启proxy代理:

devServer: {
  host: 'localhost',
  port: 3000,
  proxy: {
    '/api': {
      target: 'http://localhost:5000', // 目标服务器
      changeOrigin: true,              // 是否修改源地址为目标地址,涉及到服务器校验
      pathRewrite: { '^/api': '' }     // 重写路径(可选)
    }
  }
  historyApiFallback:true  
}

historyApiFallback

在 SPA 中使用前端路由(如 React Router、Vue Router)时:

  • 当用户从首页 / 导航到 /about 时,这实际上是由前端路由处理的
  • 但如果用户直接在浏览器中刷新 /about 页面或直接访问该 URL
  • 服务器会尝试查找 /about.html 或对应的服务器路由
  • 由于 SPA 只有一个入口文件(通常是 index.html),服务器会返回404

historyApiFallback: true 的作用

当设置为 true 时:

  1. 服务器会将所有未匹配静态文件的 GET 请求重定向到 index.html
  2. 前端路由接管后,能正确解析 URL 并渲染对应的组件
  3. 解决了直接访问或刷新非根路径时的404问题

除了布尔配置,还有rewrites重写路径:

historyApiFallback: {
  rewrites: [
    // 匹配 /user 开头的路径,重定向到 user.html
    { from: /^\/user/, to: '/user.html' },
    // 其他所有路径重定向到 index.html
    { from: /./, to: '/index.html' }
  ]
}

事实上devServer中实现historyApiFallback功能是通过connect-history-api-fallback库的:可以查看connect-history-api-fallback 文档

Git

git介绍

什么是版本控制?

  • 版本控制的英文是Version control;
  • 是维护工程蓝图的标准作法,能追踪工程蓝图从诞生一直到定案的过程;
  • 版本控制也是一种软件工程技巧,借此能在软件开发的过程中,确保由不同人所编辑的同一程序文件都得到同步;

简单来说,版本控制在软件开发中,可以帮助程序员进行代码的追踪、维护、控制等等一系列的操作。

版本控制历史:

  • 版本控制的史前时代(没有版本控制):
    • 人们通常通过文件备份的方式来进行管理, 再通过diff命令来对比两个文件的差异;
  • CVS (Concurrent Versions System):
    • 第一个被大规模使用的版本控制工具, 诞生于1985年;
    • 由荷兰阿姆斯特丹VU大学的Dick Grune教授实现的, 也算是SVN的前身(SVN的出现就是为了取代CVS的)。
  • SVN (Subversion):
    • 因其命令行工具名为svn因此通常被简称为SVN;
    • SVN由CollabNet公司于2000年资助并发起开发, 目的是取代CVS, 对CVS进行了很多的优化;
    • SVN和CVS一样, 也属于集中式版本控制工具;
    • SVN在早期公司开发中使用率非常高, 但是目前已经被Git取代;
  • Git (Linus的作品):
    • 早期的时候, Linux社区使用的是BitKeeper来进行版本控制;
    • 但是因为一些原因, BitKeeper想要收回对Linux社区的免费授权;
    • 于是Linus用了大概一周的时间, 开发了Git用来取代BitKeeper;
    • Linus完成了Git的核心设计, 在之后Linus功成身退,将Git交由另外一个Git的主要贡献者Junio C Hamano来维护;

集中式和分布式区别

  • CVS和SVN都是属于集中式版本控制系统(Centralized Version Control Systems, 简称 CVCS)

    • 它们的主要特点是单一的集中管理的服务器,保存所有文件的修订版本;
    • 协同开发人员通过客户端连接到这台服务器,取出最新的文件或者提交更新;
    • 核心问题:中央服务器不能出现故障:
  • Git是属于分布式版本控制系统 (Distributed Version Control System, 简称DVCS)

    • 客户端并不只提取最新版本的文件快照,而是把代码仓库完整地镜像下来,包括完整的历史记录;
    • 这么一来,任何一处协同工作用的服务器发生故障,事后都可以用任何一个镜像出来的本地仓库恢复;
    • 因为每一次的克隆操作,实际上都是一次对代码仓库的完整备份;

Git的环境安装搭建

下载官网:https://git-scm.com/downloads

安装git时还安装了三个工具:Bash-CMD-GUI

  • Bash, Unix shell 的一种, Linux 与 Mac OS X 都将它作为默认 shell。
  • Git Bash
    • Git Bash 就是一个 shell,移植的 Bash 终端 + Git 集成,支持linux
    • 所以建议在使用的时候,用 Bash 更加方便;
  • Git CMD
    • 命令行提示符(CMD)是 Windows 操作系统上的命令行解释程序;
    • 当你在 Windows 上安装 git 并且习惯使用命令行时,可以使用 cmd 来运行 git 命令;
  • Git GUI
    • 基本上针对那些不喜欢黑屏(即命令行)编码的人;
    • 它提供了一个图形用户界面来运行 git 命令;

git创建时默认的分支是master,社区提倡main

Git初始化本地仓库

获取Git仓库 - git init/git clone=>把当前目录的所有文件添加到暂存区-git add .=>查看提交信息-git log

文件状态的划分

未跟踪状态:git没有将此文件添加到git仓库管理中,需要add命令

已跟踪:添加到Git仓库管理的文件处于已跟踪状态,Git可以对其进行各种跟踪管理

  • 已跟踪的文件又可以进行细分状态划分:
    • staged: 暂存区中的文件状态;
    • Unmodified: 未修改的文件,commit命令可以将staged中文件提交到Git仓库
    • Modified: 修改了某个文件后, 会处于Modified状态;

Git记录更新变化过程

检测文件更新状态-git status,会显示当前处于哪个分支、分支内的文件哪些未跟踪(也不一定是未跟踪,主要显示分支内比较特殊的文件)-git status -s/–short可以查看更简短的消息

git log可以查看提交的历史日志,退出git log需要按q键;git log --pretty=oneline一行内显示;git log --pretty=oneline --graph用图显示,在多个分支时比较明显;

进行版本回退-git reset,git通过HEAD指针记录当前版本,总是指向该分支的上一次提交;上一个版本是HEAD^ ,上上一个版本就是HEAD^^,如果是上1000个版本,是HEAD~1000,或者指定commit id

回退版本后会回退log的内容,如果想看之前的log内容需要使用-git relog

一般我们总会有些文件无需纳入 Git 的管理,也不希望它们总出现在,.gitignore存放的是需要忽略的文件,在里面列出需要忽略的文件,通常都是些自动生成的文件,比如日志文件,或者编译过程中创建的临时文件等

该文件夹不需要手动创建,确认什么文件需要被忽略可以使用github维护的模板库,按语言/技术分类(Java、C++、Objective-C等)

git commit -m将提交信息与命令放在同一行,git commit -m ‘提交信息’,命令可以结合使用git commit -a -m ‘提交信息’

Git 中所有的数据在存储前都计算校验和, 然后以校验和来引用。

  • Git 用以计算校验和的机制叫做 SHA-1 散列 (hash, 哈希);

  • 这是一个由 40 个十六进制字符 (0-9 和 a-f) 组成的字符串, 基于 Git 中文件的内容或目录结构计算出来;

Git远程仓库和验证

常见的远程仓库有三种:

  • GitHub: https://github.com/

    • 在github创建自己的开源项目时可以写自己的开源协议,同样使用github上的其他项目做开发的时候也要注意人家的开源协议是否允许某些操作
    • 大多数都使用下面的MIT协议
  • Gitee: https://gitee.com/

  • 自己搭建Gitlab: http://152.136.185.210:7888/t

    • 在GitLab创建远程项目

      ​ 本地开发后执行git add和git commit

      ​ 先git pull检查更新

      ​ 最后执行git push推送更改

    • 分支命名:注意现代Git服务默认分支名可能为main而非master

    • 变更验证:推送后刷新GitLab页面确认文件修改已生效

对于私有仓库需要验证身份,有两种验证方式:http和ssh

HTTP协议是无状态的连接,所以每一个连接都需要用户名和密码:

下面有一些 Git Credential 的选项可以避免反复验证:

  • 选项一: 默认所有都不缓存。每一次连接都会询问你的用户名和密码;
  • 选项二: “cache”模式会将凭证存放在内存中一段时间,密码永远不会被存储在磁盘中,并且在15分钟后从内存中清除;
  • 选项三: “store”模式会将凭证用明文的形式存放在磁盘中,并且永不过期;
  • 选项四: 如果你使用的是 Mac,Git 还有一种 “osxkeychain”模式,它会将凭证缓存到你系统用户的钥匙串中(加密的);
  • 选项五: 如果你使用的是 Windows,你可以安装一个叫做“Git Credential Manager for Windows”的辅助工具;

可以在 https://github.com/Microsoft/Git-Credential-Manager-for-Windows 下载。

Secure Shell(安全外壳协议,简称SSH)是一种加密的网络传输协议,可在不安全的网络中为网络服务提供安全的传输环境。

ssh-keygen -t ed25519 -C "your_email@example.com"获取公钥和私钥

  • SSH以非对称加密实现身份验证。
    • 例如其中一种方法是使用自动生成的公钥-私钥对来简单地加密网络连接,随后使用密码认证进行登录;
    • 另一种方法是人工生成一对公钥和私钥,通过生成的密钥进行认证,这样就可以在不输入密码的情况下登录;
    • 公钥需要放在待访问的电脑之中,而对应的私钥需要由用户自行保管;
    • 默认保存在用户目录的.ssh文件夹
      • id_ed25519:私钥文件(需妥善保管)
      • id_ed25519.pub:公钥文件

关于远程仓库的常用链接:

将存储库克隆到新创建的目录中-git clone

查看有没有远程仓库-git remote;

查看远程仓库的地址-git remote -v ;

添加远程仓库-git remote add ;

创建并切换分支-git checkout -b main --track origin/main命令可以创建一个与远程分支同名的本地分支,并自动建立跟踪关系。

获取远程最新代码-git pull=>解决可能的合并冲突=>推送本地修改-git push

查看git官方文档-git config,可以修改一些默认配置,例如-git push的默认值是simple,simple的意思就是在本地的分支上去匹配远程的分支名称,如果匹配不上就会报错;根据官方文档设置可以设置默认分支为上游分支:git config push default upstream;也可以使用-git config push.default current,和simple相似,但是不同的是simple在没有匹配到相同分支时会报错,而default current如果没匹配上会

git branch - 分支管理:

git branch                  # 查看本地分支列表
git branch <新分支名>       # 创建新分支
git branch -d <分支名>      # 删除已合并的分支.移除指向该分支的指针,不能完全销毁过去的提交
git branch -D <分支名>      # 强制删除未合并的分支
git branch -a               # 查看所有分支(包括远程)
git checkout -b				#  创建并立即切换到一个新分支

git merge - 分支合并:

也可以使用 git pull --rebase 变基而不是合并

git checkout main           # 先切换到要合并到的分支
git merge feature-branch    # 把feature分支合并到当前分支(main)

git pull=拉取远程仓库更新并合并到本地 = git fetch + git merge

只下载远程变更,不合并-git fetch;合并本地工作区-git merge

本地有分支,远程也有分支,在没有跟踪关系时,必须显式指定远程仓库和分支名才能执行pull操作,所以在拉取代码的时候要确认是从哪个分支拉取的-git pull ;为了简化操作,可以设置上游分支-git branch --upstream-to= /,设置完以后可以直接-git pull了

整体流程:

情况一: 你到公司之后公司已经有项目, 并且有远程仓库了

  • 1、git clone xxxxxxxxxxxxxxxx

  • 2、进行开发

  • git add .

  • git commit -m “提交”

  • git pull

  • git push

情况二: 开发一个全新的项目(由你来搭建的)

2.1.创建一个远程仓库

方案一:

  • git clone xxxx
  • 在clone下来文件夹中开始搭建整个项目
  • git add .
  • git commit -m “”
  • git push

方案二:

  • 创建一个本地仓库和搭建本地项目

  • git remote add origin xxxxxx

  • git branch --set-upstream-to=origin/master

  • git fetch

  • git merge --allow-unrelated-histories

  • git push

Git标签

对某次非常重要的提交可以打tag标记,比较有代表性的是人们会使用这个功能来标记发布结点(v1.0、v2.0等等);

  • -创建标签:
    • Git 支持两种标签:轻量标签-git tag v1.0(lightweight)与附注标签(annotated)git tag -a v1.1 -m ‘附注标签’
    • 附注标签:通过-a选项,并且通过-m添加额外信息

默认情况下, git push 命令并不会传送标签到远程仓库服务器上;在创建完标签后你必须显式地推送标签到共享服务器上, 当其他人从仓库中克隆或拉取, 他们也能得到你的那些标签;可以看见谁提交了-git show

git的原理

查看一个文件-git cat-file -t “文件路径”

几乎所有的版本控制系统都以某种形式支持分支。

  • 使用分支意味着你可以把你的工作从开发主线上分离开来,以免影响开发主线:

在进行提交操作时,Git会保存一个提交对象(commit object):

  • 该提交对象会包含一个指向暂存内容快照的指针;

  • 该提交对象还包含了作者的姓名和邮箱、提交时输入的信息以及指向它的父对象的指针;

    • ✓ 首次提交产生的提交对象没有父对象;普通提交操作产生的提交对象有一个父对象;

    • ✓ 而由多个分支合并产生的提交对象有多个父对象;

git的工作流:

由于Git上分支使用的便捷性,产生了很多Git的工作流:

  • 也就是说,在整个项目开发周期的不同阶段,你可以同时拥有多个开放的分支;

  • 你可以定期地把某些主题分支合并入其他分支中,例如一个重大更新,可以合并在main分支里

  • //2.9以后不在共同的base下不能直接合并分支
    //这个命令用于合并两个没有共同祖先的分支(即它们的提交历史完全不相关)。
    git merge --allow-unrelated-histories
    //合并分支可以用于重大版本的bug修复或新功能开发,可以单开一个分支来修复bug最后合并
    //合并的时候可能会出现冲突,解决冲突可以手动解决,也可以使用诸如vscode这样的编辑器,会配有不同的选项,保留其一或者都不保存等等
    

将本地的分支和远程的分支关联上:链接-git remote add =>获取远程分支信息-git fetch=>设置上游分支-git branch --set-upstream-to=origin/main=>创建同名本地分支-git checkout -b main --track origin/main

解决浏览器兼容的插件

针对不同的浏览器支持的特性:比如css特性、js语法之间的兼容性

市面上有大量的浏览器:Chrome、Safari、IE、Edge、Chrome for Android、UC Browser、QQ Browser等等,它们的市场占有率是多少?我们要不要兼容它们呢?

在哪里可以查询到浏览器的市场占有率呢?我们通常会查询的一个网站就是caniuse:https://caniuse.com/usage-table

如何可以在css兼容性和js兼容性下共享我们配置的兼容性条件呢?当我们设置了一个条件: > 1%,我们表达的意思是css要兼容市场占有率大于1%的浏览器,js也要兼容市场占有率大于1%的浏览器;

如果我们是通过工具来达到这种兼容性的,比如我们讲到的postcss-preset-env、babel、autoprefixer,如何让工具也共享我们的配置呢?

Browserslist是一个在不同的前端工具之间,共享目标浏览器和Node.js版本的配置,例如Autoprefixer、Babel、postcss-preset-env、eslint-plugin-compat、stylelint-no-unsupported-browser-features、postcss-normalize、obsolete-webpack-plugin

Browserslist的配置项:

  • defaults: Browserslist 的默认浏览器 (>0.5%, last 2 versions, Firefox ESR, not dead)。

  • 5%:通过全局使用情况统计信息选择的浏览器版本。 >=, <和 <= 工作过

    • cover 99.5%: 提供覆盖率的最受欢迎的浏览器。
    • cover 99.5% in US:使用美国使用情况统计信息。它接受两个字母的国家/地区代码。
    • cover 99.5% in my stats: 使用自定义用法数据。
  • dead: 24个月内没有官方支持或更新的浏览器。现在是IE 10, IE Mob 11, BlackBerry 10, BlackBerry 7, Samsung 4和OperaMobile 12.1

  • last 2 versions: 每个浏览器的最后2个版本。

    • last 2 Chrome versions: 最近2个版本的Chrome浏览器。
    • last 2 major versions或last 2 iOS major versions: 最近2个主要版本的所有次要/补丁版本。
  • node 10和node 10.4: 选择最新的Node.js10.x或10.4.x版本,

    • currentnode: Browserslist现在使用的Node.js版本。
    • maintained node versions: 所有Node.js版本, 仍由Node.js Foundation维护。
  • iOS 7: 直接使用iOS浏览器版本7.

    • Firefox > 20: Firefox的版本高十20±>,<并可以<一也可以使用。它松可以与Node.js一起使用。
    • ic 6 8: 选择一个包含范围的版本。
    • Firefox ESR: 最新的Firefox ESR版本。
    • PhantomJS 2.1和PhantomJS 1.9:选择类似于PhantomJS运行时的Safari版本。

    browserslist的使用:安装babel的时候自动安装

    如何配置browserslist:

    • 方案一:package.json配置:

    • 	{
        "browserslist": ["
        	last 2 version",
          "not dead",
          "> 0.2%"
        ]
      }
      
    • 方案二:.browserslistrc文件

    • webpack打包css资源 > .browserslistrc
      1   > 0.5%
      2   last 2 version3   not dead
      

多个条件的连接词:or、逗号、换行表示的是或的意思;and是与的意思;not是取非

确保兼容性:target属性可以做到,但是一般很少设置target,常用browserslists工具,优点:可在多个前端工具间共享,对CSS和gs都生效,保持统一;如果两个同时配置了,target会覆盖browserslists

ECMAScript 提案-Stage-X

TC39组织:

  • TC39是指技术委员会(Technical Committee)第 39 号;
  • 它是ECMA的一部分, ECMA 是 “ECMAScript” 规范下的 JavaScript 语言标准化的机构;
  • ECMAScript规范定义了JavaScript如何一步一步的进化、发展;

ECMAScript 提案阶段详解:

  • Stage 0:稻草人(Strawman)
  • Stage 1:提案(Proposal)
  • Stage 2:草案(Draft)
  • Stage 3:候选(Candidate)
  • Stage 4:完成(Finished)

webpack前端性能优化

打包结果优化

  • 分包处理 react/vue路由懒加载
  • 代码进行压缩(丑化 const message => m)
  • 删除无用的代码(tree shaking)
  • CDN服务器

打包效率优化

  • exclude排除(如node_modules)

  • 缓存配置(Cache-loader)

分包处理

所有的东西放到一个包中不方便管理

bundle.js包非常大的话路由加载慢

首屏加载速度会大大降低

长时间后用户看到的都是一个空白页面

分包处理(prefetch)

SSR:加快首屏渲染速度,增加SEO优化

  • 代码分离(Code Splitting)是webpack一个非常重要的特性:
    • 它主要的目的是将代码分离到不同的bundle中,之后我们可以按需加载,或者并行加载这些文件;
    • 比如默认情况下,所有的JavaScript代码(业务代码、第三方依赖、暂时没有用到的模块)在首页全部都加载,就会影响首页的加载速度;
    • 代码分离可以分出更小的bundle,以及控制资源加载优先级,提供代码的加载性能;
  • Webpack中常用的代码分离有三种:
    • 入口起点:使用entry配置手动分离代码;
    • 防止重复:使用Entry Dependencies或者SplitChunksPlugin去重和分离代码;
    • 动态导入:通过模块的内联函数调用来分离代码;

代码拆分-webpack多入口依赖

多入口在webpack.config.json里配置:

 entry: {
     index:'./src/index.js',
 	 main:'./src/main.js'
 },

多入口对应的是多出口,出口的名字不要写死,可以使用占位符:

output: {
    filename: '[name]-bundle.js',
    path: path.resolve(__dirname, './bundle'),
  },

如果有些包是多个入口文件公用的,可以写在share配置项里:

 entry: {
    index:
    {
      import: './webpacktest/src/index.js', // 入口文件
      dependOn: 'shared0', // 共享依赖
    },
    main: {
      import: './webpacktest/src/main.js', // 入口文件
      dependOn: 'shared1', // 共享依赖
    },
    shared0:['axios', 'lodash'], // 共享依赖
    shared1:['axios','redux']// 这里可以添加其他共享依赖
  },

代码拆分-webpack的动态导入

另外一个代码拆分的方式是动态导入, webpack提供了两种实现动态导入的方式:

  • 第一种, 使用ECMAScript中的import()语法来完成,也是目前推荐的方式

    • const btn1 = document.createElement('button')
      const btn2 = document.createElement('button')
      
      btn1.textContent = 'Hello Webpack!'
      btn2.textContent = 'Hello Category'
      
      document.body.append(btn1)
      document.body.append(btn2)
      
      btn1.addEventListener('click', () => {
        import(/*webpackChunkName: "webpack"*/ './router/index.js')
          .then((module) => {
            console.log('Module loaded:', module)
          })
          .catch((err) => {
            console.error('Error loading module:', err)
          })
      })
      
      btn2.addEventListener('click', () => {
        import(/*webpackChunkName: "category"*/ './router/category.js')
          .then((module) => {
            console.log('Module loaded:', module)
          })
          .catch((err) => {
            console.error('Error loading module:', err)
          })
      })
      

      按需导入js模块,实现路由懒加载

      上面的/**/内为魔法注释,可以为 Webpack 提供额外的编译指令

      • Webpack 支持的魔法注释包括:

        魔法注释名 示例 作用
        webpackChunkName /* webpackChunkName: "category" */ 指定打包出来的 chunk 名
        webpackPrefetch /* webpackPrefetch: true */ 浏览器空闲时预取该模块
        webpackPreload /* webpackPreload: true */ 优先加载该模块
        webpackMode /* webpackMode: "lazy" */ 控制加载模式(lazy, eager 等)

      动态导入的打包命名

      • 基于文件名+下划线+output目录命名的,其实可以在output里针对分包的文件单独命名,使用属性chunkFilename:

        • output: {
             filename: 'main-bundle.js',
             chunkFilename: '[name]-chunk.js', // 分块文件名
             path: path.resolve(__dirname, './bundle'),
           },
          
  • 第二种, 使用webpack遗留的 require.ensure,目前已经不推荐使用;

代码拆分-SplitChunkPlugin

早期需要单独安装,现在webpack已经默认安装集成,不需要单独安装,只需要配置(例如默认配置只认为异步的操作归为分包文件)

optimization: {
    splitChunks: {
      chunks: 'all', // 分包所有类型的代码块
      minSize: 20000, // 分包时包的最小尺寸为20KB
      maxSize: 0, // 不限制最大尺寸,如果设置为20000,意为大于20kb进行分包
      minChunks: 1, // 最少被引用一次
      maxAsyncRequests: 30, // 最大异步请求数
      maxInitialRequests: 30, // 最大初始请求数
      automaticNameDelimiter: '~', // 自动命名分隔符
        cacheGroups:{
            //所有来自node_modules的文件被打包分到vernders文件夹内
            venders:{
                test:/[\\/]node_modules[\\/]/,
                filename:'打包后的文件名'
            }//所有来自路径2的文件被打包分到utils文件夹内
            utils:{
            	test:/路径2/,
            	filename:'打包后的文件名'
        }
        chunkId:named,
        //唯一标识代码块,
        //开发建议:named:使用文件路径作为 ID, 可读性强;
        //打包建议:webpack5新增:deterministic:基于模块内容生成短哈希,稳定,只要模块内容不变,ID 就不变;
        //size:按 chunk 大小分配 ID,较小的 chunk 优先获得较小 ID;
        //total-size:类似于 'size',但考虑所有依赖关系的大小;
        }
    },
  },

不是所有分包都会严格遵守大小规则,如果一个整体的组件大于maxSize的范围,会以整个类为整体,不会强制分开他们

prefetch和preload

webpack4.6新增

在声明 import 时,使用下面这些内置指令,来告知浏览器:

  • prefetch(预获取):将来某些导航下可能需要的资源
  • preload(预加载):当前导航下肯可能需要的资源

与 prefetch 指令相比, preload 指令有许多不同之处:

  • preload chunk 会在父 chunk 加载时, 以并行方式开始加载。prefetch chunk 会在父 chunk 加载结束后开始加载。
  • preload chunk 具有中等优先级, 并立即下载。prefetch chunk 在浏览器闲置时下载。
  • preload chunk 会在父 chunk 中立即请求, 用于当下时刻。prefetch chunk 会用于未来的某个时刻。

CDN加速服务器配置

CDN:内容分发网络(Content Delivery Network或Content Distribution Network, 缩写: CDN)

  • 它是指通过相互连接的网络系统,利用最靠近每个用户的服务器;
  • 更快、更可靠地将音乐、图片、视频、应用程序及其他文件发送给用户;
  • 来提供高性能、可扩展性及低成本的网络内容传递给用户;

在开发中, 我们使用CDN主要是两种方式:

  • 方式一: 打包的所有静态资源, 放到CDN服务器上,用户所有资源都是通过CDN服务器加载的;

  • 方式二: 一些放到CDN服务器上的第三方资源

如果所有的静态资源都想要放到CDN服务器上, 我们需要购买自己的CDN服务器;

  • 目前阿里、腾讯、亚马逊、Google等都可以购买CDN服务器;

  • 我们可以直接修改publicPath,在打包时添加上自己的CDN地址;

    output:
    {
        path: resolveApp("./build"),
        filename: "[name].bundle.js",
        publicPath: "https://coderwhy.com/cdn/",
        chunkFilename: "[name].[hash:6].chunk.js"
    },
    
    

通常一些比较出名的开源框架都会将打包后的源码放到一些比较出名的、免费的CDN服务器上:

  • 国际上使用比较多的是unpkg、JSDelivr、cdnjs;
  • 国内也有一个比较好用的CDN是bootcdn;

如果一些包使用cdn,那么打包的时候也应该把他们排除出去:

externals: {
    react: {root: 'React',}, // 外部依赖,避免打包到bundle中
    // key属性名: 排除的框架的名称
	// value值: 从CDN地址请求下来的JS中提供对应的名称

   'react-dom': {root: 'ReactDOM'},
    axios: 'axios',
  },

然后在html里用script标签引入cdn:

<script src="https://unpkg.com/react/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom/umd/react-dom.production.min.js"></script>

shimming

shimming是一个概念,是某一类功能的统称:

  • shimming翻译为垫片,相当于给我们的代码填充一些垫片来处理一些问题;
  • 比如我们现在依赖一个第三方的库,这个第三方的库本身依赖lodash,但是默认没有对lodash进行导入(认为全局存在lodash),那么我们就可以通过ProvidePlugin来实现shimming的效果
  • ProvidePlugin是webpack内置插件,用于自动加载模块而不必到处import或require

但是webpack不推荐shimming,不建议使用这种“隐含的依赖”,违背了模块化开发的初衷

CSS样式的单独提取

MiniCssExtractPlugin

当项目中有多个CSS文件需要统一管理时,通过该插件可以将CSS从JS文件中分离出来

plugins: [
    new CleanWebpackPlugin(), // 清理输出目录
    new MiniCssExtractPlugin({
      filename: '[name]-styles.css', // 提取的CSS文件名
      chunkFilename: '[id]-styles.css', // 分块CSS文件名
    })
  ],
 module: {
    rules: [
      {
        test: /\.css$/, // 处理CSS文件
        use: [
          MiniCssExtractPlugin.loader, // 提取CSS到单独的文件
          {
            loader: 'css-loader',
            options: {
              importLoaders: 1, // 在CSS中使用@import时,应用前面的loader
            },
          },
          'postcss-loader', // 使用PostCSS处理CSS
        ],
      }
    ],
  },

哈希生成

在我们给打包的文件进行命名的时候, 会使用placeholder(占位), placeholder中有几个属性比较相似:

 output: {
    filename: '[contenthash]-bundle.js', // 使用[hash]占位符确保唯一性
    chunkFilename: '[name]-chunk.js',
    publicPath: 'cdn路径', // 设置公共路径
    path: path.resolve(__dirname, './bundle'),
  },
  • hash、chunkhash、contenthash
    • hash本身是通过MD4的散列函数处理后, 生成一个128位的hash值 (32个十六进制)
      • 哪怕只改动了一个文件,所有的 JS/CSS 都会因为 hash 变了而重新生成。
    • chunkhash:基于每个 chunk 的内容生成 hash
      • 修改某个入口文件,只影响这个入口生成的文件,其他 chunk 不变。
    • contenthash:基于文件的实际内容生成 hash
      • 哪怕是同一个 chunk,如果某个文件(如 CSS)内容没变,它的 hash 就不变,适合:CSS 文件、图片等资源的强缓存。

DDL库(不重要)

DLL全称是动态链接库(Dynamic Link Library),是为软件在Windows中实现共享函数库的一种实现方式;

webpack中也有内置DLL的功能,它指的是我们可以将能够共享,并且不经常改变的代码,抽取成一个共享的库;

这个库在之后编译的过程中,会被引入到其他项目的代码中;

DDL库的使用分为两步:

  • 第一步: 打包一个DLL库;
  • 第二步: 项目中引入DLL库;

注意: 在升级到webpack4之后, React和Vue脚手架都移除了DLL库(下面的vue作者的回复),所以知道有这么一个概念即可。

其他构建工具

Teser

Vite 默认使用 esbuild 作为压缩器(比 Terser 快几十倍)

Terser是一个JavaScript的解释(Parser)、Mangler(绞肉机)/Compressor(压缩机)的工具集;

早期我们会使用uglify-js来压缩、丑化我们的JavaScript代码,但是目前已经不再维护,并且不支持ES6+的语法;

也就是, Terser可以帮助我们压缩、丑化我们的代码,让我们的bundle变得更小。

全局安装
npm install terser -g
局部安装
npm install terser -D

命令行使用Terser

我们可以命令行中使用Terser:

terser [input files] [options]
# 举例说明
terser js/file1.js-o foo.min.js -c -m

几个Compress option和Mangle(乱砍) option:

  • arrows: class或者object中的函数, 转换成箭头函数;

    • npx terser abc.js -o abc.min.js -c arrows=true
      
  • arguments: 将函数中使用 arguments[index]转成对应的形参名称;

  • dead_code: 移除不可达的代码 (tree shaking);

  • 其他属性可以查看文档;

    • https://github.com/terser/terser#compress-options

    • https://github.com/terser/terser#mangle-options

Terser在webpack里的配置

在webpack的配置文档里用plugin:

 minimize: true, // 启用代码压缩
  minimizer: [
    // 使用TerserPlugin进行代码压缩
    new TerserPlugin({
      terserOptions: {
        compress: {
          arguments: true, // 压缩函数参数
          dead_code: true, // 删除未使用的代码
        },
      },
      extractComments: false, // 不提取注释到单独的文件
    }),
  ],

extractComments: 默认值为true, 表示会将注释抽取到 个单独的文件中;

  • ✓ 在开发中, 我们不希望保留这个注释时, 可以设置为false;

parallel:使用多进程并发运行提高构建的速度,默认值是true

  • ✓ 并发运行的默认数量: os.cpus().length 1;
  • ✓ 我们也可以设置自己的个数, 但是使用默认值即可;

terserOptions: 设置我们的terser相关的配置

  • compress:设置压缩相关的选项;
  • mangle: 设置升化相关的选项, 可以直接设置为true;
  • toplevel: 层顶变量是否进行转换;
  • keep_classnames: 保留类的名称;
  • keep_fnames: 保留函数的名称;

CSS压缩使用

npm install css-minimizer-webpack-plugin -D

配置文件模块化

pack.json的文件里可以配置开发和生产模式不同的配置文件的路径:

"scripts": {
    "build": "webpack --config ./webpacktest/config/webpack.config.js",
    "server": "webpack serve --config ./webpacktest/config/webpack.config.js",
    "ts-check": "tsc --noEmit",
    "ts-check-watch": "tsc --noEmit --watch"
  }

修改了这里的同时也要修改webpack.config.json的位置,把它挪到package.json的相对路径上./webpacktest/config/webpack.config.js

此时并没有实现真正的配置文件模块化,在webpack.config.js里除了写一个对象,还可以写一个函数:

const commonConfig = {...}
// webpack允许导出一个函数
module.exports = function(env) {
console.log(env);
return commonConfig;
}

函数的参数是env,函数的参数在设置scripts时传入,可以告诉开发者现在是什么环境:

"scripts": {
    "build": "webpack --config ./config/comm.config.js --env production",
    "serve": "webpack serve --config ./config/comm.config.js --env development",
    "ts-check": "tsc --noEmit"
    "ts-check-watch": "tsc --noEmit --watch"
},

开发和生产模式的配置文件相同的部分可以单拉出来做一个common.config.js,然后使用webpack-merge来合并

pnpm add webpack-merge

然后在webpack导出的函数返回二者合并的内容

const { merge } = require('webpack-merge')
const devConfig = require('./dev.config')
const prodConfig = require('./prod.config')
const getCommonConfig = function(isProduction) {
return {Common的配置...}
//这样变量可以共享
// webpack允许导出一个函数
module.exports = function(env) {
const isProduction = env.production
let mergeConfig = isProduction ? prodConfig : devConfig
return merge(commonConfig, mergeConfig)
}

Tree shaking

Tree shaking 是前端构建工具(如 Webpack、Rollup、esbuild 等)中的一个 “去除未使用代码” 的优化技术,用于减小打包体积、提高加载

模块必须是使用ES Module(import/export)语法的

构建工具必须支持静态分析(能确定哪些代码被用到了)。

将mode设置为development模式:

  • 为了可以看到 usedExports带来的效果,我们需要设置为 development 模式
  • 因为在production 模式下,webpack默认的一些优化会带来很大的影响。

设置usedExports为true和false对比打包后的代码:

  • 在usedExports设置为true时,会有一段注释:unused harmony export mul;
  • 这段注释的意义是什么呢?告知Terser在优化时,可以删除掉这段代码;

minimize设置true:

  • usedExports设置为false时,mul函数没有被移除掉;
  • usedExports设置为true时,mul函数有被移除掉;

所以,usedExports实现Tree Shaking是结合Terser来完成的。

sideEffect和useExports的区别

sideEffects

告诉 Webpack 哪些模块或文件有副作用(side effects)。

如果没有副作用,就可以放心地删除未使用的导出(Tree shaking)。

{
  "sideEffects": false
}//或者
{
  "sideEffects": [
    "*.css"//以css结尾的文件都没有副作用
  ]
}

代表整个项目都没有副作用

所以未被使用的 import / export 都可以被删除。

usedExportsWebpack 自己分析后生成的字段

  • Webpack 会分析哪些导出(export)在项目中被用到了。
  • 然后在构建日志或统计文件里添加 usedExports: true/false 标志。
  • 比如:
js复制编辑// math.js
export const add = () => {};
export const sub = () => {};
js复制编辑// index.js
import { add } from './math'; // 只用了 add

此时 Webpack 在编译后会把 add 标成 usedExports: truesub 标成 false,然后摇掉 sub

你写代码 ──► Webpack 分析 ──► 标记 usedExports
        │                  │
        └──► package.json 配置 sideEffects 告诉哪些不能删

平常开发中useExports和sideEffect都需要配置

Css实现tree shaking:PurgeCSS

删除未使用的css,安装:

npm install purgecss-webpack-plugin -D

在plugins里使用:

 plugins: [
    new CleanWebpackPlugin(), // 清理输出目录
    new MiniCssExtractPlugin({
      filename: '[name]-styles.css', // 提取的CSS文件名
      chunkFilename: '[id]-styles.css', // 分块CSS文件名
    }),
    new PurgeCSSPlugin({
      path:glob.sync(`${path.resolve(__dirname,'.../src')}`)//拼接路径、
      //glob.sync(`${path.reslove(_dirname,'.../src')}/**/*`,{nodir:true})//匹配 src 目录下的所有文件(递归)忽略目录,只匹配文件(防止误匹配文件夹)
    })
  ],
写法 含义
const { A } = require('mod') mod 模块导出对象中提取名为 A 的属性
const A = require('mod') 把整个 mod 模块的导出对象整体赋值给 A

Scope Hoisting

什么是Scope Hoisting呢?

  • Scope Hoisting从webpack3开始增加的一个新功能;
  • 功能是对作用域进行提升,并且让webpack打包后的代码更小、运行更快;

默认情况下webpack打包会有很多的函数作用域,包括一些(比如最外层的)IIFE:

  • 无论是从最开始的代码运行,还是加载一个模块,都需要执行一系列的函数;

  • Scope Hoisting可以将函数合并到一个模块中来运行;

plugins: [
    new webpack.optimize.ModuleConcatenationPlugin(), // 启用作用域提升
//development模式下默认关闭
  ],

HTTP压缩

压缩格式:gzip-GNU,比较广泛的压缩算法

plugins: [
    new HtmlWebpackPlugin({
		template: './index.html',
		minify: isProduction? [
        // 移除注释
        removeComments: true,
        // 移除属性
        removeEmptyAttributes: true,
        // 移除默认属性
        removeRedundantAttributes: true
        ]: false
}),
  ]

其他的配置:

  • inject: 设置打包的资源插入的位置- true、false、body、head
  • cache: 设置为true, 只有当文件改变时, 才会生成新的文件 (默认值也是true)
  • minify: 默认会使用一个插件html-minifier-terser

打包结果的分析

分析不同插件、loader打包加载的时间:

pnpm add speed-measure-webpack-plugin -D

声明+使用:

const SpeedMeasurePlugin = require('speed-measure-webpack-plugin')
const smp = new SpeedMeasurePlugin();
// webpack 允许导出一个函数
module.exports = function(env) {
    const isProduction = env.production
    let mergeConfig= isProduction ? prodConfig: devConfig
    const finalConfig = merge(getCommonConfig(isProduction), mergeConfig)
    return smp.wrap(finalConfig)
}

分析打包后的文件:

"scripts": [
"build": "webpack --config ./config/comm.config.js --env production --profile --json=stats.json",//加上--profile,生成文件信息
],

通过执行npm run build:status可以获取到一个stats.json的文件,这个文件我们自己分析不容易看到其中的信息,可以放到 http://webpack.github.com/analyse 进行分析,打不开就把项目拉下来自己运行

另一个分析打包后的文件的工具:webpack-bundle-analyzer

安装:

npm install webpack-bundle-analyzer -D//npm
pnpm add webpack-bundle-analyzer -D//pnpm

配置:

const {BundleAnalyzerPlugin}=require('webpack-bundle-analyzer')
  plugins: [
    new BundleAnalyzerPlugin()// 分析打包结果
  ],

vite和webpack的区别

当然可以,下面是对 Webpack 和 Vite 的详细对比、原理解析 以及 常见面试问题总结,适合用于前端面试准备和实际项目选型参考。

一、Webpack 与 Vite 的对比

维度 Webpack Vite
构建方式 打包型(Bundle):一开始就将模块打包 原生 ESM + 按需编译(即用即编)
构建速度 初次构建慢、热更新慢 初次启动快、热更新快(基于 ESM)
热更新(HMR) 重编译整个模块依赖树 精准更新依赖模块
开发服务器 自带 DevServer(基于 Express) 使用 esbuild + Koa
编译原理 全部打包输出 Bundle 文件 开发时按需加载模块,生产时仍打包
插件体系 成熟、生态强大 基于 Rollup 插件系统,但生态逐渐丰富
配置复杂度 高、灵活性强 简洁,开箱即用(但可扩展)
使用语言 Node.js 使用 esbuild 编写(Go )和 Rollup
构建产物 通常较大,优化依赖技巧较多 更小、更快,依赖按需打包

二、Webpack 原理简析

Webpack 核心机制包括:

  1. 入口(Entry):配置 entry 文件,作为打包的起点。
  2. 模块解析(Module):从入口出发,递归查找所有依赖模块。
  3. Loader:把非 JS 文件转换成可打包模块(如 .css.ts)。
  4. Plugin:扩展打包流程(如压缩、拷贝文件等)。
  5. 输出(Output):将所有模块打包输出为 bundle.js 或多个 chunk 文件。

整个流程图如下:

Entry -> 依赖分析 -> Loader 转换 -> Plugin 扩展 -> Bundle 输出

三、Vite 原理简析

Vite 的构建机制依赖于 原生 ESM 和 esbuild/Rollup

  1. 开发阶段(Dev)
    • 使用 esbuild 对源码快速编译(极快)
    • 浏览器原生支持 ES Module,按需加载模块
    • 修改一个模块时,只重新编译该模块
  2. 生产阶段(Build)
    • 使用 Rollup 进行打包优化
    • Tree-shaking、代码分割、懒加载等特性继承自 Rollup

简图:

开发模式:按需编译 -> 浏览器原生 ESM 加载
生产模式:使用 Rollup 打包 -> 优化生成静态文件

四、适用场景对比

场景 推荐使用
老项目迁移、配置复杂项目 Webpack(生态更成熟)
新项目、快速开发、体验优先 Vite(开发效率高)
多页应用(MPA) Webpack 更灵活
Vue / React 项目 两者都支持,Vite 更轻快

✅ 基础概念类

  1. Webpack 的核心组成有哪些?
    • Entry、Output、Loader、Plugin、Mode(development/production)
  2. Vite 为什么启动快?
    • 使用 esbuild 编译(Go 编写,性能高),并利用浏览器原生 ESM 实现模块按需加载
  3. Loader 和 Plugin 的区别?
    • Loader:用于模块转换(如 CSS → JS)
    • Plugin:用于扩展构建过程(如打包后清理目录、生成 HTML)
  4. Tree Shaking 是什么?
    • 去除未使用的代码(通常只对 ES Module 有效)
  5. 如何实现代码分割?
    • Webpack:import() 动态导入
    • Vite:支持动态导入 + Rollup 自动优化

✅ 性能优化类

  1. Webpack 如何优化构建速度?
    • 使用 thread-loader 多进程构建
    • 开启 cache
    • include/exclude 减少 Loader 作用范围
    • 使用 babel-loader 的缓存
    • 使用 DLLPlugin 拆分公共依赖
  2. Vite 如何优化构建产物?
    • 使用 build.rollupOptions 优化代码分割
    • 静态资源放入 public/
    • 配置 definealias 等减少解析成本
  3. 如何压缩打包体积?
    • 开启代码分割(Code Splitting)
    • 使用 Tree Shaking
    • 使用第三方工具(如 Terser、ImageMin、PurgeCSS)

五、项目中混合使用

Vite 是一个封装了 Rollup 的更高级别工具,你可以认为:Vite 是站在 Rollup(构建)+ ESBuild(开发)+ Dev Server(热更新) 之上的现代神器。

辨析一堆乱七八糟的工具

分类 框架 用途
前端框架 React、Vue、Svelte、Angular 页面渲染、用户交互
后端框架 Express、Koa、Fastify、NestJS 数据接口、业务逻辑、数据库
构建工具 Webpack、Vite、Rollup 打包前端代码
SSR 框架 Next.js(基于 React)、Nuxt.js(基于 Vue) ;同时处理前后端渲染逻辑

开发辅助类

工具 功能 常见用途
ESBuild 超快编译器(Go 写的) 用于 Vite 的开发阶段
Babel 代码转译(ES6+ ➝ ES5) 兼容老浏览器
PostCSS CSS 转换器 Tailwind、autoprefixer
TypeScript JS 的类型超集 静态检查、增强可维护性

前端框架类

类型 框架 特点
UI 框架 React / Vue / Angular 构建用户界面
SSR 框架 Next.js / Nuxt.js 支持服务端渲染
构建库 Svelte / SolidJS 更轻更快,面向未来

开发服务器 / 本地调试工具

工具 功能
Vite Dev Server 热更新、模块热替换
Webpack Dev Server Webpack 的热更新服务
Browsersync / Live Server 静态页面热重载

包管理器 / 模块工具

工具 功能
npm / yarn / pnpm 安装/管理依赖
npx 快速运行 CLI 工具
vite-plugin-xxx Vite 插件机制(构建增强)