--- title: KumaScript slug: MDN/Tools/KumaScript tags: - Kuma - KumaScript - MDN Meta - NeedsContent - Site-wide - ガイド - ツール translation_of: MDN/Tools/KumaScript ---
MDN を構築している Kuma プラットフォームにおいて、 wiki 上のコンテンツを自動化するテンプレートシステムは KumaScript と呼ばれています。 KumaScript はサーバー側 JavaScript で実行されており、 Node.js を使用して実装されています。この記事は、 KumaScript の使い方について基本的な情報を提供するものです。
詳細な概要や KumaScript の Q&A については、 MDN 開発チームの KumaScript Fireside Chat を見ていてください (ミーティングはビデオで10分で開始します)。 KumaScript は、以前の MDN で使われていたプラットフォームであり、 MindTouch のためのテンプレート言語であった DekiScript を置き換えました。
KumaScript は MDN で埋め込み JavaScript テンプレートに利用されています。これらのテンプレートは MDN の筆者ならば誰でも文書内で、マクロを使用して呼び出すことができます。
KumaScript のスクリプトはテンプレートであり、それぞれのテンプレートは Github の KumaScript リポジトリの macros ディレクトリに格納されているファイルです。テンプレートは以下のようなものです。
<% for (var i = 0; i < $0; i++) { %> Hello #<%= i %> <% } %>
テンプレートは wiki コンテンツのどこからでもマクロで呼び出すことができます。マクロは次のようなものです。
\{{hello(3)}}
マクロの出力は以下のようなものです。
Hello #0 Hello #1 Hello #2
KumaScript のテンプレートは、以下のようにドキュメントのコンテンツの中でマクロとして呼び出すことができます。
\{{templateName("arg0", "arg1", ..., "argN")}}
マクロの構文は、以下の規則に基づいて構成されます。
\{{
より始まり、 \}}
で終わります。半実験的な機能 (動作保証なし) として、以下のように引数が一つだけの場合は、引数に JSON オブジェクトを指定できます。
\{{templateName({ "Alpha": "one", "Beta": ["a", "b", "c"], "Foo": "https:\/\/mozilla.org\/" }
)}}
このマクロからのデータは、テンプレートコード内で $0
引数のオブジェクトとして利用できます (例えば、 $0.Alpha
, $0.Beta
, $0.Foo
)。これにより、引数の単純なリストで実現することが難しい又は不可能な複雑なデータ構造を、マクロ引数で表すことができます。
なお、この引数の形はとても繊細です。 — 正確に JSON の構文に従っていなければならず、間違いを犯しやすいエスケープ文字の要件が求められます (例えば、すべてのスラッシュをエスケープするなど)。疑わしい場合は、 JSON をバリデーターに掛けてみてください。
"\{{
" という文字の並びはマクロの開始を示すため、実際にページ内で "\{{
" および "}}
" を使用したい場合は問題になります。おそらく DocumentParsingError
メッセージが発生するでしょう。
この場合、 \\{
のように最初の中括弧をバックスラッシュでエスケープすることができます。
それぞれの KumaScript テンプレートは、 KumaScript の macros ディレクトリに格納されているファイルです。これらのファイルは GitHub 上の何らかのオープンソースプロジェクトのファイルとして作成したり編集したりします (詳しくは the KumaScript README をご覧ください)。
KumaScript テンプレートは、いくつかの簡単な規則で、組込み JavaScript テンプレートエンジンによって処理されます。
$0
, $1
, $2
, などのような変数として利用できます。引数のリスト全体は、テンプレート内で変数 arguments
として利用できます。<%= expr %>
— 出力される前に HTML エスケープされた JavaScript の式の値 (<
および >
は <
および >
となる)<%- expr %>
— エスケープされない JavaScript の式の値 (マークアップを動的に構築したり、マークアップを含むことができる他のテンプレートの結果に使用してください)<%
と %>
の間は JavaScript として解釈される。すなわち、ループ、条件文、関数などを含めることが可能。<% %>
ブロック内は出力ストリームに含まれない。ブロック内の変数などをブロック外で使用する場合は、以下のように <%= %>
を用いる。
<% for (var i = 0; i < $0; i++) { %> Hello #<%= i %> <% } %>
Note how the JavaScript code is contained in <% ... %>
, and output happens in the space between %> ... <%
. The for
loop in JS can begin with one <% %>
block, transition to output mode, and finish up in a second <% %>
JS block.
マクロのリストと、マクロが MDN でどの様に使用されているかマクロダッシュボードで確認することができます。
KumaScript には前章までに紹介したもの以外に、より高度な機能もあります。
When the wiki makes a call to the KumaScript service, it passes along some context on the current document that KumaScript makes available to templates as variables:
env.path
env.url
env.id
env.files
env.review_tags
env.locale
env.title
env.slug
env.tags
env.modified
env.cache_control
Cache-Control
header sent in the request for the current wiki document, useful in deciding whether to invalidate caches個々の file オブジェクトは以下の様なフィールドを持ちます。
title
description
filename
size
author
mime
url
The env.tags
and env.review_tags
variables return arrays of tags. You can work with these in many ways, of course, but here are a couple of suggestions.
You can look to see if a specific tag exists on a page like this:
if (env.tags.indexOf("tag") != −1) { // The page has the tag "tag" }
You can also iterate over all the tags on a page, like this:
env.tag.forEach(function(tag) { // do whatever you need to do, such as: if (tag.indexOf("a") == 0) { // this tag starts with "a" - woohoo! } });
KumaScript offers some built-in methods and APIs for KumaScript macros. Macros can also use module.exports
to export new API methods.
KumaScript では、ビルトインのユーティリティ API だけでなく、wiki 文書として編集可能なモジュール内で新規の API を定義する機能も提供されています。
This manually-maintained documentation is likely to fall out of date with the code. With that in mind, you can always check out the latest state of built-in APIs in the KumaScript source. But here is a selection of useful methods exposed to templates:
md5(string)
template("name", ["arg0", "arg1", ..., "argN"])
Executes and returns the result of the named template with the given list of parameters.
Example: <%- template("warning", ["foo", "bar", "baz"]) %>
.
Example using the DOMxRef
macro: <%- template("DOMxRef", ["Event.bubbles", "bubbles"]) %>
.
This is a JavaScript function. So, if one of the parameters is an arg variable like $2, do not put it in quotes. Like this: <%- template("warning", [$1, $2, "baz"]) %>
. If you need to call another template from within a block of code, do not use <%
... %>
. Example: myvar = "<li>" + template("LXRSearch", ["ident", "i", $1]) + "</li>";
require(name)
module.exports
in the template is returned.<% const my_module = require('MyModule'); %>
.cacheFn(key, timeout, function_to_cache)
env.cache_control
to invalidate cache on no-cache
, which can be sent by a logged-in user hitting shift-refresh.request
mikeal/request
, a library for making HTTP requests. Using this module in KumaScript templates is not yet very friendly, so you may want to wrap usage in module APIs that simplify things.log.debug(string)
There are a set of built in API that are automatically loaded and made available to every template by the environment script.
For the most part, these attempt to provide stand-ins for legacy DekiScript features to ease template migration. But, going forward, these can be used to share common variables and methods between templates:
kuma.*
- KumaMDN.*
- MDN:Commonpage.*
- DekiScript:Pagestring.*
- DekiScript:Stringuri.*
- DekiScript:Uriweb.*
- DekiScript:Webwiki.*
- DekiScript:WikiYou can see the most up to date list of methods under kuma
from the KumaScript source code, but here are a few:
kuma.inspect(object)
log.debug()
. See also: node.js util.inspect()
.kuma.htmlEscape(string)
&, <, >, "
to &, <, >, "
, respectively.kuma.url
url
module.kuma.fetchFeed(url)
InsertFeedLinkList
Using the built-in require()
method, you can load a template as a module to share common variables and methods between templates. A module can be defined in a template like this:
<% module.exports = { add: function (a, b) { return a + b; } } %>
Assuming this template is saved under https://github.com/mdn/kumascript/tree/master/macros as MathLib.ejs
, you can use it in another template like so:
<% var math_lib = require("MathLib"); %> The result of 2 + 2 = <%= math_lib.add(2, 2) %>
このテンプレートの出力は以下の様になるでしょう。
The result of 2 + 2 = 4
A useful tip when debugging. You can use the log.debug()
method to output text to the scripting messages area at the top of the page that's running your template. Note that you need to be really sure to remove these when you're done debugging, as they're visible to all users! To use it, just do something like this:
<%- log.debug("Some text goes here"); %>
You can, of course, create more complex output using script code if it's helpful.
KumaScript templates are heavily cached to improve performance. For the most part, this works great to serve up content that doesn't change very often. But, as a logged-in user, you have two options to force a page to be regenerated, in case you notice issues with scripting:
Cache-Control: max-age=0
header.Cache-Control: no-cache
header.When using templates, it's common to open the template's code in a browser window to review the comments at the top, which are used to document the template, its parameters, and how to use it properly. To quickly access templates, you can create a Firefox search keyword, which gives you a shortcut you can type in the URL box to get to a template more easily.
To create a search keyword, open the bookmarks window by choosing "Show all bookmarks" in the Bookmarks menu, or by pressing Control+Shift+B (Command+Shift+B on Mac). Then from the utility ("Gear") menu in the Library window that appears, choose "New Bookmark...".
This causes the bookmark editing dialog to appear. Fill that out as follows:
https://github.com/mdn/kumascript/blob/master/macros/%s
The resulting dialog looks something like this:
Then click the "Add" button to save your new search keyword. From then on, typing your keyword, then a space, then the name of a macro will open that macro in your current tab. So if you used "t" as the keyword, typing t ListSubpages will show you the page at {{TemplateLink("ListSubpages")}}.
This section will list examples of common patterns for templates used on MDN, including samples of legacy DekiScript templates and their new KumaScript equivalents.
It bears repeating: To force templates used on a page to be reloaded after editing, hit Shift-Reload. Just using Reload by itself will cause the page contents to be regenerated, but using cached templates and included content. A Shift-Reload is necessary to invalidate caches beyond just the content of the page itself.
ページ読込時に、時折、このようなスクリプティング・メッセージが表示される事があります。
Kumascript service failed unexpectedly: <class 'httplib.BadStatusLine'>
これはおそらく、 KumaScript サービスの一時的な障害です。ページの再読込みでこの問題が解決する事があります。これで問題が解決しない場合はスーパーリロード(shift + F5)を試してみて下さい。これらの試みの後もエラーが解消されない場合は、file an IT bug for Mozilla Developer Network to ask for an investigation.
幾つかのページで、以下の様なスクリプトエラーメッセージを見かける場合があるでしょう。
Syntax error at line 436, column 461: Expected valid JSON object as the parameter of the preceding macro but...
その様なページを編集状態にした場合、ページ下部に以下の様なマクロが見つかるかもしれません。
\{{ wiki.languages({ "zh-tw": "zh_tw/Core_JavaScript_1.5_教學/JavaScript_概要", ... }
) }}
To fix the problem, just delete the macro. Or, replace the curly braces on either side with HTML comments <!-- -->
to preserve the information, like so:
<!-- wiki.languages({ "zh-tw": "zh_tw/Core_JavaScript_1.5_教學/JavaScript_概要", ... }) -->
Because Kuma supports localization differently, these macros aren't actually needed any more. But, they've been left intact in case we need to revisit the relationships between localized pages. Unfortunately, it seems like migration has failed to convert some of them properly.
In KumaScript, the locale of the current document is exposed as an environment variable:
const lang = env.locale;
The env.locale
variable should be reliable and defined for every document.
You can read the contents of an attached file by using the mdn.getFileContent()
function, like this:
<% let contents = mdn.getFileContent(fileUrl); // ... do stuff with the contents ... %>
or
<%- mdn.getFileContent(fileObject); %>
In other words, you may specify either the URL of the file to read or as a file object. The file objects for a page can be accessed through the array env.files
. So, for example, to embed the contents of the first file attached to the article, you can do this:
<%- mdn.getFileContent(env.files[0]); %>
Note: You probably don't want to try to embed the contents of a non-text file this way, as the raw contents would be injected as text. This is meant to let you access the contents of text attachments.
If the file isn't found, an empty string is returned. There is currently no way to tell the difference between an empty file and a nonexistent one. But if you're putting empty files on the wiki, you're doing it wrong.
Templates are not translated like wiki pages, rather any single template might be used for any number of locales.
So the main way to output content tailored to the current document locale is to pivot on the value of env.locale
. There are many ways to do this, but a few patterns are common in the conversion of legacy DekiScript templates:
The KumaScript equivalent of this can be achieved with simple if/else blocks, like so:
<% if ("fr" == env.locale) { %> <%- template("CSSRef") %> « <a href="/fr/docs/Référence_CSS/Extensions_Mozilla">Référence CSS: Extensions Mozilla</a> <% } else if ("ja" == env.locale) { %> <%- template("CSSRef") %> « <a href="/ja/docs/CSS_Reference/Mozilla_Extensions">CSS リファレンス: Mozilla 拡張仕様</a> <% } else if ("pl" == env.locale) { %> <%- template("CSSRef") %> « <a href="/pl/docs/Dokumentacja_CSS/Rozszerzenia_Mozilli">Dokumentacja CSS: Rozszerzenia Mozilli</a> <% } else if ("de" == env.locale) { %> <%- template("CSSRef") %> « <a href="/de/docs/CSS_Referenz/Mozilla_CSS_Erweiterungen">CSS Referenz: Mozilla Erweiterungen</a> <% } else { %> <%- template("CSSRef") %> « <a href="/en-US/docs/CSS_Reference/Mozilla_Extensions">CSS Reference: Mozilla Extensions</a> <% } %>
Depending on what text editor is your favorite, you may be able to copy & paste from the browser-based editor and attack this pattern with a series of search/replace regexes to get you most of the way there.
My favorite editor is MacVim, and a series of regexes like this does the bulk of the work with just a little manual clean up following:
%s#<span#^M<span#g %s#<span lang="\(.*\)" .*>#<% } else if ("\1" == env.locale) { %>#g %s#<span class="script">template.CSSxRef(#<%- template("CSSxRef", [# %s#)</span> </span>#]) %>
Your mileage may vary, and patterns change slightly from template to template. That's why the migration script was unable to just handle this automatically, after all.
Rather than switch between full chunks of markup, you can define a set of strings, switch them based on locale, and then use them to fill in placeholders in a single chunk of markup:
<% var s_title = 'Firefox for Developers'; switch (env.locale) { case 'de': s_title = "Firefox für Entwickler"; break; case 'fr': s_title = "Firefox pour les développeurs"; break; case 'es': s_title = "Firefox para desarrolladores"; break; }; %> <span class="title"><%= s_title %></span>
mdn.localString()
A recent addition to the MDN:Common
module is mdn.localString()
, used like this:
<% var s_title = mdn.localString({ "en-US": "Firefox for Developers", "de": "Firefox für Entwickler", "es": "Firefox para desarrolladores" }); %> <span class="title"><%= s_title %></span>
これは switch 文による分岐よりも簡潔であり、テンプレート内で一種類や二種類程度の文字列のみの翻訳が必要なケースなどでは、switch 文より良い選択となるかもしれません。ロケールにより多くの文字列を切り替える必要がある場合(※例: Template:CSSRef | MDN)は、switch 文 を用いた方が良いでしょう。 if 文を用いた場合が良い場合もあります。適切なものを選択して御利用下さい。
オブジェクトに適切なロケールが無い場合、 "en-US" の値が初期値として使用されます。