概念:
Webpack 是一种用于构建 JavaScript 应用程序的静态模块打包器,通过分析模块间的依赖关系,在其内部构建出一个依赖图,最终编绎输出模块为 HTML、JavaScript、CSS 以及各种静态文件(图片、字体等),并将其合并打包成浏览器兼容的 Web 资源文件。
主要作用
模块打包。可以将不同模块的文件打包整合在一起,并且保证它们之间的引用正确,执行有序。利用打包我们就可以在开发的时候根据我们自己的业务自由划分文件模块,保证项目结构的清晰和可读性。编译兼容。在前端的“上古时期”,手写一堆浏览器兼容代码一直是令前端工程师头皮发麻的事情,而在今天这个问题被大大的弱化了,通过webpack的Loader机制,不仅仅可以帮助我们对代码做polyfill,还可以编译转换诸如.less,.vue,.jsx这类在浏览器无法识别的格式文件,让我们在开发的时候可以使用新特性和新语法做开发,提高开发效率。能力扩展。通过webpack的Plugin机制,我们在实现模块化打包和编译兼容的基础上,可以进一步实现诸如按需加载,代码压缩等一系列功能,帮助我们进一步提高自动化程度,工程效率以及打包输出的质量。
构建流程
-
初始化参数:从配置文件和 Shell 语句中读取与合并参数,得出最终的参数 -
开始编译:用上一步得到的参数初始化 Compiler 对象,加载所有配置的插件,执行对象的 run 方法开始执行编译 -
确定入口:根据配置中的 entry 找出所有的入口文件 -
编译模块:从入口文件出发,调用所有配置的 loader 对模块进行翻译,再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理 -
完成模块编译:在经过上一步使用 loader 翻译完所有模块后,得到了每个模块被翻译后的最终内容以及它们之间的依赖关系 -
输出资源:根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 Chunk,再把每个 Chunk 转换成一个单独的文件加入到输出列表,这步是可以修改输出内容的最后机会 -
输出完成:在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统
一句话: 从 entry 出发,针对每个 Module 串行调用对应的 loader 去翻译文件的内容,再找到该 Module 依赖的 Module,递归地进行编译处理。
我们的项目使用的是Vue CLI Service build(封装了 Webpack)Vue CLI Service 对 Webpack 进行了封装,它提供了一个预配置的 Webpack 环境,这样开发者就不需要手动设置复杂的 Webpack 配置。 简化开发工作流:使用 vue-cli-service build,开发者可以通过简单的命令来执行诸如构建、开发服务器启动等任务,而无需直接与 Webpack 配置打交道。
vue打包流程: 打包命令 num run build 输入完命令后 会在项目的根目录创建一个dist目录 这个目录里面就是我们打包后的结果。
常见的loader:
-
image-loader:加载并且压缩图片文件。 -
less-loader: 加载并编译 LESS 文件。 -
sass-loader:加载并编译 SASS/SCSS 文件。 -
css-loader:加载 CSS,支持模块化、压缩、文件导入等特性,使用css-loader必须要配合使用style-loader。 -
style-loader:用于将 CSS 编译完成的样式,挂载到页面的 style 标签上。需要注意loader执行顺序,style-loader要放在第一位,loader都是从后往前执行。 -
babel-loader:把 ES6 转换成 ES5 -
postcss-loader:扩展 CSS 语法,使用下一代 CSS,可以配合autoprefixer插件自动补齐 CSS3 前缀。 -
eslint-loader:通过 ESLint 检查 JavaScript 代码。 -
vue-loader:加载并编译 Vue 组件。 -
file-loader:把文件输出到一个文件夹中,在代码中通过相对 URL 去引用输出的文件 (处理图片和字体) -
url-loader:与file-loader类似,区别是用户可以设置一个阈值,大于阈值会交给file-loader处理,小于阈值时返回文件 base64 形式编码 (处理图片和字体)
常见的plugin:
-
HtmlWebpackPlugin:简化 HTML 文件创建 (依赖于 html-loader) -
mini-css-extract-plugin: 分离样式文件,CSS 提取为独立文件,支持按需加载 (替代extract-text-webpack-plugin) -
clean-webpack-plugin: 目录清理
loader和plugin的区别?
loader是文件加载器,能够加载资源文件,并对这些文件进行一些处理,诸如编译、压缩等,最终一起打包到指定的文件中;plugin赋予了webpack各种灵活的功能,例如打包优化、资源管理、环境变量注入等,目的是解决 loader无法实现的其他事。
在运行时机上,loader 运行在打包文件之前;plugin则是在整个编译周期都起作用。
在配置上,loader在module.rules中配置,作为模块的解析规则,类型为数组。每一项都是一个 Object,内部包含了 test(类型文件)、loader、options (参数)等属性;plugin在 plugins中单独配置,类型为数组,每一项是一个 plugin 的实例,参数都通过构造函数传入。
打包优化(重点)
vuecli中提供一个打包分析的工具:vue-cli-service build --report
操作:在package.json 中的build项中,添加 --report,dist文件下会多一个report.html 这里面就有对打包分析的结果。
1. 路由懒加载
路由注册的两种方式
- 先引入 后设置
import user from './user.vue' { path: '/user/profile', name: 'article', component: user }
- 路由懒加载
{ path: '/user/profile', name: 'article', component: () => import(/* webpackChunkName: "user" */ '../views/setting/user.vue') } 两种的区别:
先引入 后设置:直接先导入,在设置,会导致所有引入的文件全部打包到1个.js文件中。
路由懒加载:会导致所有引入的文件单个打包到每一个.js文件中 如果我们使用路由懒加载, 他会单独把单独的路由组件加载进来。
2. 按需引入ui库
- element
npm install babel-plugin-component -D
3. 去掉控制台的console.log()
先 npm install terser-webpack-plugin -D
module.exports = {
configureWebpack: (config) => {
// 在webpack的配置对象 config的 optimization属性的minimizer数组的第一个 元素的options中设置....
// 在打包之后的js中去掉console.log config.optimization.minimizer[0].options.terserOptions.compress.drop_console = true
},
publicPath: './'
}
webpack的热更新原理是?
模块热替换(HMR - hot module replacement),又叫做热更新,在不需要刷新整个页面的同时更新模块,能够提升开发的效率和体验。热更新时只会局部刷新页面上发生了变化的模块,同时可以保留当前页面的状态,比如复选框的选中状态等。
热更新的核心就是客户端从服务端拉去更新后的文件,准确的说是 chunk diff (chunk 需要更新的部分),实际上webpack-dev-server与浏览器之间维护了一个websocket,当本地资源发生变化时,webpack-dev-server会向浏览器推送更新,并带上构建时的hash,让客户端与上一次资源进行对比。客户端对比出差异后会向webpack-dev-server发起 Ajax 请求来获取更改内容(文件列表、hash),这样客户端就可以再借助这些信息继续向webpack-dev-server发起 jsonp 请求获取该chunk的增量更新。
后续的部分(拿到增量更新之后如何处理?哪些状态该保留?哪些又需要更新?)由HotModulePlugin 来完成,提供了相关 API 以供开发者针对自身场景进行处理,像react-hot-loader和vue-loader都是借助这些 API 实现热更新。
6.什么是Code Splitting
Code Splitting代码分割,是一种优化技术。它允许将一个大的chunk拆分成多个小的chunk,从而实现按需加载,减少初始加载时间,并提高应用程序的性能 。
在Webpack中通过optimization.splitChunks配置项来开启代码分割
7.Webpack的Source Map是什么?如何配置生成Source Map?
Source Map是一种文件,它建立了构建后的代码与原始源代码之间的映射关系。通常在开发阶段开启,用来调试代码,帮助找到代码问题所在。
在Webpack配置文件中的devtool选项中指定devtool: 'source-map'来开启
8.Webpack的Tree Shaking原理
Webpack的Tree Shaking是一个利用ES6模块静态结构特性来去除生产环境下不必要代码的优化过程。其工作原理在于:
- 当Webpack分析代码时,它会标记出所有的import语句和export语句。
- 然后,当Webpack确定某个模块没有被导入时,它会在生成的bundle中排除这个模块的代码。
- 同时,Webpack还会进行递归的标记清理,以确保所有未使用的依赖项都不会出现在最终的bundle中。
为了启用Tree Shaking,需要在webpack配置文件中添加如下设置:
yaml
代码解读
复制代码
javascriptmodule.exports = { // ... optimization: { usedExports: true, concatenateModules: true, minimize: true, }, // ...};
确保你使用的是ES6模块语法(即import和export),因为只有这样才能让Tree Shaking发挥作用。
9.如何提高webpack的打包速度
-
代码压缩
-
JS压缩
webpack 4.0默认在生产环境的时候是支持代码压缩的,即mode=production模式下。 实际上webpack 4.0默认是使用terser-webpack-plugin这个压缩插件,在此之前是使用uglifyjs-webpack-plugin,两者的区别是后者对 ES6 的压缩不是很好,同时我们可以开启parallel参数,使用多进程压缩,加快压缩。 -
CSS压缩
CSS 压缩通常是去除无用的空格等,因为很难去修改选择器、属性的名称、值等。可以使用另外一个插件:css-minimizer-webpack-plugin。 -
HTML压缩
使用HtmlWebpackPlugin插件来生成 HTML 的模板时候,通过配置属性minify进行 html 优化。java 代码解读 复制代码 module.exports = { plugin:[ new HtmlwebpackPlugin({ minify:{ minifyCSS: false, // 是否压缩css collapseWhitespace: false, // 是否折叠空格 removeComments: true // 是否移除注释 } }) ] }
-
-
图片压缩
配置image-webpack-loader -
Tree Shaking
Tree Shaking是一个术语,在计算机中表示消除死代码,依赖于ES Module的静态语法分析(不执行任何的代码,可以明确知道模块的依赖关系)。 在webpack实现Tree shaking有两种方案:-
usedExports:通过标记某些函数是否被使用,之后通过
Terser来进行优化的ini 代码解读 复制代码 module.exports = { ... optimization:{ usedExports } }使用之后,没被用上的代码在
webpack打包中会加入unused harmony export mul注释,用来告知Terser在优化时,可以删除掉这段代码。 -
sideEffects:跳过整个模块/文件,直接查看该文件是否有副作用
sideEffects用于告知webpack compiler哪些模块时有副作用,配置方法是在package.json中设置sideEffects属性。如果sideEffects设置为false,就是告知webpack可以安全的删除未用到的exports。如果有些文件需要保留,可以设置为数组的形式,如:json 代码解读 复制代码 "sideEffecis":[ "./src/util/format.js", "*.css" // 所有的css文件 ]
-
-
缩小打包域
排除webpack不需要解析的模块,即在使用loader的时候,在尽量少的模块中去使用。可以借助include和exclude这两个参数,规定loader只在那些模块应用和在哪些模块不应用。 -
减少ES6转为ES5的冗余代码
使用bable-plugin-transform-runtime插件 -
提取公共代码
通过配置CommonsChunkPlugin插件,将多个页面的公共代码抽离成单独的文件
10.如何减少打包后的代码体积
- 代码分割(Code Splitting):将应用程序的代码划分为多个代码块,按需加载
- Tree Shaking:配置Webpack的Tree Shaking机制,去除未使用的代码
- 压缩代码:使用工具如UglifyJS或Terser来压缩JavaScript代码
- 使用生产模式:在Webpack中使用生产模式,通过设置mode: 'production'来启用优化
- 使用压缩工具:使用现代的压缩工具,如Brotli和Gzip,来对静态资源进行压缩
- 利用CDN加速:将项目中引用的静态资源路径修改为CDN上的路径,减少图片、字体等静态资源等打包
11. vite比webpack快在哪里
(一)、开发模式的差异
在开发环境中,Webpack 是先打包再启动开发服务器,而 Vite 则是直接启动,然后再按需编译依赖文件。(大家可以启动项目后检查源码 Sources 那里看到)
这意味着,当使用 Webpack 时,所有的模块都需要在开发前进行打包,这会增加启动时间和构建时间。
而 Vite 则采用了不同的策略,它会在请求模块时再进行实时编译,这种按需动态编译的模式极大地缩短了编译时间,特别是在大型项目中,文件数量众多,Vite 的优势更为明显。
Webpack启动
Vite启动
(二)、对ES Modules的支持
现代浏览器本身就支持 ES Modules,会主动发起请求去获取所需文件。Vite充分利用了这一点,将开发环境下的模块文件直接作为浏览器要执行的文件,而不是像 Webpack 那样先打包,再交给浏览器执行。这种方式减少了中间环节,提高了效率。
什么是ES Modules?
通过使用 export 和 import 语句,ES Modules 允许在浏览器端导入和导出模块。
当使用 ES Modules 进行开发时,开发者实际上是在构建一个依赖关系图,不同依赖项之间通过导入语句进行关联。
主流浏览器(除IE外)均支持ES Modules,并且可以通过在 script 标签中设置 type="module"来加载模块。默认情况下,模块会延迟加载,执行时机在文档解析之后,触发DOMContentLoaded事件前。
(3)、底层语言的差异
Webpack 是基于 Node.js 构建的,而 Vite 则是基于 esbuild 进行预构建依赖。esbuild 是采用 Go 语言编写的,Go 语言是纳秒级别的,而 Node.js 是毫秒级别的。因此,Vite 在打包速度上相比Webpack 有 10-100 倍的提升。
什么是预构建依赖?
预构建依赖通常指的是在项目启动或构建之前,对项目中所需的依赖项进行预先的处理或构建。这样做的好处在于,当项目实际运行时,可以直接使用这些已经预构建好的依赖,而无需再进行实时的编译或构建,从而提高了应用程序的运行速度和效率。
(4)、热更新的处理
在 Webpack 中,当一个模块或其依赖的模块内容改变时,需要重新编译这些模块。
而在 Vite 中,当某个模块内容改变时,只需要让浏览器重新请求该模块即可,这大大减少了热更新的时间。
12. 说一下你对Monorepo的理解
Monorepo 是一种项目代码管理方式,指单个仓库中管理多个项目,有助于简化代码共享、版本控制、构建和部署等方面的复杂性,并提供更好的可重用性和协作性。Monorepo 提倡了开放、透明、共享的组织文化,这种方法已经被很多大型公司广泛使用,如 Google、Facebook 和 Microsoft 等。
Monorepo优劣:
| 场景 | MultiRepo | MonoRepo |
|---|---|---|
| 代码可见性 | ✅ 代码隔离,研发者只需关注自己负责的仓库 ❌ 包管理按照各自owner划分,当出现问题时,需要到依赖包中进行判断并解决。 | ✅ 一个仓库中多个相关项目,很容易看到整个代码库的变化趋势,更好的团队协作。 ❌ 增加了非owner改动代码的风险 |
| 依赖管理 | ❌ 多个仓库都有自己的 node_modules,存在依赖重复安装情况,占用磁盘内存大。 | ✅ 多项目代码都在一个仓库中,相同版本依赖提升到顶层只安装一次,节省磁盘内存, |
| 代码权限 | ✅ 各项目单独仓库,不会出现代码被误改的情况,单个项目出现问题不会影响其他项目。 | ❌ 多个项目代码都在一个仓库中,没有项目粒度的权限管控,一个项目出问题,可能影响所有项目。 |
| 开发迭代 | ✅ 仓库体积小,模块划分清晰,可维护性强。 ❌ 多仓库来回切换(编辑器及命令行),项目多的话效率很低。多仓库见存在依赖时,需要手动 npm link,操作繁琐。 ❌ 依赖管理不便,多个依赖可能在多个仓库中存在不同版本,重复安装,npm link 时不同项目的依赖会存在冲突。 | ✅ 多个项目都在一个仓库中,可看到相关项目全貌,编码非常方便。 ✅ 代码复用高,方便进行代码重构。 ❌ 多项目在一个仓库中,代码体积多大几个 G,git clone时间较长。 ✅ 依赖调试方便,依赖包迭代场景下,借助工具自动 npm link,直接使用最新版本依赖,简化了操作流程。 |
| 工程配置 | ❌ 各项目构建、打包、代码校验都各自维护,不一致时会导致代码差异或构建差异。 | ✅ 多项目在一个仓库,工程配置一致,代码质量标准及风格也很容易一致。 |
| 构建部署 | ❌ 多个项目间存在依赖,部署时需要手动到不同的仓库根据先后顺序去修改版本及进行部署,操作繁琐效率低。 | ✅ 构建性 Monorepo 工具可以配置依赖项目的构建优先级,可以实现一次命令完成所有的部署。 |
13.你在项目是怎么做Monorepo?
工具对比
| 工具 | Turborepo | Rush | Nx | Lerna | Pnpm Workspace |
|---|---|---|---|---|---|
| 依赖管理 | ❌ | ✅ | ❌ | ❌ | ✅ |
| 版本管理 | ❌ | ✅ | ❌ | ✅ | ❌ |
| 增量构建 | ✅ | ✅ | ✅ | ❌ | ❌ |
| 插件扩展 | ✅ | ✅ | ✅ | ❌ | ❌ |
| 云端缓存 | ✅ | ✅ | ✅ | ❌ | ❌ |
| Stars | 20.4K | 4.9K | 17K | 34.3K | 22.7K |
详细对比:
选型建议
- 建议采用渐进式架构方案,即对于轻量级 Monorepo 项目,我们初期可以选择 Lerna + pnpm workspace + lerna-changelog,解决了依赖管理、发版管理等问题,为开发者带来便利;随着后续项目迭代,代码变多或多个项目间依赖关系复杂,可以很平滑的接入 Nx 来提升构建打包效率。
14.为什么pnpm比npm快
Pnpm 比 npm 快的原因在于其优化的文件存储方式、依赖管理方式以及并行下载能力。 以下是详细介绍:
- Pnpm 使用基于内容寻址的文件系统来存储磁盘上的所有文件,这意味着它不会在磁盘中重复存储相同的依赖包,即使这些依赖包被不同的项目所依赖。这种存储方式使得Pnpm在安装依赖时能够更高效地利用磁盘空间,同时也减少了下载和安装的时间。
- Pnpm 在下载和安装依赖时采用了并行下载的能力,这进一步提高了安装速度。
- Pnpm 还具有一些其他特性,例如节省空间的硬链接和符号链接的使用,这些都有助于提高其性能。
15.ESLint概念及原理
Lint会对代码做静态分析,检查出其中的一些结构错误或者格式错误。在前端领域中,我们常用的lint就是ESLint,它用于检查JavaScript代码是否符合规则 。
基本原理
ESLint基本架构图如下:
lib/linter/ - 这个模块是核心的 Linter 类,根据配置选项进行代码验证、检查并修复问题。这个文件不做任何文件 I/O,并且完全不与console互动。
Linter 是 eslint 最核心的类了,它提供了这几个 api:
- 检查:verify
- 检查并修复:verifyAndFix
- 获取 AST:getSourceCode
- 定义 Parser:defineParser
- 定义 Rule:defineRule
- 获取所有的 Rule:getRules
其中SourceCode指的是AST(抽象语法树),源代码字符串通过Parser解析成AST,之后ESLint就可以通过AST提供的信息与Rules对比,从而给出代码规范分析的结果,指出错误,并且还可以自动修复。
Linter 主要的功能是在 verify 和 verifyAndFix 里实现的,当命令行指定 --fix 或者配置文件指定 fix: true 就会调用 verifyAndFix 对代码进行检查并修复,否则会调用 verify 来进行检查。
AST
ESLint拿到源代码后会进行parse操作,生成AST用于静态分析。ESLint使用的是Espree parser。
Estree是一套AST标准,Esprima基于estree标准实现了AST。Acorn,它在Exprima之后出现,也是 estree 标准的实现,但是它速度比 esprima 快,而且支持插件,可以通过插件扩展语法支持。
Espree最初Fork自Esprima,因为Acorn的各种优点现在它建立在Acorn之上。
下面简单介绍下Espree解析器下AST的几个常见的节点,也可以在estree中查看更多详情。
Literal
Literal 是字面量的意思,它的值可以是布尔、数字、字符串等。
Identifer
Identifer 是标识符的意思,变量名、属性名、参数名等各种声明和引用的名字,都是Identifer。
statement
statement 是语句,它是可以独立执行的单位,比如 break、continue、debugger、return 或者 if 语句、while 语句、for 语句,还有声明语句,表达式语句等。我们写的每一条可以独立执行的代码都是语句。语句末尾一般会加一个分号分隔,或者用换行分隔。
下面这些我们经常写的代码,每一行都是一个 Statement:
javascript
代码解读
复制代码
js复制代码break;
continue;
return;
debugger;
throw Error();
{}
try {} catch(e) {} finally{}
for (let key in obj) {}
for (let i = 0;i < 10;i ++) {}
while (true) {}
do {} while (true)
switch (v){case 1: break;default:;}
with (a){}
Verify & Fix
PreProcess阶段
1.确定是否需要process
上面介绍过,ESLint 处理器可以从其他类型的文件中提取 JavaScript 代码,然后让 ESLint 对 JavaScript 代码进行检查,这就是processor的作用之一。例如,对vue类型文件做ESLint检查,processor就派上用场了。更详细的介绍可以看我另外一篇文章 Processor
Parse阶段
1.确定parser:
默认是 ESLint 自带的Espree,也可以通过配置来切换成别的 parser,比如 @eslint/babel-parser、@typescript/eslint-parser 等。
2.执行parse生成Source Code(AST):
检查阶段
调用rule对SourceCode检查,生成linting problems
那么是如何检查?
1.遍历AST并存储AST Node
2.遍历规则列表
为每条规则添加对应AST Node的Listener
为constructor-super规则绑定对应Listener(ReturnStatement、"Program:exit"等),当AST遍历执行到ReturnStatement类型的节点的时候便会执行constructor-super规则ReturnStatement方法里的逻辑。
3.Emit对应AST Node的Listener
这样AST Node遍历完成后也就执行所有的rules了。
在执行rules的过程中对比AST发现和rule规则不匹配,就可以添加问题到linting problems
最后生成的linting problems就是lint检查结果了。从哪一行(line)哪一列(column)到哪一行(endLine)哪一列(endColumn),有什么错误(message)。
问:在检查阶段为什么需要先存储AST Node然后再从AST Node Queue遍历来Emit Listener呢?这样不是遍历两次了吗?
因为rules一直有个小问题,node的parent属性只会在节点被遍历后才能被访问到。为了解决这个问题ESLint延迟执行了Emit,这样node parent属性就可以被访问到了。相关issue:github.com/eslint/esli…
PostProcess阶段
这个阶段主要用来对生成的linting problems做一些处理,例如过滤、修改之类的。
Fix阶段
对于可以fix的规则在lint检查完后会,linting problems里会有生成的fix信息,用于自动修复问题。
其中range表示范围,text表示替换的内容。结合到一起就是,range内的字符串替换成text即完成修复。
举个例子:
1.源代码
2.配置rule:no-extra-semi,不允许多余的分号。 3.运行ESLint检查,生成如下结构
css
代码解读
复制代码
js复制代码{
fix: {
range: [9, 11],
text: ';'
}
}
表示替换源码字符串(中index从9到11的内容为';',即替换";;"为";",替换后结果如下:
源码fix流程分析:
1.根据linting problems替换
问:这里为什么要加循环呢?
答:因为多个linting problem之间的range也就是替换的范围可能是有重叠的,如果有重叠就放到下一次来修复,下一次修复则会根据当前修复过一次的代码再继续verify,生成linting problems,以此循环直至没有problem可以修复。不过这样的循环最多修复 10 次,如果还有linting problems没修复就不修了。
2.替换算法:其实就是简单的字符串拼接与替换。
至此,ESLint工作的主要流程完成。
16.Bable概念及原理
Babel是一个流行的用于将新版本ES6+代码转换为向后兼容版本(ES5)代码的JavaScript编译器。它还为JSX语法提供了编译支持,还有一些其他插件可用于转换特定类型的代码 。
代码解读
复制代码
Babel 的工作原理:三类功能
解析
当 Babel 接收到源代码时,将会调用一个叫做解析器的工具,用于将源代码转换为抽象语法树(AST)。在这个过程中,解析器会识别代码中的语法结构,并将其转换为对应的节点类型。 例如,当解析器遇到一个变量声明语句时,它将会创建一个 “VariableDeclaration” 节点,并将该节点的信息存储在 AST 中。AST 是一个以节点为基础组成的树形结构,每个节点都有相应的类型、属性和子节点等信息。
转换
一旦 AST 被创建,Babel 将遍历整个树形结构,对每个节点进行转换。这些转换可以是插件、预设或手动创建的。转换器会检查 AST 中的每个节点,然后对其进行相应的修改或替换,以将新语法转换为旧语法。 例如,如果 Babel 遇到一个包含箭头函数的节点,而你已经启用了转换插件,该插件将会将箭头函数转换为其等效的体函数。代码转换后,Babel 将会生成一个新的 AST。
生成
最后,Babel 将基于转换后的 AST 生成代码文本。在这个步骤中,Babel 将遍历转换后的 AST,并创建对应的代码字符串,并将这些字符串组合成一个完整的 JavaScript 文件。如果启用了代码压缩,Babel 还可以将生成的代码进行压缩。 总结来说,Babel 的原理就是将 JavaScript 源代码转换为抽象语法树(AST),然后对 AST 进行转换,生成与源代码功能相同但向后兼容的代码。Babel 提供了一个强大的生态系统,使得开发者可以轻松扩展并自定义转换器,实现自己的功能需求。
17.npm install 的执行过程
npm install 是 Node.js 包管理器 (npm) 的一个命令,用于安装一个项目所依赖的模块。
执行过程大致如下:
- 读取
package.json文件,该文件列出了项目所需要的依赖。 - 根据
package.json中的依赖信息以及node_modules目录状态,npm 会决定哪些模块需要下载和安装。 - npm 会查看每个模块的可用版本,并选择符合
package.json中指定版本范围的最新版本进行安装。 - 下载所需模块到本地的
node_modules目录。 - 如果模块包含子模块(
package.json中dependencies或devDependencies中的模块),则递归执行上述步骤安装这些子模块。
18.npm run start 的整个过程?
npm run start 是一个常见的命令,用于启动基于 Node.js 的应用程序。这个命令实际上是一个快捷方式,它告诉 npm 运行在 package.json 文件中定义的 "start" 脚本。
当你执行 npm run start 时,以下是发生的事情:
- 查找当前目录下的 package.json 文件。
- 在 package.json 文件中,找到 "scripts" 对象。
- 在 "scripts" 对象中,找到 "start" 键。
- 执行与 "start" 键关联的命令字符串。
例如,如果你的 package.json 文件中的 "scripts" 对象像这样:
-
json 代码解读 复制代码 "scripts": { "start": "node app.js"}
当你运行 npm run start 时,npm 将执行 node app.js。
这是一个简单的例子,实际的 "start" 脚本可能会包含更多步骤,比如预处理、打包、转译、加载模块绑定等。
总结:npm run start 执行 package.json 中定义的 "start" 脚本,这个脚本可以启动一个 Node.js 应用程序或执行更复杂的前端构建过程。
19.对 CSS 工程化的理解
CSS 工程化是为了解决以下问题:
- 宏观设计:CSS 代码如何组织、如何拆分、模块结构怎样设计?
- 编码优化:怎样写出更好的 CSS?
- 构建:如何处理我的 CSS,才能让它的打包结果最优?
- 可维护性:代码写完了,如何最小化它后续的变更成本?如何确保任何一个同事都能轻松接手?
以下三个方向都是时下比较流行的、普适性非常好的 CSS 工程化实践:
- 预处理器:Less、 Sass 等;
- 重要的工程化插件: PostCss;
- Webpack loader 等 。
基于这三个方向,可以衍生出一些具有典型意义的子问题,这里我们逐个来看:
(1)预处理器:为什么要用预处理器?它的出现是为了解决什么问题? 预处理器,其实就是 CSS 世界的“轮子”。预处理器支持我们写一种类似 CSS、但实际并不是 CSS 的语言,然后把它编译成 CSS 代码:
那为什么写 CSS 代码写得好好的,偏偏要转去写“类 CSS”呢?这就和本来用 JS 也可以实现所有功能,但最后却写 React 的 jsx 或者 Vue 的模板语法一样。
随着前端业务复杂度的提高,前端工程中对 CSS 提出了以下的诉求:
- 宏观设计上:我们希望能优化 CSS 文件的目录结构,对现有的 CSS 文件实现复用;
- 编码优化上:我们希望能写出结构清晰、简明易懂的 CSS,需要它具有一目了然的嵌套层级关系,而不是无差别的一铺到底写法;我们希望它具有变量特征、计算能力、循环能力等等更强的可编程性,这样我们可以少写一些无用的代码;
- 可维护性上:更强的可编程性意味着更优质的代码结构,实现复用意味着更简单的目录结构和更强的拓展能力,这两点如果能做到,自然会带来更强的可维护性。
这三点是传统 CSS 所做不到的,也正是预处理器所解决掉的问题。预处理器普遍会具备这样的特性:
- 嵌套代码的能力,通过嵌套来反映不同 css 属性之间的层级关系 ;
- 支持定义 css 变量;
- 提供计算函数;
- 允许对代码片段进行 extend 和 mixin;
- 支持循环语句的使用;
- 支持将 CSS 文件模块化,实现复用。
(2)PostCss:PostCss 是如何工作的?我们在什么场景下会使用 PostCss?
它和预处理器的不同就在于,预处理器处理的是 类CSS,而 PostCss 处理的就是 CSS 本身。Babel 可以将高版本的 JS 代码转换为低版本的 JS 代码。PostCss 做的是类似的事情:它可以编译尚未被浏览器广泛支持的先进的 CSS 语法,还可以自动为一些需要额外兼容的语法增加前缀。更强的是,由于 PostCss 有着强大的插件机制,支持各种各样的扩展,极大地强化了 CSS 的能力。
PostCss 在业务中的使用场景非常多:
- 提高 CSS 代码的可读性:PostCss 其实可以做类似预处理器能做的工作;
- 当我们的 CSS 代码需要适配低版本浏览器时,PostCss 的 Autoprefixer 插件可以帮助我们自动增加浏览器前缀;
- 允许我们编写面向未来的 CSS:PostCss 能够帮助我们编译 CSS next 代码;
(3)Webpack 能处理 CSS 吗?如何实现?
- Webpack 在裸奔的状态下,是不能处理 CSS 的,Webpack 本身是一个面向 JavaScript 且只能处理 JavaScript 代 码的模块化打包工具;
- Webpack 在 loader 的辅助下,是可以处理 CSS 的。
如何用 Webpack 实现对 CSS 的处理:
- Webpack 中操作 CSS 需要使用的两个关键的 loader:css-loader 和 style-loader
- 注意,答出“用什么”有时候可能还不够,面试官会怀疑你是不是在背答案,所以你还需要了解每个 loader 都做了什么事情:
- css-loader:导入 CSS 模块,对 CSS 代码进行编译处理;
-
style-loader:创建style标签,把 CSS 内容写入标签。