- Webpack
- 1.什么是webpack?
- 2.webpack有哪些配置?
- 3.code splitting 代码切割=>splitChunks?
- 4.如何做打包体积分析?
- 5.什么是模块?
- 6.webpack运行时(Runtime)做了什么?
- 7.webpack的工作流程?
- 8.webpack和gulp区别?
- 9.webpack有哪些常见的loader和plugin?
- loader和plugin区别
- 10.loader开发?(分同步和异步loader)
- 11.plugin开发?
- 12.source map有哪些配置?
- 13.treeShaking了解过吗,原理是什么(treeShaking前提是esm)
- 14.hash,contentHash,chunkHash区别?(都是hash策略) hash=>chunkHash=>contentHash
- 15.AST及应用?
- 16.webpack中什么是HMR?
- 17.如何提升webpack构建资源的速度?
- 18.webpack/rollup/vite如何加载json,image等非js资源?
- 19.如何将js资源注入到html中?
- 20.为什么可以在webpack配置文件使用path模块?
- 21.mode有几种模式?
- 22.vue-loader实现原理是什么?
- 23.webpack4为什么选择了terser?
- 24.minify原理?
- 25.模块和包有什么区别
- *26.配置时thread-loader在前还是cache-loader?
- *27.babel-loader,cache-loader缓存的区别?
- 28.proxy原理?
- 29.vite为什么比webpack快
- 30.webpack自带优化配置
- 31.模块联邦
- 32.watch监听模式的原理
# Webpack
# 1.什么是webpack?
- webpack(基于node,只能处理js和json文件)是一个打包工具,在webpack里一切文件皆是模块
- 将项目中的文件打包成合适的格式给浏览器使用
- 主要四个模块entry output loader plugins
# 为什么要用webpack对前端进行打包?
- 打包后体积更小页面加载更快
- 编译高级语法(TS,ES6)
- 原生支持tree shaking(去除用不上的代码)
- webpack中tree-shaking是通过uglifySPlugin来处理js,通过purify-css处理css
# webpack3和4区别(webpack4号称零配置开箱即用)
- 4相对于3分包做了优化,移除CommonsChunkPlugin用splitChunksPlugin切割代码
- 分离css原来用extract-text-webpack-plugin,现在用MiniCssExtractPlugin
- 增加mode属性
- 默认的压缩插件从uglifyjs-webpack-plugin变成terser-webpack-plugin
- webpack4:支持零配置直接打包,按约定以src/index.js为入口,dist/main.js为出口
# webpack4和5区别(webpack5node最低要求10.13)=》webpack5用其他的替换dll,不再需要dll
- webpack4加载资源需要不同的loader(file-loader,url-loader等),
- 而webpack5有资源模块(asset module)来替代,比如file-loader可以用asset/resource,url-loader可以用asset/inline来替代
- webpack5支持原生worker
- 缓存在webpack5默认开启,缓存在内存里(cacheDirectory)
# 2.webpack有哪些配置?
- mode:模式
- entry:入口
- output:出口
- optimization:策略,比如分包splitChunks=》node_modules,moduleL配置根据正则匹配对应加载器 plugin:配置插件,resolve配置模块如何解析=》vue配置别名会被精准匹配=》用来配置importVue时候的vue路径,context指定entry的根路径
# 3.code splitting 代码切割=>splitChunks?
默认所有代码会被打包到一起,bundle体积会太大,通过分包进行优化
- import()动态导入=>按需加载
- splitChunks可以将公共的依赖模块提取到已有的chunk中,在optimization中配置
- 通过entry来多入口打包(手动分离)=>存在多个打包结果有相同模块的情况,需要将optimization的splitChunks的chunks设置为'all'(将公共模块提取到单独的bundle)=>重要
optimization: {
splitChunks: {
chunks: 'all' | 'async' | 'initial' =》 默认只将异步导入的模块单独打包 all是所有都单独打包,最后打在一个文件上 initial是都单独打包,打包到各自的文件上
}
}
2
3
4
5
# 魔法注释
目的就是设置动态导入的文件打包出来的名称, 命名相同的会被打包到一起
# code splitting(代码切割) 是如何动态加载chunk的
主要是通过jsonp的方式来动态加载chunk
# optimization
- usedExports: true, 打包结果中只导出外部用到的成员
- minimize: true 压缩打包结果 =>生产默认开启tree-shaking,如果没有开启可以通过配置usedExports和minimize开启=》一个标记无用的代码,一个用来压缩时进行清除
- sideEffects:用来删除副作用代码 =>先去webpack.config.js的optimization设置sideEffect为true来开启功能,再去package.json设置sideEffects为false,标记代码没有副作用 =>如果想标记哪些文件有副作用,在package.json的sideEffects可以定义一个数组,里面放有副作用的文件路径
# 4.如何做打包体积分析?
- 通过webpack-bundle-analyzer分析打包体积
- 原理是根据webpack打包后的stats数据进行分析,在webpack compiler的done hook进行处理
- 在可视化页面中,可以看到三个选项。1.stat每个模块原始体积2.parsed经webpack打包后的体积3.gzip经gzip压缩后的体积
# webpack如何优化项目体积
- uglify或terser进行js压缩
- 使用externals配置来提取常用库(这样不会被打到bundle中,使用时通过cdn来引入)
- gzip压缩,在nginx开启
- 替换大体积的第三方库=>moment->dayjs
- 使用支持tree-shaking的库,按需引入模块
- code splitting=>splitChunksPlugin 分包(需要注意避免把一个库多次引用多次打包) => 分包不能把总体积变小,但能提高加载性能
- 路由懒加载
# 5.什么是模块?
将一个复杂的项目按一定的规范封装成几个模块,只是向外部暴露一些方法来进行通信
# module,chunk,bundle区别
webpack中一切都是(module) 多个模块合成一个chunk(代码块) bundle是最终输出文件
# 6.webpack运行时(Runtime)做了什么?
- webpack打包后的bundle实际就是个立即执行函数(IIFE)
# webpack4
立即执行函数主要这几块,__webpack__modules存放编译后的模块内容,__webpack__module_cache用来做模块缓存(缓存被引用过的模块), __webpack__require__是模块引用函数,然后还有几个工具函数,最后通过__webpack_require,传递索引0,即入口文件来启动整个项目
# webpack5
在立即执行函数里主要是这几块,__webpack__modules用来存放编译后的模块的js内容,__webpack__module_cache__用来做模块缓存, __webpack_require__是模块引用函数,来实现模块的导入导出,最后通过__webpack_require引入入口文件main.js启动整个项目=》不通过索引,而是具体文件位置?
- __webpack_modules__是一个维护所有模块的数组。将入口模块解析成AST,根据AST深度优先搜索所有的模块,并构建出这个模块数组。 每个模块都由一个包裹函数(module,module.export,webpack_require)对模块进行包裹构成
- __webpack_require__手动实现加载一个模块。对已加载过的模块进行缓存,对未加载过的模块,通过id定位到__webpack_modules__中的包裹函数, id就是数组的key(索引),执行并返回module.export,最后缓存
- webpack_require(0)运行入口模块=》也就是配置的main.js (rollup不会将所有模块放在modules中进行维护,它是将所有模块进行展开,如果两个模块中的变量名发生冲突,会直接重新命名)
name.js const name = '111'
friend.js const name = '111'
=> const name$1 = '111' const name = '111'
2
3
# 7.webpack的工作流程?
- 合并命令行和webpack配置文件里配置的参数,得到最终的配置对象=>webpack CLI作用(通过yargs模块解析命令行参数)
- 初始化compiler对象,加载所有配置的插件
- 执行compiler对象的run方法开始编译
- 根据配置的entry找到入口文件,解析模块依赖形成依赖树。递归依赖树,将每个模块交给对应的loader处理
- 根据entry和output配置生成(代码块)chunk
- 根据output输出chunk到对应文件目录
# 8.webpack和gulp区别?
- 侧重点不同,webpack侧重模块打包,gulp侧重流程管理。
- gulp通过配置task任务,来定义任务的执行顺序,按顺序对项目进行构建
- webpack把图片,js文件,css文件等看成是模块,通过加载器(loader)和插件(plugins)对资源进行处理
# 9.webpack有哪些常见的loader和plugin?
babel-loader => es6+转es5
css-loader => 加载css并压缩(支持模块化) =》打包css文件需要同时使用css-loader和style-loader=>style-loader用来注入css
style-loader => 通过script方式把css注入js中
image-loader:加载并且压缩图片文件
vue-loader => 加载 Vue.js 单文件组件
*sass-loader =>把sass转成css =》核心原理是通过ast =>同一个模块同时use多个loader,执行顺序从后往前=》常见打包css,用到了style-loader和css-loader,先css-loader后style-loader
uglifyjs-webpack-plugin 通过uglify压缩js代码
mini-css-extract-plugin 分离css文件,支持按需加载
html-webpack-plugin 会把webpack打包后的静态资源注入到html中
webpack-bundle-analyzer 分析文件体积
copy-webpack-plugin 指定需要拷贝的目录会拷贝到dist下
clean-webpack-plugin 清除dist目录 =》webpack为每个工作环节都预留了钩子,插件只要找合适的时机去做指定的事情即可
# loader和plugin区别
- webpack本身只能解析js文件,想打包其他文件就需要引入对应loader进行处理
- plugin是用来扩展webpack功能。
# loader作用
loader是用来处理其他类型的文件比如将less转成css文件,es6转es5,ts转js等
# loader执行顺序(pre>normal>inline>post)
从右到左,从下到上
# loader为什么右边的先执行
函数组合有两种方式,一种从左往右(类似pipe流),一种从右往左(compose),webpack采用的是第二种
# plugin是在什么阶段执行
可以在webpack整个编译的生命周期内执行
# 10.loader开发?(分同步和异步loader)
定义一个函数用来对资源进行处理,并导出(函数不能用箭头函数,因为过程中会改变this指向,用箭头函数会报错) (输入是加载到的资源,输出是处理后的结果)
# 加载本地loader方法
- npm link
- resolveloader
# 11.plugin开发?
- plugin是通过钩子机制实现的。在不同事件节点上挂载不同的任务来扩展插件=>插件是通过在生命周期的钩子中挂载函数实现扩展
- 插件是一个函数或者类,必须包含apply方法(用来在webpack中注册)
- 通过监听webpack在编译过程中的事件emit,done,compiler等来执行相应的处理。 (通过compilation.assets就可以拿到本次打包的资源,然后再做相应处理,最后要调用回调函数callback())
// plugin是个类
apply(compiler) {
compiler.hooks.emit.tapAsync('LicenseWebpackPlugin', (compilation, cb) => {
// 执行相应处理
cb()
}
}
// plugin是个函数
XXPlugin.prototype.apply = function (compiler) {
compiler.plugin('emit', function (compilation, callback) {
// 执行相应处理
cb()
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 生命周期
编译阶段(compilation):
- before-run:开始编译之前触发=》异步钩子CleanWebpackPlugin
- compile:编译器开始编译时触发 构建阶段(build):
- compilation:编译器创建新的编译实例时触发(DefinePlugin,HotModuleReplacementPlugin) 优化阶段(optimization):
- optimize:在执行优化和插件应用之前触发(optimizeCssAssetsPlugin) 产出阶段(output):
- emit:在生成资源到output目录之后触发(miniCssExtractPlugin,HtmlWebpackPlugin) 完成阶段(completion):
- done:编译完成后触发,包括成功和失败的情况(webpack-bundle-analyzer)
- watch-run:监视模式下,每次重新编译之前触发
# 12.source map有哪些配置?
sourceMap作用:映射文件,用来将打包后的代码映射到源码文件中,便于定位错误
- eval 使用eval执行模块代码,构建速度快(只能知道文件路径,不知道具体行信息)
- sourceMap 有行信息,产生.map文件
- cheap 只映射到源代码的某一行
- module 得到loader处理前的代码,和源代码一模一样
- inline:不单独生成.map文件(较少见此配置) 使用cheap-module-eval-source-map模式,打包出来的就和源代码一模一样=》开发时推荐使用,启动打包慢,监视的情况重新打包快 较优配置生产环境使用none,推荐使用nosources-source-map不会暴露所有源代码,只会展示出现问题的代码位置 =》线上也可以用sourcemap 通过nginx设置.map文件只对白名单开放
# 常规使用配置
- 开发环境:cheap-module-eval-source-map(不会产生文件,集成在打包后的文件中,不会产生列)
- 生产环境:cheap-module-source-map(是一个单独的映射文件,便于调试)
# 13.treeShaking了解过吗,原理是什么(treeShaking前提是esm)
摇树,基于esm进行静态分析,通过AST将用不到的代码进行移除。(还可以通过引入支持treeShaking的包来减少打包体积。比如lodash-es替换lodash)
# 14.hash,contentHash,chunkHash区别?(都是hash策略) hash=>chunkHash=>contentHash
=》一般服务器都会对前端资源设置静态资源缓存,需要通过合理的hash策略来使性能达到最佳
- 只要项目有文件改变就会改变hash(通过文件名+文件哈希值的方式判断资源是否有更新)
- chunkHash文件改变,同个chunk下的文件名都会改变
- contentHash文件改变只有这个文件的打包文件改变
# 作用
依赖的公共模块一般很少更改(没有更改即公共模块hash不变,不同模块用不同的hash策略),可以利于长期缓存=》比如css用contentHash来判断
# 15.AST及应用?
AST是抽象语法树的简称 涉及ts转js sass/less转css es6转es5 生成AST涉及词法分析(应用=>代码检查,语法高亮)和语法分析两个阶段
# AST核心步骤 不同的语言拥有不同的解析器 js用babel
- 把代码解析成AST
- 把TS的AST转换JS的AST
- 再把AST转成代码
# 16.webpack中什么是HMR?
HMR(Hot Module Replacement)热模块替换.无需刷新即可替换掉旧模块(将修改的模块实时替换到页面上) 开启方式:
- 通过webpack-dev-server --hot可以开启 =>devServer替代方式是开启--watch模式+browserSync(当dist发生变更就会刷新浏览器)
- devServer的hot设置为true,引入插件HotModuleReplacementPlugin
# webpack热更新原理
(webpack通过watch监听到文件变化,变化就重新打包)
- 客户端从服务端拉取最新的代码进行chunk diff(就是将本次打包的chunk与上一次进行对比)
- WDS(Webpack-dev-server)与浏览器之间维护了一个websocket,资源发生变化WDS会向客户端推送更新,会带上本次打包生成的hash。
- 拿到hash后客户端发起ajax请求来获取更改内容(文件列表和hash)。通过jsonp获取该chunk增量更新
# 17.如何提升webpack构建资源的速度?
使用speed-measure-webpack-plugin可评估每个loader和plugin的执行耗时
- webpack耗时最久是负责AST转换的loader,使用js性能低下,可以换成rust编写的swc-loader
- webpack5内置缓存插件,可通过cache: {type: filesyetem}开启,将Module,chunk,moduleChunk等信息序列化到磁盘中,二次构建避免重新编译 比如一个js文件配置了eslint,ts,babel等loader,有可能执行多次编译,开启持久化缓存,再次编译无需解析AST webpack4中也可以用cache-loader对loader进行缓存,但是目前已经是@deprecated(不久的将来会取消)的状态
- thread-loader官方推荐开启多进程的loader,可以对babel解析AST时开启多进程处理,提升编译性能 =>webpack4用happypack,webpack5采用thread-loader
# 18.webpack/rollup/vite如何加载json,image等非js资源?
- 通过loader转成模块的形式
- 比如处理css通过css-loader和style-loader。css-loader通过postcss来解析处理css中的url和@import,转成模块引入
- 再用style-loader将样式注入到dom中,原理是使用DOM提供的API去构建style标签,把css内容注入到style中
- 由于性能问题(css会在js资源加载完后加载,容易出现页面抖动,且性能低),在生产环境我们通常需要单独加载css资源,
- 所以要借助mini-css-exact-plugin将css单独抽离出来。 (rollup只允许通过插件的方式进行扩展)
# 19.如何将js资源注入到html中?
原理是:当webpack生成js资源后,获取其文件名及publicPath,最后将其注入到html中。 (可以通过html-webpack-plugin)
# 20.为什么可以在webpack配置文件使用path模块?
因为webpack的配置文件是运行在nodejs环境中,所以可以在这个文件中使用所有nodejs的内置模块
# 21.mode有几种模式?
production,development,none=>默认production development:提供优化特性,启动更详细的错误日志,保留原始代码 production:优化输出文件,代码压缩,减少文件体积 none:关闭所有默认优化选项,只执行基本打包的功能
# 22.vue-loader实现原理是什么?
- vue-loader会把sfc中的内容拆分为template,script,style三个虚拟模块,分别匹配webpack配置中对于的rules,
- 比如script会匹配所有跟处理js或ts相关的loader
- template中的内容会通过vue complier转换成render函数后合并到script虚拟模块中
- scoped style处理后会变成只匹配特定元素的私有样式
# 23.webpack4为什么选择了terser?
因为uglify-js不支持ES6+,webpack4默认内置使用terser-webpack-plugin =>uglifyjs主要针对webpack3,webpack4开始都用terser
terser开启多进程能显著加快构建速度,建议开启
module.exports = {
optimization: {
minimizer: [
new TerserPlugin({
parallel: true,
}),
],
},
}
2
3
4
5
6
7
8
9
10
# 如何自动去除console.log=>可以通过terser插件的drop_console?
optimization: {
minimize: true,
minimizer: [
new TerserPlugin({
terserOptions: {
compress: {
drop_console: false, // 默认false,设置为true, 则会删除所有console.* 相关的代码。
pure_funcs: ["console.log"], // 单纯禁用console.log
}
}
})
]
}
2
3
4
5
6
7
8
9
10
11
12
13
# 24.minify原理?
先AST分析,再根据配置的策略来生成更小体积的AST,最后生成代码
# js文件压缩做了什么 =》terser/uglifyjs做了哪些东西
- 去除空格,换行符,注释
- 把较长的变量名替换为短名称
- 编译预计算 let minute = 60 * 1000; let minute = 60000;
- 合并声明及布尔值简化
# 为什么要压缩js文件?
减少js代码体积,提高执行速度。并且压缩后不具备可读性,防止被人轻易使用
# 25.模块和包有什么区别
模块可以是任何一个文件或目录 包必须有一个package.json才行
# *26.配置时thread-loader在前还是cache-loader?
应该是thread-loader,这样cache-loader也能使用到多进程 (thread-loader只在耗时的loader中使用,这些loader会被放在独立的worker池中运行 (在worker池中的loader是有限制的,所以不推荐所有的loader都用thread-loader))
# *27.babel-loader,cache-loader缓存的区别?
babel-loader根据内容缓存,cache-loader根据mtime(修改时间)来缓存
# 28.proxy原理?
webpack-dev-server本地启动一个服务器,然后与目标服务器之前进行请求不存在跨域
# 29.vite为什么比webpack快
vite采用了esModule的方式来加载模块,实现了按需加载(webpack通过require来加载) 使用了 esbuild 去解析 JavaScript 文件,esbuild是基于Go的,执行速度比js快
# 30.webpack自带优化配置
usedExports=》打包结果中只导出外部用到的成员 minimize=》摇树
# 31.模块联邦
指多个项目之间共享代码的机制
# 32.watch监听模式的原理
每隔一段时间(通过poll设置)轮询判断文件的最后编辑时间是否变化,变化就缓存起来等aggregateTimeout(可配置)到了把所有检测到修改的文件一起打包