标签: handlebars

  • 在 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
  • Template was precompiled with an older version of Handlebars

    Template was precompiled with an older version of Handlebars

    gulp-handlebars 对 Handlebars 的要求是 ^3.0.0,预编译器的版本是 6。Handlebars 4 之后升级预编译器到 7,所以如果使用最新版本 Handlebars,就会报 “Template was precompiled with an older version of Handlebars” 错误。

    这个时候有几个解决方案,比如 这个页面 中提到的先删再装。不过经我实测,npm update 可能导致失效,还要重弄,太麻烦。所以最简单的,就是直接用本地的高版本覆盖依赖中的版本:

    npm install handlebars --save-dev
    rm -rf ./node_modules/gulp-handlebars/node_modules/handlebars
    cp -r ./node_modules/handlebars ./node_modules/gulp-handlebars/node_modules
    
  • Gulp 中顺序执行任务

    Gulp 中顺序执行任务

    书接上文,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

  • 在Chrome 扩展中使用 Handlebars

    在Chrome 扩展中使用 Handlebars

    Chrome 扩展可以访问到各种用户敏感数据,比如 cookie 之类,所以 Chrome 团队对它的限制非常严格(见 Using eval in Chrome Extensions. Safely.),比如常规环境下完全不能使用 evalnew Function()。这给我们使用 Handlebars 之类的模板工具带来不小的麻烦。

    一个解决方案就是使用上文中介绍的 sandbox。将可能使用到 eval 的代码放到一个独立沙盒中,不让它访问到那些敏感信息;然后通过 postMessage API 与之进行数据通讯,完成模板生成工作。

    我觉得这个方案不太理想。首先我不喜欢 <iframe>;其次每次渲染模板还要 post 来 post 去,渲染结果也是异步传递(这点暂时存疑)。

    另一套方案,则是利用 Handlebars 的预编译功能,将模板在开发环境中编译成函数,在扩展中直接使用。这样做的好处也很明显,因为发布插件时也会先处理代码,这个时候将模板处理完,工作效率更高。

    等写完再补充具体代码吧。

  • Handlebars 4

    Handlebars 4

    图文……好像有关。

    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的文档,发现新增了不少特性,尤其开始支持子表达式,看来有必要再好好看一遍。

  • 记一个踩过三次的坑……

    记一个踩过三次的坑……

    图文无关。太长时间没写文被降权了么,怎么最近流量跌这么厉害……

    老后台的架构仍然是PHP渲染页面,这里我选择mustache.php作为模板引擎。在某些场景下(比如搜索),我希望在前端使用复用同样的模板。正如前面一篇文章所说,我选择的是Handlebars.js,它号称支持mustache语法。

    然则在使用当中,我发现二者的实现仍然是有区别的。先上模板。

        {{#value}}{{value}}{{/value}}
    

    这段模板很简单,检查value是否存在,如果是的话则将其输出。这时:

    • mustache.php先以value作为逻辑判断依据;接下来,在value里找不到value这个key,便回到上一层,输出value的值
    • 而在handlebars.js眼中,{{value}}的类型也是很重要的,如果是String或者Number,那就直接输出;如果是Object,它就会采用{{#with ..}}{{/with}}操作,从value里面取值,而value里没有value,所以输出空。

    当模板为

        {{#has_value}}{{value}}{{/has_value}}
    

    此时,因为has_value是布尔值,所以Handlebars.js放弃把这个对象当做执行环境,而是继续维持当前的层级,所以就能正确输出value的值。


    这个问题虽然不很复杂,但是调试起来并不方便,而且文档中也没提过,所以三次踩坑每次都耽误不少时间。记录如此,避免再次遭遇。

  • 助力Handlebars

    助力Handlebars

    单页应用中,模板引擎是必备工具。这方面的选择比较丰富,旧轮子不断获得改进,新的轮子也不断被发明出来。我比较喜欢Handlebars,原因如下:

    1. 模板语言简单。继承自mustache,用{{content}}标记出要替换的内容即可。
    2. 包含逻辑判断和循环。在mustache基础上加强了这个部分,使得代码更易读。
    3. 不支持复杂的逻辑,尤其是嵌套JS。我认为这是模板引擎的关键,任何复杂的逻辑放在表现层都会使得系统不稳定。
    4. 支持预编译。可以加快实际运行的速度。
    5. 提供扩展功能,可以方便地增加自定义功能。这也是本文重点。

    简单,是Handlebars的优势;但是原始版有点过分过头,它既缺少Angular模板中的过滤器,难以格式化输出数据;也没有足够的逻辑判断。使得我们必须在使用模板前进行大量预运算,逻辑据处理成模板能辨识的标记,把数据格式化成用户能识别的内容,这与代码复用的原则相违背。所幸Handlebars提供好用的扩展接口,给我们增添功能,适配业务逻辑的机会。

    (更多…)