使用 grunt-webpack 生成 client 与 server 两份 bundle
解读
面试官真正想考察的是:
- 你是否理解 Grunt 的任务编排哲学(配置驱动、插件化、串行/并行控制)。
- 能否把 webpack 的多入口、多配置 无缝嫁接到 Grunt 流程里,而不是“用 webpack 命令行硬跑”。
- 对 国内主流场景(SSR、同构、微前端)是否做过真实落地:client 包供浏览器拉取,server 包供 Node 渲染,两份产物路径、publicPath、target、externals 完全不同。
- 是否知道 grunt-webpack 3.x 与 4.x 的差异(缓存、watch、error callback)以及 Grunt 1.5 之后对并行任务的原生支持,避免“跑两次 webpack 耗时翻倍”这类低级坑。
- 有没有踩过 Windows 中文路径、公司私有 Nexus 源、CI 内存溢出 这些国内特色坑,并给出兜底方案。
知识点
- grunt-webpack 插件的两种调用方式:
webpack单配置 vswebpack-dev-server调试模式。 - webpack 多配置数组(multi-compiler):返回
[clientConfig, serverConfig],grunt-webpack 会一次性跑完,输出两份 stats。 - Grunt 任务并行:使用
grunt-concurrent或 Grunt 1.5 的grunt.registerTask('parallel', ['webpack:client', 'webpack:server'])配合grunt.util.spawn实现并行,缩短 40%+ 构建时间。 - 路径隔离规范:client 产物放在
dist/public,server 产物放在dist/server,并在serverConfig.externals = [webpackNodeExternals()]避免把 node_modules 打进去。 - 国内 CI 限速:在
clientConfig.optimization.splitChunks.cacheGroups.vendor.test里把公司私库@xxx统一拆包,配合hard-source-webpack-plugin做二级缓存,解决“每次 npm ci 后全量重编译”问题。 - 错误码收敛:grunt-webpack 默认把 webpack 错误吞掉,需在
options.stats显式配'errors-warnings',并在done callback里grunt.fail.fatal让 Jenkins 红灯,防止“构建失败却绿屏上线”。 - 环境区分:借助
process.env.BUILD_ENV === 'prod'动态切换mode、devtool、sourceMap,避免测试环境生成慢速的source-map而拖慢流水线。
答案
-
安装依赖
npm i -D grunt-webpack webpack webpack-cli webpack-node-externals grunt-concurrent -
在项目根新建
webpack.config.js导出多配置数组const nodeExternals = require('webpack-node-externals'); const path = require('path'); const client = { name: 'client', mode: process.env.NODE_ENV === 'production' ? 'production' : 'development', entry: './src/client/index.js', output: { path: path.resolve('dist/public'), filename: '[name].[contenthash].js', publicPath: '/static/' }, optimization: { splitChunks: { chunks: 'all' } } }; const server = { name: 'server', target: 'node', mode: 'development', // 服务端不需要压缩,降低 CI 耗时 entry: './src/server/index.js', output: { path: path.resolve('dist/server'), filename: 'server.js', libraryTarget: 'commonjs2' }, externals: [nodeExternals({ allowlist: [/^@company/] // 公司内网私有包仍需打包 })] }; module.exports = [client, server]; -
在
Gruntfile.js里注册 grunt-webpack 任务module.exports = function(grunt) { grunt.initConfig({ webpack: { options: require('./webpack.config.js'), // 直接引用数组 stats: 'errors-warnings' } }); grunt.loadNpmTasks('grunt-webpack'); // 串行方案(本地开发) grunt.registerTask('build', ['webpack']); // 并行方案(CI 提速) grunt.registerTask('build:parallel', function() { const done = this.async(); grunt.util.spawn({ grunt: true, args: ['webpack:client'] }, () => {}); grunt.util.spawn({ grunt: true, args: ['webpack:server'] }, () => { done(); }); }); }; -
运行
# 本地 npx grunt build # CI NODE_ENV=production npx grunt build:parallel产物结构
dist/ public/ main.abc123.js vendor.def456.js server/ server.js -
验证
- 浏览器访问
http://localhost/static/main.abc123.js返回 200。 - Node 端
node dist/server/server.js正常拉起 Express,无Cannot find module 'react'报错,证明 externals 生效。
- 浏览器访问
拓展思考
-
如何做到“一次 watch,两份热更新”:
在webpack.config.js里给 client 配devServer.hot=true,给 server 配webpack/hot/poll?300,再用grunt-concurrent同时起webpack-dev-server与nodemon dist/server/server.js,实现 “浏览器热替换 + Node 端自动重启” 同构开发,解决国内面试常问的“ssr 热更新卡顿”问题。 -
微前端场景下子应用双 bundle:
把client.output.library='subApp'设为 umd,把server.output.libraryTarget='commonjs2'保持不动,主应用通过require('subapp/server')做 SSR,浏览器通过script src="/static/subApp.js"做 hydrate,实现 “子应用独立构建、主应用零改造” 的国内微前端落地范式。