1. 说说你对 Webpack 的理解?它解决了什么问题?

Webpack 是一个现代的 JS 应用程序的静态模块打包器。它主要做的事情就是:分析你的项目结构,找到 JavaScript 模块以及其他的一些浏览器不能直接运行的扩展语言(Sass TS 等)。并将其打包为合适的格式以供浏览器使用。

  • Webpack 的主要功能:
    1. 代码转换
    2. 文件优化
    3. 代码分割
    4. 模块合并
    5. 自动刷新
    6. 代码校验
    7. 自动发布等等

早期模块化?

  • 把单独的功能抽离到单独的 js 文件,通过 script 引入。

    出现的问题:模块都在全局中,大量模块污染环境,并且模块与模块之间没有依赖关系,维护困难,没有私有空间等问题

    解决方案:出现了 命名空间 方式,规定每个模块只暴露一个全局对象,模块的内容都挂载在这个对象中; ---》还是没有解决第一种方式的 依赖 等问题。再后来,使用 立即执行函数 模块提供私有空间,通过 参数 的形式作为依赖声明; ---》这种方式还是存在一些问题。比如:通过 script 引入模块,这些模块的加载并不受代码的控制

    理想的解决方式:在页面中引入一个 JS 入口文件,其余用到的模块可以通过代码控制,按需加载进来

  • 除了 模块加载 的问题以外,还需要规定模块化的规范。如今流行的:CommonJSES Module

    从前后端渲染的 JSP、PHP。到前端原生 JavaScript。再到 jQuery 开发。再到目前三大框架 Vue, React, Angular 开发。也从 JavaScript 到后面的 es5,6,7,8...。再到 TypeScript。有些编写的 CSS 预处理器 less、sass 等。如今的前端变得十分复杂,所以我们开发过程中会遇到以下问题:

    1. 项目需要通过模块化的方式来开发
    2. 使用一些高级的特性来加快我们的开发效率,如:ES6+、TypeScript 开发脚本逻辑,通过 Less、Sass 等方式来编写 css 样式代码
    3. 监听文件的变化并且反映到浏览器上,提高开发效率
    4. JS 代码需要模块化,HTML 和 CSS 这些资源文件有些也需要模块化
    5. 开发完后我们需要将代码压缩、合并以及一些优化等问题

综合:Webpack 恰巧可以解决以上问题!

扩展:webpack -> 垫片 Shimming

比如,我们使用 jQuery 中的$符号时,浏览器不认识($ is not defined)。我们可以使用 webpack 内置插件 ProvidePlugin({\$: 'jquery'})。模块中使用了$就自动引入jquery,并将jquery赋值给$.这种方式就叫垫片 Shimming

2. Webpack 的构建流程?

  • 初始化参数:解析 webpack 配置参数,合并 shell 传入和 webpack.config.js 文件配置的参数, 形成最后的配置结果
  • 开始编译:上一步得到的参数初始化 Compiler 对象,注册所有配置的插件,插件会监听 webpack 构建生命周期的事件节点,做出相应的反应。然后执行 run 方法开始执行编译
  • 确认入口:根据配置的 entry 入口,开始解析文件构建 AST(抽象语法树),找出依赖,递归下去
  • 编译模块:递归中根据文件类型和 loader 配置,调用所有配置的 loader 对文件进行转换,再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理
  • 完成模块编译并输出资源:递归完事后,得到每个文件结果,包含每个模块以及他们之间的依赖关系,根据 entry 或分包配置生成代码块 chunk
  • 输出完成:输出所有的 chunk 到文件系统

3. 常见的 Loader、Plugin 有哪些?能手写吗?

Plugin 的理解

Plugin 就是一个扩展器,它比 Loader 更加灵活,因为它可以接触到 Webpack 编译器。在 Webpack 运行的生命周期中会广播出许多的事件,Plugin 可以监听这些事件,在合适的时机通过 Webpack 提供的 API 改变输出结果。这样 Plugin 就可以通过一些 hook 函数来拦截 Webpack 的执行,做一些 Webpack 打包之外的事情。像:打包优化资源管理注入环境变量等等。

插件实例上都会有个 apply 方法,并将 compiler 作为其参数。(类似于:Vue 插件都有个 install 方法)

在开发 Plugin 时最常用的两个对象 CompilerCompilation,它们都继承自Tapable,是 PluginWebpack 之间的桥梁。类似于 react-redux 是连接 ReactRedux 的桥梁。(Tapable 有同步钩子和异步钩子(异步串行钩子异步并行钩子))

注册钩子的方式:同步(tap 注册 -》 call 执行);异步(tap -》calltapAsync -》callAsynctapPromise -》promise

通过 schema-utils 验证 options 的合法性

  • 常见的 Plugin:

    1. html-webpack-plugin:可以根据模板自动生成 html 代码,并自动引用 css 和 js 文件
    2. extract-text-webpack-plugin:将 js 文件中引用的样式单独抽离成 css 文件(webpack4 推荐使用 mini-css-extract-plugin)
      • 两者有啥区别?
        1. 后者:更容易使用、异步加载、而且只针对 CSS,并且不重复编译,性能更好
        2. 该插件一般在(生产环境)使用。代替 loaders 中的 style-loader,暂时不支持 HMR
    3. clean-webpack-plugin: 清理每次打包的文件
    4. speed-measure-webpack-plugin: 可以看每个 Loader 和 Plugin 执行耗时(webpack5 使用 speed-measure-webpack5-plugin)
    5. webpack-bundle-analyzer: 可视化 Webpack 输出文件的体积
    6. copy-webpack-plugin:拷贝插件
    7. friendly-errors-webpack-plugin: 识别某些类别的 webpack 错误,并清理,聚合和优先级,以提供更好的开发人员体验
      • webpack 中提供了 stats 选项显示打包信息(只展示错误信息、都展示等等)
    8. optimize-css-assets-webpack-plugin:压缩 css
    9. purgecss-webpack-plugin: 去除无用的 css
    10. uglifyJs-webpack-plugin:压缩 js (压缩 es6 的代码不是很友好;并且是单线程压缩代码,打包时间慢,所以开发环境将其关闭,生产环境打开(parallelUglifyPlugin 开启多个子进程打包,每个子进程还是 UglifyJS 打包,但并行执行)-》webpack4 推荐用 terser-webpack-plugin(开启 parallel 参数【一般是电脑的 CPU 核数减 1】,使用进程压缩);此插件 webpack5 中内置了)
    11. compression-webpack-plugin:(生产环境可采用)gzip 压缩 JS 和 CSS【需要后台配置 nginx】
    12. HotModuleReplacementPlugin:热更新(自带的)
    13. happypack:开启多进程打包,提高打包速度(不维护了)
    14. ProvidePlugin:自动加载模块,代替 require 和 import(自带的)
    15. DefinePlugin:定义全局变量(浏览器获取的值,需使用 JSON.stringify 包裹)
    16. IgnorePlugin:忽略或排除(moment 不用全部加载,只加载中文)
    17. DllPlugin:动态链接库,配合 DllReferencePlugin 一起使用(自带的)

Loader 的理解

就是一个 代码转码器,对各种资源进行转换。它的特点:单一原则,每个 loader 只做对应的事情。它的执行顺序:从右到左从下到上。有几种分类:pre、normal(默认)、inlinepostLoader 就是一个函数,接受原始资源作为参数,输出进行转换后的内容。

loader 的执行分为两个阶段:Pitch 阶段和 Normal 阶段。loader 会先执行 pitch,然后获取资源再执行 normal loader。如果 pitch 有返回值时,就不会走之后的 loader,并将返回值返回给之前的 loader。这就是为什么 pitch熔断 的作用!

loader-utilsgetOptions(this) 方法用来获取 loaderoptions 的配置

schema-utilsvalidate 方法用来验证 loaderoptions 的配置是否合法 {type: 'object', properties: {}, additionalProperties: true}

loader 分为同步returnthis.callback(null, source, map, meta)两种方式)和 异步this.async()

  • 常见的 Loader:

    1. file-loader:
    2. url-loader:
    3. babel-loader:
    4. css-loader:
    5. style-loader:
    6. eslint-loader:
    7. cache-loader:
    8. less-loader、sass-loader、styles-loader:
    9. image-webpack-loader:压缩图片
    10. postcss-loader、autoprefixer-loader
  • Compiler 上有哪些钩子? environment、run、make、emit、afterEmit、done、thisCompilation(初始化 compilation 时调用) 等等

  • Compilation 上有哪些钩子?

4. 如何提高 Webpack 的构建速度?

优化webpack构建的方式有很多,主要可以从优化搜索时间缩小文件搜索范围减少不必要的编译等方面入手:

  • 多线程/多实例构建:HappyPack(不维护了)、thread-loader
  • 优化 loader 的配置
    1. includeexclude
    2. 配置 babel-loader 时,可以配置 cacheDirectory 开启缓存
  • 合理的使用 resolve.extensions
    1. extensions: [".js",".json"]
  • 优化 resolve.modules
    1. 用于配置 webpack 去哪些目录下寻找第三方模块。默认值为['node_modules'],配置了可以减少查找路径
  • 优化 resolve.alias
    1. "@":path.resolve(\_\_dirname,'./src') 减少查找过程
  • 使用 DllPlugin 插件
    1. 打包成一个 Dll 库,webpack.DllPlugin() -> 生成 mainfest.json 文件
    2. 引入 Dll 库,webpack.DllReferencePlugin()
  • 使用 cache-loader
    1. 针对一些开销较大的 loader 前添加 cache-loader,将其结果缓存到磁盘里,显著提升二次构建速度(保存和读取这些缓存文件会有一些时间开销,只针对一些开销大的 loader
    2. use: ['cache-loader', ...loaders]
  • terser 开启多线程
    1. 使用多进程并行运行来提高构建速度
    2. optimization: { minimizer: [new TerserPlugin({ parallel: true })] }
  • 合理使用 sourceMap

5. Webpack4 和 Webpack5 有哪些区别?

  • 压缩代码

    内部自带 terser-webpack-plugin 插件(生产环境自动开启压缩)【webpack4 需要独立安装】

    // webpack5开发环境启动压缩
    const TerserPlugin = require('terser-webpack-plugini')
    module.exports = {
      optimization: {
        usedExports: true, // 只导出被使用的模块
        minimize: true, // 启动压缩
        minimizer: [new TerserPlugin()],
      },
    }
    

    扩展:开发模块开启 Tree Shaking 的方式?【生产环境自动开启】

    • optimization.sideEffects: true; // 开启 (package.json 里也要配置:'sideEffects: false | true | []'

    • Tree-shaking 机制的原理?

      1. treeShaking 也叫摇树优化,是指在打包中去除那些引入了,但是在代码中没有被用到的死代码。从而来优化打包体积。生产环境默认开启
      2. 它可以在代码不运行的状态下,分析出不需要的代码
      3. 利用es6模块的规范 ES6 Module 引入进行静态分析,故而编译的时候正确判断到底加载了那些模块
      4. Tree-shaking 的实现:
        1. 标记出模块导出值中哪些没有被用过
        2. 使用 Terser 删掉这些没被用到的导出语句
        3. 标记过程大致可划分为三个步骤:
          1. Make 阶段,收集模块导出变量并记录到模块依赖关系图 ModuleGraph 变量中
          2. Seal 阶段,遍历 ModuleGraph 标记模块导出变量有没有被使用
          3. 生成产物时,若变量没有被其它模块使用则删除对应的导出语句

      扩展CSS 可以 Tree-Shaking 吗?

      webpackTree-Shaking 是通过 uglifyJSPluginTree-Shaking JS 的。CSS 需要使用 Purify-CSS[不在维护],使用 purgecss-webpack-pluginmini-css-extract-plugin 提取 css 插件配合使用。

  • 缓存配置

    1. webpack4 通过 hard-source-webpack-plugin 缓存
    2. webpack5 内置了 cache 缓存机制
    module.exports = {
      // 使用持久化缓存
      cache: {
        type: 'filesystem',
        cacheDirectory: path.join(__dirname, 'node_modules/.cache/webpack'),
      },
    }
    

    扩展:什么是长缓存?

    webpack 中可以在 output 选项中指定 hash, 通过 HashedModuleIdsPlugin 或者 NamedModulesPlugin 来生成唯一的 moduleId,保证没有改动的文件 hash 值不变

    注意cache 在开发模式默认设置成 type: memory; 【生产模块把 cache 给禁用掉了】

  • 启动服务的差别

    1. webpack4 启动服务 -> webpack-dev-server
    2. webpack5 启动服务使用内置的 webpack serve 启动
  • 输出代码

    1. webpack4 只能输出 es5 的代码
    2. webpack5 新增属性 output.ecmaVersion,可以生成 es5es6 的代码
  • 代码分割

    1. webpack4 将超过 30kb 文件单独提为一个 chunkminSize: 30000
    2. webpack5 可以区分是 js 还是 css,精准划分(minSize: {javascript: 30000, css: 50000}
  • 模块联邦 Module Federation

  • devtool 差别

    1. webpack4 有 13 种

    2. webpack5 有 26 种

      扩展webpack4 一般开发环境配置 cheap-eval-module-source-map,在生产用 none;webpack5 使用 eval-cheap-module-source-map

6. Grunt、Gulp、Webpack、Rollup、Vite 的比较?

模块化管理工具自动化构建工具 是不同的。两者主要的侧重点不一样。自动化构建工具 则侧重于 前端开发的整个过程 的控制管理(像流水线)。而模块化管理工具更侧重于 模块打包。可以把开发中的所有资源(图片、js、css 文件等)都当成 模块

  • Webpack: 是当前最流行的 模块化管理工具打包工具。其通过 loader 的转换,可以将任何形式的资源都当成模块。它还可以将各个模块通过其 依赖关系 打包成符合生产环境部署的前端资源。它还可以将应用程序分解成可管理的代码块,可以按需加载

    打包原理:解析各个模块的依赖关系,使用 loader 转换文件,使用 plugin 注入钩子,打包合并模块,最终生成 bundle 文件,使用 express 开启本地服务器,浏览器请求的是 bundle 文件

    • 优点
      1. 基本之前 gulp 可以操作的,webpack 都可以做
      2. 同时支持热更新tree-shakingScope Hoisting动态加载代码拆分文件指纹代码压缩静态资源处理
      3. 支持多种打包方式
    • 缺点
      1. 各个模块之间的依赖关系过于复杂 会导致打包速度很慢
      2. 使用热更新时,改动一个模块,其他有依赖关系的模块也会重新打包
      3. 不支持打包出 esm 格式的代码(打包后的代码再次被引用时 tree shaking 困难),打包后亢余代码较多
  • Vite:和 webpack 差不多,vite 是当下新型的 模块化管理工具打包工具。它本地启动速度比 webpack 快了很多。但是 vite 还完成没有替换 webpack 的能力,不管是从社区还是从能力来说,vite 本身还是太过脆弱,它的产生和火热完成依赖于 vue 本身的热度

    打包原理:就是启动一个 koa 服务器拦截由浏览器请求的 ESM 的请求,通过请求的路径找到目录下对应的文件做一定的处理,最终以 ESM 的格式返回给客户端

    Vite1.x 使用 koa, Vite2.x 使用 connect

    Vite 作为一个基于浏览器原生的 ESM 的构建工具,它省略了开发环境的打包过程,利用浏览器去解析 import,在服务端按需编译返回。同时,开发环境模块热更新也非常快,不会随着模块的增多而变慢。

    • Vite 的主要特性:

      1. Instant Server Start —— 即时服务启动
      2. Lightning Fast HMR —— 闪电般快速的热更新
      3. Rich Features —— 丰富的功能
      4. Optimized Build —— 经过优化的构建
      5. Universal Plugin Interface —— 通用的 Plugin 接口
      6. Fully Typed APIs —— 类型齐全的 API
    • 缺点

      1. 项目的开发浏览器要支持 es module, 要求浏览器比较新【@vitejs/plugin-legacya】插件打包出一个兼容性比较好的版本
      2. 生产环境使用 rollup 打包会造成开大环境与生产环境不一致
      3. 生态没有 webpack 丰富
      4. 很多第三方 SDK 没有产出 ems 格式的代码,这就需要我们自己去做一些兼容

      对于一些 没有产出 esm 的模块,如何去兼容呢?

      业界是有一些如 lebab 的方法可以将 commjs 代码快速转化为 esm 的,但是对于一些格式不规范的代码,可能还是需要单独处理。

    构建工具的发展:Browserify -> Gulp -> Parcel -> Webpack -> Rollup -> (后来的非打包)Snowpack 和 Vite

    注意:在 Vite 中约定若 path 的请求路径满足 /^\/@modules\// 格式时,被认为是一个 node_modules 模块。

    • Vite 的热加载原理,其实就是在客户端与服务端建立了一个 websocket 连接,当代码被修改时,服务端发送消息通知客户端去请求修改模块的代码,完成热更新。

      1. 服务端:服务端做的就是监听代码文件的改变,在合适的时机向客户端发送 websocket 信息通知客户端去请求新的模块代码。
      2. 客户端:Vite 中客户端的 websocket 相关代码在处理 html 中时被写入代码中。可以看到在处理 html 时,vite/client 的相关代码已经被插入。
    • Vite 的实现核心:

      1. 实现静态服务功能 -> koa-static
      2. 解析 import 语法 重写路径 -> es-module-lexer parse 方法
      3. 解析 以/@modules 文件开头的内容 找到对应的结果,设置响应类型 type='js'返回给客户端
      4. 处理 html 不存在 process
      5. 解析.vue 文件
  • Rollup:是下一代 ES6 模块打包工具,可以将我们按照 ESM(ES2015 Module)规范编写的源码构建输出如下格式:

    1. IIFE:自执行函数,可通过 script 标签加载
    2. AMD:通过 requirejs 加载
    3. CommonJS:Node 默认的模块规范,可通过 webpack 加载
    4. UMD:兼容 IIFE、AMD、CJS 三种模块规范
    5. ESM:ES2015 Module 规范,可用 webpack,rollup 加载
    • 优点

      1. 支持动态导入
      2. 支持 tree-shaking。仅加载模块里用得到的函数以减少文件大小
      3. Scope Hoisting。rollup 可以将所有的小文件生成到一个大文件中,所有代码都在同一个函数作用域里;不会像 webpack 那样用很多函数来包装模块
      4. 没有其他冗余代码, 执行很快。除了必要的 cjs, umd 头外,bundle 代码基本和源码差不多,也没有奇怪的 __webpack_require__, Object.defineProperty 之类的东西
    • 缺点

      1. 不支持热更新功能
      2. 对于 commonjs 模块,需要额外的插件将其转化为 es2015 供 rollup 处理
      3. 无法公共代码拆分

    适用场景:开发第三方库、生成单一的 umd 文件的场景

    比较 Webpack

    1. Rollup 目前还不支持代码拆分(Code Splitting)和模块的热更新(HMR)
    2. 一般,对于应用开发使用 Webpack,对于类库开发使用 Rollup
    3. 需要代码拆分(Code Splitting),或者很多静态资源需要处理,再或者构建的项目需要引入很多 CommonJS 模块的依赖时,使用 webpack。代码库是基于 ES6 模块,而且希望代码能够被其他人直接使用,使用 Rollup
    4. React 已经将构建工具从 Webpack 换成了 Rollup
  • Gulp:是基于 前端自动化构建工具,采用代码优于配置的策略,更容易学习和使用,它让简单的任务简单,复杂的任务复杂

    • 优点
      1. gulp 文档简单,学习成本低,使用也比较简单
      2. 对大量源文件可以进行流式处理,借助插件,可以对文件类型进行多种操作处理
    • 缺点
      1. 不支持 tree-shaking、热更新、代码拆分等
      2. gulp 对 js 模块化方案无能为力,只是对静态资源做流式处理,处理之后并未做有效的优化整合

    适用场景:静态资源密集操作型场景,主要用于 css、图片等静态资源的处理操作

    比较 grunt:

    1. 易用,Gulp 相比于 Grunt 更简洁,而且遵循代码优于配置策略,维护 Gulp 更像是写代码
    2. 高效,Gulp 核心设计是基于 Unix 的的概念,通过管道连接,不需要写中间文件
    3. 易学,Gulp 的核心 API 只有 5 个,之后可以通过管道流组合自己想要的任务
    4. 流:使用 Grunt 的 I/O 过程中会产生一些中间态的临时文件,一些任务生成临时文件,其它任务可能会基于临时文件再做处理并生成最终的构建后文件。而使用 Gulp 的优势就是利用流的方式进行文件的处理,通过管道将多个任务和操作连接起来,因此只有一次 I/O 的过程,流程更清晰,更纯粹
    5. 代码优于配置:维护 Gulp 更像是写代码,而且 Gulp 遵循 CommonJS 规范,因此跟写 Node 程序没有区别
  • Grunt:是一套 前端自动化工具,帮助处理反复重复的任务。一般用于:编译、压缩、合并文件,简单语法检查等

    • 特点
      1. Grunt 有一个完成的社区,插件丰富
      2. 它简单易学,你可以随便安装插件并配置它们

Webpack 的定位是模块打包器,而 Gulp/Grunt 属于构建工具

7. 了解热更新原理吗?它是如何做到的?说说其原理?

开启了 express 应用,添加了对 webpack 编译的监听,添加了和浏览器的 websocket 长连接,当文件变化触发 webpack 进行编译并完成后,会通过 socket 告诉浏览器准备刷新。而为了减少刷新的代价,就是不用刷新页面,而是刷新某个模块,webpack-dev-server 可以支持热更新,通过生成文件的 hash 来对比需要更新的模块,浏览器再进行热替换

扩展:hash、chunkhash、contenthash 三者的区别?

  • hash 一般是结合 CDN 缓存来使用的

    1. hash:是跟整个项目的构建相关,只要项目里有文件更改,整个项目构建的 hash 值都会更改,并且全部文件都公用相同的 hash 值(每一次构建都会生成新的 hash 值(不管文件是否有改动)-》导致没有办法实现缓存效果)
    2. chunkhash:和 hash 不一样,它根据不同的入口文件(entry)进行依赖文件解析,构建对于的 chunk,生成对应的哈希值。-》(同一个 chunk 的一个依赖改变了,其他的依赖哈希值也会变)
    3. contenthash:针对的是对应的内容是否改变
  • 如何避免相同的随机值? webpack 在计算hash后分割chunk产生相同随机值可能是因为这些文件属于同一个chunk,可以将某个文件提到独立的chunk(如放入entry)

  • 扩展:CDN 原理?

    1. 用户输入域名,首先经过 dns 解析,请求 cname 指向的那台 cdn 专用的 dns 服务器
    2. dns 服务器返回全局负载均衡的服务器 IP 给用户
    3. 用户请求全局负载均衡服务器,服务器根据 IP 返回所在区域的负载均衡服务器 IP 给用户
    4. 用户请求区域负载均衡服务器,服务器根据 IP 选择距离最近的,负载比较合适的一台缓存服务器 IP 给用户。(当没有对应内容时,会去上一级缓存服务器去找,直到找到资源所在的服务器,并且缓存在缓存服务器中,供下次访问)

域名 -> dns 解析 -> 全局负载均衡的服务器 -> 所在区域的负载均衡服务器 -> 缓存服务器

  • CDN 回溯:就是 CDN 发现自己没有这个资源(一般是缓存的数据过期了),转头向根服务器(或者它的上层服务器)去要这个资源的过程

8. sourceMap 有哪些?对应的作用是什么?

9. Babel 的原理?

BabelJS 语法转换器(将一些高级语法转换成浏览器可以识别的低级语法)

  • Babel 的功能很纯粹,它只是一个编译器。大多数编译器的工作过程可以分为三部分:

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

    1. 使用 babylon源代码 进行 解析 -> 得到 AST
    2. 使用 babel-traverseAST 树进行遍历转义 -> 得到新的 AST
    3. 使用 babel-generator 通过 AST 树生成 ES5 代码
  • Babel 的包构成

    1. babel-core: babel 的核心库,提供一下 babel 转义 API,如 babel.transform 等,用于对代码进行转译。(webpackbabel-loader 是调用这些 API 来完成转译的)
    2. babylonjs 的词法解析器
    3. babel-traverse:用于对 AST 的遍历
    4. babel-generator: 根据 AST 生成代码
  • 工具包和功能包

    1. babel-clibabel 的命令行工具,通过命令行对 js 代码进行转译
    2. babel-register:通过绑定 node.jsrequire 来自动转译 require 引用的 js 代码文件
    3. babel-types:用于检验、构建和改变 AST 树的节点
    4. babel-polyfill:JS 标准新增的原生对象和 API 的 shim,实现上仅仅是 core-jsregenerator-runtime 两个包的封装
    5. babel-runtime:功能类似 babel-polyfill,一般用于 libraryplugin 中,因为它不会污染全局作用域

扩展babel-runtimebabel-polyfill 的区别?

babel 默认只转译新的 JS 语法,而不转译新的 API(如:IteratorSetGeneratorProxySymbol 等全局对象),以及一些定义在全局对象上的方法(如:Object.assign)都不会转译。如果想使用这些新的对象和方法,则需要为当前环境提供一个 polyfill

  • babel-polyfill,它会加载整个 polyfill 库,解决了 babel 不转译新 API 的问题。并且在代码中 插入一些帮助函数

    缺点:直接在代码中插入帮助函数,会导致污染了全局环境;并且全部引入,打包后会有很多 重复的代码,导致编译后的代码体积变大

  • babel-runtimebabel 为了解决以上问题,提供了单独的包,用以提供编译模块的工具函数。启用 babel-plugin-transform-runtime(它会帮我自动动态 require @babel/runtime 中的内容)后,babel 就会使用 babel-runtime 下的工具函数;这样可以避免自行引入 polyfill 时导致的污染全局命名空间的问题

  • babel-runtime 适合在组件,类库项目中使用,而 babel-polyfill 适合在业务项目中使用

扩展babel-runtime 为什么适合 JavaScript 库和工具包的实现?

  1. 避免 babel 编译的工具函数在每个模块里重复出现,减小库和工具包的体积
  2. 在没有使用 babel-runtime 之前,库和工具包一般不会直接引入 polyfill。否则像 Promise 这样的全局对象会污染全局命名空间。在使用 babel-runtime 后,库和工具只要在 package.json 中增加依赖 babel-runtime,交给 babel-runtime 去引入 polyfill 就行了

注意:具体项目还是需要使用 babel-polyfill,只使用 babel-runtime 的话,实例方法不能正常工作(例如 "foobar".includes("foo")

10. module、chunk、bundle 分别是什么意思,有何区别?

  • 对于一份同逻辑的代码,当我们手写下一个一个的文件,它们无论是 ESM 还是 commonJS 或是 AMD,他们都是 module
  • 当我们写的 module 源文件传到 webpack 进行打包时,webpack 会根据文件引用关系生成 chunk 文件,webpack 会对这个 chunk 文件进行一些操作
  • webpack 处理好 chunk 文件后,最后会输出 bundle 文件,这个 bundle 文件包含了经过加载和编译的最终源文件,所以它可以直接在浏览器中运行

总结:我们直接写出来的是 modulewebpack 处理时是 chunk,最后生成浏览器可以直接运行的 bundle

11. Webpack optimize 有配置过吗?可以简单说说吗?