使用 grunt-contrib-jade 预编译 Pug 模板为函数
解读
国内前端项目面试常把“老插件 + 新名称”混用,考察候选人能否快速定位历史遗留配置并给出最小侵入式改造方案。grunt-contrib-jade 在 npm 上已重命名为 grunt-contrib-pug,但企业旧代码库仍保留 jade 关键字。面试官想看三点:
- 是否知道 jade 与 pug 的渊源;
- 能否在 Gruntfile 里**正确写出预编译为“可复用函数”**而非简单 HTML 的配置;
- 是否理解客户端运行时复用与服务端渲染两种产物差异,避免把模板函数当字符串返回。
知识点
- grunt-contrib-jade 插件的 runtime:true 选项:把模板预编译成
function(locals){ return ... },不直接输出 HTML。 - wrap:true 与 amd:true 参数:决定产物是 CommonJS、AMD 还是全局变量,国内项目多数要求 UMD 以便同时支持 Webpack 与 Sea.js。
- namespace 字段:默认
JST,国内团队常改成项目代码仓库统一命名空间,如window.APP.TPL。 - concat 任务后置处理:预编译产物是零散 JS,需配合 grunt-contrib-concat 合并为单个 tpl.js,减少浏览器请求。
- sourcemap 关闭:国内生产环境普遍关闭 sourcemap,避免源码泄漏;但面试时要说明开发环境可打开以方便调试。
- grunt-legacy 兼容性:Node 14+ 运行 grunt-contrib-jade 需加
--legacy-peer-deps,否则 npm 7+ 会报 peer 冲突,面试官会追问解决方案。
答案
-
安装并锁定版本(保证 CI 可复现):
npm i -D grunt-contrib-jade@1.0.0 --legacy-peer-deps -
Gruntfile.js 关键片段:
module.exports = function(grunt) { grunt.initConfig({ jade: { options: { // 核心:输出可执行函数 runtime: true, // 产物用 UMD 包装,兼容 Webpack/Sea.js wrap: true, amd: false, // 统一命名空间 namespace: 'APP.TPL', // 开发阶段打开 sourcemap,面试时主动说明 sourcemap: grunt.option('dev') ? true : false }, compile: { files: [{ expand: true, cwd: 'src/tpl', src: '**/*.jade', dest: '.tmp/tpl', ext: '.js' }] } }, concat: { tpl: { src: ['.tmp/tpl/**/*.js'], dest: 'dist/assets/js/tpl.js' } }, clean: { tmp: ['.tmp'] } }); grunt.loadNpmTasks('grunt-contrib-jade'); grunt.loadNpmTasks('grunt-contrib-concat'); grunt.loadNpmTasks('grunt-contrib-clean'); grunt.registerTask('tpl', ['jade', 'concat:tpl', 'clean:tmp']); }; -
使用示例(浏览器端):
// 预编译产物已挂到 window.APP.TPL var html = APP.TPL['user/list']({users: data}); document.querySelector('#panel').innerHTML = html; -
面试加分句:
“如果后续迁移到 Webpack5,可用 pug-loader 达到同样效果,但保留 grunt 阶段可让存量项目零重构上线。”
拓展思考
- 性能优化:预编译函数体积比字符串模板大 20% 左右,可配合 grunt-contrib-uglify 开启
--compress --mangle把局部变量缩短,再使用 gzip 在 Nginx 层压缩,国内云厂商 CDN 默认开启 br 压缩,可再减少 15% 体积。 - 服务端同构:若同一套模板需要服务端渲染,可在 jade 任务里再建一个
ssrtarget,关闭runtime:true,输出纯 HTML 片段,通过grunt-contrib-copy推到 views 目录,供 Express 使用,实现“一次编写,两端运行”。 - 类型安全:在 TypeScript 项目中,可手写 d.ts 声明模块
APP.TPL,把每个模板函数签名写成(locals?: Locals) => string,避免 any 类型,提高维护性;面试时提到这一点可体现工程化深度。