6.前端工程化面试题

181 阅读18分钟

1. webpack 的原理

概念

webpack 是一个用于JavaScript应用程序的静态模块打包工具。

核心名词

webpack有如下几个核心概念entry chunk. loader,plugin, output,

  1. entry,是依赖分析和打包的入口
  2. chunk,多个文件组成一个代码块。可以将可执行的模块和他所依赖的模块组合成一个chunk
  3. loader,它主要是对模块源代码或者文件进行转换的
  4. plugin:扩展webpack功能的插件。在webpack构建的生命周期节点上加入扩展hook,添加功能
  5. out,是打包结果输出的这个路径,

webpack 的打包过程是这样的:

  1. 初始化 webpack会将从配置文件和 Shell语句中读取与合并参数,得出最终的参数
  2. 根据配置中的 entry 找出所有的⼊⼝⽂件,然后从这些⼊⼝⽂件出发,调⽤当前入口文件配置的 Loader 进行处理,如果这些模块还依赖的其他模块,递归的对这些依赖的模块进行处理,这一步处理完之后会得到了每个模块被翻译后的最终内容以及它们之间的依赖关系,
  3. 根据⼊⼝和模块之间的依赖关系,组装成⼀个包含多个模块的 Chunk,再把每个 Chunk 转换成⼀个单独的⽂件加⼊到输出列表,在确定好输出内容后,根据配置确定输出的路径和⽂件名,把⽂件内容写⼊到⽂件系统。

2. webpack的构建流程(详细版)

  1. 初始化参数:从配置文件和 Shell语句中读取与合并参数,得出最终的参数
  2. 开始编译:用上一步得到的参数初始化 Compiler 对象,加载所有配置的插件,执行对象的 run 方法开始执行编译
  3. 确定入口:根据配置中的 entry 找出所有的入口文件
  4. 编译模块:从入口文件出发,调用所有配置的Loader 对模块进行翻译,再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理
  5. 完成模块编译:在经过第四步使上oader 翻译完所有模块后,得到了每个模块被翻译后的最終内容以及它们之间的依赖关系
  6. 输出资源:根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 Chunk,再把每个 Chunk 转换成一个单独的文件加入到输出列表,这步是可以修改输出内容的最后机会
  7. 输出完成:在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统,在以上过程中,webpack 会在特定的时间点广播出特定的事件,插件在监听到感兴趣的事件后会执行特定的逻辑,并且插件可以调用

3. webpack 热更新的原理

webpack的热更新⼜称热替换(Hot Module Replacement),缩写为HMR。 这个机制可以做到不⽤刷新浏览器⽽将新变更的模块替换掉旧的模块。

在项目启动后,在浏览器和webpack-dev-serve 之间会建立一个stocket连接,当代码改变了,开发服务器会带着修改后的hash值,然后推送一条消息给浏览器,浏览器会与上一次的hash值进行比较,判断是否更新了,更新了会向服务器请求更新列表,然后根据请求列表利用jsonp技术更新并执行,实现热更新。

vue vue-load 提供的方案,css style-load 提供的热更新方案

4. 提高webpack 构建速度

提高构建效率可以从缓存、并行处理、减少编译范围 三个方面着手。

  1. 首先可以设置缓存,提高二次构建性能。webpack 5之前是配置cache-loader,而现在简化了,只需要配置 cache 属性就可以了。

  2. 其次应该尽量采用并行处理方式,比如使用 Tread-loader,或者在 terser-webpack-plugin 中开启并行处理功能。

对于基于node的webpack来讲受限于单线程驱动,默认情况下只能使用cpu的一个核心进行工作。对于多核cpu已经成为标配的时代算是一种极大的浪费。最好的方法就是开启并行处理方式。

  1. 在减少编译范围方面可以,使用 noparse 跳过第三库编译、使用 exclude 约束执行范围,禁止不必要的产物优化

产物优化主要包括: minimize 压缩 splitChunks 分包 concatenateModules 模块连接 usedExports Tree-shaking 功能

  base.optimization = {
    removeAvailableModules: false,
    removeEmptyChunks: false, //
    splitChunks: false, // 代码分包
    minimize: false, //代码压缩
    concatenateModules: false,
    usedExports: false, // Treeshaking
  };
};

5.编写Loader,Plugin的思路?

实现loader: 首先创建一个loader的js文件,然后定义一个方法,方法传入sourcecode,然后进行处理,处理完之后return处理结果,然后把该函数导出。 接着就可以去webpack配置了,配置主要在rules选项里面配置,用test选项配置要处理的文件,use配置该loader路径。

module.exports = function (sourceCode) {
    var code = `var style = document.createElement("style");
    style.innerHTML = \`${sourceCode}\`;
    document.head.appendChild(style);
    module.exports = \`${sourceCode}\``;
    return code;
}

module.exports = {
    mode: "development",
    devtool: "source-map",
    module: {
        rules: [{
            test: /\.css$/,
            use: ["./loaders/style-loader"]
        }]
    }
}

实现plugin 的思路:插件就是包含apply方法的对象,首先创建插件的类,然后定义一个apply方法,传入一个compiler对象,compiler对象其实就是webpack编译器的一个实例,这个实例绑定很多钩子函数,就可以在钩子函数上执行我们插件的逻辑,然后把该类导出。 使用的时候就是可以在plugins的数组里面,创建一个插件对象放进去。

module.exports = class MyPlugin {
    apply(compiler) {
        //在这里注册事件,类似于window.onload  $(function(){})
        compiler.hooks.done.tap("MyPlugin-done", function(compilation){
            //事件处理函数
            console.log("编译完成");
        })
    }
}
var MyPlugin = require("./plugins/MyPlugin")
module.exports = {
    mode: "development",
    watch: true,
    plugins: [
        new MyPlugin()
    ]
}

6.vite 为啥比webpack快?

vite 的快主要体现在开发阶段比较快。 webpack 他在一开始的就会从入口文件开始,把所有依赖的文件都编译一遍,随着项目越来越复杂,项目的模块越来越多,打包时间会越来越长。而vite 启动的时候不需要打包,也就意味着它不需要编译,不需要分析依赖,所以他的启动速度是非常快的。当浏览器请求某个模块的时候,然后根据需要对模块的内容进行编译。由于现代浏览器是支持ES Module的,会自动向依赖的模块发动请求,vite充分利用了这一点,这种按需动态的编译极大的缩短了编译时间。项目越复杂,模块越多,vite的优势越明显。 在热更新方面,仅需让浏览器重新请求该模块即可,不想webpack,他会将所有和该模块相关的都打包一遍。 在底层实现上,vite是基于es build 构建的,esbuild 是用go语言写的,比js编写的打包器构建快。

7.有哪些常见的Loader?

webpack默认只能加载js.json资源,其他资源就需要loader转换之后才能加载使用。 JS:

  • ES6: babel-loader
  • TS ts-loader
  • 代码规范的 eslint-loader
  • source-map-loader

CSS:

  • sass,less - 预处理器 sassloader lessloader
  • style-loader, css-loader
  • post-css-loader

图片资源:

  • file-loader
  • url-loader
  • image-loader
  1. file-loader:把⽂件输出到⼀个⽂件夹中,在代码中通过相对 URL 去引⽤输出的⽂件
  1. url-loader:和 file-loader 类似,但是能在⽂件很⼩的情况下以 base64 的⽅式把⽂件内容注⼊到代码中去
  2. source-map-loader:加载额外的 Source Map ⽂件,以⽅便断点调试
  3. image-loader:加载并且压缩图⽚⽂件
  4. babel-loader:把 ES6 转换成 ES5
  5. css-loader:加载 CSS,⽀持模块化、压缩、⽂件导⼊等特性
  6. style-loader:把 CSS 代码注⼊到 JavaScript 中,通过 DOM 操作去加载 CSS。
  7. eslint-loader:通过 ESLint 检查 JavaScript 代码

在Webpack中,loader的执行顺序是从右向左执行的。因为webpack选择了compose这样的函数式编程方式,这种方式的表达式执行是从右向左的。

8.有哪些常见的Plugin?

1.打包优化:

全局

  1. CleanWebpackPlugin:用于在每次构建前清理输出目录中的文件。
  2. CompressionWebpackPlugin :用于对打包后的资源文件进行 gzip 压缩。

HTML

  1. HtmlWebpackPlugin:用于生成 HTML 文件,并将打包后的资源文件自动引入

CSS

  1. MiniCssExtractPlugin:用于将 CSS 提取为单独的文件。 Javascript
  2. DefinePlugin:用于定义环境变量。
  3. UglifyJsPlugin:用于压缩 JavaScript 代码,

图片静态资源

  1. CopyWebpackPlugin:用于将静态文件直接复制到输出目录中

2.调试分析

  1. webpack-bundle-analyzer:用于分析井可视化打包后的模块大小和依赖关系
  2. FriendlyErnrsWebpackPlugin:用于发好地展示 Webpack 构建错误信意。
  3. HotModuleReplepementPlugin:用于实现热模块替换功能

9.Loader和Plugin的不同?

不同的作用:

  1. Loader直译为"加载器"。Webpack将⼀切⽂件视为模块,但是webpack原⽣是只能解析js,json⽂件,如果想将其他⽂件也打包的话,就会⽤到 loader 。 所以Loader的作⽤是让webpack拥有了加载和解析⾮JavaScript⽂件的能⼒。

  2. Plugin直译为"插件"。Plugin可以扩展webpack的功能,让webpack具有更多的灵活性。 在 Webpack 运⾏的⽣命周期中会⼴播出许多事件,Plugin 可以监听这些事件,在合适的时机通过 Webpack 提供的 API 改变输出结果。

不同点:

  1. Loader在 module.rules 中配置,也就是说他作为模块的解析规则⽽存在。 类型为数组,每⼀项都是⼀个 Object ,⾥⾯描述了对于什么类型的⽂件( test ),使⽤什么加载( loader )和使⽤的参数( options )

  2. Plugin在 plugins 中单独配置。 类型为数组,每⼀项是⼀个 plugin 的实例,参数都通过构造函数传⼊。

10. bundle,chunk,module是什么?

  • bundle:是由webpack打包出来的⽂件;
  • chunk:代码块,⼀个chunk由多个模块组合⽽成,⽤于代码的合并和分割;
  • module:是开发中的单个模块,在webpack的世界,⼀切皆模块,⼀个模块对应⼀个⽂件,webpack会从配置的 entry中递归开始找出所有依赖的模块。

11.如何用webpack来优化前端性能?

性能优化策略: 体积压缩方面:

  1. 代码和资源文件进行压缩,常规的有JS,CSS,image,比方说某个活动我们使用到一个特殊的中文字体,但是只有这几个字,我们就将某几个字从字体文件中提出来,就可以显著的缩小他的大小
  2. 利⽤CDN加速 :我们可以将静态资源部署到CDN 上,并配置 webpack 的 publicPath,或者使用第三库时引入CDN,提高访问速度。
  3. 代码拆分

对于SPA 应用路由可以在根据 import()动态引用分包,比如对于SPA 应用 路由中可以使用 impor()方法,就可以将每一个视图所依赖的模块进行分包 还可以公共的代码分包提取出去 懒加载的方式导入模块,提高首屏的加载速度

提高打包速度方面:

  1. 首先可以设置缓存,提高二次构建性能。webpack 5之前是配置cache-loader,而现在简化了,只需要配置 cache 属性就可以了。

  2. 其次应该尽量采用并行处理方式,比如使用 Tread-loader,或者在 terser-webpack-plugin 中开启并行处理功能。

  3. 在减少编译范围方面可以,使用 noparse 跳过第三库编译

  1. 代码和资源文件进行压缩,JS -> Terser, CSS -> cssnano,图片可以使用image-webpack-loader压缩图片大小,

字体可以使用压缩,和子集化(主要针对中文,比方说某个活动使用某种特殊字体,可以只把这几个字体弄出来就好)

  1. 利⽤CDN加速 :我们可以将静态资源部署到CDN 上,并配置 webpack 的 publicPath,或者使用第三库时引入CDN,提高访问速度。
  1. treeshaking 将代码中永远运行的⽚段删除掉。 可以通过webpack配置usedExports和package.json中的sideEffects配置来实现。
  1. 代码拆分 将代码按路由或者组件进行拆分,这样做到按需加载,同时可以充分利⽤浏览器缓存。

对于SPA 应用路由可以在根据 import()动态引用分包,比如对于SPA 应用 路由中可以使用 impor()方法,就可以将每一个视图所依赖的模块进行分包 还可以公共的代码分包提取出去 懒加载的方式导入模块,提高首屏的加载速度

  1. 缓存优化 打包输出的文件名带上hashcode,这样可以利用浏览器的缓存,在文件变化的时候才进行下载。

12.webpack 打包是hash码是如何生成的?

Webpack 在打包过程中生成 hash 码主要用于缓存和版本管理。主要有三种类型的 hash 码:

hash:是和整个项目的构建相关,只要项目文件有修改,整个项目构建的 hash 值就会更改。这意味着任何一个文件的改动都会影响到整体的 hash 值。

chunkhash:与 webpack 打包的 chunk 有关,不同的 entry 会生成不同的 chunkhash 值。例如,如果你的配置生成了多个 chunk(例如使用了 code splitting),每个 chunk 的更新只会影响到它自身的 chunkhash。

contenthash:根据文件内容来定义 hash,内容不变,则 contenthash 不变。这在使用诸如 CSS 提取到单独文件的插件时特别有用,因此只有当文件的内容实际改变时,浏览器才会重新下载文件。

生成方式:

hash 和 chunkhash 主要是通过某种 hash 算法(默认 MD5)来对文件名或者 chunk 数据进行编码。 contenthash 是通过构建时的 webpack 插件(如 mini-css-extract-plugin)来处理的,它会对文件内容进行 hash。 Hash 码的生成可以被 webpack 配置的 hashFunction,hashDigest,hashDigestLength 等选项影响。例如,你可以选择不同的算法如 SHA256 或者 MD5,以及可以决定 hash 值的长度。 在 webpack 的配置文件中,可以通过如下方式设定 hash:

output: {
  filename: '[name].[chunkhash].js',
  path: __dirname + '/dist'
}

这会将输出的文件名设置为入口名称加上基于每个 chunk 内容的 hash。在使用 webpack-dev-server 或者 webpack --watch 时,不会生成实际的文件,所以这些 hash 值是在内存中计算并关联的。

13. webpack treeShaking机制的原理?

treesharking其实就是代码进行静态分析,分析出来没用的,删除掉。 在webpack中, TreeShaking 又叫做 usedExports 意思就是导出有用的部分,存在于 optimization 属性中,只需要在确保使用静态模块的前提下,开启这个功能就可以实现 Treeshaking 了。

Snip20241115_1.png

14.Babel的原理是什么?

  • 解析 Parse: 将代码解析⽣成抽象语法树(AST),即词法分析与语法分析的过程;
  • 转换 Transform: 对于 AST 进⾏变换⼀系列的操作,babel 接受得到 AST 并通过 babel-traverse 对其进⾏遍历,在此过程中进⾏添加、更新及移除等操作;
  • ⽣成 Generate: 将变换后的 AST 再转换为 JS 代码, 使⽤到的模块是 babel-generator。

15.分包策略?

分包就是把一个大的bundle文件分成几个文件。 分包的好处有:

  1. 降低页面初始代码包体积,提高首屏渲染性能
  2. 提高应用缓存效率

常见的分包策略有三个

  1. 分包对于多入口的项目可以根据 Entry分包
  2. SPA 应用可以在根据 import()动态引用分包,比如路由中可以使用 impor()方法,就可以将每一个视图所依赖的模块进项分包
  3. 最后一种是将运行时代码单独分包,但是由于运行时代码相对体积较小,所以他的优化效果并不十分明显。

16.常见的构建工具有哪些?

前端构建工具是一种针对前端开发的构建工具,用于自动化前端项目的构建 过程。 常见的构建工具主要是Webpack、和 Rollup 这种打包器。 在老项目中还会看到Grunt 和 Gulp 这种基于工作流的构建工具 当然新还有一种 Vite 这种Bundlerless高效的构建工具

Webpack 的特点是 应用广泛、生态鉴权特别适合在项目型项目使用 在新的 4.0 和 50 版本中比如现在流行的 Treesharking 这种新特性 Rollup: 是特别适合类型库型项目的开发,比如组件库。比如常见的 ElementUI构建工具 Vite:是一种新型的构建工具,开发的时候使用 Bundless 方式启动快 Vue 的亲儿子。未来对 Vue3.0 的支持也肯定能够得到很好的保证

总结:这三个流行的框架主态体系都很健全,而且也都有相当多的成功案例。 在现行上我推荐从应用场景出发: 项目型:尤其是核心项目选择 Webpack,清掉可靠性 类库型: Rollup 非核心项目: 可以尝试 Vite

17.sourcemap 是什么?

sourcemap 是编译后代码和原始代码的映射, 在生产环境,$sourcemap 不能发布到线上。 通常的做法是上传到异常监控服务器。 用于解析线上错误信息。

18.文件指纹

文件指纹就是将文件内容进行哈希运算。 然后将结果反映到文件上。 确保文件内容不同文件名也不同。webpack 中的文件指纹策略一般分三种, 全局、Chunk、文件粒度从大至小。 通常情况下选择文件粒度。

hash是打包编译后生成的一个hash,打包进去的文件有一个改变该hash就会改变 chunk hash 是基于入口文件及其关联文件的chunk生成的,某个文件的改动只会影响与它有关联的chunk的hash值,不会影响其他文件 content hash 是当前文件的内容改变了,就会生成一个新的hash.

19.如何配置Babel可以将ES6转化为ES5?

主要用途:

  • ES6 -> ES5
  • JSX->JS
  • TS->JS
  • Flow类型注释转换

所谓 ES6 转 ES5 是指将工程代码中新语法转换为浏览器运行时环境支持的语法。 通常情况需要 Babel 和 @babel/preset-env 预设一起完成. 例如在 webpack 构建的项目中 babel 通过loader 的形式引入 在配置的时候需要在 @babel/preset-env 预设配置内容指定运行时环境,比如浏览器的版本。和 polyfill 等内容。

20.Webpack配置CodeSplitting代码分割

代码分割

  1. 多个Entry 入口法 将一个大的单页面应用拆解为多个单页面应用,配置多个入口

  2. 动态导入方法 懒加载,其实就是使用import 的静态导入改成使用import 函数的动态导入,最常见的做法就是在路由部分将视图进行动态导入,这个时候webpack就会将动态导入的部分单独划分成一个chunk,这种分出来的trunk并不会在使用中加载,而是用到的时候加载

21. Webpack 和vite的区别

Webpack是一个打包模块化 JavaScript 的工具,在 Webpack 里一切文件皆模块,通过 Loader 转换文件,通过 Plugin 注入钩子,最后输出由多个模块组合成的文件。Webpack 专注于构建模块化项目。 一切文件:JavaScript、CSS、SCSS、图片、模板,在 Webpack 眼中都是一个个模块,这样的好处是能清晰的描述出各个模块之间的依赖关系,以方便 Webpack 对模块进行组合和打包。 经过 Webpack 的处理,最终会输出浏览器能使用的静态资源。因此每次编译都需要重新构建。

vite主要遵循的是使用ESM(Es modules模块)的规范来执行代码,由于现代浏览器基本上都支持了ESM规范,所以在开发阶段并不需要将代码打包编译成es5模块,即可直接在浏览器上运行。我们只需要从入口文件出发, 在遇到对应的 import 语句时,将对应的模块加载到浏览器中就可以了。当项目越大,文件越多时,vite的开发时优势越明显。 vite热更新比webpack,vite在HRM方面,当某个模块内容改变时,让浏览器直接重新请求该模块即可,而不是像webpack重新将该模块的所有依赖重新编译。

**Vite的使用简单,只需执行初始化命令,就可以得到一个预设好的开发环境,开箱即获得一堆功能,**包括:CSS预处理、html预处理、异步加载、分包、压缩、HMR等。使用复杂度介于Parcel和Webpack的中间,只是暴露了极少数的配置项和plugin接口,既不会像Parcel一样配置不灵活,又不会像Webpack一样需要了解庞大的loader、plugin生态,灵活适中、复杂度适中。

总体来说,Vite在前端构建工具领域上开辟了一条和webpack完全不同的道路,很好地解决了前端开发阶段构建速度慢的问题

22.如何使用CSS预处理器(sass,less)来增强CSS的编写能力?

Snip20241115_2.png

23.PostCSS?

Snip20241115_3.png