1. 说说你对 Webpack 的理解?它解决了什么问题?
Webpack
是一个现代的 JS
应用程序的静态模块打包器
。它主要做的事情就是:分析你的项目结构,找到 JavaScript
模块以及其他的一些浏览器不能直接运行的扩展语言(Sass
TS
等)。并将其打包为合适的格式以供浏览器使用。
- Webpack 的主要功能:
- 代码转换
- 文件优化
- 代码分割
- 模块合并
- 自动刷新
- 代码校验
- 自动发布等等
早期模块化?
把单独的功能抽离到单独的
js
文件,通过script
引入。出现的问题:模块都在全局中,大量模块污染环境,并且模块与模块之间没有依赖关系,维护困难,没有私有空间等问题
解决方案:出现了
命名空间
方式,规定每个模块只暴露一个全局对象,模块的内容都挂载在这个对象中; ---》还是没有解决第一种方式的依赖
等问题。再后来,使用立即执行函数
模块提供私有空间,通过参数
的形式作为依赖声明; ---》这种方式还是存在一些问题。比如:通过 script 引入模块,这些模块的加载并不受代码的控制理想的解决方式:在页面中引入一个
JS
入口文件,其余用到的模块可以通过代码控制,按需加载进来除了 模块加载 的问题以外,还需要规定
模块化的规范
。如今流行的:CommonJS
、ES Module
从前后端渲染的 JSP、PHP。到前端原生 JavaScript。再到 jQuery 开发。再到目前三大框架 Vue, React, Angular 开发。也从 JavaScript 到后面的 es5,6,7,8...。再到 TypeScript。有些编写的 CSS 预处理器 less、sass 等。如今的前端变得十分复杂,所以我们开发过程中会遇到以下问题:
- 项目需要通过模块化的方式来开发
- 使用一些高级的特性来加快我们的开发效率,如:ES6+、TypeScript 开发脚本逻辑,通过 Less、Sass 等方式来编写 css 样式代码
- 监听文件的变化并且反映到浏览器上,提高开发效率
- JS 代码需要模块化,HTML 和 CSS 这些资源文件有些也需要模块化
- 开发完后我们需要将代码压缩、合并以及一些优化等问题
综合: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
时最常用的两个对象 Compiler
和 Compilation
,它们都继承自Tapable
,是 Plugin
和 Webpack
之间的桥梁。类似于 react-redux
是连接 React
和 Redux
的桥梁。(Tapable
有同步钩子和异步钩子(异步串行钩子
和 异步并行钩子
))
注册钩子的方式:同步(tap
注册 -》 call
执行);异步(tap -》call
、tapAsync -》callAsync
、tapPromise -》promise
)
通过 schema-utils
验证 options
的合法性
常见的
Plugin
:- html-webpack-plugin:可以根据模板自动生成 html 代码,并自动引用 css 和 js 文件
- extract-text-webpack-plugin:将 js 文件中引用的样式单独抽离成 css 文件(webpack4 推荐使用 mini-css-extract-plugin)
- 两者有啥区别?
- 后者:更容易使用、异步加载、而且只针对 CSS,并且不重复编译,性能更好
- 该插件一般在(生产环境)使用。代替 loaders 中的 style-loader,暂时不支持 HMR
- 两者有啥区别?
- clean-webpack-plugin: 清理每次打包的文件
- speed-measure-webpack-plugin: 可以看每个 Loader 和 Plugin 执行耗时(webpack5 使用 speed-measure-webpack5-plugin)
- webpack-bundle-analyzer: 可视化 Webpack 输出文件的体积
- copy-webpack-plugin:拷贝插件
- friendly-errors-webpack-plugin: 识别某些类别的 webpack 错误,并清理,聚合和优先级,以提供更好的开发人员体验
- webpack 中提供了 stats 选项显示打包信息(只展示错误信息、都展示等等)
- optimize-css-assets-webpack-plugin:压缩 css
- purgecss-webpack-plugin: 去除无用的 css
- uglifyJs-webpack-plugin:压缩 js (压缩 es6 的代码不是很友好;并且是单线程压缩代码,打包时间慢,所以开发环境将其关闭,生产环境打开(parallelUglifyPlugin 开启多个子进程打包,每个子进程还是 UglifyJS 打包,但并行执行)-》webpack4 推荐用 terser-webpack-plugin(开启 parallel 参数【一般是电脑的 CPU 核数减 1】,使用进程压缩);此插件 webpack5 中内置了)
- compression-webpack-plugin:(生产环境可采用)gzip 压缩 JS 和 CSS【需要后台配置 nginx】
- HotModuleReplacementPlugin:热更新(自带的)
- happypack:开启多进程打包,提高打包速度(不维护了)
- ProvidePlugin:自动加载模块,代替 require 和 import(自带的)
- DefinePlugin:定义全局变量(浏览器获取的值,需使用 JSON.stringify 包裹)
- IgnorePlugin:忽略或排除(moment 不用全部加载,只加载中文)
- DllPlugin:动态链接库,配合 DllReferencePlugin 一起使用(自带的)
Loader 的理解
就是一个 代码转码器
,对各种资源进行转换。它的特点:单一原则,每个 loader
只做对应的事情。它的执行顺序:从右到左
,从下到上
。有几种分类:pr
e、normal
(默认)、inline
、post
。Loader
就是一个函数,接受原始资源作为参数
,输出进行转换后的内容。
loader
的执行分为两个阶段:Pitch
阶段和 Normal
阶段。loader
会先执行 pitch
,然后获取资源再执行 normal loader
。如果 pitch
有返回值时,就不会走之后的 loader
,并将返回值返回给之前的 loader
。这就是为什么 pitch
有 熔断 的作用!
loader-utils
中 getOptions(this)
方法用来获取 loader
中 options
的配置
schema-utils
中 validate
方法用来验证 loader
中 options
的配置是否合法 {type: 'object', properties: {}, additionalProperties: true}
loader
分为同步(return
或 this.callback(null, source, map, meta)
两种方式)和 异步(this.async()
)
常见的 Loader:
- file-loader:
- url-loader:
- babel-loader:
- css-loader:
- style-loader:
- eslint-loader:
- cache-loader:
- less-loader、sass-loader、styles-loader:
- image-webpack-loader:压缩图片
- postcss-loader、autoprefixer-loader
Compiler
上有哪些钩子? environment、run、make、emit、afterEmit、done、thisCompilation(初始化 compilation 时调用) 等等Compilation
上有哪些钩子?
4. 如何提高 Webpack 的构建速度?
优化webpack
构建的方式有很多,主要可以从优化搜索时间
、缩小文件搜索范围
、减少不必要的编译
等方面入手:
- 多线程/多实例构建:
HappyPack
(不维护了)、thread-loader
- 优化
loader
的配置include
和exclude
- 配置
babel-loader
时,可以配置cacheDirectory
开启缓存
- 合理的使用
resolve.extensions
extensions
: [".js",".json"]
- 优化
resolve.modules
- 用于配置
webpack
去哪些目录下寻找第三方模块。默认值为['node_modules']
,配置了可以减少查找路径
- 用于配置
- 优化
resolve.alias
"@":path.resolve(\_\_dirname,'./src')
减少查找过程
- 使用
DllPlugin
插件- 打包成一个
Dll
库,webpack.DllPlugin()
-> 生成mainfest.json
文件 - 引入
Dll
库,webpack.DllReferencePlugin()
- 打包成一个
- 使用
cache-loader
- 针对一些开销较大的
loader
前添加cache-loader
,将其结果缓存到磁盘里,显著提升二次构建速度(保存和读取这些缓存文件会有一些时间开销,只针对一些开销大的loader
) use
:['cache-loader', ...loaders]
- 针对一些开销较大的
terser
开启多线程- 使用多进程并行运行来提高构建速度
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
机制的原理?treeShaking
也叫摇树优化
,是指在打包中去除那些引入了,但是在代码中没有被用到的死代码
。从而来优化打包体积。生产环境默认开启
- 它可以在
代码不运行
的状态下,分析出不需要的代码
- 利用
es6模块
的规范ES6 Module
引入进行静态分析
,故而编译的时候正确判断到底加载了那些模块
Tree-shaking
的实现:- 先标记出模块导出值中哪些没有被用过
- 使用
Terser
删掉这些没被用到的导出语句 - 标记过程大致可划分为三个步骤:
Make 阶段
,收集模块导出变量并记录到模块依赖关系图ModuleGraph
变量中Seal 阶段
,遍历ModuleGraph
标记模块导出变量有没有被使用生成产物时
,若变量没有被其它模块使用则删除对应的导出语句
扩展:
CSS
可以Tree-Shaking
吗?在
webpack
中Tree-Shaking
是通过uglifyJSPlugin
来Tree-Shaking JS
的。CSS
需要使用Purify-CSS
[不在维护],使用purgecss-webpack-plugin
,mini-css-extract-plugin
提取 css 插件配合使用。
缓存配置
webpack4
通过hard-source-webpack-plugin
缓存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
给禁用掉了】启动服务的差别
webpack4
启动服务 ->webpack-dev-server
webpack5
启动服务使用内置的webpack serve
启动
输出代码
webpack4
只能输出es5
的代码webpack5
新增属性output.ecmaVersion
,可以生成es5
和es6
的代码
代码分割
webpack4
将超过30kb
文件单独提为一个chunk
(minSize: 30000
)webpack5
可以区分是js
还是css
,精准划分(minSize: {javascript: 30000, css: 50000}
)
模块联邦
Module Federation
devtool 差别
webpack4
有 13 种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 文件
- 优点:
- 基本之前 gulp 可以操作的,webpack 都可以做
- 同时支持
热更新
、tree-shaking
、Scope Hoisting
、动态加载
、代码拆分
、文件指纹
、代码压缩
、静态资源处理
等 - 支持多种打包方式
- 缺点:
- 各个模块之间的依赖关系过于复杂 会导致打包速度很慢
- 使用热更新时,改动一个模块,其他有依赖关系的模块也会重新打包
- 不支持打包出 esm 格式的代码(打包后的代码再次被引用时 tree shaking 困难),打包后亢余代码较多
- 优点:
Vite:和 webpack 差不多,vite 是当下新型的
模块化管理工具
和打包工具
。它本地启动速度比 webpack 快了很多。但是 vite 还完成没有替换 webpack 的能力,不管是从社区还是从能力来说,vite 本身还是太过脆弱,它的产生和火热完成依赖于 vue 本身的热度打包原理:就是启动一个
koa
服务器拦截由浏览器请求的 ESM 的请求,通过请求的路径找到目录下对应的文件做一定的处理,最终以 ESM 的格式返回给客户端Vite1.x
使用koa
,Vite2.x
使用connect
Vite 作为一个基于浏览器原生的 ESM 的构建工具,它省略了开发环境的打包过程,利用浏览器去解析 import,在服务端按需编译返回。同时,开发环境模块热更新也非常快,不会随着模块的增多而变慢。
Vite 的主要特性:
- Instant Server Start —— 即时服务启动
- Lightning Fast HMR —— 闪电般快速的热更新
- Rich Features —— 丰富的功能
- Optimized Build —— 经过优化的构建
- Universal Plugin Interface —— 通用的 Plugin 接口
- Fully Typed APIs —— 类型齐全的 API
缺点:
- 项目的开发浏览器要支持
es module
, 要求浏览器比较新【@vitejs/plugin-legacy
a】插件打包出一个兼容性比较好的版本 - 生产环境使用 rollup 打包会造成开大环境与生产环境不一致
- 生态没有
webpack
丰富 - 很多第三方 SDK 没有产出 ems 格式的代码,这就需要我们自己去做一些兼容
对于一些 没有产出 esm 的模块,如何去兼容呢?
业界是有一些如
lebab
的方法可以将commjs
代码快速转化为esm
的,但是对于一些格式不规范的代码,可能还是需要单独处理。- 项目的开发浏览器要支持
构建工具的发展:Browserify -> Gulp -> Parcel -> Webpack -> Rollup -> (后来的非打包)Snowpack 和 Vite
注意:在
Vite
中约定若path
的请求路径满足/^\/@modules\//
格式时,被认为是一个node_modules
模块。Vite 的热加载原理,其实就是在客户端与服务端建立了一个 websocket 连接,当代码被修改时,服务端发送消息通知客户端去请求修改模块的代码,完成热更新。
- 服务端:服务端做的就是监听代码文件的改变,在合适的时机向客户端发送 websocket 信息通知客户端去请求新的模块代码。
- 客户端:Vite 中客户端的 websocket 相关代码在处理 html 中时被写入代码中。可以看到在处理 html 时,vite/client 的相关代码已经被插入。
Vite 的实现核心:
- 实现静态服务功能 ->
koa-static
- 解析 import 语法 重写路径 ->
es-module-lexer
parse 方法 - 解析 以/@modules 文件开头的内容 找到对应的结果,设置响应类型 type='js'返回给客户端
- 处理 html 不存在 process
- 解析.vue 文件
- 实现静态服务功能 ->
Rollup:是下一代 ES6 模块打包工具,可以将我们按照 ESM(ES2015 Module)规范编写的源码构建输出如下格式:
- IIFE:自执行函数,可通过 script 标签加载
- AMD:通过 requirejs 加载
- CommonJS:Node 默认的模块规范,可通过 webpack 加载
- UMD:兼容 IIFE、AMD、CJS 三种模块规范
- ESM:ES2015 Module 规范,可用 webpack,rollup 加载
优点:
- 支持动态导入
- 支持 tree-shaking。仅加载模块里用得到的函数以减少文件大小
- Scope Hoisting。rollup 可以将所有的小文件生成到一个大文件中,所有代码都在同一个函数作用域里;不会像 webpack 那样用很多函数来包装模块
- 没有其他冗余代码, 执行很快。除了必要的
cjs
,umd
头外,bundle 代码基本和源码差不多,也没有奇怪的__webpack_require__
,Object.defineProperty
之类的东西
缺点:
- 不支持热更新功能
- 对于 commonjs 模块,需要额外的插件将其转化为 es2015 供 rollup 处理
- 无法公共代码拆分
适用场景:开发第三方库、生成单一的 umd 文件的场景
比较 Webpack:
- Rollup 目前还不支持代码拆分(Code Splitting)和模块的热更新(HMR)
- 一般,对于应用开发使用 Webpack,对于类库开发使用 Rollup
- 需要代码拆分(Code Splitting),或者很多静态资源需要处理,再或者构建的项目需要引入很多 CommonJS 模块的依赖时,使用 webpack。代码库是基于 ES6 模块,而且希望代码能够被其他人直接使用,使用 Rollup
- React 已经将构建工具从 Webpack 换成了 Rollup
Gulp:是基于 流 的 前端自动化构建工具,采用代码优于配置的策略,更容易学习和使用,它让简单的任务简单,复杂的任务复杂
- 优点:
- gulp 文档简单,学习成本低,使用也比较简单
- 对大量源文件可以进行流式处理,借助插件,可以对文件类型进行多种操作处理
- 缺点:
- 不支持 tree-shaking、热更新、代码拆分等
- gulp 对 js 模块化方案无能为力,只是对静态资源做流式处理,处理之后并未做有效的优化整合
适用场景:静态资源密集操作型场景,主要用于 css、图片等静态资源的处理操作
比较 grunt:
- 易用,Gulp 相比于 Grunt 更简洁,而且遵循代码优于配置策略,维护 Gulp 更像是写代码
- 高效,Gulp 核心设计是基于 Unix 的流的概念,通过管道连接,不需要写中间文件
- 易学,Gulp 的核心 API 只有 5 个,之后可以通过管道流组合自己想要的任务
- 流:使用 Grunt 的 I/O 过程中会产生一些中间态的临时文件,一些任务生成临时文件,其它任务可能会基于临时文件再做处理并生成最终的构建后文件。而使用 Gulp 的优势就是利用流的方式进行文件的处理,通过管道将多个任务和操作连接起来,因此只有一次 I/O 的过程,流程更清晰,更纯粹
- 代码优于配置:维护 Gulp 更像是写代码,而且 Gulp 遵循 CommonJS 规范,因此跟写 Node 程序没有区别
- 优点:
Grunt:是一套 前端自动化工具,帮助处理反复重复的任务。一般用于:编译、压缩、合并文件,简单语法检查等
- 特点:
- Grunt 有一个完成的社区,插件丰富
- 它简单易学,你可以随便安装插件并配置它们
- 特点:
Webpack 的定位是模块打包器,而 Gulp/Grunt 属于构建工具
7. 了解热更新原理吗?它是如何做到的?说说其原理?
开启了 express 应用,添加了对 webpack 编译的监听,添加了和浏览器的 websocket 长连接,当文件变化触发 webpack 进行编译并完成后,会通过 socket 告诉浏览器准备刷新。而为了减少刷新的代价,就是不用刷新页面,而是刷新某个模块,webpack-dev-server 可以支持热更新,通过生成文件的 hash 来对比需要更新的模块,浏览器再进行热替换
扩展:hash、chunkhash、contenthash 三者的区别?
hash 一般是结合 CDN 缓存来使用的
- hash:是跟整个项目的构建相关,只要项目里有文件更改,整个项目构建的 hash 值都会更改,并且全部文件都公用相同的 hash 值(每一次构建都会生成新的 hash 值(不管文件是否有改动)-》导致没有办法实现缓存效果)
- chunkhash:和 hash 不一样,它根据不同的入口文件(entry)进行依赖文件解析,构建对于的 chunk,生成对应的哈希值。-》(同一个 chunk 的一个依赖改变了,其他的依赖哈希值也会变)
- contenthash:针对的是对应的内容是否改变
如何避免相同的随机值? webpack 在
计算hash后分割chunk
。产生相同随机值可能是因为这些文件属于同一个chunk,可以将某个文件提到独立的chunk(如放入entry)
扩展:CDN 原理?
- 用户输入域名,首先经过 dns 解析,请求 cname 指向的那台 cdn 专用的 dns 服务器
- dns 服务器返回全局负载均衡的服务器 IP 给用户
- 用户请求全局负载均衡服务器,服务器根据 IP 返回所在区域的负载均衡服务器 IP 给用户
- 用户请求区域负载均衡服务器,服务器根据 IP 选择距离最近的,负载比较合适的一台缓存服务器 IP 给用户。(当没有对应内容时,会去上一级缓存服务器去找,直到找到资源所在的服务器,并且缓存在缓存服务器中,供下次访问)
域名 -> dns 解析 -> 全局负载均衡的服务器 -> 所在区域的负载均衡服务器 -> 缓存服务器
- CDN 回溯:就是
CDN
发现自己没有这个资源(一般是缓存的数据过期了),转头向根服务器(或者它的上层服务器)去要这个资源的过程
8. sourceMap 有哪些?对应的作用是什么?
9. Babel 的原理?
Babel
是 JS
语法转换器(将一些高级语法转换成浏览器可以识别的低级语法)
Babel
的功能很纯粹,它只是一个编译器。大多数编译器的工作过程可以分为三部分:解析
(Parse) :将源代码转换成更加抽象的表示方法(例如抽象语法树
)。包括词法分析
和语法分析
。词法分析主要把字符流
源代码(Char Stream
)转换成令牌流
(Token Stream
)。语法分析主要是将令牌流
转换成抽象语法树
(Abstract Syntax Tree
,AST)。转换
(Transform) :通过Babel
的插件能力,对(抽象语法树)做一些特殊处理,将高版本语法的 AST 转换成支持低版本语法的 AST。让它符合编译器的期望,当然在此过程中也可以对 AST 的 Node 节点进行优化操作,比如添加、更新以及移除节点等。生成
(Generate) :将 AST 转换成字符串形式
的低版本
代码,同时也能创建 Source Map 映射。
Babel 的原理:
- 使用
babylon
将源代码
进行解析
-> 得到AST
- 使用
babel-traverse
对AST
树进行遍历转义 -> 得到新的AST
树 - 使用
babel-generator
通过AST
树生成ES5
代码
- 使用
Babel 的包构成:
babel-core
:babel
的核心库,提供一下 babel 转义 API,如babel.transform
等,用于对代码进行转译。(webpack
的babel-loader
是调用这些 API 来完成转译的)babylon
:js
的词法解析器babel-traverse
:用于对AST
的遍历babel-generator
: 根据AST
生成代码
工具包和功能包:
babel-cli
:babel
的命令行工具,通过命令行对 js 代码进行转译babel-register
:通过绑定node.js
的require
来自动转译require
引用的js
代码文件babel-types
:用于检验、构建和改变 AST 树的节点babel-polyfill
:JS 标准新增的原生对象和 API 的 shim,实现上仅仅是core-js
和regenerator-runtime
两个包的封装babel-runtime
:功能类似babel-polyfill
,一般用于library
或plugin
中,因为它不会污染全局作用域
扩展:babel-runtime
和 babel-polyfill
的区别?
babel
默认只转译新的 JS 语法
,而不转译新的 API
(如:Iterator
、Set
、Generator
、Proxy
、Symbol
等全局对象),以及一些定义在全局对象上的方法(如:Object.assign
)都不会转译。如果想使用这些新的对象和方法,则需要为当前环境提供一个 polyfill
babel-polyfill
,它会加载整个polyfill
库,解决了babel
不转译新 API
的问题。并且在代码中插入一些帮助函数
缺点:直接在代码中插入帮助函数,会导致
污染了全局环境
;并且全部引入,打包后会有很多重复的代码
,导致编译后的代码体积变大babel-runtime
:babel
为了解决以上问题,提供了单独的包,用以提供编译模块的工具函数。启用babel-plugin-transform-runtime
(它会帮我自动动态require @babel/runtime
中的内容)后,babel
就会使用babel-runtime
下的工具函数;这样可以避免自行引入 polyfill 时导致的污染全局命名空间的问题babel-runtime
适合在组件,类库项目中使用,而babel-polyfill
适合在业务项目中使用
扩展:babel-runtime
为什么适合 JavaScript
库和工具包的实现?
- 避免
babel
编译的工具函数在每个模块里重复出现,减小库和工具包的体积 - 在没有使用
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 文件包含了经过加载和编译的最终源文件,所以它可以直接在浏览器中运行
总结:我们直接写出来的是 module
,webpack
处理时是 chunk
,最后生成浏览器可以直接运行的 bundle