# Webpack

# 1.什么是webpack?

  • webpack(基于node,只能处理js和json文件)是一个打包工具,在webpack里一切文件皆是模块
  • 将项目中的文件打包成合适的格式给浏览器使用
  • 主要四个模块entry output loader plugins

# 为什么要用webpack对前端进行打包?

  1. 打包后体积更小页面加载更快
  2. 编译高级语法(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是都单独打包,打包到各自的文件上
    }
}
1
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启动整个项目=》不通过索引,而是具体文件位置?

  1. __webpack_modules__是一个维护所有模块的数组。将入口模块解析成AST,根据AST深度优先搜索所有的模块,并构建出这个模块数组。 每个模块都由一个包裹函数(module,module.export,webpack_require)对模块进行包裹构成
  2. __webpack_require__手动实现加载一个模块。对已加载过的模块进行缓存,对未加载过的模块,通过id定位到__webpack_modules__中的包裹函数, id就是数组的key(索引),执行并返回module.export,最后缓存
  3. webpack_require(0)运行入口模块=》也就是配置的main.js (rollup不会将所有模块放在modules中进行维护,它是将所有模块进行展开,如果两个模块中的变量名发生冲突,会直接重新命名)
name.js const name = '111'
friend.js const name = '111'
=> const name$1 = '111' const name = '111'
1
2
3

# 7.webpack的工作流程?

  1. 合并命令行和webpack配置文件里配置的参数,得到最终的配置对象=>webpack CLI作用(通过yargs模块解析命令行参数)
  2. 初始化compiler对象,加载所有配置的插件
  3. 执行compiler对象的run方法开始编译
  4. 根据配置的entry找到入口文件,解析模块依赖形成依赖树。递归依赖树,将每个模块交给对应的loader处理
  5. 根据entry和output配置生成(代码块)chunk
  6. 根据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()
        }
    }
1
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

  1. 把代码解析成AST
  2. 把TS的AST转换JS的AST
  3. 再把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的执行耗时

  1. webpack耗时最久是负责AST转换的loader,使用js性能低下,可以换成rust编写的swc-loader
  2. webpack5内置缓存插件,可通过cache: {type: filesyetem}开启,将Module,chunk,moduleChunk等信息序列化到磁盘中,二次构建避免重新编译 比如一个js文件配置了eslint,ts,babel等loader,有可能执行多次编译,开启持久化缓存,再次编译无需解析AST webpack4中也可以用cache-loader对loader进行缓存,但是目前已经是@deprecated(不久的将来会取消)的状态
  3. 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,
            }),
        ],
    },
}
1
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
          }
        }
      })
    ]
  }
1
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(可配置)到了把所有检测到修改的文件一起打包