面试看这一篇webpack

前端代码为什么要构建和打包

代码方面

  1. 体积更小(Tree-Shaking,压缩,合并)
  2. 编译高级语言或语法(TS,ES6+,模块化,scss)
  3. 兼容性和错误检查(PolyFill,postcss,eslint)

研发流程方面

  1. 统一,高效的开发环境
  2. 统一的构建流程和产出标准
  3. 集成公司构建规范(提测,上线等)
  • 提高性能
  1. 代码压缩,减小文件体积,
  2. 代码分割,按需加载提高性能
  3. 资源优化:压缩图片,移除未使用的字体,压缩字体。移除未使用的代码
  • 解决兼容性

    1. 使用 Babel 将 ES6+转化为 ES5
    2. 确保 css 在不同浏览器的兼容性
  • 优化部署

    1. 文件哈希命名,避免浏览器缓存导致的更新问题
    2. 区分环境:加载不同配置
  • 提高开发效率

    1. 减少手动操作
    2. 统一规范
    3. 错误检测(Eslint)
  • 解决复杂依赖关系

    1. 自动解析和安装依赖
    2. 将所有依赖打包到一个文件,避免重复加载

module chunk bundle 什么意思,有什么区别?

  1. module:模块,一个模块就是一个文件,一个文件就是一个模块

  2. chunk:一个 chunk 就是一个模块集合,是打包的中间产物

    • Chunk 的生成方式取决于 Webpack 的配置: 入口文件:每个入口文件会生成一个初始 chunk。 动态导入:动态 import() 会生成一个新的 chunk。 代码分割:通过 splitChunks 配置可以进一步优化 chunk 的生成。
  3. bundle:最终的输出文件,每个 bundle 对应一个 chunk,通常是经过优化和压缩后的代码文件。output 输出的文件

loader plugin 区别

  • loader
    • webpack 默认只能处理 js 文件,使用 loader 后可以处理不同类型文件
    • loader 是链式调用按照从后往前的顺序执行
  • plugin
    • 通过监听 webpack 构建生命周期中的事件,在构建过程中执行自定义逻辑。
    • 可以修改输出文件,优化资源,生成额外文件等
    • 典型:清理构建目录,压缩 js,css 文件,自动生成 html 等

webpack 如何实现懒加载

webpack 支持使用 import()动态导入,打包时会将动态导入的包打包成独立文件,会返回一个 Promise,模块加载完后会返回 resolve。只有在 import()调用才会加载

常见性能优化

优化打包构建速度

  1. 优化 babel-loader use:'babel-loader?cacheDirectory' 开启缓存 include 和 exclude 可以选取明确需要缓存处理的文件范围 只要代码没有改就不会重新编译,使用缓存
  2. ingorePlugin
  • 避免引入无用模块

    javascript 复制代码
    new webpack.IngorePlugin({
      resourceRegExp: /^\.\/locale$/, //一个正则表达式,用于匹配需要忽略的模块名称(通常是模块路径的最后部分)
      contextRegExp: /moment$/  //一个正则表达式,用于匹配模块所在的上下文路径(通常是模块路径的前面部分)。
    })
  1. noParse
  • 跳过对模块的解析和分析依赖,间接避免重复打包、

    忽略对一些模块的解析。一些模块是独立的,不依赖其他模块。它可以是一个正则表达式、一个函数或者一个数组。

css 复制代码
module: {
  noParse: /jquery|lodash/
}
  1. happyPack
  • 多进程打包 为一类文件配置多进程打包
javascript 复制代码
 module: {
          rules: [
            {
                test: /\.js$/,
                 use: happypack/loader?id=js,
             },
          ]
  },
   plugins: [
        new HappyPack({
            id: 'js', // 与上面的 loader 中的 id 对应
            loaders: ['babel-loader?cacheDirectory=true'], // 实际使用的 loader
            threadPool: happyThreadPool, // 使用共享的线程池
            verbose: true, // 显示详细日志
        }),
   ]
  1. paralleIUglifyPlugin
  • 多进程压缩 js,项目比较小,开启多线程会增大开销
yaml 复制代码
 plugins: [
        new ParallelUglifyPlugin({
            // 设置使用的 uglifyjs 版本,默认是自带版本
            uglifyJS: {
                output: {
                    beautify: false, // 是否美化输出
                    comments: false, // 是否保留注释
                },
                compress: {
                    warnings: false, // 如果为 true,则显示压缩警告
                    drop_console: true, // 删除所有的 `console` 语句
                    collapse_vars: true, // 内嵌已定义但只用到一次的变量
                    reduce_vars: true, // 提取出出现多次但是没有定义成变量去引用的静态值
                }
            },
            // 指定要并行处理的文件数量,默认为当前系统的核心数减去1
            workerCount: os.cpus().length - 1,
            // 可选参数:指定缓存目录,默认不缓存
            cacheDir: '.cache/',
        }),
    ],
  • terser-webpack-plugin webpack4 的产物,它被引入作为 UglifyJSPlugin 的替代品,因为 UglifyJS 对 ES6+ 代码的支持有限,而 Terser 提供了更好的兼容性。
yaml 复制代码
  optimization: {
    minimize: true, // 启用代码压缩功能(默认在生产模式下为 true)
    minimizer: [
        new TerserPlugin({
            parallel: true, // 启用多线程压缩,利用多核 CPU 提高构建速度
            terserOptions: {
                output: {
                    comments: false, // 移除所有注释,包括版权信息等,减少文件体积
                },
                compress: {
                    drop_console: true, // 删除所有的 `console` 语句(如 console.log、console.warn 等),避免在生产环境中暴露调试信息
                },
            },
        }),
    ],
  },
  1. 自动刷新(非生产环境)
  • 判断文件是否变化是通过不断的去询问系统指定文件是否变化
javascript 复制代码
module.exports = {
  watch: true,// 开启监听模式,默认false
  //注意,使用了webpack-dev-server会开启自动刷新
  watchOptions: {
    ignored: /node_modules/, //忽略node_modules下的文件
    poll: 1000, // 轮询间隔,默认为 3000ms
    aggregateTimeout: 500,//监听到会等待500ms,防止编译的频繁
  }
}
  1. 热更新(非生产环境)
  • 正常刷新 整个网页全部刷新,加载慢,状态丢失
  • 热更新 新代码生效,网页不刷新,状态不会丢失
javascript 复制代码
entry:[
  index: ['webpack-dev-server/client?http://localhost:5500',
            'webpack/hot/dev-server',
            path.join(__dirname, '../src', 'index.js')
        ],
]
devServer: {
  hot: true,
},
plugins: [
  new HotModuleReplacementPlugin()
]

<!-- 在程序的入口文件里引入以下代码 -->
if (module.hot) {
  //需要监听的文件,数组,目录
  //module.hot.accept(); // 监听所有模块的变化
    module.hot.accept('./App', () => {
      //监听到后的回调函数
        const NextApp = require('./App').default;
        render(NextApp);
    });
}
  1. Dllplugin(非生产环境) 将不常更新的第三方库打包成动态链接库 Dll
  • 需要额外配置 dll 的 webpack 配置文件进行打包,再将文件引入启动的 webpack 文件

  • Dllplugin 预打包出 dll 文件和描述模块索引的 json 文件,DllReferencePlugin 使用 dll 文件

css 复制代码
//在启动配置下
module:{
  rules: [
    exclude: /node_modules/,可以忽略,已经把一些模块(react)打包了
  ]
}
 plugins: [
        new webpack.DllReferencePlugin({
            manifest: require('./dist/vendor-manifest.json'), // 引用之前生成的 manifest的json 文件
        }),
    ],

优化产出代码

  1. 小图片 base64 编码

  2. 文件名使用 bundle 加哈希

  3. 懒加载

  4. 提取公共代码

  5. IngorePlugin

  6. 使用 cdn,http://cdn...,配置 publicPath 为 cdn

  7. 使用 production

    • 自动压缩代码
    • vue React 会自动删掉调试代码(如开发时的警告)
    • 启用 Tree-Shaking
  8. 使用 Scope Hosting

    • 将多个模块合并到同一个函数作用域内,从而避免了每个模块被包裹在单独的立即执行函数表达式(IIFE)中,减少了函数调用的开销,并提高了代码压缩的效果

HtmlWebpackPlugin

  1. template 自定义 html 的模板路径
  2. chunks: 当存在多个路口文件时,指定需要在 html 中引入的 chunks(包)
vbnet 复制代码
entry: {
index: './src/index.js',
print: './src/print.js',
},
plugins: [
new HtmlWebpackPlugin({
title: '管理输出',
chunks: ['index', 'print']
})
],
  1. inject:控制 chunks 注入到 html 的哪个标签中,'head' or 'body' or false
  2. minify:在生产环境中,你可能希望最小化生成的 HTML 文件。可以通过设置这个选项为 true 或者传递一个对象来自定义最小化选项。
vbnet 复制代码
minify: {
collapseWhitespace: true, // 折叠空白区域
removeComments: true, // 移除注释
removeRedundantAttributes: true, // 删除多余的属性
}

source map

更容易地追踪错误与警告在源代码中的原始位置

devtool: 'inline-source-map'

自动编译工具

  1. 观察模式:script 添加 "watch": "webpack --watch",npm run watch 启动。保存文件后会自动重新编译修改后的文件。 缺点:需要手动刷新浏览器才能看到修改后效果

  2. webpack-dev-server:

    • 优点:自动刷新浏览器,不需要手动刷新浏览器

    • webpack 配置添加

      arduino 复制代码
      devServer: {
          static: './dist'    //多个可以是数组
          open: true,         //自动打开浏览器,boolean or 浏览器名称
          port: 8080,         //监听的端口号,默认8080
          hot: true,    //启用 Hot Module Replacement (HMR),提高开发效率。
          proxy: {
            '/api': {}配置代理
          }//将我们本地前端 http://localhost:5137/api 代理到服务器地址 http://localhost:3000
      },
    • script 添加 "start": "webpack serve --open",

    • 它会将在 output.path 中定义的目录中的 bundle 文件作为可访问资源部署在 server 中,简而言之就是可以直接浏览器访问该路径。

    • 如果页面希望在不同路径中找到 bundle 文件,可以修改 dev server 配置中的 devMiddleware.publicPath 选项。

  3. webpack-dev-middleware

    • 包装器:它可以把 webpack 处理过的文件发送到 server,这是 webpack-dev-server 内部的原理,但是它也可以作为一个单独的包使用

optimization

  • runtimeChunk:

    • 'single', 表示创建一个单独的运行时文件,该文件将在所有 chunk 之间共享。这有助于缓存优化,因为如果应用的入口点不变,则运行时文件也不变,从而允许浏览器使用缓存版本,而不是每次都重新下载。
  • 每页有多个入口点是否可以使用多个入口点而无需重复模块 (如果你的应用程序每页有多个入口点,并且你希望这些入口点之间共享模块而不重复打包相同的依赖)

多个 chunks 只需要实例化一次 多个 chunks 所依赖的模块可以提取到多个页面使用的公共包中,但是有些模块在很少的页面使用,打包器可能会将他们内联到每个 chunks 中而不需要提取到共享包。 无论提取还是内联,esmodule 和 commonJS 都规定模块只能在每个 js 上下文实例化一次,保证模块的顶级范围是全局并在该模块的所有用法之间共享 实例化多次会为正确的代码引入错误或者效率低下

基本配置

loader 的执行顺序是从后往前的

  • 拆分、merge公共的配置

    使用 const {smart} = require('webpack-merge'); module.exports = smart(baseConfig, {})// baseConfig 为公共配置

  • 处理 ES6

    • 使用 loader:babel-loader,还需要配置.babeirc 文件

      perl 复制代码
          "presets": ["@babel/preset-env"]
          "plugins": []
      
      ```
  • 处理样式 loader:css-loader,style-loader... 使用 postcss-loader 需要配置 postcss-config.js

  • 处理图片

  • 使用 loader:url-loader,图片小可以直接使用 base64,减少请求

  • output

arduino 复制代码
output: {
filename: '[name].[contenthash:8].js',//动态名称,为入口名称
//使用 8 位的哈希字符串设置文件,文件内容没有更改哈希就不变,请求会命中缓存,加载更快
},

高级配置

  1. 生成多入口 html 并引入指定包:
arduino 复制代码
entry:{
index: './src/index.js',
print: './src/print.js',
}
output:{
filename: '[name].[contentHash:8].js',//动态名称,为入口名称
}
//多个 html 需要 new 多个 HtmlWebpackPlugin
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html',//自定义引用的 html 的模板路径
filename: 'index.html',//自定义 html 的文件名
chunks: ['index', 'print']//指定需要在 html 中引入的 chunks(包),不指定会把入口的 js 文件全部引入
})
]
  1. 抽离压缩 css 文件
  • 使用 loader:mini-css-extract-plugin 在 module 的 rule 里面使用,在 plugins 里面使用。 压缩需要配置
css 复制代码
module: {
    rules: [
    {
        test: /\.css$/,
        use: [
            MiniCssExtractPlugin.loader,
            'css-loader',
            'postcss-loader'
            ]
    }]
},
plugins: [
new MiniCssExtractPlugin({
filename: '[name].bundle.css',
})
]

optimization: {
runtimeChunk: 'single',//生成共享文件
minimizer: [
// 默认情况下会包含 TerserPlugin 来压缩 JS 文件
`...`,
new CssMinimizerPlugin(), // 添加 CSS 压缩插件
],
},
  1. css 文件并没有在入口文件中引入,而是直接在 html 引入,所以需要手动引入,使用'copy-webpack-plugin'
css 复制代码
new CopyWebpackPlugin({
patterns: [
{ from: 'src/index.css', to: 'assets' },//to 默认指向打包路径这里会在 dist 下创建 assets 文件夹存储
],
})
  1. 抽离公共代码 抽离公共代码,使得文件修改后不会重新请求,而是使用缓存
yaml 复制代码
optimization: {
//分割代码
    splitChunks: {
        chunks: 'all',
        /\*\*
        _ all: 所有的 chunks
        _ initial: 直接引入了的入口 chunks,不处理异步文件
        _ async: 只针对所有的异步 chunks
        _ function: 自定义函数,返回 true 或 false。
        \*/
        cacheGroups: {
            //第三方模块
            vendor: {
            name: 'vendor',//chunk 名称
            priority: 1,//优先级,越大优先级越高
            test: /node_modules/,
            minSize: 0,//最小尺寸,默认 0
            minChunks: 1,//最少被几个 chunk 引用
        },
        // 公共模块
        common: {
            name: 'common',//chunk 名称
            priority: 0,//优先级
            minSize: 0,//最小尺寸,默认 0
            minChunks: 2,//最少被几个 chunk 引用
        }
      }
    }
  1. 懒加载 使用 import('文件路径').then(res => {}),webpack 会打包出一个单独的 js 文件,然后异步加载,不会阻塞页面的渲染

  2. 处理 jsx 在.babelrc 里面使用 使用'@babel/preset-react' 在 module 的 rules 会使用 loader 处理 jsx,匹配 jsx 文件,他会自动处理 jsx

  3. vue vue loader 在 module 的 rules 会使用 loader 处理 vue 的规则,匹配 vue 文件,使用 vue loader

ESModule 和 CommonJS

ESModule 静态引入,编译时引入 CommonJS 动态引入,执行时引入 静态分析才能实现 Tree-Shaking,执行时引入无法分析

babel

  • 只解析语法,将箭头函数,解构,类等转换为向后兼容的语法。不会自动处理新的内置对象或方法,如 Promise,Array.from 等

  • 用于将 ES6+代码转换为兼容向下得代码

  • 删除类型注释,不做类型检查

  1. 环境搭建
  2. .babelrc 配置
  3. presets 和 plugins

babel-runtime 和 babel-polyfill 的区别

babel-polyfill

旨在填补不同环境之间的兼容性差距

  • core-js 和 regenerator-runtime 的集合

说明:从 7.4 开始被弃用。

  • 它通过修改原生对象(Object.prototype)实现向后兼容,可能与其他库产生冲突。 Array.prototype.includes = function () {}//重新定义方法
  • 会引入整个 core-js 的 polyfills,无法按需引入,增大打包体积

如何按需引入

json 复制代码
"presets": [
    [
      "@babel/preset-env",
      //添加如下代码
      {
        "useBuiltIns": "usage",
        "corejs": 3 //版本
      }
    ]
  ],

babel-runtime

用于支持模块化和非全局污染的 polyfill 和 helper 函数。它与 @babel/polyfill 不同,后者会全局修改原生对象(如 Array.prototype),而 babel-runtime 通过模块化的方式引入 polyfills 和 helpers,避免了全局污染。

核心概念

  1. 核心概念 (1) Helper Functions Babel 在编译代码时会生成一些辅助函数(helper functions),例如 _classCallCheck、_defineProperty 等。这些函数通常用于实现类、继承等特性。

    默认情况下,这些辅助函数会被重复嵌入到每个文件中。这会导致打包体积增大,尤其是在大型项目中。

(2) Polyfills 为了支持新的 JavaScript API(如 Promise、Map 等),需要引入 polyfills。babel-runtime 提供了一种方式,通过模块化的方式引入这些 polyfills,而不是全局污染。

如何产出一个 lib

配置 webpack 文件,output 的 library 可以定义库的全局名称。 使用 npm publish 发布到 npm。更新需要更新版本号 npm version patch # 更新补丁版本

  • 支持 ts 需要 webpack 去处理 ts 文件

  • 支持多平台 为了让库能够同时支持浏览器和 Node.js 环境,建议:

    设置 libraryTarget: 'umd'。 使用 globalObject: 'this' 来兼容不同环境的全局对象。

相关推荐
weixin_382395235 小时前
为小工厂量身打造:本地部署的物料管理系统带缺料计算
前端·制造
__zRainy__5 小时前
解决pnpm v10+不自动构建
前端·pnpm·工程化
猫猫不是喵喵.6 小时前
Vue3 Props 属性
前端·javascript·vue.js
醉城夜风~7 小时前
CSS元素显示模式(display)
前端·css
AI大模型-小华8 小时前
Codex 三方充值快速入门指南
java·前端·数据库·chatgpt·ai编程·codex·chatgpt pro
做前端的娜娜子10 小时前
同一链接实现 PC Web 与移动 H5 自适应
前端·掘金·金石计划
小帅不太帅10 小时前
架构没变、规模没变,DeepSeek V4 Flash 正式版凭什么暴涨 47 分?
前端·aigc·deepseek
jarvisuni10 小时前
DeepSeekFlash前端依旧拉垮,而且变慢了很多!
前端·javascript·算法
卷福同学12 小时前
AI编程出海第二步:验证关键词能否做站
前端·人工智能·后端