前端知识点整理-工程化(持续更新...)

208 阅读10分钟

模块化

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 的区别

  1. commonjs 运行时加载;esm 编译时加载;
    • commonjs 导出的是一个对象(即module.exports属性),只有脚本执行了对象才会生成;esm 导出的是静态定义,在编译阶段就会生成
  2. commonjs 导入的是值;esm 导入的是引用;
    • commonjs 导出的是值的拷贝,只要值输出了,那模块内部对值的修改也不会影响到输出的值
    • esm 导出的是引用,值会随着导出的值更新
  3. commonjs 使用动态语法;esm 使用静态语法;
    • commonjs 的 导入语句可以写在条件判断语句中,模块参数可以是变量
    • esm 的静态性:1、import必须在顶层;2、模块参数只能是字符串;
    • 因为 esm 的静态特性,可以对代码做优化分析,比如 treeshaking
  4. commonjs 是同步导入;esm 是异步导入
    • commonjs 用于服务端,文件从本地直接获取,不会对主线程造成多大影响
    • 浏览器需要下载文件,如果 esm 是同步导入的话对渲染不友好
  5. 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 完成的。 三个作用:

  1. 自动移除语法转义后嵌入的辅助函数,替换为从 @babel/runtime 中引入
  2. 当代码里使用了 core-js 中的 api,会自动引入 @babel/runtime-corejs3/core-js-stable/
  3. 当代码里使用了 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 可以监听这些事件,在特定的时间对生产线做一些改变。

概念解释

详解

  1. chunk:将模块按照引用关系合并的模块集合,产生 chunk 的几种情况
    • entry 入口
    • 异步加载模块
    • 代码分割
  2. bundle:chunk 经过处理之后,能发布使用的文件 区别详解

工作流程

  1. 初始化阶段
  • 初始化参数:合并 config 文件和 shell 语句中的参数
  1. 编译阶段
  • 用上一步的参数初始化 Compiler 对象,加载所有配置好的插件,调用 run 方法开始编译
  • 根据 entry 找到入口,从入口开始调用 loader 对模块进行翻译,再递归找出所有依赖的模块,直到所有模块都经过了这个步骤的处理。
  • 经过上一步骤的处理,模块都被翻译好了,并且得到了一个模块依赖的关系图
  1. 输出阶段
  • 根据依赖图,将模块组装为 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 打包优化

  1. treeshaking,webpack5 的 production 模式下自动开启
  2. 缩小包体积
    • splitChunks 拆分代码
    • 将公共包 externals 排除,使用 cdn 引入
    • 代码压缩
  3. 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"));
    });
}
  1. 在 webpack 的 watch 模式下,文件系统中的文件发生更改,webpack 监听到变化,会根据配置对模块进行重新编译打包,并存到内存中
    • 因为访问内存比访问文件系统要快,webpack-dev-middleware 通过 memory-fs 实现静态资源请求直接访问内存文件
  2. devServer 通知浏览器端文件发生改变
    • devServer 在启动的时候,会跟浏览器建立一个 webSocket 长连接,以便 将 webpack 打包的消息告知浏览器
  3. webpack-dev-server/client 接收到 devServer 的消息,从而做出响应
    • webpack-dev-server/client 是怎么来的:webpack-dev-server 会修改 webpack 中配置的 entry 属性,将 webpack-dev-server/client 添加到代码中,它会处理接受到的 websocket 的信息,这样才能实现变更文件的通知
    • 浏览器收到 typehash 消息后会将 hash 值暂存起来,如果配置了模块热更新,就调用 webpack/hot/emitter 将最新 hash 值发送给 webpack,然后将控制权交给 webpack 客户端代码
    • 浏览器接收到 type 为 ok 的消息后对应用执行 reload 操作
  4. webpack 接收到最新 hash 值验证并请求模块代码,浏览器向服务端发送请求是否有更新的文件
    • [hash].hot-update.json,内容是更新的文件列表和下次热更新文件的 hash 前缀
    • [hash].hot-update.js,返回更新的新模块代码
  5. 对模块进行替换:把旧模块删掉,新模块添加到 modules 中

webpack 面试总结

前端路由

hash

hashchange 能监听到 url 上 hash 值的变化,

  1. 路由使用 # 拼接,window.hash 可以获取
  2. 服务端接收不到 # 后面的信息
  3. 通过 hashchange 来监听,路由变化页面不会刷新
  4. 浏览器兼容性较好

history api

  1. 使用 html5 的新 api 实现,window.history 可以获取
  2. 主要的 api
    • pushState、replaceState、go、back、forward
  3. 通过 popstate 事件能监听 go、back、forward 的动作
  4. 需要后端配合,将路由的 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 文件解决了这个问题)

单元测试