diff --git a/docs/source/tags/include.md b/docs/source/tags/include.md
index 19c46a243d..02e88265cb 100644
--- a/docs/source/tags/include.md
+++ b/docs/source/tags/include.md
@@ -53,12 +53,12 @@ When filename is specified as literal string, it supports Liquid output and filt
```
{% note info Escaping %}
-In LiquidJS, `"` within quoted string literals need to be escaped. Adding a slash before the quote, e.g. `\"`. Using Jekyll-like filenames can make this easier, see below.
+In LiquidJS, `"` within quoted string literals need to be escaped by adding a slash before the quote, e.g. `\"`. Using Jekyll-like filenames can make this easier, see below.
{% endnote %}
## Jekyll-like filenames
-Setting [dynamicPartials][dynamicPartials] to `false` will enable Jekyll-like includes, file names are specified as literal string. And it also supports Liquid outputs and filters.
+Setting [dynamicPartials][dynamicPartials] to `false` will enable Jekyll-like filenames, where file names are specified as literal string without surrounding quotes. Liquid outputs and filters are also supported within that, for example:
```liquid
{% include prefix/{{ page.my_variable }}/suffix %}
@@ -70,6 +70,35 @@ This way, you don't need to escape `"` in the filename expression.
{% include prefix/{{name | append: ".html"}} %}
```
+## Jekyll include
+
+[jekyllInclude][jekyllInclude] is used to enable Jekyll-like include syntax. Defaults to `false`, when set to `true`:
+
+- Filename will be static: `dynamicPartials` now defaults to `false` (instead of `true`). And you can set `dynamicPartials` back to `true`.
+- Use `=` instead of `:` to separate parameter key-values.
+- Parameters are under `include` variable instead of current scope.
+
+For example, the following template:
+
+```liquid
+{% include name.html header="HEADER" content="CONTENT" %}
+```
+
+`name.html` with following content:
+
+```liquid
+
+{{include.content}}
+```
+
+Note that we're referencing the first parameter by `include.header` instead of `header`. Will output following:
+
+```html
+
+CONTENT
+```
+
[extname]: ../api/interfaces/liquid_options_.liquidoptions.html#Optional-extname
[root]: ../api/interfaces/liquid_options_.liquidoptions.html#Optional-root
[dynamicPartials]: ../api/interfaces/liquid_options_.liquidoptions.html#dynamicPartials
+[jekyllInclude]: ../api/interfaces/liquid_options_.liquidoptions.html#jekyllInclude
diff --git a/docs/source/zh-cn/tags/include.md b/docs/source/zh-cn/tags/include.md
index f62babcbcc..934b57085a 100644
--- a/docs/source/zh-cn/tags/include.md
+++ b/docs/source/zh-cn/tags/include.md
@@ -44,5 +44,61 @@ title: Include
上面的例子中,子模板中 `product` 会保有父模板中的 `featured_product` 变量的值。
+## 输出和过滤器
+
+文件名为字符串字面量时,支持 Liquid 输出和过滤器。在拼接文件名时很方便:
+
+```liquid
+{% include "prefix/{{name | append: \".html\"}}" %}
+```
+
+{% note info 转义 %}
+字符串字面量里的 `"` 需要转义为 `\"`,使用静态文件名可以避免这个问题,见下面的 Jekyll-like 文件名。
+{% endnote %}
+
+## Jekyll-like 文件名
+
+设置 [dynamicPartials][dynamicPartials] 为 `false` 来启用 Jekyll-like 文件名,这时文件名不需要用引号包含,会被当作字面量处理。 这样的字符串里面仍然支持 Liquid 输出和过滤器,例如:
+
+```liquid
+{% include prefix/{{ page.my_variable }}/suffix %}
+```
+
+这样文件名里的 `"` 就不用转义了。
+
+```liquid
+{% include prefix/{{name | append: ".html"}} %}
+```
+
+## Jekyll include
+
+[jekyllInclude][jekyllInclude] 用来启用 Jekyll-like include 语法。默认为 `false`,当设置为 `true` 时:
+
+- 默认启用静态文件名:`dynamicPartials` 的默认值变为 `false`(而非 `true`)。但你也可以把它设置回 `true`。
+- 参数的键和值之间由 `=` 分隔(本来是 `:`)。
+- 参数放到了 `include` 变量下,而非当前作用域。
+
+例如下面的模板:
+
+```liquid
+{% include name.html header="HEADER" content="CONTENT" %}
+```
+
+其中 `name.html` 的内容是:
+
+```liquid
+
+{{include.content}}
+```
+
+注意我们通过 `include.header` 引用第一个参数,而不是 `header`。输出如下:
+
+```html
+
+CONTENT
+```
+
[extname]: ../../api/interfaces/liquid_options_.liquidoptions.html#Optional-extname
[root]: ../../api/interfaces/liquid_options_.liquidoptions.html#Optional-root
+[dynamicPartials]: ../../api/interfaces/liquid_options_.liquidoptions.html#dynamicPartials
+[jekyllInclude]: ../../api/interfaces/liquid_options_.liquidoptions.html#jekyllInclude
diff --git a/src/builtin/tags/include.ts b/src/builtin/tags/include.ts
index fd1538a41d..a8d026983e 100644
--- a/src/builtin/tags/include.ts
+++ b/src/builtin/tags/include.ts
@@ -20,7 +20,7 @@ export default {
} else tokenizer.p = begin
} else tokenizer.p = begin
- this.hash = new Hash(tokenizer.remaining())
+ this.hash = new Hash(tokenizer.remaining(), this.liquid.options.jekyllInclude)
},
render: function * (ctx: Context, emitter: Emitter) {
const { liquid, hash, withVar } = this
@@ -34,7 +34,7 @@ export default {
const scope = yield hash.render(ctx)
if (withVar) scope[filepath] = evalToken(withVar, ctx)
const templates = yield liquid._parsePartialFile(filepath, ctx.sync, this['currentFile'])
- ctx.push(scope)
+ ctx.push(ctx.opts.jekyllInclude ? { include: scope } : scope)
yield renderer.renderTemplates(templates, ctx, emitter)
ctx.pop()
ctx.restoreRegister(saved)
diff --git a/src/liquid-options.ts b/src/liquid-options.ts
index 0603e3e23d..fe98d86c3c 100644
--- a/src/liquid-options.ts
+++ b/src/liquid-options.ts
@@ -17,6 +17,8 @@ export interface LiquidOptions {
layouts?: string | string[];
/** Allow refer to layouts/partials by relative pathname. To avoid arbitrary filesystem read, paths been referenced also need to be within corresponding root, partials, layouts. Defaults to `true`. */
relativeReference?: boolean;
+ /** Use jekyll style include, pass parameters to `include` variable of current scope. Defaults to `false`. */
+ jekyllInclude?: boolean;
/** Add a extname (if filepath doesn't include one) before template file lookup. Eg: setting to `".html"` will allow including file by basename. Defaults to `""`. */
extname?: string;
/** Whether or not to cache resolved templates. Defaults to `false`. */
@@ -93,6 +95,7 @@ export interface NormalizedFullOptions extends NormalizedOptions {
partials: string[];
layouts: string[];
relativeReference: boolean;
+ jekyllInclude: boolean;
extname: string;
cache: undefined | Cache>;
jsTruthy: boolean;
@@ -122,6 +125,7 @@ export const defaultOptions: NormalizedFullOptions = {
layouts: ['.'],
partials: ['.'],
relativeReference: true,
+ jekyllInclude: false,
cache: undefined,
extname: '',
fs: fs,
@@ -161,7 +165,7 @@ export function normalize (options: LiquidOptions): NormalizedFullOptions {
else cache = options.cache ? new LRU(1024) : undefined
options.cache = cache
}
- options = { ...defaultOptions, ...options }
+ options = { ...defaultOptions, ...(options.jekyllInclude ? { dynamicPartials: false } : {}), ...options }
if (!options.fs!.dirname && options.relativeReference) {
console.warn('[LiquidJS] `fs.dirname` is required for relativeReference, set relativeReference to `false` to suppress this warning, or provide implementation for `fs.dirname`')
options.relativeReference = false
diff --git a/src/parser/tokenizer.ts b/src/parser/tokenizer.ts
index 511d7af62a..f0399c1a38 100644
--- a/src/parser/tokenizer.ts
+++ b/src/parser/tokenizer.ts
@@ -233,16 +233,16 @@ export class Tokenizer {
return new IdentifierToken(this.input, begin, this.p, this.file)
}
- readHashes () {
+ readHashes (jekyllStyle?: boolean) {
const hashes = []
while (true) {
- const hash = this.readHash()
+ const hash = this.readHash(jekyllStyle)
if (!hash) return hashes
hashes.push(hash)
}
}
- readHash (): HashToken | undefined {
+ readHash (jekyllStyle?: boolean): HashToken | undefined {
this.skipBlank()
if (this.peek() === ',') ++this.p
const begin = this.p
@@ -251,7 +251,8 @@ export class Tokenizer {
let value
this.skipBlank()
- if (this.peek() === ':') {
+ const sep = jekyllStyle ? '=' : ':'
+ if (this.peek() === sep) {
++this.p
value = this.readValue()
}
diff --git a/src/template/tag/hash.ts b/src/template/tag/hash.ts
index e7fe3f34a8..27394528eb 100644
--- a/src/template/tag/hash.ts
+++ b/src/template/tag/hash.ts
@@ -16,9 +16,9 @@ export interface HashValue {
*/
export class Hash {
hash: HashValue = {}
- constructor (markup: string) {
+ constructor (markup: string, jekyllStyle?: boolean) {
const tokenizer = new Tokenizer(markup, {})
- for (const hash of tokenizer.readHashes()) {
+ for (const hash of tokenizer.readHashes(jekyllStyle)) {
this.hash[hash.name.content] = hash.value
}
}
diff --git a/test/integration/builtin/tags/include.ts b/test/integration/builtin/tags/include.ts
index bae15a24b9..800bb0905f 100644
--- a/test/integration/builtin/tags/include.ts
+++ b/test/integration/builtin/tags/include.ts
@@ -248,4 +248,44 @@ describe('tags/include', function () {
return expect(html).to.equal('Xchild with redY')
})
})
+
+ describe('Jekyll include', function () {
+ before(function () {
+ liquid = new Liquid({
+ root: '/',
+ extname: '.html',
+ jekyllInclude: true
+ })
+ })
+ it('should support Jekyll style include', function () {
+ mock({
+ '/current.html': '{% include bar/foo.html content="FOO" %}',
+ '/bar/foo.html': '{{include.content}}-{{content}}'
+ })
+ const html = liquid.renderFileSync('/current.html')
+ return expect(html).to.equal('FOO-')
+ })
+ it('should support multiple parameters', function () {
+ mock({
+ '/current.html': '{% include bar/foo.html header="HEADER" content="CONTENT" %}',
+ '/bar/foo.html': '{{include.header}}
{{include.content}}'
+ })
+ const html = liquid.renderFileSync('/current.html')
+ return expect(html).to.equal('HEADER
CONTENT')
+ })
+ it('should support dynamicPartials=true', function () {
+ mock({
+ '/current.html': '{% include "bar/foo.html" content="FOO" %}',
+ '/bar/foo.html': '{{include.content}}-{{content}}'
+ })
+ liquid = new Liquid({
+ root: '/',
+ extname: '.html',
+ jekyllInclude: true,
+ dynamicPartials: true
+ })
+ const html = liquid.renderFileSync('/current.html')
+ return expect(html).to.equal('FOO-')
+ })
+ })
})