FORMA

企业级实践

大型前端项目在 Webpack 场景下的多环境拆分、Docker 构建缓存、自定义 CLI 与微前端(Module Federation / single-spa)等实践。入门见 工程化概览

一、多环境配置拆分

生产、开发、测试等环境往往需要不同的配置(API 地址、source map 策略、优化选项等)。推荐使用 webpack-merge 将公共配置与环境特定配置分离。

1. 文件结构

text
build/
  webpack.common.js   # 公共配置(entry、output、module.rules)
  webpack.dev.js      # 开发配置(devServer、cheap-source-map)
  webpack.prod.js     # 生产配置(压缩、contenthash、splitChunks)

2. 使用 webpack-merge 合并

js
// webpack.dev.js
const { merge } = require("webpack-merge");
const common = require("./webpack.common.js");

module.exports = merge(common, {
  mode: "development",
  devtool: "eval-cheap-module-source-map",
  devServer: { hot: true, port: 8080 },
});
js
// webpack.prod.js
const { merge } = require("webpack-merge");
const common = require("./webpack.common.js");

module.exports = merge(common, {
  mode: "production",
  output: { filename: "[name].[contenthash].js" },
  optimization: { minimize: true },
});

3. 通过 --env 传递环境变量

package.json 中使用 --env 参数:

json
"scripts": {
  "build:dev": "webpack --env production=false --config build/webpack.common.js",
  "build:prod": "webpack --env production=true"
}

Webpack 配置接收 env 参数:

js
// webpack.common.js
module.exports = (env, argv) => {
  const isProd = env.production;
  return {
    // ... 公共配置
    plugins: [
      new webpack.DefinePlugin({
        "process.env.API_BASE": JSON.stringify(
          isProd ? "https://api.prod.com" : "https://api.dev.com",
        ),
      }),
    ],
  };
};

二、构建流程的 Docker 化

将 Webpack 构建封装到 Docker 中,可以保证环境一致性,并利用缓存加速 CI。

1. 多阶段构建 Dockerfile

dockerfile
# 第一阶段:安装依赖(缓存 node_modules)
FROM node:22-alpine AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --only=production

# 第二阶段:开发/构建阶段
FROM node:22-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build

# 第三阶段:生产运行(可选,若只需构建产物)
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html

2. 利用 Webpack 持久化缓存优化 CI

Webpack 5 支持 cache: filesystem,可以将模块编译结果缓存到磁盘,加速二次构建。

js
// webpack.common.js
module.exports = {
  cache: {
    type: "filesystem",
    buildDependencies: {
      config: [__filename], // 配置文件变化时重新缓存
    },
  },
};

在 Docker 中,可以将缓存目录挂载为卷,保留跨构建的缓存:

dockerfile
# 构建时挂载 cache 目录
RUN --mount=type=cache,target=/app/node_modules/.cache/webpack npm run build

或在 docker-compose.yml 中:

yaml
services:
  build:
    volumes:
      - webpack_cache:/app/node_modules/.cache/webpack
volumes:
  webpack_cache:

3. CI 优化技巧

  • 使用 npm ci 替代 npm install(严格依据 lockfile,更快)。
  • node_modules 进行缓存(GitLab CI: cache、GitHub Actions: actions/cache)。
  • 将 Webpack 构建与 Docker 镜像构建分离,只打包 dist 到最终镜像。

三、自定义 CLI 封装

对于中大型团队,往往需要封装内部脚手架,统一构建规范。可以基于 webpack Node API + commander 实现。

1. 基本结构

js
// bin/cli.js
#!/usr/bin/env node
const { program } = require('commander');
const webpack = require('webpack');
const devServer = require('webpack-dev-server');
const getConfig = require('../config');

program
  .command('dev')
  .description('启动开发服务器')
  .action(() => {
    const config = getConfig('development');
    const compiler = webpack(config);
    const server = new devServer(compiler, config.devServer);
    server.start();
  });

program
  .command('build')
  .description('生产构建')
  .option('-p, --production', '生产模式')
  .action((options) => {
    const config = getConfig('production');
    webpack(config, (err, stats) => {
      if (err) throw err;
      console.log(stats.toString({ colors: true }));
    });
  });

program.parse(process.argv);

2. 动态配置生成(getConfig 函数)

js
const { merge } = require("webpack-merge");
const baseConfig = require("../webpack.common.js");

module.exports = (mode) => {
  const envConfig =
    mode === "development" ? require("../webpack.dev.js") : require("../webpack.prod.js");
  return merge(baseConfig, envConfig, { mode });
};

3. 发布到 npm

  • package.json 中设置 bin 字段:"bin": { "mycli": "./bin/cli.js" }
  • 本地测试:npm link 之后即可全局使用 mycli dev

四、微前端集成:Module Federation + single-spa

目前主流的微前端方案之一是将 Webpack 5 的 Module Federation 与 single-spa 结合,实现主子应用独立开发部署,运行时共享依赖。

1. Module Federation 配置

主应用(shell)

js
new ModuleFederationPlugin({
  name: "shell",
  remotes: {
    app1: "app1@http://localhost:3001/remoteEntry.js",
    app2: "app2@http://localhost:3002/remoteEntry.js",
  },
  shared: {
    react: { singleton: true, eager: true, requiredVersion: "^18.0.0" },
    "react-dom": { singleton: true, eager: true },
  },
});

子应用(app1)

js
new ModuleFederationPlugin({
  name: "app1",
  filename: "remoteEntry.js",
  exposes: {
    "./bootstrap": "./src/bootstrap",
  },
  shared: {
    react: { singleton: true, requiredVersion: "^18.0.0" },
    "react-dom": { singleton: true },
  },
});

2. 集成 single-spa

  • 每个子应用需要导出 bootstrap 函数,并注册到 single-spa。
  • 主应用使用 System 动态加载远程模块。

子应用入口(bootstrap.js)

js
import React from "react";
import ReactDOM from "react-dom";
import singleSpaReact from "single-spa-react";
import Root from "./Root";

const reactLifecycles = singleSpaReact({
  React,
  ReactDOM,
  rootComponent: Root,
  errorBoundary(err, info, props) {
    return <div>Error</div>;
  },
});

export const { bootstrap, mount, unmount } = reactLifecycles;

主应用注册子应用

js
import { registerApplication, start } from "single-spa";

registerApplication({
  name: "@org/app1",
  app: () => import("app1/bootstrap"), // 来自 Module Federation remote
  activeWhen: ["/app1"],
});

start();

3. 共享依赖策略与版本冲突

  • 使用 singleton: true 确保 React 等库只加载一次。
  • 使用 requiredVersion 声明兼容版本范围,Webpack 会在加载时检查主从版本是否匹配,不匹配时会在控制台 warning 但继续执行(可选择 eager: true 强制主应用提供)。
  • 对于非核心库(如 lodash),可以设置 singleton: false,允许主子应用各自加载独立版本,避免版本冲突。

4. 构建与部署

  • 每个子应用独立构建,输出 remoteEntry.js 及各自的 chunk。
  • 主应用构建时不需要知道子应用的具体代码,只需远程入口地址(可在运行时动态注入)。
  • 生产环境可将子应用的 remoteEntry.js 上传到 CDN,主应用通过环境变量读取地址。

总结

实践方向关键技术收益
多环境配置webpack-merge + --env配置复用、环境隔离
Docker 化构建多阶段构建、Webpack 持久化缓存、volume 缓存环境一致、CI 提速
自定义 CLIcommander + Webpack Node API统一团队构建命令,可定制
微前端集成Module Federation + single-spa独立部署、技术栈无关、依赖共享

具体方案须结合团队基础设施与发布流程评估,不宜照搬配置。

参考文献

以下链接在编写时均可正常访问:

资料说明
webpack-merge配置合并
Module Federation微前端
single-spa微前端编排

Series

webpack

3 / 6