模块化
AMD
- 一个文件为一个模块。require 导入;define 定义模块,依赖前置
defined("a", [deps], fn)
require([dps], cb)
- 异步加载模块(需要引入 Require.js)
UMD
判断全局存在 define,则输出 AMD;判断全局存在 exports,则输出 Commonjs;若都没有,则挂在 window 上
Commonjs
- 一个文件为一个模块。require 导入;exports.xxx 导出
- 在第一次加载模块的时候会执行整个文件,然后在内存中存一个对象 exports
- 同步导入模块(适用于服务端),导入的是对象的副本,多次导入也只会执行一次
- 支持动态导入
ESM
它是未来浏览器的模块标准
- export 导出;import 导入
- 输出值的引用,加载的时候会做静态优化
- 兼容 node 环境
- 浏览器加载
<script type="module" src="index.js"></script>
Commonjs 和 ESM 的区别
- commonjs 运行时加载;esm 编译时加载;
- commonjs 导出的是一个对象(即module.exports属性),只有脚本执行了对象才会生成;esm 导出的是静态定义,在编译阶段就会生成
- commonjs 导入的是值;esm 导入的是引用;
- commonjs 导出的是值的拷贝,只要值输出了,那模块内部对值的修改也不会影响到输出的值
- esm 导出的是引用,值会随着导出的值更新
- commonjs 使用动态语法;esm 使用静态语法;
- commonjs 的 导入语句可以写在条件判断语句中,模块参数可以是变量
- esm 的静态性:1、import必须在顶层;2、模块参数只能是字符串;
- 因为 esm 的静态特性,可以对代码做优化分析,比如 treeshaking
- commonjs 是同步导入;esm 是异步导入
- commonjs 用于服务端,文件从本地直接获取,不会对主线程造成多大影响
- 浏览器需要下载文件,如果 esm 是同步导入的话对渲染不友好
- commonjs 导出的值是可以被修改的,相当于一个变量;esm 导出的值是只读的
Babel
作用
Babel 是下一代 Javascript 编译器,将 ES6 转义为 ES5,让代码在不支持新生语法的浏览器中能正常运行。
写法
在工程根目录有一个 .babelrc 文件。
{
"presets": ["@babel/presets"],
"plugins": []
}
预设
是官方内置了一组插件的规则集合。 常用的有:
- @babel/presets-env:es6 语法转换
- @babel/preset-react
- @babel/preset-typescript
- @babel/preset-follow
@babel/polyfill
Babel 只转义预发,对新的 api (Promise、Set)和原生的对象不会处理,所以需要对这部分进行处理
- @babel/polyfill 是 core-js2.x.x 和 regenerator-runtime 两个包的集合
- @babel/polyfill 中的 core-js 不会再升级,官方推荐使用core-js3 和 regenerator-runtime 两个小包
@babel/runtime
当使用 @babel/presets 做转义的时候,在转换后的代码里会看到新生成了一些辅助函数,当我们前端工程比较大的时候,打出来的包有重复的内容且非常大。所以将这些辅助函数单独放在了一个 npm 包中,需要使用的时候,require 引入就好。这个 npm 包就是 @babel/runtime
@bable/plugin-transform-runtime
通过 @babel/runtime 将辅助函数分离了出来,那么就需要将之前转义生成的辅助函数部分替换成 require 引入,这部分工作就是 @bable/plugin-transform-runtime 完成的。 三个作用:
- 自动移除语法转义后嵌入的辅助函数,替换为从 @babel/runtime 中引入
- 当代码里使用了 core-js 中的 api,会自动引入 @babel/runtime-corejs3/core-js-stable/
- 当代码里使用了 generator/async,会自定引入 @babel/runtime/regenerator 2、3 可以代替 polyfill 的作用。polyfill 是给浏览器打了补丁,将浏览器未支持的 api 添加到对象的原型上,从一定程度上来说污染了全局的方法。而且当在编写 npm 包的时候,使用的 polyfill 版本与调用方不一致的话,可能会导致 bug,因此建议在 npm 包里,都使用 @bable/plugin-transform-runtime 来做 api 转换而不是 polyfill 打补丁。
{
plugins:["@babel/plugin-transform-runtime", {
"helpers": true, // 是否要自动引入辅助函数包
"corejs": false, // 在普通前端工程里可以取 false,因为一般会全局引入 polyfill 的 cdn,在 npm 包中建议开启,对 Promise 等新的 api 做转换支持
"regenerator": true, // generator/async 的实现代码少,可以默认开启
"useESModules": false,
"absoluteRuntime": false,
"version": "7.0.0-beta.0"
}]
}
转换原理
- Parse 解析代码为 AST
- Transform 将 AST 使用配置好的 plugins/presets 转换为新的 AST
- Generator 将 AST 生成代码
Webpack
webpack 是一个静态模块打包器。当 webpack 处理程序时,它会递归的构建一个依赖关系图,其中包含程序所需要的每一个模块,最终输出一个或多个 bundle 文件。整个流程是一环扣一环的生产线,webpack 通过 Tapable 来组织,在过程中会广播出许多事件,plugins 可以监听这些事件,在特定的时间对生产线做一些改变。
概念解释
- chunk:将模块按照引用关系合并的模块集合,产生 chunk 的几种情况
- entry 入口
- 异步加载模块
- 代码分割
- bundle:chunk 经过处理之后,能发布使用的文件 区别详解
工作流程
- 初始化阶段
- 初始化参数:合并 config 文件和 shell 语句中的参数
- 编译阶段
- 用上一步的参数初始化 Compiler 对象,加载所有配置好的插件,调用 run 方法开始编译
- 根据 entry 找到入口,从入口开始调用 loader 对模块进行翻译,再递归找出所有依赖的模块,直到所有模块都经过了这个步骤的处理。
- 经过上一步骤的处理,模块都被翻译好了,并且得到了一个模块依赖的关系图
- 输出阶段
- 根据依赖图,将模块组装为 chunk,将 chunk 转换为独立的文件(这个步骤是最后修改内容的机会)
- 将 chunk 文件输出到文件系统 在整个流程中,会广播出许多事件,插件在监听到特定的事件就会执行,从而对运行结果进行修改。
基础配置
const htmlWebpackPlugin = require("html-webpack-plugin");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
module.exports = {
mode: "development" | "production", // 环境
entry: "./index", // 入口
output: { // 输出
filename: "bundle.[contenthash:8].js",
path: "./dist"
},
module: {
rules: [
{
test: /\.js$/.
use: ["babel-loader"],
include: "./src",
exclude: /node_modules/
},
{
test: /\.less$/,
use: ["style-loader", "css-loader", "less-loader"],
}
]
},
plugins: [ // 插件
new CleanWebpackPlugin(), // 清除之前的打包文件
new htmlWebpackPlugin({
template: "index.html"
})
],
optimization: {
}
};
Loader
Loader 是一个函数,它对接受到的内容做转换之后再返回结果。它将各类型的模块翻译为浏览器能处理的内容。 Loader 的执行顺序是从后往前的,因为 webpack 是函数式编程,使用了 compose。
treeshaking
用来移除程序中无用的代码。它依赖于 es6 的静态结构的特性(import、export)。
-
无用的代码
- 不会被执行,执行的结果不会被用到
- 代码只会影响死变量
-
在 webpack5 中,env:"production" 会默认开启 treeshaking
-
失效的原因
- 函数存在副作用:修改全局变量,函数外的变量或修改参数等;
- export default 可能会导致失效,export default 打包后会作为一个整体对象,要么整体引入,要么整体删除,若里面某个模块被用到,那整体都会被打包;
- babel-loader 会导致 treeshaking 失效,因为 treeshaking 依赖 ESM 的静态特性,但是 babel-loader 会将代码转换成 commonjs。但是最新版本的 babel-loader 已经默认关闭了转换 esm 的功能。 也可以手动配置禁用:
{
loader: "babel-loader",
options: {
presets: ["@babel/presets-env", {"modules": false}]
}
}
- 副作用 当一个模块执行之后,有可能会对后续的逻辑产生影响,那么这个模块就是有副作用的。 webpack 会使用 terser 分析该模块是否有副作用。如果一个模块有副作用,但是并没有引用它,那么这个分析就是额外的开销,所以对于比较确定的没有副作用模块的工程,可以将这个选项关掉。
"sideEffects": false
但是 sideEffects 更多是用来说明哪些模块需要做副作用检测的,所以会使用数组来说明:
"sideEffects": ["**/*.js"]
webpack 打包优化
- treeshaking,webpack5 的 production 模式下自动开启
- 缩小包体积
- splitChunks 拆分代码
- 将公共包 externals 排除,使用 cdn 引入
- 代码压缩
- happypack 多进程打包处理
热更新(HMR)原理
- HMR:能够在保持页面状态的情况下替换变更模块,提供丝滑的开发体验
- 如何启用
// webpack 配置
devServer:{
hot: true,
port: 8080,
static: path.resolve(__dirname, "static")
}
// react-hot-reload
if(module.hot){
module.hot.accept("./App", () => {
ReactDOM.render(<App />, document.getElementById("root"));
});
}
- 原理 详解
- 在 webpack 的 watch 模式下,文件系统中的文件发生更改,webpack 监听到变化,会根据配置对模块进行重新编译打包,并存到内存中
- 因为访问内存比访问文件系统要快,webpack-dev-middleware 通过 memory-fs 实现静态资源请求直接访问内存文件
- devServer 通知浏览器端文件发生改变
- devServer 在启动的时候,会跟浏览器建立一个 webSocket 长连接,以便 将 webpack 打包的消息告知浏览器
- webpack-dev-server/client 接收到 devServer 的消息,从而做出响应
- webpack-dev-server/client 是怎么来的:webpack-dev-server 会修改 webpack 中配置的 entry 属性,将 webpack-dev-server/client 添加到代码中,它会处理接受到的 websocket 的信息,这样才能实现变更文件的通知
- 浏览器收到
type为hash消息后会将hash值暂存起来,如果配置了模块热更新,就调用 webpack/hot/emitter 将最新hash值发送给webpack,然后将控制权交给webpack客户端代码 - 浏览器接收到 type 为 ok 的消息后对应用执行 reload 操作
- webpack 接收到最新 hash 值验证并请求模块代码,浏览器向服务端发送请求是否有更新的文件
- [hash].hot-update.json,内容是更新的文件列表和下次热更新文件的 hash 前缀
- [hash].hot-update.js,返回更新的新模块代码
- 对模块进行替换:把旧模块删掉,新模块添加到 modules 中
前端路由
hash
hashchange 能监听到 url 上 hash 值的变化,
- 路由使用 # 拼接,window.hash 可以获取
- 服务端接收不到 # 后面的信息
- 通过 hashchange 来监听,路由变化页面不会刷新
- 浏览器兼容性较好
history api
- 使用 html5 的新 api 实现,window.history 可以获取
- 主要的 api
- pushState、replaceState、go、back、forward
- 通过 popstate 事件能监听 go、back、forward 的动作
- 需要后端配合,将路由的 url 做重定向处理,否则服务器通过 url 找不到资源会 404
性能优化
- 压缩
- 开启 Gzip 压缩
- webpack 相关优化
- splitChunks 拆包
- html、css、js 打包压缩
- 体验优化
- 骨架屏
- preload,预加载。通过 rel="preload" 声明一个需要预加载的资源,等到资源使用的时候立即可用
- preFetch,预判加载。通过 rel="prefetch" 告诉浏览器未来可能会用到的某个资源,浏览器就会在闲时去加载
- 图片懒加载 详解
- 缓存
- 开启 tcp 长连接
- 设置资源浏览器缓存
- 减少重绘重排
yarn 和 npm 有什么区别
- yarn 并行安装包,npm 串行安装包
- yarn 已经安装过的包会缓存,再次安装会从缓存中获取,npm 每次都从网络下载
yarn 最开始是为了解决 npm 的缺点出现的,npm v3 版本扁平化了依赖树,会导致幽灵依赖,而且当不同的包依赖了同一个包的不同版本,还是会出现依赖重复安装的现象(npm v5版本出现了 lock 文件解决了这个问题)