【问题标题】:How to translate an Underscore template to a Handlebars template?如何将 Underscore 模板翻译成 Handlebars 模板?
【发布时间】:2021-02-13 15:03:01
【问题描述】:

我正在升级使用旧主题的 Shopify 商店。

在(旧)购物车页面中是“运费估算器”的代码(因为它运行良好)他们希望在新主题中重复使用。 我已经复制了相关文件,但在执行并按下计算按钮时,我们会显示以下内容:

class="成功" class="错误" > 1) { %> 有 适用于 的运费,从 。 ....

这来自以下代码:

<script id="shipping-calculator-response-template" type="text/template">
  <p id="shipping-rates-feedback" <% if (success) { %> class="success" <% } else { %> class="error" <% } %>>
  <% if (success) { %>
    <% if (rates.length > 1) { %> 
    There are <%= rates.length %> shipping rates available for <%= address %>, starting at <%= rates[0].price %>.
    <% } else if (rates.length == 1) { %>
    ... 
</script>

所以,我猜脚本没有被识别/视为“文本/模板”

新主题包括对以下内容的引用:

<script src="//cdnjs.cloudflare.com/ajax/libs/handlebars.js/4.0.10/handlebars.min.js"></script>

还有旧主题:

<script src="//cdnjs.cloudflare.com/ajax/libs/underscore.js/1.6.0/underscore-min.js" type="text/javascript"></script>

所以我将 handlebar 注释掉,并替换为 underscore。但结果还是一样。

我是在正确的轨道上,还是上述无关紧要?

我需要从下划线破译的完整代码 - 并为 HandleBars 重新编码如下:

<script id="shipping-calculator-response-template" type="text/template">
  <p id="shipping-rates-feedback" <% if (success) { %> class="success" <% } else { %> class="error" <% } %>>
  <% if (success) { %>
    <% if (rates.length > 1) { %> 
    There are <%- rates.length %> shipping rates available for <%- address %>, starting at <%= rates[0].price %>.
    <% } else if (rates.length == 1) { %>
    There is one shipping rate available for <%- address %>.
    <% } else { %>
    We do not ship to this destination.
    <% } %>
  <% } else { %>
    <%- errorFeedback %>
  <% } %>
  </p>
  <ul id="shipping-rates">
    <% for (var i=0; i<rates.length; i++) { %>
    <li><%- rates[i].name %> at <%= rates[i].price %></li>
    <% } %>
  </ul> 
</script>

如果我们能做到这一点,会有很多 Shopify 商家会非常高兴 ;)

【问题讨论】:

  • 是的,您使用 underscore.js 是正确的,但是建议删除它,因为它已经过时了。
  • 感谢@CharlesC。与您的信息。我再次进行了所有更改 - 小心!并注释掉“Handlesbars.js”并添加了一个指向“underscore.js”的链接,然后代码就可以正常工作了。不幸的是,其他代码位停止正常工作;((。有没有办法我可以指示一些脚本代码使用下划线和其他(大多数)使用把手?或者有没有办法将'下划线'脚本'转换为' Handlebars 是否合规?[无需我学习所有关于模板的知识——这远远超出了我的薪酬等级/能力;)]
  • 车把和下划线可以在一起。没有直接的脚本翻译afaik。由于您使用的是车把主题,因此建议将所有从下划线(小部分)更改为车把。车把不硬,非常有用。
  • 谢谢,但我不明白。如果他们可以在一起 - 怎么样?或者,我很高兴自始至终从下划线更改为把手 - 但是如何?如果我提供完整的下划线脚本,有人可以将其转换为与我兼容的 HandleBar。我已经阅读了有关 Underscore -v- Handlebars 的文档……但不明白我在读什么;(
  • 在搜索中 - 我找到了
    如果这将它指向车把,是否有指向下划线的东西?

标签: javascript handlebars.js shopify underscore.js


【解决方案1】:

通常,UnderscoreHandlebars 并不是真正的替代品。 Underscore 是一个具有通用功能实用程序的工具包,可帮助您编写更短、更易于维护的函数式代码。另一方面,Handlebars 是一个完全致力于模板渲染的库,它可以帮助您编写更简洁、更易于维护的模板。

使用 Underscore 时,您可能会发现它的函数在整个 JavaScript 代码中到处都被调用,而 Handlebars 仅在您将呈现模板的地方被调用。出于这个原因,这些库通常根本不会发生冲突。完全有可能编写一个依赖于两者的应用程序(事实上,我在大多数应用程序中已经这样做了一段时间)。只需将两个库链接到您的页面,

<script src="https://cdn.jsdelivr.net/npm/underscore@1.12.0/underscore-min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/handlebars@4.7.7/dist/handlebars.js"></script>

或将两者都作为模块导入,

import _ from 'underscore';
import * as Handlebars from 'handlebars';

或对 AMD 或 CommonJS 等较旧的模块格式执行相同操作。

不过,Underscore 确实有一个 template 函数,这是一个非常小的模板实现,可以用作 Handlebars 等库的替代方案。在您的应用程序的旧版本中似乎就是这种情况,并且由于 Underscore 的最小模板语言与 Handlebars 的模板语言不同而导致冲突。这些库之间的编译和渲染步骤也有些不同。

比较模板语言

这两种模板语言都允许您在一段文本中插入特殊标签,以使该文本的某些部分具有参数化、条件化或重复性。但是它们支持的标签是不同的。

下划线:

  • &lt;%= expression %&gt; 将在文本中插入 JavaScript expression 的字符串值。这称为插值标记。
  • &lt;%- expression %&gt; 会做同样的事情,但 HTML 转义。
  • &lt;% code %&gt; 允许您编写任意 JavaScript code,这将使模板的某些部分成为有条件的或重复的。通常,您会发现一个这样的标签类似于&lt;% for (...) { %&gt;,然后在模板的下方,有一个匹配的&lt;% } %&gt;。这两个代码标签之间的模板部分是一个循环,将由for 的逻辑重复。同样,您可能会发现&lt;% if (...) { %&gt;...&lt;% } %&gt; 使模板的... 部分成为条件。 (老实说,这很丑陋,但它有助于实现最小化。Underscore 的template module 只有一页。)
  • &lt;% code %&gt;code 部分中,您可能偶尔会找到print(expression)。这是一种简写方式,旨在避免中断代码标签,插入带有expression 的插值标签,然后立即使用新的代码标签继续。换句话说,&lt;% code1 print(expression) code2 %&gt;&lt;% code1 %&gt;&lt;%= expression %&gt;&lt;% code2 %&gt; 的简写。

车把:

  • {{name}} 在模板中插入带有键 name 的属性的 HTML 转义字符串值。
  • {{{name}}} 做同样的事情,但没有 HTML 转义。
  • 仅当满足 condition 时,{{#if condition}}...{{/if}} 才会插入 ... 部分。
  • {{#each name}}...{{/each}} 将为name 的每个元素或属性重复...name 成为...上下文;也就是说,如果您在... 中写入{{otherName}},Handlebars 将尝试查找otherName 作为name 标识的对象的属性。
  • {{#name}}...{{/name}} 是 Handlebars 继承自 Mustache 的符号。当name 是一个数组时,它的行为类似于{{#each name}},否则类似于{{#if name}}(如果它是一个对象,也将上下文更改为name)。这背后的想法(在 Mustache 中)是使模板更具声明性,或者作者称之为“无逻辑”。
  • 还有更多的标签,我现在不再赘述。

将下划线模板转换为把手

由于 Underscore 允许在模板中插入任意 JavaScript 代码,因此并不总是可以将 Underscore 模板转换为等效的 Handlebars 模板。然而,幸运的是,模板并不真正需要 JavaScript 的全部表达能力,因此 Underscore 模板的编写方式很可能可以移植到更具限制性的模板语言(“幸运的是偶然”)。如果可能,大多数情况下以下策略就足够了:

  1. 将出现在任何&lt;%...%&gt; 标记内的所有print(_.escape(expression)) 替换为%&gt;&lt;%- expression %&gt;&lt;%
  2. &lt;%...%&gt; 中出现的任何其他print(expression) 替换为%&gt;&lt;%= expression %&gt;&lt;%
  3. {{expression}} 替换所有出现的&lt;%- expression %&gt;(包括在第1 步之前已经存在的)。
  4. {{{expression}}} 替换所有出现的&lt;%= expression %&gt;(包括在第2 步之前已经出现的)。
  5. 如果您在任何地方发现var name = otherName.propertyName 形式的别名(exceptfor (...) 语句的括号内),请用otherName.propertyName 替换name,只要此变量在范围内并删除变量。 不要for (...) 内的循环变量执行此操作;这些在第 7 步中被替换。
  6. 如果您发现&lt;% if (condition1) { %&gt;...&lt;% } else if (condition2) { %&gt;...&lt;% } else ... if (conditionN) { %&gt;...&lt;% } else { %&gt;...&lt;% } %&gt; 形式的任何模式,请从最后一个最里面的块开始,然后从那里向外工作,如下所示:
    • 将最后的 &lt;% } else { %&gt; 替换为 {{else}}(Handlebars 认为这是一个特殊的符号)。
    • 将最终中间体&lt;% } else if (conditionN) { %&gt;...&lt;% } %&gt; 替换为{{else}}{{#if conditionN }}...{{/if}}&lt;% } %&gt;。重复此步骤,直到不再有else if。注意最后的&lt;% } %&gt; 保持原位;您将为每个中间 else if 在其前面插入一个额外的 {{/if}}
    • 将最外层的&lt;% if (condition1) { %&gt;...&lt;% } %&gt; 替换为{{#if condition1}}...{{/if}}。这一次,最后的&lt;% } %&gt; 消失了。
  7. 替换循环,再次从最里面的表达式开始,然后从那里向外工作:
    • 用符号{{#each objectName}}...{{/each}} 替换&lt;% _.each(objectName, function(valueName, keyName, collectionName) { %&gt;...&lt;% }) %&gt; 形式的对象上的函数循环。检查... 中嵌套的{{}}/{{{}}}/{{#if}}/... 标签是否出现keyNamecollectionNameobjectName,并将它们替换为@key4 和@987654分别为..(这不是错字;collectionNameobjectName 都应替换为..,因为它们指的是同一个对象)。请注意,在 Underscore 版本中传递给 _.each 的函数可能需要更少的参数,在这种情况下,collectionName 甚至 keyName 可能不存在;无论如何,替换都是一样的。
    • 用符号{{#each arrayName}}...{{/each}} 替换&lt;% _.each(arrayName, function(valueName, indexName, collectionName) { %&gt;...&lt;% }) %&gt; 形式的数组上的函数循环。检查嵌套的{{}}/{{{}}}/{{#if}}/... 中的indexNamecollectionNamearrayName 标记,并用@index4 和@987654 替换它们.. 分别。同样,在 Underscore 版本中传递给 _.each 的函数可能需要更少的参数;无论如何,替换都是一样的。
    • 用符号{{#each objectName}}...{{/each}} 替换&lt;% for (var keyName in objectName) { %&gt;...&lt;% } %&gt; 形式的对象上的过程循环。检查嵌套{{}}/{{{}}}/{{#if}}/... 内的keyNameobjectName[keyName]objectName 标记,并将它们替换为@key4 和@987654 ..
    • 用符号{{#each arrayName}}...{{/each}} 替换&lt;% for (var indexName = 0; indexName &lt; arrayName.length; ++indexName) { %&gt;...&lt;% } %&gt; 形式的数组上的过程循环。检查嵌套{{}}/{{{}}}/{{#if}}/... 内的indexNamearrayName[indexName]arrayName 标记,并用@index4 和@987654 替换它们..
  8. 修复表达式语法:
    • 如果您在上一步中创建了 ...propertyName 形式的表达式,其中前两个句点 .. 最初是一个名称(objectNamearrayName,如步骤 7 所述),请将其替换为 @ 987654457@。这种形式的路径可能更长,例如../../propertyName
    • name[index1][index2] 形式的子表达式应转换为 name.[index].[index2](注意句点)。
  9. 检查您翻译的所有expressions 和conditions 是否可以由Handlebars 按原样评估。根据经验,Handlebars 只能直接评估当前上下文的(嵌套)属性名称(例如 keyNamekeyName.subProperty)以及它识别的一些特殊符号,例如 @key@index、@987654467 @、this..。使用helpers 来评估不仅仅是某些对象名称和@root.. 所需的锚名称的表达式:
    • 请注意,this.propertyName 仅等同于 propertyName,因为 this 指的是当前上下文。
    • 当将像{a: foo, b: {c: baz}} 这样的对象传递给模板时,这个最外层对象的属性总是可以用@root.a@root.b.c 等来引用。请注意,该对象可能已在原始 Underscore 模板中指定了自己的名称;在这种情况下,此名称本身可以替换为 @root
    • .. 用于引用循环内的父上下文,正如我们在步骤 7-8 中看到的那样。有时,原始 Underscore 模板中的循环可能会直接通过关闭父上下文的属性来引用它;在这种情况下,您可以根据需要在该属性的名称前加上 ../ 前缀,以帮助 Handlebars 找到正确的属性。
  10. 在之前的转换之后,您可能有剩余的空&lt;% %&gt; 标签;这些可以安全移除。

如果在上述步骤之后,您的模板中仍然有 &lt;% code %&gt; 符号,或者 Handlebars 无法计算的表达式,您可能需要使用 Handlebars 语言中的其他工具或创建特殊的解决方法。如果你很倒霉,模板根本无法翻译,但大多数时候还是有办法的。

演示:您的模板

在此处重复您的问题中的下划线模板:

<p id="shipping-rates-feedback" <% if (success) { %> class="success" <% } else { %> class="error" <% } %>>
<% if (success) { %>
  <% if (rates.length > 1) { %>
  There are <%- rates.length %> shipping rates available for <%- address %>, starting at <%= rates[0].price %>.
  <% } else if (rates.length == 1) { %>
  There is one shipping rate available for <%- address %>.
  <% } else { %>
  We do not ship to this destination.
  <% } %>
<% } else { %>
  <%- errorFeedback %>
<% } %>
</p>
<ul id="shipping-rates">
  <% for (var i=0; i<rates.length; i++) { %>
  <li><%- rates[i].name %> at <%= rates[i].price %></li>
  <% } %>
</ul>

按照上述算法,使用表达式rates.[1]而不是rates.length &gt; 1(因为Handlebars无法评估开箱即用的比较),我们成功获得了以下Handlebars模板:

<p id="shipping-rates-feedback" {{#if success}} class="success" {{else}} class="error" {{/if}}>
{{#if success}}
  {{#if rates.[1]}}
  There are {{rates.length}} shipping rates available for {{address}}, starting at {{{rates.[0].price}}}.
  {{else}}{{#if rates}}
  There is one shipping rate available for {{address}}.
  {{else}}
  We do not ship to this destination.
  {{/if}}{{/if}}
{{else}}
  {{errorFeedback}}
{{/if}}
</p>
<ul id="shipping-rates">
  {{#each rates}}
  <li>{{name}} at {{{price}}}</li>
  {{/each}}
</ul>

您可能还会发现其他需要翻译的模板。您可以对其他模板采用相同的方法。

最后一点:嵌入在 HTML 中的模板

您的主题在页面中包含具有以下符号的模板。

<script id="shipping-calculator-response-template" type="text/template">
    // the template
</script>

重要的是要认识到,尽管这是一个&lt;script&gt; 标记,但浏览器实际上并没有将内容解释为脚本。相反,因为标签有一个浏览器不知道的type,浏览器只是将标签原样留在 DOM 中并继续解释下一个元素。这是在 HTML 页面中嵌入任意文本数据的常用技巧,以便稍后可以由脚本获取,而无需用户查看。在这种特殊情况下,您的 JavaScript 中的某处会执行类似的操作

var templateText = document.querySelector('#shipping-calculator-response-template').textContent;

然后将templateText 传递给Handlebars 以便对其进行处理。这也是为什么用 Underscore 替换 Handlebars 并不能解决您的问题的原因;该脚本仍会尝试将模板传递给 Handlebars。最后,在您的特定情况下,可能不需要放回下划线引用。

【讨论】:

  • 我将旧的 t'plate 脚本替换为新脚本,并将对下划线的引用替换为对车把的引用。执行时不显示任何文本,但我得到一个控制台消息:未捕获的类型错误:_.template 不是 _render 的函数(jquery.cart.min.js?v=10050315836398378908:11)。我猜 jquery.cart.min.js 也需要修改,还是我的替换有错误?我应该将 jquery.cart.min.js 的完整成绩单添加到 Q 中吗?
  • 我尝试将 jquery.cart.min.js 更改为较新主题的版本。执行时会显示:“有适用于 的运费,从 } 开始。有一种适用于 的运费。我们不运送到这个目的地。在 }”没有控制台错误。
  • 这听起来像是传递给 Handlebars 的数据中的属性名称已更改。最好的办法是找到调用 Handlebars 的代码片段以呈现模板并设置断点以了解发生了什么。
  • 虽然,转念一想,令人惊讶的是 if/else if/else 的所有三个分支都被渲染了。不看代码很难知道发生了什么。
猜你喜欢
  • 2017-02-08
  • 1970-01-01
  • 1970-01-01
  • 2014-11-12
  • 1970-01-01
  • 1970-01-01
  • 2013-10-23
  • 1970-01-01
  • 2012-11-13
相关资源
最近更新 更多