原文见:语雀(https://www.yuque.com/deepstates/interview/dcl9yb)
● 脚手架与项目脚本
● 构建工具
○ webpack
○ grunt、gulp
脚手架
架构
如何设计突发大规模并发架构?
框架
知道各种JS框架(Angular, Backbone, Ember, React, Meteor, Knockout…)么? 能讲出他们各自的优点和缺点么?
构建工具
webpack
⭐️ 相关知识点参考:
如何设计代码依赖
webpack是解决什么问题而生的?
如果像以前开发时一个html文件可能会引用十几个js文件,而且顺序还不能乱,因为它们存在依赖关系,同时对于ES6+等新的语法,less, sass等CSS预处理都不能很好的解决……,此时就需要一个处理这些问题的工具。
前端为什么要进行打包和构建?
● 代码层面:体积更小(Tree-shaking、压缩、合并),加载更快编译高级语言和语法(TS、ES6、模块化、scss)、兼容性和错误检查(polyfill,postcss,eslint)
● 研发流程层面:统一、高效的开发环境,统一的构建流程,产出标准集成公司构建规范(提测、上线)
术语
什么是module,什么是chunk,什么是bundle?
● module:是开发中的单个模块
● chunk:是指webpack在进行模块依赖分析的时候,代码分割出来的代码块
● bundle:是由webpack打包出来的文件
原理
webpack打包原理? / webpack工作原理?(字节)/ webpack原理(数字马力)
一、webpack可以看做是模块打包机:
● 根据文件间的依赖关系对其进行静态分析,
● 然后将这些模块按指定规则生成静态资源,
● 当 webpack 处理程序时,它会递归地构建一个依赖关系图(dependency graph),其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个 bundle。
webpack打包过程?/webpack 的构建流程是什么?
● 初始化参数:解析webpack配置参数,合并shell传入和webpack.config.js文件配置的参数,形成最后的配置结果。
● 开始编译:上一步得到的参数初始化compiler对象,注册所有配置的插件,插件监听webpack构建生命周期的事件节点,做出相应的反应,执行对象的run方法开始执行编译,生成compilation对象
● 确定入口:从配置的entry入口,开始解析文件构建AST语法树,找出依赖,递归下去。
● 编译模块:递归中根据文件类型和loader配置,调用所有配置的loader对文本进行转换,再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理。
● 完成模块编译并输出:递归完事后,得到每个文件结果,包含每个模块以及他们之间的依赖关系,根据entry或分配置生成代码块chunk
● 输出完成:输出所有的chunk到文件系统。
webpack解析import()的原理/webpack懒加载原理/webpack异步加载原理(字节)
jsonp + promise
一、简单来说
● 首先,webpack遇到import方法时,会将其当成一个代码分割点,也就是说碰到import方法了,那么就去解析import方法。
● 然后,import引用的文件,webpack会将其编译成一个jsonp,也就是一个自执行函数,然后函数内部是引用的文件的内容,因为到时候是通过jsonp的方法去加载的。
二、具体就是,
● import引用文件,会先调用require.ensure方法(打包的结果来看叫require.e),这个方法主要是构造一个promise,会将resolve,reject和promise放到一个数组中,将promise放到一个队列中。
● 然后,调用require.load(打包结果来看叫require.l)方法,这个方法主要是创建一个jsonp,也就是创建一个script标签,标签的url就是文件加载地址,然后塞到document.head中,一塞进去,就会加载该文件了。
● 加载完,就去执行这段jsonp,主要就是把moduleId和module内容存到modules数组中,然后再去走webpack内置的require。
● webpack内置的require,主要是先判断缓存,这个moduleId是否缓存过了,如果缓存过了,就直接返回。如果没有缓存,再继续往下走,也就是加载module内容,然后最终内容会挂在都module,exports上,返回module.exports就返回了引用文件的最终执行结果。
webpack配置
webpack的核心概念?
● Entry:入口,Webpack 执行构建的第一步将从 Entry 开始,可抽象成输入。告诉webpack要使用哪个模块作为构建项目的起点,默认为./src/index.js
● output :出口,告诉webpack在哪里输出它打包好的代码以及如何命名,默认为./dist
● Module:模块,在 Webpack 里一切皆模块,一个模块对应着一个文件。Webpack 会从配置的 Entry 开始递归找出所有依赖的模块。
● Chunk:代码块,一个 Chunk 由多个模块组合而成,用于代码合并与分割。
● Loader:模块转换器,用于把模块原内容按照需求转换成新内容。
● Plugin:扩展插件,在 Webpack 构建流程中的特定时机会广播出对应的事件,插件可以监听这些事件的发生,在特定时机做对应的事情。
output
hash
webpack 打包是hash码是如何生成的?
1.webpack生态中存在多种计算hash的方式
● hash
○ hash代表每次webpack编译中生成的hash值,所有使用这种方式的文件hash都相同。每次构建都会使webpack计算新的hash。
● chunkhash
○ chunkhash基于入口文件及其关联的chunk形成,某个文件的改动只会影响与它有关联的chunk的hash值,不会影响其他文件
● contenthash
○ contenthash根据文件内容创建。当文件内容发生变化时,contenthash发生变化
2.避免相同随机值
● webpack在计算hash后分割chunk。产生相同随机值可能是因为这些文件属于同一个chunk,可以将某个文件提到独立的chunk(如放入entry)
module 模块
loader
webpack有哪些常⻅的Loader
● css
○ css-loader:加载 CSS,⽀持模块化、压缩、⽂件导⼊等特性
○ style-loader:把 CSS 代码注⼊到 JavaScript 中,通过 DOM 操作去加载 CSS。
● 图片、字体等
○ v5以前
■ file-loader:把⽂件输出到⼀个⽂件夹中,在代码中通过相对 URL 去引⽤输出的⽂件
■ url-loader:和 file-loader 类似,但是能在⽂件很⼩的情况下以 base64 的⽅式把⽂件内容注⼊到代码中去
■ image-loader:加载并且压缩图⽚⽂件
● js
○ babel-loader:把 ES6 转换成 ES5
● source-map-loader:加载额外的 Source Map ⽂件,以⽅便断点调试
● eslint-loader:通过 ESLint 检查 JavaScript 代码
loader输入什么产出什么?(字节)
本质上,webpack loader 将所有类型的文件,转换为应用程序的依赖图和最终的 bundle可以直接引用的模块。
Loader机制的作用是什么?
webpack默认只能打包js文件,配置里的module.rules数组配置了一组规则,告诉 Webpack 在遇到哪些文件时使用哪些 Loader 去加载和转换打包成js。
如何解析一个html文本?(字节)
还是考ast
默认css处理
一、webpack是不能直接识别css资源的,一定要通过loader资源来帮助webpack解析样式资源
二、相关loader:
● style-loader
● css-loader
● postcss-loader
● less-loader
babel
babel是什么?有什么作用
● Babel是一个JS编译器,自带一组ES6/ES7语法转化器,用于转换JS代码
○ 这些转化器让开发者提前使用最新的JS语法(ES6/ES7),而不用等浏览器全部兼容
● Babel默认只转换新的JS句法(syntax),而不转换新的API
babel原理(字节)
一、babel工作原理:三个主要处理步骤分别是: 解析(parse),转换(transform),生成(generate)。
1、解析:babel使用@babel/parse将原始代码转换为抽象语法树
● 词法解析:字符串形式的代码转换为 令牌(tokens) 流
○ tokens是一个扁平的语法片段数组
● 语法分析:把一个令牌流转换成 AST 的形式
2、转换:第二步是babel通过@babel/traverse对抽象语法树进行遍历修改并获得新的抽象语法树
3、生成:使用 @babel/generator将抽象语法树转换为代码
babel插件如何实现?/怎么写babel插件?(字节)
一、编写自己的插件需要默认为插件提供一个方法,该方法返回一个包含visitor属性的对象。visitor也是一个对象,该对象属性支持不同节点类型对应的钩子函数,在这个函数内针对该类型的节点进行操作。
1、先从一个接收了当前babel对象作为参数的 function 开始。
export default function(babel) {
// plugin contents
}
2、接着返回一个对象,其 visitor 属性是这个插件的主要访问者。
(1) Visitor 中的每个函数接收2个参数:path 和 state
export default function({ types: t }) {
return {
visitor: {
Identifier(path, state) {},
ASTNodeTypeHere(path, state) {}
}
};
};
devtool/ sourcemap
sourceMap
一、是一个映射关系,将打包后的文件映射到源代码,用于定位报错位置
二、配置方式:
例如:devtool:'source-map’加不同前缀意义:
● inline: 不生成映射关系文件,打包进main.js
● cheap: 1.只精确到行,不精确到列,打包速度快 2.只管业务代码,不管第三方模块
● module:不仅管业务代码,而且管第三方代码
● eval: 执行效率最快,性能最好
三、最佳实践:
● 开发环境:cheap-module-eval-source-map
● 线上环境:cheap-mudole-source-map
plugins 插件
Plugin(插件)的作用是什么?
一、Plugin 是用来扩展 Webpack 功能的,通过在构建流程里注入钩子实现,它给 Webpack 带来了很大的灵活性。
二、Webpack 是通过plugins属性来配置需要使用的插件列表的。plugins属性是一个数组,里面的每一项都是插件的一个实例,在实例化一个组件时可以通过构造函数传入这个组件支持的配置属性。
webpack 插件如何实现?
一、webpack本质是一个事件流机制,核心模块:tapable(Sync + Async)Hooks 构造出 === Compiler(编译) + Compilation(创建bundles)
● compiler对象代表了完整的webpack环境配置。这个对象在启动webpack时被一次性建立,并配置好所有可操作的设置,包括options、loader和plugin。当在webpack环境中应用一插件时,插件将收到此compiler对象的引用。可以使用它来访问webpack的主环境
● compilation对象代表了一次资源版本构建。当运行webpack开发环境中间件时,每当检测到一个文件变化,就会创建一个新的compilation, 从而生成一个新的编译资源。一个compilation对象表现了当前的模块资源、编译生成资源、变化的文件、以及被跟踪依赖的状态的信息。compilation对象也提供了很多关键时机的回调,以供插件做自定义处理时选择使用
二、实现:
● 创建一个插件函数,在其prototype上定义apply方法,指定一个webpack自身的事件钩子
● 函数内部处理webpack 内部实例的特定数据
● 处理完成后,调用webpack提供的回调函数
function MyWebpackPlugin(){
};
// prototype 上定义 apply 方法
MyWebpackPlugin.prototype.apply=function(){
// 指定一个事件函数挂载到webpack
compiler.pluginCwebpacksEventHook"funcion (compiler)( console. log(“这是一个插件”);
// 功能完成调用后webpack提供的回调函数
callback()
})
webpack插件的基本组成
● 一个具名 JavaScript 函数;
● 在它的原型上定义 apply 方法;
● 指定一个触及到 Webpack 本身的事件钩子;
● 操作 Webpack 内部的实例特定数据;
● 在实现功能后调用 Webpack 提供的 callback。
// 一个基本插件
class HelloWorldPlugin {
apply(compiler) {
compiler.hooks.done.tap(‘Hello World Plugin’, (
stats /* 在 hook 被触及时,会将 stats 作为参数传入。*/
) => {
console.log(‘Hello World!’);
});
}
}
module.exports = HelloWorldPlugin;
loader、plugins的区别?
一、从功能作用的角度区分:
1、 loader从字面的意思理解,是「加载」的意思。只专注于转化文件(transform)这一个领域,完成压缩,打包,语言翻译, 仅仅只是为了打包。
2、plugin 不仅只局限在打包,资源的加载上,它的功能要更加丰富。从打包优化和压缩,到重新定义环境变量,功能强大到可以用来处理各种各样的任务。
二、从运行时机的角度区分
1 . loader 运行在打包文件之前(loader为在模块加载时的预处理文件)
2. plugins 在整个编译周期都起作用。
常见的插件
webpack 常见的plugin有哪些?
● ProvidePlugin:自动加载模块,代替require和import
● 资源处理
○ html
■ html-webpack-plugin可以根据模板自动生成html代码,并自动引用css和js文件
○ css
■ extract-text-webpack-plugin 将js文件中引用的样式单独抽离成css文件
■ optimize-css-assets-webpack-plugin 不同组件中重复的css可以快速去重
● 开发环境
○ HotModuleReplacementPlugin 热更新
● 编译
○ DefinePlugin 编译时配置全局变量,这对开发模式和发布模式的构建允许不同的行为非常有用。
○ clean-webpack-plugin 清理每次打包下没有使用的文件
● 性能优化
○ webpack-bundle-analyzer:可视化Webpack输出文件的体积(业务组件、依赖第三方模块)
○ speed-measure-webpack-plugin:可以查看每个Loader和Plugin执行耗时(整个打包耗时、每个Plugin和 Loader 耗时)
○ compression-webpack-plugin 生产环境可采用gzip压缩JS和CSS
○ happypack:通过多进程模型,来加速代码构建
ExtractTextPlugin插件的作用
ExtractTextPlugin插件的作用是提取出 JavaScript 代码里的 CSS 到一个单独的文件。
对此你可以通过插件的filename属性,告诉插件输出的 CSS 文件名称是通过[name]_[contenthash:8].css字符串模版生成的,里面的[name]代表文件名称,[contenthash:8]代表根据文件内容算出的8位 hash 值, 还有很多配置选项可以在ExtractTextPlugin的主页上查到。
指南
构建性能
webpack 离线缓存静态资源如何实现?
● 在配置webpack时,我们可以使用html-webpack-plugin来注入到和html一段脚本来实现将第三方或者共用资源进行 静态化存储。在html中注入一段标识,例如 <% HtmlWebpackPlugin.options.loading.html %> ,在 html-webpack-plugin 中即可通过配置html属性,将script注入进去
● 利用 webpack-manifest-plugin 并通过配置 webpack-manifest-plugin ,生成 manifest.json 文件,用来对比js资源的差异,做到是否替换,当然,也要写缓存script
● 在我们做Cl以及CD的时候,也可以通过编辑文件流来实现静态化脚本的注入,来降低服务器的压力,提高性能
● 可以通过自定义plugin或者html-webpack-plugin等周期函数,动态注入前端静态化存储script
基本的脚手架功能:webpack相关
Webpack的基本功能有哪些?
● 管理资源
● 管理输出
○ 模块合并:在采用模块化的项目有很多模块和文件,需要构建功能把模块分类合并成一个文件
○ 代码校验:在代码被提交到仓库前需要检测代码是否符合规范,以及单元测试是否通过
○ 自动发布:更新完代码后,自动构建出线上发布代码并传输给发布系统。
● 开发环境
○ 自动刷新:监听本地源代码的变化,自动构建,刷新浏览器
● 环境配置
● 性能优化
○ 代码转换:TypeScript 编译成 JavaScript、SCSS 编译成 CSS 等等文件优化:压缩 JavaScript、CSS、html 代码,压缩合并图片等
○ 代码分割:提取多个页面的公共代码、提取首屏不需要执行部分的代码让其异步加载
管理输出
html自动构建
怎么配置单页应用?怎么配置多页应用?
一、单页应用可以理解为webpack的标准模式,直接在entry中指定单页应用的入口即可,这里不再赘述
二、多页应用的话,可以使用webpack的 AutoWebPlugin来完成简单自动化的构建,但是前提是项目的目录结构必须遵守他预设的规范。
1、多页应用中要注意的是:每个页面都有公共的代码,可以将这些代码抽离出来,避免重复的加载。比如,每个页面都引用了同一套css样式表随着业务的不断扩展,页面可能会不断的追加,所以一定要让入口的配置足够灵活,避免每次添加新页面还需要修改构建配置
开发环境
devServer
webpack-dev-server 和 http 服务器的区别?
webpack-dev-server使用内存来存储webpack开发环境下的打包文件,并且可以使用模块热更新,比传统的http服务对开发更加有效。
热更新
什么是模热更新?有什么优点?
一、模块热更新是webpack的一个功能,它可以使得代码修改之后,不用刷新浏览器就可以更新。
在应用过程中替换添加删除模块,无需重新加载整个页面,是高级版的自动刷新浏览器。
二、优点:
1、只更新变更内容,以节省宝贵的开发时间。调整样式更加快速,几乎相当于在浏览器中更改样式
webpack 的热更新原理?(字节)
一、其实是自己开启了express应用,添加了对webpack编译的监听,添加了和浏览器的websocket长连接,当文件变化触发webpack进行编译并完成后,会通过sokcet消息告诉浏览器准备刷新。而为了减少刷新的代价,就是不用刷新网页,而是刷新某个模块,webpack-dev-server可以支持热更新,通过生成文件的hash值来比对需要更新的模块,浏览器再进行热替换
二、服务端
● 启动 webpack-dev-server服务器
● 创建webpack实例
● 创建server服务器
● 添加webpack的done事件回调
● 编译完成向客户端发送消息
● 创建express应用app
● 设置文件系统为内存文件系统
● 添加 webpack-dev-middleware 中间件
● 中间件负责返回生成的文件
● 启动webpack编译
● 创建http服务器并启动服务
● 使用sockjs在浏览器端和服务端之间建立一个websocket长连接
● 创建socket服务器
二、客户端
● webpack-dev-server/client端会监听到此hash消息
● 客户端收到ok消息后会执行reloadApp方法进行更新
● 在reloadApp中会进行判断,是否支持热更新,如果支持的话发生 webpackHotUpdate事件,如果不支持就直接刷新浏览器
● 在 webpack/hot/dev-server.js 会监听 webpackHotUpdate 事件
● 在check方法里会调用module.hot.check方法
● HotModuleReplacement.runtime请求Manifest
● 通过调用 JsonpMainTemplate.runtime 的 hotDownloadManifest方法
● 调用JsonpMainTemplate.runtime的hotDownloadUpdateChunk方法通过JSONP请求获取最新的模块代码
● 补丁js取回来或会调用 JsonpMainTemplate.runtime.js 的 webpackHotUpdate 方法
● 然后会调用 HotModuleReplacement.runtime.js 的 hotAddUpdateChunk方法动态更新 模块代码
● 然后调用hotApply方法进行热更
webpack打包优化
如何⽤webpack来优化前端性能? / webpack优化方式?(阿里)/做过哪些配置优化?(数字马力)
构建速度
如何提高webpack构建速度的?(字节)
● 减少查找过程
○ resolve.extensions
○ resolve.modules
○ resolve.alas,减少查找过程
● 缩小构建目标:include、exclude
● 多线程
○ 多线程代码压缩
○ 多线程打包:thread-loader(官方推出)
● 缓存构建
○ DLL动态链接库
○ 持久化缓存
多线程打包的原理?线程越多打包越快么?(数字马力)
一、原理
每次 webapck 解析一个模块,HappyPack或thread-loader等多线程打包插件会将它及它的依赖分配给 worker 线程中。处理完成之后,再将处理好的资源返回给 HappyPack或thread-loader 的主线程,从而加快打包速度
二、线程越多打包越快吗?
不是。因为线程有创建和上下文切换的开销。
webpack如何实现持久化缓存?
一、webpack v5
● 在 webpack 配置中使用 cache 选项。使用 package.json 中的 “postinstall” 清除缓存目录。
○ cache.type 设置成filesystem
二、webpack v4
● loader自带的缓存功能
○ 设置 cacheDirectory = true , 开启 babel-loader 持久化缓存功能
○ 设置 cache = true,开启eslint-loader 缓存
● 设置cache-loader, 将 cache-loader 配置在 loader 数组首位。
打包体积
webpack treeShaking机制的原理?
一、treeShaking 也叫摇树优化,是一种通过移除多余代码,来优化打包体积的,生产环境默认开启。可以在代码不运行的状态下,分析出不需要的代码;
二、利用es6模块的规范
● ES6 Module引入进行静态分析,故而编译的时候正确判断到底加载了哪些模块
● 静态分析程序流,判断哪些模块和变量未被使用或者引用,进而删除对应代码
运行速度
lazy loading(模块懒加载)
借助import()语法异步引入组件,实现文件懒加载:prefetch, preloading。 webpack提倡多写异步代码,提升代码利用率,从而提升页面性能先加载主业务文件,prefetch利用网络空闲时间,异步加载组件。
什么是长缓存?在webpack中如何做到长缓存优化?
一、浏览器在用户访问页面的时候,为了加快加载速度,会对用户访问的静态资源进行存储,但是每一次代码升级或者更新,都需要浏览器去下载新的代码,最方便和最简单的更新方式就是引入新的文件名称。
二、如何做到长缓存优化
● 在webpack中,可以在output给出输出的文件制定contenthash
● 并且分离经常更新的代码和框架代码
○ v5:使用SplitChunksPlugin提取引导模板
○ v4:使用SplitChunksPlugin提取引导模板、通过optimization.moduleIds设置模块标识符
○ v2:通过NameModulesPlugin或者HashedModulesPlugin使再次打包文件名不变。
vite
webpack与vite的不同,vite的速度提升在哪里
● webpack会先打包,然后启动开发服务器,请求服务器时直接给予打包结果。
○ 而vite是直接启动开发服务器,请求哪个模块再对该模块进行实时编译。
● 由于现代浏览器本身就支持ES Module,会自动向依赖的Module发出请求。
○ vite充分利用这一点,将开发环境下的模块文件,就作为浏览器要执行的文件,而不是像webpack那样进行打包合并。
● 由于vite在启动的时候不需要打包,也就意味着不需要分析模块的依赖、不需要编译,因此启动速度非常快。当浏览器请求某个模块时,再根据需要对模块内容进行编译。这种按需动态编译的方式,极大的缩减了编译时间,项目越复杂、模块越多,vite的优势越明显。
● 在HMR(热更新)方面,当改动了一个模块后,仅需让浏览器重新请求该模块即可,不像webpack那样需要把该模块的相关依赖模块全部编译一次,效率更高。
● 当需要打包到生产环境时,vite使用传统的rollup(也可以自己手动安装webpack来)进行打包,因此,vite的主要优势在开发阶段。另外,由于vite利用的是ES Module,因此在代码中(除了vite.config.js里面,这里是node的执行环境)不可以使用CommonJS
rollup
rollup和webpack的区别?
简单的来说rollup是一个Js模块打包器,可以将小块代码编译成大块复杂的代码。现在已经有很多类库都在使用 rollup 进行打包了,比如:react, vue, preact, three.js,moment, d3 等。
优点:小巧而专注
项目(特别是类库)只有 js,而没有其他的静态资源文件,使用 webpack 就有点大才小用了,因为 webpack bundle 文件的体积略大,运行略慢,可读性略低。这时候 rollup就是一种不错的解决方案
结论:对于应用使用 webpack,对于类库使用 Rollup
grunt、gulp
grunt和gulp的区别?
1、易用:Gulp相比Grunt更简洁,而且遵循代码优于配置策略,维护Gulp更像是写代码。
2、高效:Gulp相比Grunt更有设计感,核心设计基于Unix流的概念,通过管道连接,不需要写中间文件。
3、高质量:Gulp的每个插件只完成一个功能,这也是Unix的设计原则之一,各个功能通过流进行整合并完成复杂的任务。例如:Grunt的imagemin插件不仅压缩图片,同时还包括缓存功能。他表示,在Gulp中,缓存是另一个插件,可以被别的插件使用,这样就促进了插件的可重用性。目前官方列出的有673个插件。
4、易学:Gulp的核心API只有5个,掌握了5个API就学会了Gulp,之后便可以通过管道流组合自己想要的任务。
5、流:使用Grunt的I/O过程中会产生一些中间态的临时文件,一些任务生成临时文件,其它任务可能会基于临时文件再做处理并生成最终的构建后文件。而使用Gulp的优势就是利用流的方式进行文件的处理,通过管道将多个任务和操作连接起来,因此只有一次I/O的过程,流程更清晰,更纯粹。
6、代码优于配置:维护Gulp更像是写代码,而且Gulp遵循CommonJS规范,因此跟写Node程序没有差别。