面试看这一篇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' 来兼容不同环境的全局对象。

相关推荐
careybobo1 小时前
海康摄像头通过Web插件进行预览播放和控制
前端
杉之3 小时前
常见前端GET请求以及对应的Spring后端接收接口写法
java·前端·后端·spring·vue
喝拿铁写前端3 小时前
字段聚类,到底有什么用?——从系统混乱到结构认知的第一步
前端
再学一点就睡3 小时前
大文件上传之切片上传以及开发全流程之前端篇
前端·javascript
木木黄木木4 小时前
html5炫酷图片悬停效果实现详解
前端·html·html5
请来次降维打击!!!5 小时前
优选算法系列(5.位运算)
java·前端·c++·算法
難釋懷5 小时前
JavaScript基础-移动端常见特效
开发语言·前端·javascript
自动花钱机6 小时前
WebUI问题总结
前端·javascript·bootstrap·css3·html5
拉不动的猪6 小时前
简单回顾下pc端与mobile端的适配问题
前端·javascript·面试
拉不动的猪6 小时前
刷刷题49(react中几个常见的性能优化问题)
前端·react.js·面试