如何在 grunt 中实现模板热替换不重启 Node

解读

面试官真正想考察的是:

  1. 你是否理解“模板热替换”=文件内容变更后,浏览器或 Node 进程立即拿到最新结果,而无需手动重启 Grunt 任务或 Node 服务
  2. 你是否能把 Grunt 的“任务只跑一遍”模型改造成“持续监听 + 内存级缓存刷新”模型,同时保证不重启 Node 主进程
  3. 你是否知道国内主流场景(Vue/React 脚手架已内置 HMR)下,Grunt 老项目如何低成本补齐热替换能力,而不是直接说“换 Webpack/Vite”。

知识点

  1. grunt-contrib-watchoptions: { livereload: true } 只能刷新浏览器,不会触发模板引擎重新编译
  2. grunt-express-servergrunt-contrib-connect 提供的静态服务默认把模板当静态文件,不会走模板引擎
  3. 模板引擎(Swig、EJS、Handlebars、Nunjucks)在 Node 端都有编译缓存,必须手动清缓存才能让新模板生效。
  4. 国内线上环境常配合 nodemon/pm2 重启进程,但题目明确禁止重启 Node,因此必须在同进程内完成“清缓存→重编译→推送浏览器”三连。
  5. 浏览器端需要WebSocket 通道(livereload 协议或自建 socket)接收“模板已更新”事件并局部刷新 DOM,不能整页刷新才算“热替换”。

答案

分四步落地,全部在同一个 Grunt 进程内完成,Node 主进程绝不重启

  1. 选一套带内存缓存的模板引擎,以 Nunjucks 为例:

    const nunjucks = require('nunjucks');
    const env = nunjucks.configure('src/views', { watch: false }); // 关闭引擎自身的 watch
    
  2. 在 Gruntfile 里注册一个内存级缓存清理任务 clean-tpl-cache

    grunt.registerTask('clean-tpl-cache', function() {
      // 遍历 nunjucks 内部缓存对象并删除
      Object.keys(nunjucks.cache).forEach(k => delete nunjucks.cache[k]);
    });
    
  3. 配置 grunt-contrib-watch,一旦 src/views/**/*.html 变动,顺序执行“清缓存→重新渲染→推送浏览器”:

    watch: {
      tpl: {
        files: ['src/views/**/*.html'],
        tasks: ['clean-tpl-cache', 'render-index'],
        options: {
          livereload: 35729 // 默认端口,可自定义
        }
      }
    }
    

    其中 render-index 是自定义任务,负责调用 env.render('index.njk', data) 把最新 HTML 生成到 .tmp/index.htmldev-server 指向该目录

  4. 浏览器端注入 livereload 脚本(grunt-contrib-connect 已自动注入),收到 WebSocket 推送后局部替换 DOM,完成热替换。
    若需更细粒度控制,可改用 tiny-lr 自建 WebSocket 通道,在 render-index 任务末尾执行 lr.changed({body: {files: ['.tmp/index.html']}})只刷新改动的模板片段

以上四步全部跑在同一个 Node 进程没有重启任何服务,满足“模板热替换不重启 Node”的硬性要求。

拓展思考

  1. 如果模板依赖后端 JSON 数据,可把数据抽成独立的 mock/api.js 模块,同进程内 require 缓存也清掉delete require.cache[require.resolve('./mock/api.js')]),实现“模板 + 数据”双热替换。
  2. 对于多页面项目,可在 clean-tpl-cache 里用 glob 动态匹配改动的文件,只清除对应缓存,避免全量清缓存带来的性能抖动。
  3. 国内老项目往往jQuery + 后端模板混合,浏览器端无虚拟 DOM,此时可引入 htmx 或 alpine 做局部刷新,把 Grunt 热替换能力延伸到组件级,让面试官看到你对“老项目渐进升级”有落地经验。