webpack5/ vite为什么会出现?

在早期的 Web 开发中,我们只需要编写 HTML、CSS 和 JavaScript,浏览器可以直接运行这些文件。

但随着前端工程规模不断扩大,单纯“写代码 → 浏览器运行”的模式逐渐暴露出问题。

  1. 浏览器“听不懂”开发语言
    • 问题:浏览器只能读懂标准的 JS、CSS。但为了开发效率,我们用了 .vue 文件、JSX、TS、Less/Sass,浏览器直接运行这些会报错。
    • Webpack / Vite 的作用:构建工具本质上充当的是一个 “翻译官”:JSX → 普通 JS、TypeScript → JavaScript、Less / Sass → CSS、新语法 → 浏览器可识别的语法,它们的目标不是让你写得更复杂,而是让浏览器最终只看到它能运行的代码。
  2. 网络请求的“拥堵”
    • 问题:如果你有 50 个 JS 文件(比如 utils.js, api.js, components…),浏览器加载页面时就要发 50 次 HTTP 请求。这会导致页面加载极慢(队头阻塞), 请求排队、页面加载缓慢、首屏渲染延迟等问题。
    • Webpack 的作用:它把 50 个零散文件“缝合”成 1 个或几个 bundle 文件(模块合并),浏览器只需请求一次,从而减少请求数量,提高加载效率。
  3. 资源管理的“混乱”
    • 问题:图片、字体、CSS 在以前是分开管理的,很难知道哪张图被哪个组件用了。删代码时不敢删图,导致项目越来越臃肿。
    • Webpack 的作用“依赖图谱”。在 Webpack 眼里,一切皆模块。它从入口文件出发,顺藤摸瓜找到所有依赖(JS引用了图片,Vue引用了CSS)。只有被引用的资源才会被打包,没用的直接丢弃(Tree Shaking)。

因此基于上述种种问题,我们需要解析引擎,能够针对我们业务文件不同的代码,输出为浏览器能够使用的代码,并且能够对我们的文件进行管理输出,在我们的产物文件中能够发挥更好的效果

构建工具在整体架构中的位置

在传统服务端渲染(SSR 或模板渲染)场景中,整体流程通常是:

Server(Koa / Express)
   ↓
模板(.tpl / .html)
   ↓
浏览器渲染页面

而在现代前端工程中,构建工具被插入到了服务与浏览器之间

业务源码(Vue / React / TS / CSS)
        ↓
构建工具(Webpack / Vite)
        ↓
产物文件(JS / CSS / 图片)
        ↓
Server(Koa / Nginx)
        ↓
浏览器

构建引擎内部做了什么?

因此,基于上述分析可以看出,Webpack / Vite 本质上就是前端工程中的构建引擎。整体来看,一个构建引擎的工作流程可以抽象为三个核心步骤:

																							输出界面
																								^
业务文件(vue...)  -> 构建引擎(webpack/vite) -> 产物文件(public|								^
															|								Koa
															|
							(解析编译-> 模块分包 -> 压缩优化 )

1️⃣ 解析与编译

  • 遇到 .vue → Vue 解析器
  • 遇到 JSX → React 解析器
  • 遇到 CSS / Less → 对应的样式处理器

输出浏览器可以执行的资源如下:

xxx.js
xxx.css
xxx.png

2️⃣ 模块拆分与组织

  • 将业务代码拆分为多个模块
  • 根据策略生成一个或多个 chunk
  • 输出最终入口文件(如 entry.js

3️⃣ 压缩与优化(区分环境)

  • 生产环境:
    • 压缩 JS / CSS
    • 移除无用代码
    • 优化体积
  • 开发环境:
    • 启动本地服务
    • 模块热更新(HMR)
    • 快速反馈

通过这三个步骤,构建工具将复杂的前端工程,转化为高效、可部署、可运行的最终产物。

具体配置案例(以webpack5为例)

因此基于上面的案例,我们从核心原因探究了webpack/vite具体在工程中的作用,接下来我们用实际案例从上述介绍的三个流程完成webpack5的配置

首先我们先引入一下webpack,具体代码如下,我们使用pnpm run build将调用这个文件

const webpack = require('webpack');
const webpackConfig = require('./config/webpack.prod.js');   

console.log('\nbuilding... \n');

webpack(webpackConfig, (err, stats) => {
    if (err) throw err;

    process.stdout.write(stats.toString({
        colors: true, // 终端中输出带颜色
        modules: false, // 不输出模块信息
        children: false, // 不输出子级信息
        chunks: false, // 不输出块信息
        chunkModules: true // 输出块模块信息
    }) + '\n\n');
});

接下来才是我们最核心的配置也就是webpackConfig,

entry 用来指定应用的入口文件

module 主要通过 rules 配置各种 loader,用来告诉 Webpack:

  • 遇到 .js / .jsx 用什么解析
  • 遇到 .vue 用什么解析
  • 遇到 .css / .less 用什么解析

output 用来配置最终构建产物的输出方式,包括:

  • 输出路径
  • 文件名规则
  • 是否使用 hash

plugins 用来扩展 Webpack 的能力,覆盖更广泛的构建场景,例如:

  • 生成 HTML 文件
  • 注入环境变量
  • 清理构建目录
  • 提取 CSS

optimization 主要用于配置构建产物的优化策略,例如:

  • 代码拆分
  • 缓存策略
  • 压缩配置

具体的流程可以具体看webpack 文档,以下配置仅供参考

/**
 * webpack 基础配置
 */
module.exports = {
    // 入口配置  多页面配置
    entry: pathEntries,
    // 模块解析配置(决定了要加载解释哪些模块以及用什么方式去解释)
    module: {
        rules: [
            {
                test: /\.vue$/, // 匹配所有以.js结尾的文件
                use: { loader: "vue-loader" }, // 使用vue-loader进行转译
            },
            {
                test: /\.js$/, // 匹配所有以.js结尾的文件
                // 只对业务代码进行打包
                include: [path.resolve(process.cwd(), "app/pages")],
                use: { loader: "babel-loader" }, // 使用babel-loader进行转译
            },
            {
                test: /\.(png|jpg?g|gif)(\?.+)?$/,
                use: {
                    loader: "url-loader",
                    options: { limit: 300, esModule: false },
                },
            },
            {
                test: /\.css$/,
                use: ["style-loader", "css-loader"],
            },
            {
                test: /\.less$/,
                use: ["style-loader", "css-loader", "less-loader"],
            },
            {
                test: /\.(woff2|woff|eot|ttf|ttf)(\?\S*)?$/,
                use: "file-loader",
            },
        ],
    },
    // 产物输出路径,因为开发跟生产环境输出不一致,在各自环境中自己配置
    output: {
        filename: "js/[name]_[chunkhash:8].bundle.js",
        path: path.join(process.cwd(), "app/public/dist/prod"),
        publicPath: "/dist/prod",
        crossOriginLoading: "anonymous",
    },
    
    // 配置模块解析的具体行为
    resolve: {
        //js': ['.js', '.jsx']
        extensions: [".js", ".vue", ".less", ".css"],
        alias: {
            // 用$pages代替项目中app/pages的绝对路径
            $pages: path.resolve(process.cwd(), "app/pages"),
            $common: path.resolve(process.cwd(), "app/pages/common"),
            $widgets: path.resolve(process.cwd(), "app/pages/widgets"),
            $store: path.resolve(process.cwd(), "app/pages/store"),
        },
    },
    // 配置webpack插件
    plugins: [
        // 处理.vue文件必备插件 ,处理 .vue文件的三个板块
        new VueLoaderPlugin(),
        // 把第三方库暴露到 window context 上
        new webpack.ProvidePlugin({
            Vue: "vue",
        }),
        // 定义全局常量
        new webpack.DefinePlugin({
            __VUE_OPTIONS_API__: "true", //支持vue解析option api
            __VUE_PROD_DEVTOOLS__: "false", //生产环境禁用devtools,
            __VUE_PROD_HYDRATION_MISMATCH_DETAILS__: "false", //生产环境禁用显示‘水合’信息
        }),
        //构造最终渲染的页面模板
        ...htmlWebpackPlugins,
    ],
    //配置打包输出优化(代码分割,模块合并 缓存 Treesharing 压缩等优化策略)
    // 针对vue这种包只需要打包一次,针对一些通用模块比如 common 也会被多个地方使用, z也只需要独立打包

    optimization: {
        // 分包,对上面产出的js进行模块划分
        splitChunks: {
            chunks:'all',
            maxAsyncRequests: 10, //每次最大异步请求数量
            maxInitialRequests: 10, //入口点的最大请求数量
            cacheGroups: {
                vendor: { //第三方库
                    name: "vendor",
                    test: /[\\/]node_modules[\\/]/,
                    priority: 20, //优先级 数字越大优先级越高
                    enforce: true, //强制执行
                    reuseExistingChunk: true, //可复用已经存在的模块
                },
                common: { //公共模块
                    name: "common",
                    priority: 10,
                    minSize: 1, //最小分割文件大小(1 byte) 
                    minChunks: 2, //被两次引用就是公共模块
                    reuseExistingChunk: true, //可复用已经存在的模块
                },

            }
        },
        // 将 webpack运行时的代码打包到 runtime.js 文件中
        runtimeChunk: true
    },
};

上述是一个开发环境,但是在实际项目中,我们需要配置一个生产环境和开发环境,两种环境具体需要配置的东西是稍微有些不同的,
开发环境中,构建工具除了完成代码解析和打包之外,还需要提供一套 开发时的联动能力。 当业务代码发生变化时,让浏览器尽可能快地看到最新结果,也就是实现热更新的能力

																				 浏览器运行	
											            	         |
业务文件   <- 【  监控能力    通知能力 】 -> 产物文件
											devServe

具体流程介绍如下:

在开发环境下,devServer 会监听业务文件的变化(JS、CSS、模板等)。

一旦文件被修改,构建工具能够立刻感知到变化,而不需要手动重新执行打包命令。


当变更被捕获后,Webpack / Vite 会重新执行相关模块的编译流程:

  • 只重新构建受影响的模块
  • 生成新的构建结果(通常存在于内存中,而不是磁盘)

最后,devServer 会通过 WebSocket 等方式通知浏览器:

  • 构建已完成
  • 可以刷新或执行热更新

浏览器收到通知后,完成页面刷新或局部替换,从而看到最新效果。

具体配置可以看官网

Reference

起步 | webpack 中文文档 | webpack中文文档 | webpack中文网

玄哲前端

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐