Skip to content

webpack

构建发展史

grunt => gulp => rollup webpack parcel => vite

webpack打包构建流程

为什么需要打包?

  1. 前端开发有很多资源,img、font、css、js、ts、vue,输出产物
  2. 有些资源需要加工
    • ts ts-loader
    • js babel-loader
    • css css-loader + (mini/style-loader)
    • html html-webpack-plugin
  3. 对产物进行优化,不可能所有资源都打包到一个文件,提出了分 chunk css 提取
    • optimization
    • splitChunkPlugin
  4. 兼容性问题,很多浏览器不支持ES6

核心模块

webpack-cli、webpack、webpack-devServer

  • 脚手架只是一个柯里化的调用webpack方法
js
// webpack.config.js
webpack({
    ...config
})

初始化操作

  1. 读取配置(包括配置文件和shell语句,合并以后得到最终的参数)
    • entry 入口
    • output 出口
    • module
    • loader
    • plugins
  2. 用上一步的参数初始化 compiler 对象
    • compiler
    • compilation
  3. 所有的插件挂载,执行对象的run方法开始编译
    • webpack最核心的内容,tapable 实现,插件编写 compliler.run.top

入口文件解析工作

  • 根据配置中的entry找到所有的入口文件
    • 单入口 - 字符串,""
    • 多入口 - 数组,[]
    • 指定多入口 - 对象,{}
  • 解析依赖,生成依赖图,depsGraph

模块处理(module)

  • 识别对应目标文件,然后匹配对应 loader 来进行解析处理,递归本步骤直到所有的文件都处理完成,看下面的正则匹配:
    • js babel-loader (swc、esbilud、rsbuild)
    • ts ts-loader
    • css css-loader
    • chunk
js
module: {
    rules: [
        {
            test: /ts/
        }
    ]
}

优化

  • 模块去重合并
  • 代码压缩,esbuild terser
  • tree shaking

资源输出

  • 根据配置给到 output,输出内容
  • 根据入口和模块的依赖关系,组装为一个个包含多个模块的chunl,每个chunk转化为一个单独的文件加入输出列表,这是最后可以修改输出内容的机会
  • 根据配置确定输出的路径和文件名,写入文件系统,fs操作

结束构建

  • webpack钩子触发

热更新(HRM)

  • 文件变化监听
  • 局部模块更新
    • 开一个ws 服务(webpack-dev-server浏览器)
    • 更新的内容抽象为json,将这个json(文件列表和hash)推送给客户端作对比
    • 对比以后会请求ajax更新更改内容(文件列表和hash)
    • 然后客户端在发送jsonp请求获取chunk的增量更新
    • 后续利用HotModulePlugin完成处理

面试话术

  1. 先说一下webpack5背景,为什么会有他,解决什么问题
  2. 大致思路,整体流程,就以四级标题为模板
  3. 我之前某个场景,自定义了loader、plugin解决了什么问题
  4. webpack中有很多很好的思想
    • 基于 tabpable 的钩子机制
    • 面向切面的编程思想AOP(在不修改源代码的情况下给程序动态统一添加某种特定功能的一种技术)

简单介绍一下工作中常用的loader和plugin,又实现过自定义loader、plugin么

常用loader

  1. babel-loader
  2. css-loader
  3. style-loader
  4. url-loader
  5. sass-loader

常用plugin

  1. HtmlWebpackPlugin
  2. MiniCssExtractPlugin css并行加载,否则css会被打包到js中
  3. DefinePlugin
  4. TerserPlugin

自定义 loader、plugin

loader 本质是函数

myLoader.js

js
module.exports = function (source){
    source = source.replace(/hei/g,'皮辟!hei!')
    return source
}

plugin 本质是类

myPlugin.js

js
const htmlStr = `
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  {{js}}
</head>
<body>
  
</body>
</html>
`
class myPlugin {
    constructor() {
    }

    apply(compiler) {
        console.log("ppPlugin 启动", compiler)

        compiler.hooks.emit.tap('ppPlugin', (compilation) => {
            // assets是一个对象,直接可以拿到key操作
            console.log("ppPlugin emit", compilation.assets)
            const assetsKeys = Object.keys(compilation.assets)

            const newhtml = htmlStr.replace("{{js}}",
                `<script>alert(111)</script>
                ${assetsKeys.filter(key => key.indexOf('.map') === -1).map(key => `<script src="${key}"></script>`)}
                `)
            compilation.assets['pp.html'] = {
                source: () => newhtml,
                size: () => newhtml.length
            }
        })
    }
}
module.exports = myPlugin

如何提高webpack的构建速度

代码压缩

  1. js压缩,webpack4默认在生产环境下支持代码压缩,且使用的是terser-webpack-plugin插件,此前使用的是uglifyjs-webpack-plugin,后者对es6的压缩不是很好,我们可以开启parallel参数使用多进程压缩,提高速度
  2. css压缩,主要是去除无用的空格,一般使用css-minimizer-webpack-plugin
  3. html压缩,使用 HtmlWebpackPlugin 插件生成html模板的时候,通过配置属性minify进行html优化
js
module.exports = {
    plugin:[
        new HtmlwebpackPlugin({
            minify:{
                minifyCSS: false, // css
                collapseWhitespace: false, // 
                removeComments: true // 
            }
        })
    ]
}

图片压缩

image-webpack-loader

tree shaking

  • 方案一:usedExports,通过标记某些函数是否被使用,之后通过Terser来进行优化,使用以后,没有被用上的代码在webpack打包的时候会加入unused harmony export mul注释,用来告知Terser在优化的时候可以删除这段代码。
js
module.exports = {
    //...
    optimization:{
        usedExports 
    }
}
  • 方案二:sideEffects:跳过整个模块/文件,直接查看该文件是否有副作用sideEffects 用于告知 webpack compiler 哪些模块时有副作用,配置方法是在 package.json中设置 sideEffects 属性。如果 sideEffects 设置为 false ,就是告知 webpack 可以安全的删除未用到的 exports 。如果有些文件需要保留,可以设置为数组的形式,如:
js
"sideEffecis":[
    "./src/util/format.js",
    "*.css" // css
]

缩小打包域

  • 排除 webpack 不需要解析的模块,即在使用 loader 的时候,在尽量少的模块中去使用。
  • 可以借助includeexclude 这两个参数,规定 loader 只在那些模块应用和在哪些模块不应用。

减少ES6转化ES5之后的冗余代码

使用 bable-plugin-transform-runtime 插件

提取公共代码

通过配置 CommonChunkPlugin 插件,将多个页面的公共代码抽离为单独的文件

鄂ICP备2024055897号