如何对 server bundle 关闭代码拆分避免异步错误

解读

在国内前端面试中,**“服务端打包关闭代码拆分”**常被用来考察候选人对构建工具链的深入理解。
Grunt 本身只做任务编排,真正的代码拆分逻辑由 webpack/Browserify/Rollup 等打包器完成;而 Grunt 通过 grunt-webpack、grunt-rollup 等插件把打包器“包”成任务。
题目表面问 Grunt,实质问 “如何在 Grunt 驱动的 webpack 流程里,把 server bundle 配置成单文件、零异步块,从而杜绝 Node 端因找不到 chunk 而抛出的 Cannot find module 'xxx.bundle.js' 等运行时错误”
面试官期望你:

  1. 定位到 server 与 client 各自独立的 webpack 配置
  2. 在 server 配置里显式关闭所有异步加载能力
  3. 通过 Grunt 任务把该配置注入流程,并保证产物为单文件、无额外 chunk、无 JSONP 注入

知识点

  • Grunt 任务生命周期:initConfig → registerTask → run;
  • grunt-webpack 多编译实例:可同时暴露 clientConf 与 serverConf;
  • webpack 代码拆分触发点
    optimization.splitChunks
    – 动态 import() / require.ensure
    React.lazy / loadable-component
  • Node 端零拆分三件套
    1. target: 'node'
    2. optimization.splitChunks = false
    3. output.chunkLoading = false(webpack 5)或 output.chunkFormat = 'commonjs'
  • Grunt 注入技巧:利用 grunt.config.merge 在运行时把 server 配置写成 只读单文件模式,防止被 client 配置污染。

答案

  1. 在项目根目录安装依赖

    npm i -D grunt grunt-webpack webpack webpack-node-externals
    
  2. Gruntfile.js 中声明双配置:

    module.exports = function(grunt) {
      const nodeExternals = require('webpack-node-externals');
    
      const clientConfig = {
        mode: 'production',
        entry: './src/client.js',
        output: { filename: 'client.[contenthash].js', path: 'dist/public' },
        optimization: { splitChunks: { chunks: 'all' } }
      };
    
      const serverConfig = {
        mode: 'production',
        target: 'node',
        entry: './src/server.js',
        output: {
          filename: 'server.js',
          path: 'dist',
          libraryTarget: 'commonjs2',
          // 关键:禁用任何异步加载
          chunkLoading: false,
          chunkFormat: 'commonjs'
        },
        externals: [nodeExternals()],
        optimization: {
          // 彻底关闭代码拆分
          splitChunks: false,
          runtimeChunk: false
        },
        module: { rules: [{ test: /\.js$/, exclude: /node_modules/, use: 'babel-loader' }] }
      };
    
      grunt.initConfig({
        webpack: {
          client: clientConfig,
          server: serverConfig
        }
      });
    
      grunt.loadNpmTasks('grunt-webpack');
      grunt.registerTask('build', ['webpack:client', 'webpack:server']);
    };
    
  3. 运行 npx grunt build,产物仅生成 dist/server.js 单文件,无任何异步 chunk,Node 端直接 node dist/server.js 即可启动,彻底避免异步模块找不到错误

拓展思考

  • 同构项目如何兼顾 client 拆分与 server 单文件?
    可在 Grunt 任务里再增加 webpack --json > stats.json 步骤,利用 webpack-stats-plugin 把 client 的 chunk 映射注入 server 模板,但 server 自身仍保持零拆分,实现“客户端按需加载,服务端一次性直出”。
  • 老项目用 Grunt + Browserify 怎么办?
    Browserify 无官方拆分能力,若引入 factor-bundle 做拆分,需在 Grunt 的 browserify 任务中移除 factor-bundle 插件并加 --no-dedupe 参数,即可回退到单文件。
  • Webpack 5 Module Federation 场景下如何保护 server?
    在 server 配置里加 experiments: { outputModule: false }把 RemoteEntry 入口剔除,防止服务端被远程 chunk 反向依赖,保持纯本地单文件形态。