├── test
├── expected
│ ├── custom_options
│ └── default_options
├── fixtures
│ ├── 123
│ ├── 123-tpl.js
│ └── 123.tpl.js
└── tpl_compiler_test.js
├── .gitignore
├── .jshintrc
├── LICENSE-MIT
├── package.json
├── tasks
├── template.js.tpl
└── tpl_compiler.js
├── Gruntfile.js
└── README.md
/test/expected/custom_options:
--------------------------------------------------------------------------------
1 | Testing: 1 2 3 !!!
--------------------------------------------------------------------------------
/test/expected/default_options:
--------------------------------------------------------------------------------
1 | Testing, 1 2 3.
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | npm-debug.log
3 | tmp
4 | .idea
--------------------------------------------------------------------------------
/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "curly": true,
3 | "eqeqeq": true,
4 | "immed": true,
5 | "latedef": true,
6 | "newcap": true,
7 | "noarg": true,
8 | "sub": true,
9 | "undef": true,
10 | "boss": true,
11 | "eqnull": true,
12 | "node": true
13 | }
14 |
--------------------------------------------------------------------------------
/test/fixtures/123:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
${title}
6 |
7 | {@each prods as prod}
8 | - ${prod.name} - ${prod.price}
9 | {@/each}
10 |
11 |
12 |
13 |
14 |
15 |
16 | {@each moreList as item}
17 | - ${item.title}
18 | - ${item.desc}
19 | {@/each}
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/LICENSE-MIT:
--------------------------------------------------------------------------------
1 | Copyright (c) 2014 弘树
2 |
3 | Permission is hereby granted, free of charge, to any person
4 | obtaining a copy of this software and associated documentation
5 | files (the "Software"), to deal in the Software without
6 | restriction, including without limitation the rights to use,
7 | copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | copies of the Software, and to permit persons to whom the
9 | Software is furnished to do so, subject to the following
10 | conditions:
11 |
12 | The above copyright notice and this permission notice shall be
13 | included in all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22 | OTHER DEALINGS IN THE SOFTWARE.
23 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "grunt-tpl-compiler",
3 | "description": "Grunt plugin for juicer-based template compile to kissy module",
4 | "version": "0.1.6",
5 | "homepage": "https://github.com/dickeylth/grunt-tpl-compiler",
6 | "author": {
7 | "name": "弘树",
8 | "email": "tiehang.lth@alibaba-inc.com",
9 | "url": "http://dickeylth.github.io"
10 | },
11 | "repository": {
12 | "type": "git",
13 | "url": "git://github.com/dickeylth/grunt-tpl-compiler.git"
14 | },
15 | "bugs": {
16 | "url": "https://github.com/dickeylth/grunt-tpl-compiler/issues"
17 | },
18 | "licenses": [
19 | {
20 | "type": "MIT",
21 | "url": "https://github.com/dickeylth/grunt-tpl-compiler/blob/master/LICENSE-MIT"
22 | }
23 | ],
24 | "engines": {
25 | "node": ">= 0.8.0"
26 | },
27 | "scripts": {
28 | "test": "grunt test"
29 | },
30 | "devDependencies": {
31 | "grunt-contrib-jshint": "~0.6.0",
32 | "grunt-contrib-clean": "~0.4.0",
33 | "grunt-contrib-nodeunit": "~0.2.0",
34 | "grunt": "~0.4.2"
35 | },
36 | "peerDependencies": {
37 | "grunt": "~0.4.2 || ^1.0.0"
38 | },
39 | "keywords": [
40 | "gruntplugin"
41 | ],
42 | "dependencies": {
43 | "ast-query": "^0.2.4",
44 | "cheerio": "^0.17.0",
45 | "juicer": "^0.6.5-stable-p2"
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/tasks/template.js.tpl:
--------------------------------------------------------------------------------
1 | /**
2 | * KISSY Template Module for ${page}
3 | */
4 | KISSY.add(function (S, Juicer){
5 |
6 | "use strict";
7 |
8 | /**
9 | * 维护所有用到的模板
10 | * @class Template
11 | * @constructor
12 | */
13 |
14 | var templates = {
15 | {@each templates as template, idx}
16 |
17 | ${template.key}: '$${template.value}'{@if idx != (templates.length - 1)},{@/if}
18 |
19 | {@/each}
20 | };
21 |
22 | return {
23 |
24 | /**
25 | * 注册模板自定义函数
26 | * @param name 需要替换的模板中用到的名字
27 | * @param fn 自定义函数
28 | */
29 | register: function(name, fn){
30 |
31 | Juicer.register(name, fn);
32 |
33 | },
34 |
35 | /**
36 | * 覆盖已有模板
37 | * @param key {String} template 模板键
38 | * @param tmpl {String} 模板
39 | */
40 | set: function(key, tmpl){
41 |
42 | templates[key] = tmpl;
43 |
44 | },
45 |
46 | /**
47 | * 获取已有模板
48 | * @param key {String} 模板Key
49 | * @returns {String} 模板内容
50 | */
51 | get: function(key){
52 |
53 | return templates[key];
54 |
55 | },
56 |
57 | /**
58 | * 根据指定的模板key和数据渲染生成html
59 | * @param key 模板的key
60 | * @param data json数据
61 | * @returns {String}
62 | */
63 | render: function(key, data){
64 |
65 | return Juicer(templates[key], data);
66 |
67 | }
68 |
69 | };
70 |
71 | }, {
72 | requires: ['gallery/juicer/1.3/index']
73 | });
--------------------------------------------------------------------------------
/test/fixtures/123-tpl.js:
--------------------------------------------------------------------------------
1 | /**
2 | * KISSY Template Module for 123
3 | */
4 | KISSY.add(function (S, Juicer) {
5 | 'use strict';
6 | /**
7 | * 维护所有用到的模板
8 | * @class Template
9 | * @constructor
10 | */
11 | var templates = {
12 | ProdList: ' ${title}
{@each prods as prod} - ${prod.name} - ${prod.price}
{@/each}
',
13 | MoreList: ' {@each moreList as item} - ${item.title}
- ${item.desc}
{@/each}
'
14 | };
15 | return {
16 | /**
17 | * 注册模板自定义函数
18 | * @param name 需要替换的模板中用到的名字
19 | * @param fn 自定义函数
20 | */
21 | register: function (name, fn) {
22 | Juicer.register(name, fn);
23 | },
24 | /**
25 | * 覆盖已有模板
26 | * @param key {String} template 模板键
27 | * @param tmpl {String} 模板
28 | */
29 | set: function (key, tmpl) {
30 | templates[key] = tmpl;
31 | },
32 | /**
33 | * 获取已有模板
34 | * @param key {String} 模板Key
35 | * @returns {String} 模板内容
36 | */
37 | get: function (key) {
38 | return templates[key];
39 | },
40 | /**
41 | * 根据指定的模板key和数据渲染生成html
42 | * @param key 模板的key
43 | * @param data json数据
44 | * @returns {String}
45 | */
46 | render: function (key, data) {
47 | return Juicer(templates[key], data);
48 | }
49 | };
50 | }, { requires: ['gallery/juicer/1.3/index'] });
--------------------------------------------------------------------------------
/test/fixtures/123.tpl.js:
--------------------------------------------------------------------------------
1 | /**
2 | * KISSY Template Module for 123
3 | */
4 | KISSY.add(function (S, Juicer) {
5 | 'use strict';
6 | /**
7 | * 维护所有用到的模板
8 | * @class Template
9 | * @constructor
10 | */
11 | var templates = {
12 | ProdList: ' ${title}
{@each prods as prod} - ${prod.name} - ${prod.price}
{@/each}
',
13 | MoreList: ' {@each moreList as item} - ${item.title}
- ${item.desc}
{@/each}
'
14 | };
15 | return {
16 | /**
17 | * 注册模板自定义函数
18 | * @param name 需要替换的模板中用到的名字
19 | * @param fn 自定义函数
20 | */
21 | register: function (name, fn) {
22 | Juicer.register(name, fn);
23 | },
24 | /**
25 | * 覆盖已有模板
26 | * @param key {String} template 模板键
27 | * @param tmpl {String} 模板
28 | */
29 | set: function (key, tmpl) {
30 | templates[key] = tmpl;
31 | },
32 | /**
33 | * 获取已有模板
34 | * @param key {String} 模板Key
35 | * @returns {String} 模板内容
36 | */
37 | get: function (key) {
38 | return templates[key];
39 | },
40 | /**
41 | * 根据指定的模板key和数据渲染生成html
42 | * @param key 模板的key
43 | * @param data json数据
44 | * @returns {String}
45 | */
46 | render: function (key, data) {
47 | return Juicer(templates[key], data);
48 | }
49 | };
50 | }, { requires: ['gallery/juicer/1.3/index'] });
--------------------------------------------------------------------------------
/test/tpl_compiler_test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var grunt = require('grunt');
4 |
5 | /*
6 | ======== A Handy Little Nodeunit Reference ========
7 | https://github.com/caolan/nodeunit
8 |
9 | Test methods:
10 | test.expect(numAssertions)
11 | test.done()
12 | Test assertions:
13 | test.ok(value, [message])
14 | test.equal(actual, expected, [message])
15 | test.notEqual(actual, expected, [message])
16 | test.deepEqual(actual, expected, [message])
17 | test.notDeepEqual(actual, expected, [message])
18 | test.strictEqual(actual, expected, [message])
19 | test.notStrictEqual(actual, expected, [message])
20 | test.throws(block, [error], [message])
21 | test.doesNotThrow(block, [error], [message])
22 | test.ifError(value)
23 | */
24 |
25 | exports.tpl_compiler = {
26 | setUp: function(done) {
27 | // setup here if necessary
28 | done();
29 | },
30 | default_options: function(test) {
31 |
32 | test.expect(1);
33 |
34 | // 处理
35 | var result = grunt.file.read('test/fixtures/123-tpl.js');
36 | test.equal(/data\-spmClick/.test(result), true, '标签属性保留驼峰');
37 |
38 | //
39 | // var actual = grunt.file.read('tmp/default_options');
40 | // var expected = grunt.file.read('test/expected/default_options');
41 | // test.equal(actual, expected, 'should describe what the default behavior is.');
42 |
43 | test.done();
44 | },
45 | custom_options: function(test) {
46 | // test.expect(1);
47 | //
48 | // var actual = grunt.file.read('tmp/custom_options');
49 | // var expected = grunt.file.read('test/expected/custom_options');
50 | // test.equal(actual, expected, 'should describe what the custom option(s) behavior is.');
51 |
52 | test.done();
53 | }
54 | };
55 |
--------------------------------------------------------------------------------
/Gruntfile.js:
--------------------------------------------------------------------------------
1 | /*
2 | * grunt-tpl-compiler
3 | * https://github.com/dickeylth/grunt-tpl-compiler
4 | *
5 | * Copyright (c) 2014 弘树
6 | * Licensed under the MIT license.
7 | */
8 |
9 | 'use strict';
10 |
11 | module.exports = function(grunt) {
12 |
13 | // Project configuration.
14 | grunt.initConfig({
15 | jshint: {
16 | },
17 |
18 | // Before generating any new files, remove any previously-created files.
19 | clean: {
20 | tests: ['tmp']
21 | },
22 |
23 | // Configuration to be run (and then tested).
24 | tpl_compiler: {
25 | default_options: {
26 | options: {
27 | ext: '-tpl',
28 | replaceEscapeMap: {
29 | '\xB0': '°'
30 | }
31 | },
32 | files: {
33 | 'tmp/default_options': ['test/fixtures/123']
34 | }
35 | },
36 | custom_options: {
37 | options: {
38 | ext: '.tpl',
39 | replaceEscapeMap: {
40 | '\xB0': '°',
41 | '<': "<"
42 | }
43 | },
44 | files: {
45 | 'tmp/custom_options': ['test/fixtures/123']
46 | }
47 | }
48 | },
49 |
50 | // Unit tests.
51 | nodeunit: {
52 | tests: ['test/*_test.js']
53 | }
54 |
55 | });
56 |
57 | // Actually load this plugin's task(s).
58 | grunt.loadTasks('tasks');
59 |
60 | // These plugins provide necessary tasks.
61 | grunt.loadNpmTasks('grunt-contrib-jshint');
62 | grunt.loadNpmTasks('grunt-contrib-clean');
63 | grunt.loadNpmTasks('grunt-contrib-nodeunit');
64 |
65 | // Whenever the "test" task is run, first clean the "tmp" dir, then run this
66 | // plugin's task(s), then test the result.
67 | grunt.registerTask('test', ['clean', 'tpl_compiler', 'nodeunit']);
68 |
69 | // By default, lint and run all tests.
70 | grunt.registerTask('default', ['test']);
71 |
72 | };
73 |
--------------------------------------------------------------------------------
/tasks/tpl_compiler.js:
--------------------------------------------------------------------------------
1 | /*
2 | * grunt-tpl-compiler
3 | * https://github.com/dickeylth/grunt-tpl-compiler
4 | *
5 | * Copyright (c) 2014 弘树
6 | * Licensed under the MIT license.
7 | */
8 |
9 | 'use strict';
10 |
11 | var fs = require('fs');
12 | var path = require('path');
13 | var program = require("ast-query");
14 | var juicer = require('juicer');
15 | var cheerio = require('cheerio');
16 |
17 | module.exports = function (grunt) {
18 |
19 | // Please see the Grunt documentation for more information regarding task
20 | // creation: http://gruntjs.com/creating-tasks
21 |
22 | grunt.registerMultiTask('tpl_compiler', 'Grunt plugin for juicer-based template compile to kissy module', function () {
23 | // Merge task-specific and/or target-specific options with these defaults.
24 | var options = this.options({
25 | ext: '-tpl'
26 | });
27 |
28 | var jsTpl = fs.readFileSync(path.join(__dirname, './template.js.tpl')).toString();
29 |
30 | // Iterate over all specified file groups.
31 | this.files.forEach(function (f) {
32 | // Concat specified files.
33 | var src = f.src.filter(function (filepath) {
34 | // Warn on and remove invalid source files (if nonull was set).
35 | if (!grunt.file.exists(filepath)) {
36 | grunt.log.warn('Source file "' + filepath + '" not found.');
37 | return false;
38 | } else {
39 | return true;
40 | }
41 | }).map(function (filepath) {
42 | // Read file source.
43 | return grunt.file.read(filepath);
44 | });
45 |
46 | // cheerio 解析模板所在 HTML 的 DOM
47 | src = src.join('');
48 | var $ = cheerio.load(src, {
49 | normalizeWhitespace: true,
50 | decodeEntities: false,
51 | lowerCaseAttributeNames: false
52 | });
53 |
54 | // 获取各个指定的模板
55 | var templates = [];
56 | $('[data-tpl]').each(function(idx, node){
57 | var tplKey = $(node).attr('data-tpl');
58 | var tplValue = $(node).html();
59 | templates.push({
60 | key: tplKey,
61 | value: tplValue
62 | });
63 | });
64 |
65 | var srcPath = f.src;
66 | if (Array.isArray(srcPath)) {
67 | srcPath = srcPath[0];
68 | }
69 |
70 | var basename = path.basename(srcPath),
71 | dirname = path.dirname(srcPath),
72 | toFileName = basename.split('.')[0] + options.ext + '.js',
73 | toFilePath = path.join(dirname, toFileName);
74 |
75 | if(!fs.existsSync(toFilePath)){
76 |
77 | // 如果是初次生成对应 js 文件
78 |
79 | // 与 模板 js 合并
80 | juicer.set('strip',false);
81 | var toJsContent = juicer(jsTpl, {
82 | templates: templates,
83 | page: basename
84 | });
85 |
86 | // Write the destination file.
87 | grunt.file.write(toFilePath, toJsContent);
88 |
89 | // Print a success message.
90 | grunt.log.writeln('File "' + toFilePath + '" created.');
91 |
92 | } else {
93 |
94 | var curJsContent = fs.readFileSync(toFilePath).toString(),
95 | template = "{{@each templates as template, idx}" +
96 | "${template.key}: '$${template.value}'" +
97 | "{@if idx != (templates.length - 1)},{@/if}" +
98 | "{@/each}}";
99 |
100 | if(curJsContent) {
101 |
102 | var tree = program(curJsContent);
103 | tree.var('templates').value(juicer(template, {
104 | templates: templates
105 | }));
106 |
107 | grunt.file.write(toFilePath, tree.toString());
108 |
109 | // Print a success message.
110 | grunt.log.writeln('File "' + toFilePath + '" updated.');
111 | }
112 |
113 | }
114 |
115 | });
116 | });
117 |
118 | };
119 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # grunt-tpl-compiler
2 |
3 | > Grunt plugin for juicer-based template compile to kissy module
4 | >
5 | > 将基于 [juicer](http://juicer.name) 语法的模板文件编译为 KISSY 模块。
6 |
7 | ## Getting Started
8 | This plugin requires Grunt `~0.4.2`
9 |
10 | If you haven't used [Grunt](http://gruntjs.com/) before, be sure to check out the [Getting Started](http://gruntjs.com/getting-started) guide, as it explains how to create a [Gruntfile](http://gruntjs.com/sample-gruntfile) as well as install and use Grunt plugins. Once you're familiar with that process, you may install this plugin with this command:
11 |
12 | ```shell
13 | npm install grunt-tpl-compiler --save-dev
14 | ```
15 |
16 | Once the plugin has been installed, it may be enabled inside your Gruntfile with this line of JavaScript:
17 |
18 | ```js
19 | grunt.loadNpmTasks('grunt-tpl-compiler');
20 | ```
21 |
22 | ## The "tpl_compiler" task
23 |
24 | ### Overview
25 | In your project's Gruntfile, add a section named `tpl_compiler` to the data object passed into `grunt.initConfig()`.
26 |
27 | ```js
28 | grunt.initConfig({
29 | tpl_compiler: {
30 | options: {
31 | // Task-specific options go here.
32 | },
33 | your_target: {
34 | // Target-specific file lists and/or options go here.
35 | },
36 | },
37 | });
38 | ```
39 |
40 | ### Options
41 |
42 | #### options.ext
43 | Type: `String`
44 | Default value: `'tpl'`
45 |
46 | 生成 js 文件的后缀字符串
47 |
48 | ### Usage Examples
49 |
50 | #### Default Options
51 | In this example, the default options are used to do something with whatever. So if the `testing` file has the content `Testing` and the `123` file had the content `1 2 3`, the generated result would be `Testing, 1 2 3.`
52 |
53 | ```js
54 | grunt.initConfig({
55 | tpl_compiler: {
56 | options: {
57 | ext: '-tpl',
58 | replaceEscapeMap: {
59 | '\xB0': '°'
60 | }
61 | },
62 | main: {
63 | files: [
64 | {
65 | expand: true,
66 | cwd: 'src/',
67 | src: ['**/*.tpl.html'],
68 | dest: 'src/'
69 | }
70 | ]
71 | }
72 | },
73 | });
74 | ```
75 |
76 | #### 词汇
77 |
78 | - HTML 模板文件
79 | - 一个普通的 HTML 文件,其中以 [juicer](http://juicer.name) 语法书写要用到的模板文件,模板文件通过 DOM 节点的 `data-tpl` 属性指定各个模板的钩子。
80 | - JavaScript 模板文件
81 | - 从 HTML 模板文件生成的对应的 JavaScript 文件,该文件为一个普通的 KISSY 模块,对 HTML 模板文件中的各个模板进行抽离,对外暴露 `get`、`set`、`register`、`render` 方法以读写模板字符串。
82 | - 一个 HTML 模板文件对应生成一个 JavaScript 模板文件。
83 |
84 | #### 使用方法
85 |
86 | 1. 在 "src" 目录下的指定 HTML 模板文件中编辑,根据 grunt config 中的 `main->files->src` 指定,如上面的 `**/*.tpl.html` 即为 `.tpl.html` 后缀的 html 文件视为需要处理的 HTML 模板文件,示例:
87 |
88 | [src/pages/index.tpl.html]
89 |
90 | ``` html
91 |
92 | ...
93 |
94 |
95 |
96 |
97 |
${title}
98 |
99 | {@each prods as prod}
100 | - ${prod.name} - ${prod.price}
101 | {@/each}
102 |
103 |
104 |
105 |
106 |
107 |
108 | {@each moreList as item}
109 | - ${item.title}
110 | - ${item.desc}
111 | {@/each}
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 | ```
120 |
121 | 其中指定了两个模板:`...
` 和 ``。
122 |
123 | 2. 执行 `grunt tpl_compiler`,生成对应的 JavaScript 模板文件:
124 |
125 | [src/pages/index-tpl.js]
126 |
127 | ``` javascript
128 | /**
129 | * KISSY Template Module for test
130 | */
131 | KISSY.add(function (S, Juicer) {
132 | 'use strict';
133 | /**
134 | * 维护所有用到的模板
135 | * @class Template
136 | * @constructor
137 | */
138 | var templates = {
139 | ProdList: '${title}
{@each prods as prod}- ${prod.name} - ${prod.price}
{@/each}
',
140 | MoreList: '{@each moreList as item}- ${item.title}
- ${item.desc}
{@/each}
'
141 | };
142 | return {
143 | /**
144 | * 注册模板自定义函数
145 | * @param name 需要替换的模板中用到的名字
146 | * @param fn 自定义函数
147 | */
148 | register: function (name, fn) {
149 | Juicer.register(name, fn);
150 | },
151 | /**
152 | * 覆盖已有模板
153 | * @param key {String} template 模板键
154 | * @param tmpl {String} 模板
155 | */
156 | set: function (key, tmpl) {
157 | templates[key] = tmpl;
158 | },
159 | /**
160 | * 获取已有模板
161 | * @param key {String} 模板Key
162 | * @returns {String} 模板内容
163 | */
164 | get: function (key) {
165 | return templates[key];
166 | },
167 | /**
168 | * 根据指定的模板key和数据渲染生成html
169 | * @param key 模板的key
170 | * @param data json数据
171 | * @returns {String}
172 | */
173 | render: function (key, data) {
174 | return Juicer(templates[key], data);
175 | }
176 | };
177 | }, { requires: ['gallery/juicer/1.3/index'] });
178 | ```
179 |
180 | 3. 这样其他模块中需要依赖模板的地方,直接通过 KISSY 模块 `require` 该 JavaScript 模板文件即可,通用的 `register` 方法也可以写在该 JavaScript 模板文件中。
181 |
182 | 4. 修改 `src/pages/index.tpl.html`,重新构建后生成的 `src/pages/index-tpl.js` 中 **只会覆盖 `var templates = {...}` 部分**。因此如果直接修改 `src/pages/index-tpl.js` 中除 `var templates = {...}` 的部分,重新构建时修改内容 **会保留而不会被覆盖掉**。
183 |
184 | 5. 建议将该任务加入到 `watch` 中实时编译模板文件,保证本地服务实时取的是最新的模块。
185 |
186 |
187 | ## Contributing
188 | In lieu of a formal styleguide, take care to maintain the existing coding style. Add unit tests for any new or changed functionality. Lint and test your code using [Grunt](http://gruntjs.com/).
189 |
190 | ## Release History
191 |
192 | - [0.1.6] fix for `this.files.src` is `Array`.
193 | - [0.1.4] 用 cheerio 替换 jsdom,避免 windows 下 jsdom 安装失败,移除 htmlmin
194 | - [0.1.3] Bugfix for html escape
195 | - [0.1.0] 基本功能完成
196 |
--------------------------------------------------------------------------------