# 前端工程化

# babel的编译过程

Babel 是一个 JavaScript 编译器,是一个工具链,主要用于将采用 ECMAScript 2015+ 语法编写的代码转换为向后兼容的 JavaScript 语法,以便能够运行在当前和旧版本的浏览器或其他环境中。

Babel 本质上就是在操作 AST 来完成代码的转译:

  • 解析(Parse):将源代码转换成更加抽象的表示方法(例如抽象语法树)。包括词法分析和语法分析。词法分析主要把字符流源代码(Char Stream)转换成令牌流( Token Stream),语法分析主要是将令牌流转换成抽象语法树(Abstract Syntax Tree,AST)
  • 转换(Transform):通过 Babel 的插件能力,对(抽象语法树)做一些特殊处理,将高版本语法的 AST 转换成支持低版本语法的 AST。让它符合编译器的期望,当然在此过程中也可以对 AST 的 Node 节点进行优化操作,比如添加、更新以及移除节点等
  • 生成(Generate):将 AST 转换成字符串形式的低版本代码,同时也能创建 Source Map 映射

# webpack的打包原理

# webpack的配置

module.exports={
	entry: {},
	output: {},
	plugins: [],
	module: [rules:[ { test: '' } ]]
}

# webpack如何实现代码分离

  • 入口起点:使用 entry 配置手动地分离代码。
  • 防止重复:使用 CommonsChunkPlugin 去重和分离 chunk。
  • 动态导入:通过模块的内联函数调用来分离代码

# webpack loader是什么,plugin是什么

loader: 是一个导出为函数的javascript模块,根据rule匹配文件扩展名,处理文件的转换器

plugin: 本质是插件,基于事件流框架Tapable,插件可以扩展 Webpack 的功能,在 Webpack 运行的生命周期中会广播出许多事件,Plugin 可以监听这些事件,在合适的时机通过 Webpack 提供的 API 改变输出结果

Loader 在 module.rules 中配置,作为模块的解析规则,类型为数组。每一项都是一个 Object,内部包含了 test(类型文件)、loader、options (参数)等属性。

Plugin 在 plugins 中单独配置,类型为数组,每一项是一个Plugin的实例,参数都通过构造函数传入

# 常见的Webpack Loader

  • file-loader:把文件输出到一个文件夹中,在代码中通过相对 URL 去引用输出的文件 (处理图片和字体)
  • url-loader: 与file-loader类似,区别是用户可以设置一个阈值,大于阈值会交给file-loader处理,小于阈值时返回文件base64 形式编码 (处理图片和字体)
  • image-loader:加载并且压缩图片文件
  • babel-loader:把 ES6 转换成 ES5
  • sass-loader:将SCSS/SASS代码转换成CSS
  • css-loader:加载 CSS,支持模块化、压缩、文件导入等特性
  • style-loader:把 CSS 代码注入到 JavaScript 中,通过 DOM 操作去加载 CSS
  • postcss-loader:扩展 CSS 语法,使用下一代 CSS,可以配合 autoprefixer 插件自动补齐 CSS3 前缀 eslint-loader:通过 ESLint 检查 JavaScript 代码

# 常见的Webpack plugin

  • html-webpack-plugin:简化 HTML 文件创建 (依赖于 html-loader)
  • uglifyjs-webpack-plugin:压缩js文件
  • clean-webpack-plugin:目录清除
  • mini-css-extract-plugin:分离样式文件,CSS 提取为独立文件,支持按需加载 (替代extract-text-webpack-plugin)

# 如何减少 Webpack 打包体积

  • 压缩代码
  • 按需加载
  • Scope Hoisting
  • Tree Shaking
  • 提取公共第三⽅库

# 如何提⾼webpack的构建速度

  • 多⼊⼝情况下,使⽤ CommonsChunkPlugin 来提取公共代码
  • 通过 externals 配置来提取常⽤库
  • 利⽤ DllPlugin 和 DllReferencePlugin 预编译资源模块 通过 DllPlugin 来对那些我们引⽤但是绝对不会修改的npm包来进⾏预编译,再通过 DllReferencePlugin 将预编译的模块加载进来。
  • 使⽤ Happypack 实现多线程加速编译
  • 使⽤ webpack-uglify-parallel 来提升 uglifyPlugin 的压缩速度。 原理上 webpack-uglify-parallel 采⽤了多核并⾏压缩来提升压缩速度
  • 使⽤ Tree-shaking 和 Scope Hoisting 来剔除多余代码

# 前端模块化,CMD、AMD、UMD、CommonJS、ESMODULE

# AMD

Asynchronous Module Definition规范其实只有一个主要接口 define(id?, dependencies?, factory),它要在声明模块的时候指定所有的依赖dependencies,并且还要当做形参传到 factory 中,对于依赖的模块提前执行,依赖前置

它采用异步加载方式加载模块,模块的加载不影响它后面语句的运行。所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回调函数才会运行

eg:

define("module", ["dep1", "dep2"], function(d1, d2) {
  return someExportedValue;
});

require(["module", "../file"], function(module, file) { /* ... */ });

实现:

  • RequireJS
  • curl

# CMD

Common Module Definition 规范和 AMD 很相似,尽量保持简单,并与 CommonJS 和 Node.js 的 Modules 规范保持了很大的兼容性

define(function(require, exports, module) {
  var $ = require('jquery');
  var Spinning = require('./spinning');
  exports.doSomething = ...
  module.exports = ...
})

实现:

  • Sea.js
  • coolie

# UMD

Universal Module Definition 规范类似于兼容 CommonJS 和 AMD 的语法糖,是模块定义的跨平台解决方案。

# CommonJS

CommonJS是服务器端模块的规范,该规范的主要内容是,模块必须通过module.exports/exports 导出对外的变量或接口,通过 require()来导入其他模块的输出到当前模块作用域中。Commonjs本不适合在浏览器环境当中执行,但依赖现代打包工具的能力,Commonjs模块也可以经过转换后在浏览器中执行。

特点

  • 使用require导入
  • 使用exports 或 module.exports导出
  • CommonJS 模块中 require 引入模块的位置不同会对输出结果产生影响,并且会生成值的拷贝
  • CommonJS 模块重复引入的模块并不会重复执行,再次获取模块只会获得之前获取到的模块的拷贝
  • 同步加载模块

缺点

  • 不能并行加载多个模块

# ESMODULE

EcmaScript6 标准增加了 JavaScript 语言层面的模块体系定义。ES6 模块的设计思想,是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。在遇到模块加载命令import时,不会去执行模块,而是只生成一个引用。等到真的需要用到时,再到模块里面去取值。CommonJS 和 AMD 模块,都只能在运行时确定这些东西

import "jquery";
export function doStuff() {}
module "localModule" {}

特点:

  • 多次导入,调用一次
  • import和export命令只能在模块的顶层,不能在代码块之中
  • 动态import可以在代码块中执行
  • import 命令会被JavaScript引擎静态分析,优先于模块内的其他内容执行。
  • export 命令会有变量声明提前的效果

缺点:

  • 原生浏览器端还没有实现该标准
  • 全新的命令字,新版的 Node.js才支持

实现:

  • Babel

# 期望的模块系统

可以兼容多种模块风格,尽量可以利用已有的代码,不仅仅只是 JavaScript 模块化,还有 CSS、图片、字体等资源也需要模块化。

# ES6 模块跟 CommonJS 模块的不同之处

  • ES6 模块输出的是值的引用,输出接口动态绑定,而 CommonJS 输出的是值的拷贝
  • ES6 模块编译时执行,而 CommonJS 模块总是在运行时加载
  • ES6 Module 静态语法只能写在顶层,CommonJs 是动态语法可以写在判断里

# Vite为什么快

  • 冷启动
    • 依赖:使用esbuild构建,内部采用go编写,快 10 - 100倍
    • 源码:采用ESM模块,现代浏览器天然支持,不需要整体打包,仅根据路由加载对应的模块,不需要构建为es5的语法。
  • 热更新
    • HMR 是在原生 ESM 上执行的
    • Vite 同时利用 HTTP 头来加速整个页面的重新加载,源码模块的请求会根据进行协商缓存,而依赖模块请求则会进行强缓存

vite主要遵循的是使用ESM(Es modules模块)的规范来执行代码,由于现代浏览器基本上都支持了ESM规范,所以在开发阶段并不需要将代码打包编译成es5模块即可在浏览器上运行。我们只需要从入口文件出发,在遇到对应的 import 语句时,将对应的模块加载到浏览器中就可以了。因此,这种不需要打包的特性,也是vite的速度能够如此快速的原因。

# 微前端

微前端(Micro-Frontends)是一种类似于微服务的架构,它将微服务的理念应用于浏览器端,即将 Web 应用由单一的单体应用转变为多个小型前端应用聚合为一的应用。各个前端应用还可以独立运行、独立开发、独立部署。微前端不是单纯的前端框架或者工具,而是一套架构体系。

微前端的背景:

  • 拆分和细化:当下前端领域,单页面应用(SPA)是非常流行的项目形态之一,而随着时间的推移以及应用功能的丰富,单页应用变得不再单一而是越来越庞大也越来越难以维护,往往是改一处而动全身,由此带来的发版成本也越来越高。微前端的意义就是将这些庞大应用进行拆分,并随之解耦,每个部分可以单独进行维护和部署,提升效率
  • 整合历史系统:在不少的业务中,或多或少会存在一些历史项目,这些项目大多以采用老框架类似(Backbone.js,Angular.js 1)的B端管理系统为主,介于日常运营,这些系统需要结合到新框架中来使用还不能抛弃,对此我们也没有理由浪费时间和精力重写旧的逻辑。而微前端可以将这些系统进行整合,在基本不修改来逻辑的同时来同时兼容新老两套系统并行运行

# webpack tree shaking的原理和应用

原理:

  • Tree shaking的本质是消除无用的JavaScript代码
  • 因为ES6模块的出现,ES6模块依赖关系是确定的,和运行时的状态无关,可以进行可靠的静态分析,这就是Tree shaking的基础

Tree shaking is a term commonly used in the JavaScript context for dead-code elimination. It relies on the static structure of ES2015 module syntax, i.e. import and export. The name and concept have been popularized by the ES2015 module bundler rollup

tree shaking可以通过添加sideEffects:false来标识

陕ICP备20004732号-3