在 Pug 模板中使用变量

如何在 Pug 里,把变量注入父模板。

我厂使用 Pug 作为 HTML 的预编译工具,写久了发现回不去了……Pug 写起来很高效,看久了习惯了阅读效率也很高,读了读文档,发现还有很丰富的可编程特性。于是我决定抛弃 Handlebars,以后都用 Pug 写模板了。

这次我厂官网改版,我就用 Pug 替换了 Handlebars,于是 build 脚本一下少了几十行,非常爽。其实我早就想这样做,不过卡在一个点:使用变量。

在 Pug 里可以这样使用变量:

- var name = 'meathill'

h1 hello #{ name }

以上代码将输出 <h1>hello meathill</h1>。这种用法比较简单,不过在 extends模板的时候,把变量放到哪里就不知道了,官方文档也语焉不详,我反复试了很多次无果,最终还是靠搜索找到了答案,原来要这样:

// Layout.pug
html
  block vars
    p 注意,这个 block 是重点,它出现在前面,用来注入变量
  head
    title #{ title } | My site
  body
    block content

// page.pug
extends Layout

block vars
  - var title = 'A blog'
  
block content
  h1 Blog title
  p blog content

Gulp 中顺序执行任务

Gulp 顺序执行支持3中模式:callback、返回 stream、返回 Promise 对象。

书接上文,Chrome 插件中无法直接使用 Handlebars 处理模板。两种方案,一是利用沙箱,将 .eval() 放在独立的环境中执行,好处是其它跨域的操作也能这样处理,坏处是写起来麻烦。另一种则是利用 Handlebars 的“预编译”功能,将模板提前编译好,直接在代码中引用。好处是写起来更顺畅,并且从发布插件的角度来看,早晚都要这样做。

我决定选用后者,于是我需要把模板提取出来进行预编译,然后我准备写个 Gulp 任务来搞定这个。

从页面中提取模板不算太难,写个正则就好,这里提前约定,模板使用 <script type="x-handlebars-template"> 标签包裹。

gulp.task('template', function () {
  let promise = new Promise((resolve, reject) => {
    fs.readFile('popup.html', (err, content) => {
      if (err) reject(err);
      resolve(content);
    }
  });
  promise.then((content) => {
    content.replace(/<script([^>]*)>([\S\s]+?)<\/script>/g, (match, attr, template) {
      // 具体处理模板,略掉了
    });
  })
});

模板编译之后,还需要用 Webpack 打包才能使用,于是要增加 Webpack 的任务。这个比较简单,参考它的 文档 即可。不过很明显,必须等全部模板预编译完成才能打包。之前我已经试过用 run sequence 顺序执行任务,不过这里我用到 Promise,情况似乎有变。

稍微 Google 一下,得知要能顺序执行任务有三种选择:

使用 callback

gulp.task('task', function (callback) {
  setTimeout(function () {
    // 任务执行完成后,调用 callback
    callback();
  }, 5000);
});

返回 stream

这个好像是标准做法,也是我之前做的。

gulp.task('task', function (callback) {
   returen gulp.src('*.js')
     .pipe(uglify())
     .pipe(gulp.desc('dist/');
});

返回 Promise 对象

gulp.task('task', function () {
  return new Promise(resolve, reject) => {
    resolve();
  });
});

既然支持 Promise 那就好办了,在 .then() 前面加一个 return 就好。


完整代码

参考文章

Handling Sync Tasks with Gulp JS

Handlebars 4

Handlebars升级到4.0后,{{#if}}不再作为一层影响查找,以前的`../../`可以改成`../`了。

图文……好像有关。

Handlebars自从升到2.0之后,就放开了,版本号蹭蹭涨,如今已经升到4.0版。

这个版本最显著的变化在于层级结构,以往的版本中,所有的Block Helper都可以形成一个层级。对于{{#each}}{{#with}}这种还好说,层级关系比较明确,很好理解;但是{{#if}},也有可能形成一个层级,这就很奇怪了。尤其像我前文所述,它只是可能形成一个层级,就更诡异了。

所以这个修改其实是件好事儿。

接下来,代码时间。

Before

    {{#each list}}
      {{#if is_true}}
        <p>这里需要向上两层才能找到值。{{../../value}}注意,这里是两层。</p>
      {{/if}}
    {{/each}}
    var data = {
        list: [
          // items..
        ],
        value: 'value'
      }
      , template = Handlebars.compile(hbs);
    template(data);

After

    {{#each list}}
      {{#if is_true}}
        <p>这里需要向上两层才能找到值。{{../value}}那,这里只有一层了</p>
      {{/if}}
    {{/each}}

JavaScript是一样的。

总结

为了写这篇文章又去看了一遍Handlebars的文档,发现新增了不少特性,尤其开始支持子表达式,看来有必要再好好看一遍。