├── .gitignore
├── examples
├── templates
│ ├── sub.ejs
│ ├── index2.ejs
│ └── index.ejs
└── index.js
├── package.json
├── lib
├── asp-cache.js
├── utils.js
└── ejs.js
├── LICENSE
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .idea
--------------------------------------------------------------------------------
/examples/templates/sub.ejs:
--------------------------------------------------------------------------------
1 |
2 | {{=data.user.nick}}
3 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ejs-lite",
3 | "description": "修改自ejs的模板引擎,使用更简单。",
4 | "keywords": [
5 | "template",
6 | "engine",
7 | "ejs"
8 | ],
9 | "version": "1.0.6",
10 | "author": "Spikef",
11 | "main": "./lib/ejs.js"
12 | }
--------------------------------------------------------------------------------
/examples/templates/index2.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | 欢迎访问
6 |
7 |
8 | {{
9 | var a = function() {
10 | return "100";
11 | }
12 | }}
13 |
14 | {{include sub.ejs}}, {{=data.welcome}}!
15 |
16 |
17 | {{-include('sub.ejs')}}, {{=data.welcome}}!
18 |
19 |
20 | 访问 {{=a()}}!
21 |
22 |
23 | 输出原始标签{{{=100}}
24 |
25 |
26 | {{var n = 200;}}
27 | {{?n && n>300}}
大于300
28 | {{??n<100}}
小于100
29 | {{??}}
介于100和300之间
30 | {{?}}
31 |
32 |
33 | {{var m = 200;}}
34 | {{:m}}
35 | {{::100}}
等于100
36 | {{::200, "200"}}
等于200
37 | {{::}}
不等于100也不等于200
38 | {{:}}
39 |
40 |
41 | {{~data.list1}}
42 | -
43 |
44 | {{~$value.a}}
45 | - {{=$value.val}}
46 | {{~}}
47 |
48 |
49 | {{~}}
50 |
51 |
52 | {{~data.list2}}
53 | -
54 | {{~$value
}}
55 | - {{=$value}}
{{~
56 |
}}
57 |
58 | {{~}}
59 |
60 |
61 | {{~~data.list3}}
62 | - {{=$value}}
63 | {{~}}
64 |
65 |
66 | {{~~data.list4}}
67 | -
68 | {{~~$value
}}
69 | - {{=$json.value}}
70 | {{~}}
71 |
72 | {{~}}
73 |
74 |
75 |
--------------------------------------------------------------------------------
/lib/asp-cache.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Usage: 修改自ejs的模板引擎,使用更简单。感谢ejs的作者tj和mde。
3 | * Author: Spikef < Spikef@Foxmail.com >
4 | * Copyright: Envirs Team < http://envirs.com >
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 |
19 | var cache_key = '__ejs_lite_template_cache__';
20 |
21 | module.exports = {
22 | set: function (key, val) {
23 | Application.Lock();
24 | Application(cache_key + key) = val;
25 | Application.Unlock();
26 | },
27 | get: function (key) {
28 | var val;
29 | Application.Lock();
30 | val = Application(cache_key + key);
31 | Application.Unlock();
32 | return val;
33 | },
34 | clear: function () {
35 | Application.Lock();
36 |
37 | var contents = new Enumerator(Application.Contents);
38 | contents.forEach(function(key) {
39 | if (key.indexOf(cache_key) === 0) {
40 | Application.Contents.Remove(key);
41 | }
42 | });
43 |
44 | Application.Unlock();
45 | },
46 | remove: function(key) {
47 | Application.Lock();
48 | Application.Contents.Remove(cache_key + key);
49 | Application.Unlock();
50 | }
51 | };
--------------------------------------------------------------------------------
/examples/index.js:
--------------------------------------------------------------------------------
1 | var http = require('http');
2 | var path = require('path');
3 |
4 | http.createServer(function(req, res) {
5 | // 忽略对favicon.ico的请求
6 | if ( /\/favicon\.ico$/i.test(req.url) ) {
7 | return;
8 | }
9 |
10 | var data = {
11 | user: {
12 | nick: "小影"
13 | },
14 | welcome: "欢迎",
15 | list1: [
16 | {
17 | a: [
18 | {val: 1},
19 | {val: 2},
20 | {val: 3}
21 | ]
22 | },
23 | {
24 | a: [
25 | {val: 4},
26 | {val: 5}
27 | ]
28 | }
29 | ],
30 | list2: [
31 | [10, 20, 30],
32 | [40, 50]
33 | ],
34 | list3: {
35 | a: 100,
36 | b: 200,
37 | c: 300
38 | },
39 | list4: {
40 | a: {
41 | text: 'aaa',
42 | value: '111'
43 | },
44 | b: {
45 | text: 'bbb',
46 | value: '222'
47 | }
48 | }
49 | };
50 |
51 | var file = path.resolve(__dirname, 'templates/index2.ejs');
52 | var options = {
53 | filename: file,
54 | delimiters: {
55 | begin: '{{',
56 | close: '}}'
57 | },
58 | cache: false,
59 | useWith: false,
60 | localsName: 'data'
61 | };
62 |
63 | var ejs = require('ejs-lite');
64 | var html = ejs.render(data, options);
65 |
66 | //var ejs = require('ejs');
67 | //var html = ejs.render(data, options);
68 |
69 | res.writeHead(200, {'Content-Type': 'text/html'});
70 | res.end(html);
71 | }).listen(8124);
72 |
73 | console.log('Server running at http://127.0.0.1:8124/');
--------------------------------------------------------------------------------
/examples/templates/index.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | 欢迎访问
6 |
7 |
8 | {{
9 | var a = function() {
10 | return "100";
11 | }
12 | }}
13 |
14 | {{include sub.ejs}}, {{=welcome}}!
15 |
16 |
17 | {{-include('sub.ejs')}}, {{=welcome}}!
18 |
19 |
20 | 访问 {{=a()}}!
21 |
22 |
23 | 输出原始标签{{{=100}}
24 |
25 |
26 | {{var n = 200;}}
27 | {{?n && n>300}}
大于300
28 | {{??n<100}}
小于100
29 | {{??}}
介于100和300之间
30 | {{?}}
31 |
32 |
33 | {{var m = 200;}}
34 | {{:m}}
35 | {{::100}}
等于100
36 | {{::200, "200"}}
等于200
37 | {{::}}
不等于100也不等于200
38 | {{:}}
39 |
40 |
41 | {{~list1}}
42 | -
43 |
44 | {{~a}}
45 | - {{=val}}
46 | {{~}}
47 |
48 |
49 | {{~}}
50 |
51 |
52 | {{~list2}}
53 | -
54 | {{~$value
}}
55 | - {{=$value}}
{{~
56 |
}}
57 |
58 | {{~}}
59 |
60 |
61 | {{~~list3}}
62 | - {{=$value}}
63 | {{~}}
64 |
65 |
66 | {{~~list4}}
67 | -
68 | {{~~$value
}}
69 | - {{=value}}
70 | {{~}}
71 |
72 | {{~}}
73 |
74 |
75 |
--------------------------------------------------------------------------------
/lib/utils.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Usage: 修改自ejs的模板引擎,使用更简单。感谢ejs的作者tj和mde。
3 | * Author: Spikef < Spikef@Foxmail.com >
4 | * Copyright: Envirs Team < http://envirs.com >
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 |
19 | var regExpChars = /[|\\{}()[\]^$+*?.]/g;
20 |
21 | /**
22 | * 根据正则表达式对字符串编码.
23 | * 如果 `string` 为 `undefined` 或者 `null`, 则直接返回空字符串.
24 | * @param {String} string 输入字符串
25 | * @return {String} 编码之后的字符串
26 | */
27 | exports.escapeRegExpChars = function (string) {
28 | // istanbul ignore if
29 | if (!string) {
30 | return '';
31 | }
32 | return String(string).replace(regExpChars, '\\$&');
33 | };
34 |
35 | var _ENCODE_HTML_RULES = {
36 | '&': '&',
37 | '<': '<',
38 | '>': '>',
39 | '"': '"',
40 | "'": '''
41 | },
42 | _MATCH_HTML_CHARS = /[&<>'"]/g;
43 |
44 | function encode_char(c) {
45 | return _ENCODE_HTML_RULES[c] || c;
46 | }
47 |
48 | /**
49 | * 转义HTML标签.
50 | * 如果 `markup` 为 `undefined` 或者 `null`, 则返回空字符串.
51 | * @param {String} markup Input string
52 | * @return {String} Escaped string
53 | */
54 | exports.escapeHTML = function (markup) {
55 | return markup == undefined
56 | ? ''
57 | : String(markup).replace(_MATCH_HTML_CHARS, encode_char);
58 | };
59 |
60 | /**
61 | * 浅拷贝JSON对象.
62 | * @param {Object} target 目标对象
63 | * @param {Object} source 源对象
64 | * @return {Object} 目标对象
65 | */
66 | exports.shallowCopy = function (target, source) {
67 | source = source || {};
68 | for (var p in source) {
69 | target[p] = source[p];
70 | }
71 | return target;
72 | };
73 |
74 | /**
75 | * 使用Application对模板进行缓存
76 | */
77 | if ( process.release.name === 'nodeasp' ) {
78 | exports.cache = require('./asp-cache');
79 | } else {
80 | exports.cache = {
81 | _data: {},
82 | set: function (key, val) {
83 | this._data[key] = val;
84 | },
85 | get: function (key) {
86 | return this._data[key];
87 | },
88 | clear: function () {
89 | this._data = {};
90 | },
91 | remove: function(key) {
92 | delete this._data[key]
93 | }
94 | };
95 | }
96 |
97 | exports.cache.reset = exports.cache.clear;
98 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "{}"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright {yyyy} {name of copyright owner}
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # EJS-Lite
2 |
3 | 修改自ejs的模板引擎,使用更简单。
4 |
5 | JavaScript templates inspired by `ejs`, more easy to use.
6 |
7 | ## Installation
8 |
9 | ```bash
10 | $ npm install ejs-lite
11 | ```
12 |
13 | ## Features
14 |
15 | * 直接执行js代码, 使用 `<% %>`
16 | * Control flow with `<% %>`
17 | * 输出原始HTML, 使用 `<%= %>`
18 | * Unescaped raw output with `<%= %>`
19 | * 输出HTML转义后的普通文本, 使用 `<%- %>`
20 | * Escaped output with `<%- %>`
21 | * 对数组或者json对象循环, 使用 `<%~ %>`
22 | * Loops for array and json with `<%~ %>`
23 | * if条件判断分支, 使用 `<%? %>`
24 | * Add if/else with `<%? %>`
25 | * switch条件选择,使用 `<%: %>`
26 | * Add switch case with `<%: %>`
27 | * 删除下面的空行, 使用 `-%>` 或者 `=%>` 作为结束标签
28 | * Newline-trim mode ('newline slurping') with `-%>` or `=%>` ending tag
29 | * 自定义所有的符号, 例如使用'{{ }}'代替'<% %>'
30 | * Custom delimiters (e.g., use '{{ }}' instead of '<% %>')
31 | * 包含子模板
32 | * Includes sub template
33 | * 支持模板缓存, 减少编译次数
34 | * Static caching of templates
35 |
36 | ## Compared with ejs
37 |
38 | * 增加了数组和json的循环, if条件判断分支和switch条件选择的符号
39 | * Add support for loops/if/switch
40 | * 所有的特殊标签全部可以自定义
41 | * All tags are customizable
42 | * 支持 [NodeAsp](http://nodeasp.com) 引擎
43 | * Support [NodeAsp](http://nodeasp.com) engine
44 | * 删除了对 `<%_ _%>` 标签的支持
45 | * Removed the `<%_ _%>` tag
46 | * 删除了 `client/strict/_with/rmWhitespace` 等选项
47 | * Removed the `client/strict/_with/rmWhitespace` options
48 | * 删除的特殊也许会在足够的测试之后重新添加回来
49 | * The removed features may be added again after enough testing
50 |
51 | ## Example
52 |
53 | ```html
54 | <%?user%>
55 | <%= user.name %>
56 | <%?%>
57 |
58 | // you can still use this
59 | <% if (user) { %>
60 | <%= user.name %>
61 | <% } %>
62 | ```
63 |
64 | ## Usage
65 |
66 | ```javascript
67 | var ejs = require('ejs-lite');
68 |
69 | var template = ejs.compile(str, options);
70 | template(data);
71 | // => Rendered HTML string
72 |
73 | ejs.render(path, data, options);
74 | // => Rendered HTML string
75 |
76 | options.filename = path;
77 | ejs.render(options, data);
78 | // => Rendered HTML string
79 | ```
80 |
81 | ## Options
82 |
83 | - `cache` 编译方法(node/io.js)或者方法的构造函数字符串(asp)将被缓存, 默认为false
84 | - `cache` Compiled functions are cached, defaults to `false`
85 | - `filename` 模板文件的完整路径, 如果指定了 template 参数, 则使用 template 参数
86 | - `filename` The template file's full name, use the template argument if you specified
87 | - `encoding` 模板文件的编码(utf8, gbk, gb2312...), 默认为utf8
88 | - `encoding` Template file's encoding(utf8, gbk, gb2312...), defaults to `utf8`
89 | - `context` 模板编译方法的this指针, 默认为null
90 | - `context` Function execution context, defaults to `null`
91 | - `debug` 是否开启模板调试, 开启之后会在发生错误时指出错误的行号, 默认为true
92 | - `debug` When `false` no debug instrumentation is compiled, defaults to `true`
93 | - `useWith` 是否使用`with() {}`, 如果设置为false则所有数据都存储在locals对象上, 默认为true
94 | - `useWith` Whether or not to use `with() {}` constructs. If false then the locals will be stored in the locals object, defaults to `true`
95 | - `localsName` 当不使用`with`时, 变量存储对象的对象名称, 默认为`locals`
96 | - `localsName` Name to use for the object storing local variables when not using with Defaults to locals
97 | - `delimiters` 所有界定标签所使用的符号, 详见下面的 `Tags` 章节
98 | - `delimiters` Characters to use for delimiter tags, see `Tags` below
99 | - `delimiters.begin` `<%`
100 | - `delimiters.close` `%>`
101 | - `delimiters.equal` `=`
102 | - `delimiters.plain` `-`
103 | - `delimiters.loops` `~`
104 | - `delimiters.check` `?`
105 | - `delimiters.shift` `:`
106 | - `delimiters.notes` `#`
107 |
108 | ## Tags
109 |
110 | - `<%` 普通的代码段, 直接执行, 不输出任何内容
111 | - `<%` 'Scriptlet' tag, for control-flow, no output
112 | - `<%=` 输出带HTML标签的内容
113 | - `<%=` Outputs the unescaped value into the template
114 | - `<%-` 输出转义HTML标签之后的文本
115 | - `<%-` Outputs the value into the template (HTML escaped)
116 | - `<%#` 注释标签, 不执行, 也不输出任何内容
117 | - `<%#` Comment tag, no execution, no output
118 | - `<%~` 对数组或者json进行循环, 不输出
119 | - `<%~` Loops for array or json, no output
120 | - `<%?` 生成if/else/else if代码块, 不输出
121 | - `<%?` Script block for if/else/else if control-flow, no output
122 | - `<%:` 生成switch/case/default/break代码块, 不输出
123 | - `<%:` Script block for switch/case/default/break control-flow, no output
124 | - `<%%` 输出 '<%'
125 | - `<%%` Outputs a literal '<%'
126 | - `%>` 普通的结束标记
127 | - `%>` Plain ending tag
128 | - `-%> =%>` 结束标记,同时将删除下一个空行
129 | - `-%> =%>` Trim-mode ('newline slurp') tag, trims following newline
130 |
131 | ## Includes
132 |
133 | 包含子模板与ejs一样, 有两种方式。略微有点不一样的是, template和options.filename两者只要指定其中一个即可。
134 |
135 | Includes either have to be an absolute path, or, if not, are assumed as
136 | relative to the template with the `include` call. For example if you are
137 | including `./views/user/show.ejs` from `./views/users.ejs` you would use
138 | `<%= include('user/show') %>`.
139 |
140 | You'll likely want to use the raw output tag (`<%=`) with your include to avoid
141 | double-escaping the HTML output.
142 |
143 | ```html
144 |
145 | <% users.forEach(function(user){ %>
146 | <%= include('user/show', {user: user}) %>
147 | <% }); %>
148 |
149 | ```
150 |
151 | Includes are inserted at runtime, so you can use variables for the path in the
152 | `include` call (for example `<%= include(somePath) %>`). Variables in your
153 | top-level data object are available to all your includes, but local variables
154 | need to be passed down.
155 |
156 | NOTE: Include preprocessor directives (`<% include user/show %>`) are
157 | still supported.
158 |
159 | ## Custom delimiters
160 |
161 | 所有的标签符号都可以自定义, 不过要注意不要定义了重复的符号.
162 |
163 | All delimiters are customizable, but to make sure them different.
164 |
165 | ```javascript
166 | // index.ejs
167 | {{: users.join(" | "); }}
168 |
169 | // index.js
170 | var ejs = require('ejs'),
171 | users = ['geddy', 'neil', 'alex'];
172 |
173 | var delimiters = {
174 | begin: '{{',
175 | close: '}}',
176 | equal: ':',
177 | shift: '#' // the shift mark is used for equal, so define another
178 | }
179 |
180 | ejs.render('index', {delimiters: delimiters}, {users: users});
181 | // => 'geddy | neil | alex'
182 | ```
183 |
184 | ## Caching
185 |
186 | EJS内置了简单的缓存功能(同时支持node和asp), 可以使用其它缓存模块来增进缓存功能.
187 |
188 | EJS ships with a basic in-process cache for caching the intermediate JavaScript
189 | functions used to render templates. It's easy to plug in LRU caching using
190 | Node's `lru-cache` library:
191 |
192 | ```javascript
193 | var ejs = require('ejs')
194 | , LRU = require('lru-cache');
195 | ejs.cache = LRU(100); // LRU cache with 100-item limit
196 | ```
197 |
198 | 如果需要清除缓存, 直接使用 `ejs.clearCache` 方法.
199 |
200 | If you want to clear the EJS cache, call `ejs.clearCache`. If you're using the
201 | LRU cache and need a different limit, simple reset `ejs.cache` to a new instance
202 | of the LRU.
203 |
204 | ## Layouts
205 |
206 | EJS没有支持所谓的模块功能, 但是可以借助include来实现类似功能, 如下所示:
207 |
208 | EJS does not specifically support blocks, but layouts can be implemented by
209 | including headers and footers, like so:
210 |
211 |
212 | ```html
213 | <%- include('header') -%>
214 |
215 | Title
216 |
217 |
218 | My page
219 |
220 | <%- include('footer') -%>
221 | ```
222 |
223 | ## If/else flow
224 |
225 | ```html
226 | <%var n = 200;%>
227 | <%?n && n>300%>大于300
228 | <%??n<100%>小于100
229 | <%??%>介于100和300之间
230 | <%?%>
231 | ```
232 |
233 | 等同于(is equal to)
234 |
235 | ```html
236 | <%var n = 200;%>
237 | <%if (n && n>300) {%>大于300
238 | <%} else if (n<100) {%>小于100
239 | <%} else {%>介于100和300之间
240 | <%}%>
241 | ```
242 |
243 | ## Switch/case flow
244 |
245 | ```html
246 | <%var m = 200;%>
247 | <%:m%>
248 | <%::100%>等于100
249 | <%::200, "200"%>等于200
250 | <%::%>不等于100也不等于200
251 | <%:%>
252 | ```
253 |
254 | 等同于(is equal to)
255 |
256 | ```html
257 | <%var m = 200;%>
258 | <%switch (m) {%>
259 | <%case 100:%>
260 | 等于100
261 | <%break;%>
262 | <%case 200:%>
263 | <%case "200":%>
264 | 等于200
265 | <%break;%>
266 | <%default:%>
267 | 不等于100也不等于200
268 | <%break;%>
269 | <%}%>
270 | ```
271 |
272 | ## Loops for array/json
273 |
274 | ### array
275 |
276 | 循环标签内部定义的html标签,如示例中的``,只有当数组不为空的时候才会显示。
277 | 特别地,如果首尾标签完全匹配,可以省略写循环结束时的标签。
278 |
279 | 在数组循环内部, 可以使用如下几个内部变量:
280 |
281 | - $array: 被循环的数组本身
282 | - $index: 当前循环的索引
283 | - $value: 当前循环的值
284 | - $length: 数组元素个数
285 |
286 | examples:
287 |
288 | ```html
289 | <%
290 | var list = [
291 | {
292 | a: [
293 | {val: 1},
294 | {val: 2},
295 | {val: 3}
296 | ]
297 | },
298 | {
299 | a: [
300 | {val: 4},
301 | {val: 5}
302 | ]
303 | }
304 | ];
305 | %>
306 | <%~list
307 | %>
308 | -
309 | <%~a
%>
310 | - <%=val%>
311 | <%~%>
312 |
313 | <%~
%>
314 |
315 | // when useWith === false, use following instead:
316 | <%~list
317 | %>
318 | -
319 | <%~$value.a
%>
320 | - <%=$value.val%>
321 | <%~%>
322 |
323 | <%~
%>
324 |
325 | // =>
326 |
327 |
328 | -
329 |
330 | - 1
331 | - 2
332 | - 3
333 |
334 |
335 | -
336 |
340 |
341 |
342 | ```
343 |
344 | ```html
345 | <%
346 | var list = [
347 | [10, 20, 30],
348 | [40, 50]
349 | ];
350 | %>
351 | <%~list
352 | %>
353 | -
354 | <%~$value
%>
355 | - <%=$value%>
356 | <%~%>
357 |
358 | <%~
%>
359 |
360 | // =>
361 |
362 | -
363 |
364 | - 10
365 | - 20
366 | - 30
367 |
368 |
369 | -
370 |
374 |
375 |
376 | ```
377 |
378 |
379 |
380 | ### json
381 |
382 | 与数组循环的区别在于,json的循环需要以两个`~~`开始。
383 |
384 | 在json循环内部, 可以使用如下几个内部变量:
385 |
386 | - $json: 被循环的json本身
387 | - $key: 当前循环的主键
388 | - $value: 当前循环的值
389 | - $length: json对象一级元素的个数
390 |
391 | examples:
392 |
393 | ```html
394 | <%
395 | var list = {
396 | a: 100,
397 | b: 200,
398 | c: 300
399 | };
400 | %>
401 | <%~~list %>
402 | - <%=$value%>
403 | <%~%>
404 |
405 | // =>
406 |
407 | - 100
408 | - 200
409 | - 300
410 |
411 | ```
412 |
413 | ```html
414 | <%
415 | var list = {
416 | a: {
417 | text: 'aaa',
418 | value: '111'
419 | },
420 | b: {
421 | text: 'bbb',
422 | value: '222'
423 | }
424 | };
425 | %>
426 | <%~~list
427 | %>
428 | -
429 | <%~~$value
%>
430 | - <%=value%>
431 | <%~%>
432 |
433 | <%~
%>
434 |
435 | // when useWith === false, use following instead:
436 | <%~~locals.list
437 | %>
438 | -
439 | <%~~$value
%>
440 | - <%=$json.value%>
441 | <%~%>
442 |
443 | <%~
%>
444 |
445 | // =>
446 |
447 | -
448 |
449 | - 111
450 | - 111
451 |
452 |
453 | -
454 |
455 | - 222
456 | - 222
457 |
458 |
459 |
460 | ```
461 |
462 | ## Related projects
463 |
464 | There are a number of implementations of EJS-Lite:
465 |
466 | * The origin repertory of this library: https://github.com/mde/ejs
467 | * TJ's implementation, the v1 of this library: https://github.com/tj/ejs
468 | * Jupiter Consulting's EJS: http://www.embeddedjs.com/
469 | * EJS Embedded JavaScript Framework on Google Code: https://code.google.com/p/embeddedjavascript/
470 | * Sam Stephenson's Ruby implementation: https://rubygems.org/gems/ejs
471 | * Erubis, an ERB implementation which also runs JavaScript: http://www.kuwata-lab.com/erubis/users-guide.04.html#lang-javascript
472 |
473 | ## License
474 |
475 | Licensed under the Apache License, Version 2.0
476 | ()
--------------------------------------------------------------------------------
/lib/ejs.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Usage: 修改自ejs的模板引擎,使用更简单。感谢ejs的作者tj和mde。
3 | * Author: Spikef < Spikef@Foxmail.com >
4 | * Copyright: Envirs Team < http://envirs.com >
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 |
19 | var fs = require('fs'),
20 | utils = require('./utils'),
21 | _DEFAULT_ENCODING = 'utf8',
22 | _DEFAULT_DELIMITERS = {
23 | begin: '<%',
24 | close: '%>',
25 | equal: '=',
26 | plain: '-',
27 | notes: '#',
28 | loops: '~',
29 | check: '?',
30 | shift: ':',
31 | slash: '%'
32 | },
33 | _DEFAULT_LOCALS_NAME = 'locals',
34 | _TRAILING_SEMICOLON = /;\s*$/,
35 | _UTF_BOM_FLAG = /^\uFEFF/;
36 |
37 | // 缓存模块,可以使用其它的缓存模块来代替
38 | exports.cache = utils.cache;
39 |
40 | /**
41 | * 获取include方法引入的子模板的真实路径
42 | * @param name
43 | * @param filename
44 | */
45 | exports.resolveInclude = function(name, filename) {
46 | var path = require('path'),
47 | includePath = path.resolve(path.dirname(filename), name),
48 | ext = path.extname(name);
49 |
50 | if (!ext) includePath += '.ejs';
51 |
52 | return includePath;
53 | };
54 |
55 | /**
56 | * 清空模板缓存
57 | */
58 | exports.clearCache = function () {
59 | exports.cache.clear();
60 | };
61 |
62 | /**
63 | * 根据数据渲染模板
64 | * @param optional {String} template 完整的模板内容
65 | * @param {Object} data 模板绑定数据
66 | * @param {Object} options 模板编译选项
67 | * @returns {String} 模板渲染结果
68 | */
69 | exports.render = function(template, data, options) {
70 | data = data || {};
71 | options = options || {};
72 |
73 | if ( template && typeof template === 'object' ) {
74 | options = data;
75 | data = template;
76 |
77 | return handleCache(options)(data);
78 | }
79 |
80 | return handleCache(options, template)(data);
81 | };
82 |
83 | /**
84 | * 编译模板
85 | * @param {String} template 完整的模板内容
86 | * @param {Object} options 模板编译选项
87 | * @returns {Function} 模板编译方法
88 | */
89 | exports.compile = function(template, options) {
90 | var TPL;
91 | TPL = new Template(template, options);
92 | return TPL.compile();
93 | };
94 |
95 | /**
96 | * 从文件或者缓存中读取模板文件
97 | * @param options
98 | * @param template
99 | * @returns {*}
100 | */
101 | function handleCache(options, template) {
102 | var fn, TPL,
103 | filename = options.filename,
104 | hasTemplate = arguments.length > 1;
105 |
106 | if ( options.cache ) {
107 | if ( !filename ) {
108 | throw new Error('请指定模板文件:filename');
109 | }
110 |
111 | fn = exports.cache.get(filename);
112 | if ( !fn ) {
113 | if (!hasTemplate) template = readTemplate(filename, options.encoding);
114 | TPL = new Template(template, options);
115 | fn = TPL.compile();
116 | // 如果是node, 则缓存fn; 如果是asp, 则缓存source
117 | if ( process.release.name === 'nodeasp' ) {
118 | exports.cache.set(filename, TPL.source);
119 | } else {
120 | exports.cache.set(filename, fn);
121 | }
122 | } else if ( typeof fn === 'string' ) {
123 | TPL = new Template('', options);
124 | TPL.source = fn;
125 | fn = TPL.compile();
126 | } else {
127 | return fn;
128 | }
129 | } else {
130 | if (!hasTemplate) template = readTemplate(filename, options.encoding);
131 | fn = exports.compile(template, options);
132 | }
133 |
134 | return fn;
135 | }
136 |
137 | /**
138 | * 读取模板文件
139 | * @param filename
140 | * @param encoding
141 | */
142 | function readTemplate(filename, encoding) {
143 | if ( !fs.existsSync(filename) ) {
144 | console.error('找不到指定的模板文件:' + filename);
145 | return '';
146 | } else {
147 | var template = fs.readFileSync(filename, encoding || _DEFAULT_ENCODING);
148 | return template.replace(_UTF_BOM_FLAG, '');
149 | }
150 | }
151 |
152 | /**
153 | * 获取被include文件的模板对象
154 | * @param path
155 | * @param options
156 | * @returns {*}
157 | */
158 | function includeFile(path, options) {
159 | var opts = utils.shallowCopy({}, options);
160 | opts.filename = exports.resolveInclude(path, opts.filename);
161 | return handleCache(opts);
162 | }
163 |
164 | /**
165 | * 获取被include文件的js方法.
166 | * @param {String} path path for the specified file
167 | * @param {object} options compilation options
168 | * @return {Object}
169 | */
170 | function includeSource(path, options) {
171 | var opts = utils.shallowCopy({}, options),
172 | includePath,
173 | template;
174 |
175 | includePath = exports.resolveInclude(path, opts.filename);
176 | template = readTemplate(includePath, options.encoding);
177 |
178 | opts.filename = includePath;
179 | var TPL = new Template(template, opts);
180 | TPL.generateSource();
181 | return {
182 | source: TPL.source,
183 | filename: includePath,
184 | template: template
185 | };
186 | }
187 |
188 | /**
189 | * 循环数组
190 | * @param {Object} obj: 被循环对象的属性
191 | * @param {String} print: 输出字面量的方法
192 | * @param {Boolean} close: 是否为结束循环
193 | * @param {Boolean} useWith: 是否使用with
194 | * @returns {string}: 循环体方法字符串
195 | */
196 | function repeatList(obj, print, close, useWith) {
197 | var arr = [],
198 | name = obj.name,
199 | tag = obj.tag,
200 | end = obj.end;
201 |
202 | if (!close) {
203 | arr = [
204 | ' ; (function($array, $length, $index, $value){',
205 | ' for ($index; $index<$length; $index++){',
206 | ' $value = $array[$index]',
207 | ' with ($value) {'
208 | ];
209 | if (!useWith) arr.pop();
210 | if (tag) {
211 | arr.splice(1, 0, ' if ($length) ' + print + '("' + tag + '");');
212 | }
213 | } else {
214 | arr = [
215 | ' }',
216 | ' }',
217 | ' }(' + name + ', ' + name + '.length, 0, null));'
218 | ];
219 | if (!useWith) arr.shift();
220 | if (end) {
221 | arr.splice(2, 0, ' if ($length) ' + print + '("' + end + '");');
222 | } else if (tag) {
223 | tag = closeTags(tag);
224 | arr.splice(2, 0, ' if ($length) ' + print + '("' + tag + '");');
225 | }
226 | }
227 |
228 | return arr.join('\n');
229 | }
230 |
231 | /**
232 | * 循环json
233 | * @param {Object} obj: 被循环对象的属性
234 | * @param {String} print: 输出字面量的方法
235 | * @param {Boolean} close: 是否为结束循环
236 | * @param {Boolean} useWith: 是否使用with
237 | * @returns {string}: 循环体方法字符串
238 | */
239 | function repeatJSON(obj, print, close, useWith) {
240 | var arr = [],
241 | name = obj.name,
242 | tag = obj.tag,
243 | end = obj.end;
244 |
245 | if (!close) {
246 | arr = [
247 | ' ; (function($json, $length, $key, $value){',
248 | ' for ($key in $json){',
249 | ' $value = $json[$key]',
250 | ' with ($value) {'
251 | ];
252 | if (!useWith) arr.pop();
253 | if (tag) {
254 | arr.splice(1, 0, ' if ($length) ' + print + '("' + tag + '");');
255 | }
256 | } else {
257 | arr = [
258 | ' }',
259 | ' }',
260 | ' }(' + name + ', Object.keys(' + name + ').length, null, null));'
261 | ];
262 | if (!useWith) arr.shift();
263 | if (end) {
264 | arr.splice(2, 0, ' if ($length) ' + print + '("' + end + '");');
265 | } else if (tag) {
266 | tag = closeTags(tag);
267 | arr.splice(2, 0, ' if ($length) ' + print + '("' + tag + '");');
268 | }
269 | }
270 |
271 | return arr.join('\n');
272 | }
273 |
274 | /**
275 | * 自动取html标签的配对结束标签
276 | * @param tag
277 | * @returns {string}
278 | */
279 | function closeTags(tag) {
280 | var outs = [];
281 | tag.split(/\\n/).forEach(function(t) {
282 | var tags = [];
283 | var matches = t.match(/<(\w+)/g);
284 | matches && matches.forEach(function(match) {
285 | tags.push('' + match.substr(1) + '>');
286 | });
287 | for (var i=tags.length-1; i>=0; i--) {
288 | t = t.replace(/<\w+[^>]*>/, tags[i]);
289 | }
290 | outs.unshift(t);
291 | });
292 |
293 | return outs.join('\\n');
294 | }
295 |
296 | /**
297 | * 重新抛出错误
298 | * @param {Error} err 原本的错误对象
299 | * @param {String} str 错误源码
300 | * @param {String} filename 模板文件名
301 | * @param {Number} lineNum 错误行号
302 | * @static
303 | */
304 | function rethrow(err, str, filename, lineNum){
305 | var lines = str.split('\n')
306 | , start = Math.max(lineNum - 3, 0)
307 | , end = Math.min(lines.length, lineNum + 3);
308 |
309 | // 错误行上下文
310 | var context = lines.slice(start, end).map(function (line, i){
311 | var curr = i + start + 1;
312 | return (curr == lineNum ? ' >> ' : ' ')
313 | + curr
314 | + '| '
315 | + line;
316 | }).join('\n');
317 |
318 | // 重新生成错误信息
319 | err.filename = filename;
320 | err.message = (filename || 'ejs') + ':'
321 | + lineNum + '\n'
322 | + context + '\n\n'
323 | + err.message;
324 |
325 | throw err;
326 | }
327 |
328 | /**
329 | * 模板引擎核心类
330 | */
331 | function Template(text, opts) {
332 | opts = opts || {};
333 | var options = {};
334 |
335 | options.escape = utils.escapeHTML;
336 | options.debug = opts.debug !== false;
337 | options.filename = opts.filename;
338 | options.delimiters = utils.shallowCopy(_DEFAULT_DELIMITERS, opts.delimiters || {});
339 | options.delimiters.slash = options.delimiters.begin.charAt(options.delimiters.begin.length - 1);
340 | options.context = opts.context;
341 | options.cache = opts.cache || false;
342 | options.localsName = opts.localsName || _DEFAULT_LOCALS_NAME;
343 | options.encoding = opts.encoding || _DEFAULT_ENCODING;
344 | options.useWith = opts.useWith === false ? false : true;
345 |
346 | this.opts = options;
347 | this.templateText = text;
348 | this.mode = null;
349 | this.truncate = false;
350 | this.currentLine = 1;
351 | this.source = '';
352 | this.repeat = [];
353 |
354 | this.regex = this.createRegex();
355 | }
356 |
357 | Template.modes = {
358 | EXECUTE: 'execute', // 执行代码, 即 <%
359 | LITERAL: 'literal', // 注释性标签, 即 <%%
360 | ESCAPED: 'escaped', // 转义HTML标签, 即 <%-
361 | OUTPUTS: 'outputs', // 原始输出, 即 <%=
362 | COMMENT: 'comment', // 注释文本, 即 <%#
363 | CIRCLES: 'circles', // 循环列表, 即 <%~
364 | PROCESS: 'process', // 条件判断, 即 <%?
365 | MATCHES: 'matches' // 条件选择, 即 <%:
366 | };
367 |
368 | Template.prototype = {
369 | createRegex: function () {
370 | var delimiters = {};
371 | for (var i in this.opts.delimiters) {
372 | delimiters[i] = utils.escapeRegExpChars(this.opts.delimiters[i]);
373 | }
374 |
375 | // '(<%%|<%=|<%-|<%#|<%?|<%:|<%~|<%|%>|-%>|=%>)'
376 | var str = [
377 | delimiters.begin + delimiters.slash,
378 | delimiters.begin + delimiters.equal,
379 | delimiters.begin + delimiters.plain,
380 | delimiters.begin + delimiters.notes,
381 | delimiters.begin + delimiters.check,
382 | delimiters.begin + delimiters.shift,
383 | delimiters.begin + delimiters.loops,
384 | delimiters.begin,
385 | delimiters.close,
386 | delimiters.plain + delimiters.close,
387 | delimiters.equal + delimiters.close
388 | ].join('|');
389 |
390 | str = '(' + str + ')';
391 |
392 | return new RegExp(str);
393 | },
394 |
395 | makeup: function () {
396 | var src,
397 | opts = this.opts,
398 | header = '',
399 | footer = '';
400 |
401 | if (!this.source) {
402 | this.generateSource();
403 |
404 | header += ' var __output = [], __append = __output.push.bind(__output);' + '\n';
405 | if (opts.useWith !== false) {
406 | header += ' with (' + opts.localsName + ' || {}) {' + '\n';
407 | footer += ' }' + '\n';
408 | }
409 |
410 | footer += ' return __output.join("");' + '\n';
411 |
412 | this.source = header + this.source + footer;
413 | }
414 |
415 | if (opts.debug) {
416 | src = 'var __line = 1' + '\n'
417 | + ' , __lines = ' + JSON.stringify(this.templateText) + '\n'
418 | + ' , __filename = ' + (opts.filename ? JSON.stringify(opts.filename) : 'undefined') + ';' + '\n'
419 | + 'try {' + '\n'
420 | + this.source
421 | + '} catch (e) {' + '\n'
422 | + ' rethrow(e, __lines, __filename, __line);' + '\n'
423 | + '}' + '\n';
424 |
425 | this.source = src;
426 | }
427 |
428 | return this.source;
429 | },
430 |
431 | compile: function () {
432 | var src = this.source || this.makeup(),
433 | fn,
434 | opts = this.opts,
435 | escape = opts.escape;
436 |
437 | try {
438 | fn = new Function(opts.localsName + ', escape, include, rethrow', src);
439 | } catch(e) {
440 | // istanbul ignore else
441 | if (e instanceof SyntaxError) {
442 | e.message = '编译ejs模板时发生错误: ' + e.message;
443 | if (opts.filename) {
444 | e.message += '\n at ' + opts.filename;
445 | }
446 | }
447 | throw e;
448 | }
449 |
450 | // 返回一个通过源码创建的可调用的方法,并传入data作为locals. 添加一个内部的include方法以执行加载子模板
451 | return function (data) {
452 | var include = function (path, includeData) {
453 | var d = utils.shallowCopy({}, data);
454 | if (includeData) {
455 | d = utils.shallowCopy(d, includeData);
456 | }
457 | return includeFile(path, opts)(d);
458 | };
459 |
460 | return fn.apply(opts.context, [data || {}, escape, include, rethrow]);
461 | };
462 | },
463 |
464 | generateSource: function () {
465 | var self = this,
466 | matches = this.parseTemplateText(),
467 | ds = this.opts.delimiters;
468 |
469 | if (matches && matches.length) {
470 | matches.forEach(function (line, index) {
471 | var opening,
472 | closing,
473 | include,
474 | includeOpts,
475 | includeObj,
476 | includeSrc;
477 |
478 | // 当找到开始标记时,同时去匹配结束标记
479 | if ( line.indexOf(ds.begin) === 0 // 如果是开始标记
480 | && line.indexOf(ds.begin + ds.slash) !== 0) { // 并且不是 <%%
481 | closing = matches[index + 2];
482 | if ( !closing || closing.substr(-ds.close.length) !== ds.close ) {
483 | closing = matches[index + 1]; // 代码块结束的情况
484 | if ( !closing || closing.substr(-ds.close.length) !== ds.close ) {
485 | throw new Error('找不到匹配的结束标签:"' + line + '".');
486 | } else {
487 | self.scanLine(line); // 处理代码块结束
488 | line = '';
489 | }
490 | }
491 | }
492 |
493 | // 编译 `include` 进来的代码
494 | if ((include = line.match(/^\s*include\s+(\S+)/))) {
495 | opening = matches[index - 1];
496 | // 必需在非转义模式下,即不能是<%-include()%>
497 | if (opening && (opening != ds.begin + ds.plain)) {
498 | includeOpts = utils.shallowCopy({}, self.opts);
499 | includeObj = includeSource(include[1], includeOpts);
500 | if (self.opts.debug) {
501 | includeSrc =
502 | ' ; (function(){' + '\n' +
503 | ' var __line = 1' + '\n' +
504 | ' , __lines = ' + JSON.stringify(includeObj.template) + '\n' +
505 | ' , __filename = ' + JSON.stringify(includeObj.filename) + ';' + '\n' +
506 | ' try {' + '\n' +
507 | includeObj.source + '\n' +
508 | ' } catch (e) {' + '\n' +
509 | ' rethrow(e, __lines, __filename, __line);' + '\n' +
510 | ' }' + '\n' +
511 | ' ; }).call(this);' + '\n';
512 | }else{
513 | includeSrc =
514 | ' ; (function(){' + '\n' + includeObj.source +
515 | ' ; }).call(this);' + '\n';
516 | }
517 |
518 | self.source += includeSrc;
519 |
520 | return;
521 | }
522 | }
523 |
524 | self.scanLine(line);
525 | });
526 | }
527 |
528 | },
529 |
530 | // 根据分割符对整个模板进行切割
531 | parseTemplateText: function () {
532 | var str = this.templateText,
533 | pat = this.regex,
534 | result = pat.exec(str),
535 | arr = [],
536 | firstPos,
537 | lastPos;
538 |
539 | while (result) {
540 | firstPos = result.index;
541 | lastPos = pat.lastIndex;
542 |
543 | if (firstPos !== 0) {
544 | arr.push(str.substring(0, firstPos));
545 | str = str.slice(firstPos);
546 | }
547 |
548 | arr.push(result[0]);
549 | str = str.slice(result[0].length);
550 | result = pat.exec(str);
551 | }
552 |
553 | if (str) {
554 | arr.push(str);
555 | }
556 |
557 | return arr;
558 | },
559 |
560 | scanLine: function (line) {
561 | var self = this,
562 | ds = this.opts.delimiters,
563 | uw = this.opts.useWith,
564 | newLineCount;
565 |
566 | function _addOutput() {
567 | if (self.truncate) {
568 | // 删除后面的空行
569 | line = line.replace(/^(?:\r\n|\r|\n)/, '');
570 | self.truncate = false;
571 | }
572 |
573 | if (!line || self.startSwitch) {
574 | return;
575 | }
576 |
577 | // 保留字面斜杠
578 | line = line.replace(/\\/g, '\\\\');
579 |
580 | // 转换换行符
581 | line = line.replace(/\n/g, '\\n');
582 | line = line.replace(/\r/g, '\\r');
583 |
584 | // 转义双引号
585 | // - 这将在编译时作为分割符
586 | line = line.replace(/"/g, '\\"');
587 | self.source += ' ; __append("' + line + '")' + '\n';
588 | }
589 |
590 | newLineCount = (line.split('\n').length - 1);
591 |
592 | switch (line) {
593 | case ds.begin:
594 | this.mode = Template.modes.EXECUTE;
595 | break;
596 | case ds.begin + ds.equal:
597 | this.mode = Template.modes.OUTPUTS;
598 | break;
599 | case ds.begin + ds.plain:
600 | this.mode = Template.modes.ESCAPED;
601 | break;
602 | case ds.begin + ds.loops:
603 | this.mode = Template.modes.CIRCLES;
604 | break;
605 | case ds.begin + ds.notes:
606 | this.mode = Template.modes.COMMENT;
607 | break;
608 | case ds.begin + ds.check:
609 | this.mode = Template.modes.PROCESS;
610 | break;
611 | case ds.begin + ds.shift:
612 | this.mode = Template.modes.MATCHES;
613 | break;
614 | case ds.begin + ds.slash:
615 | this.mode = Template.modes.LITERAL;
616 | this.source += ' ; __append("' + line.replace(ds.begin + ds.slash, ds.begin) + '")' + '\n';
617 | break;
618 | case ds.close:
619 | case ds.equal + ds.close:
620 | case ds.plain + ds.close:
621 | // 结束注释标签 <%% %>
622 | if (this.mode == Template.modes.LITERAL) {
623 | _addOutput();
624 | }
625 |
626 | this.mode = null;
627 | this.truncate = line.indexOf(ds.close) > 0;
628 | break;
629 | default:
630 | // 根据不同的类型对标签内部的内容进行处理
631 | if (this.mode) {
632 | // 处理JS代码中的行注释,如果内部有'//'且未换行, 添加一个换行符防止后面的HTML内容被注释掉.
633 | switch (this.mode) {
634 | case Template.modes.EXECUTE:
635 | case Template.modes.ESCAPED:
636 | case Template.modes.OUTPUTS:
637 | if (line.lastIndexOf('//') > line.lastIndexOf('\n')) {
638 | line += '\n';
639 | }
640 | }
641 | switch (this.mode) {
642 | // 执行JS代码
643 | case Template.modes.EXECUTE:
644 | this.source += ' ; ' + line + '\n';
645 | break;
646 | // 编码html
647 | case Template.modes.ESCAPED:
648 | this.source += ' ; __append(escape(' +
649 | line.replace(_TRAILING_SEMICOLON, '').trim() + '))' + '\n';
650 | break;
651 | // 条件判断处理
652 | case Template.modes.PROCESS:
653 | line = line.trim();
654 | if ( line.indexOf(ds.check) === 0 ) {
655 | // else 或者 else if
656 | line = line.substr(1);
657 | if ( !line ) {
658 | this.source += ' ; } else {\n';
659 | } else {
660 | this.source += ' ; } else if (' + line.replace(_TRAILING_SEMICOLON, '') + ') {\n';
661 | }
662 | } else {
663 | // if 或者 end if
664 | if ( !line ) {
665 | this.source += ' ; }\n';
666 | } else {
667 | this.source += ' ; if (' + line.replace(_TRAILING_SEMICOLON, '') + ') {\n';
668 | }
669 | }
670 | break;
671 | // 条件分支处理
672 | case Template.modes.MATCHES:
673 | line = line.trim();
674 | if ( line.indexOf(ds.shift) === 0 ) {
675 | // case 或者 default
676 | line = line.substr(1);
677 | if ( !line ) {
678 | this.source += ' ; break;\n';
679 | this.source += ' ; default:\n';
680 | } else {
681 | if (this.startSwitch) {
682 | this.startSwitch = false;
683 | } else {
684 | this.source += ' ; break;\n';
685 | }
686 | var items = JSON.parse("[" + line + "]");
687 | for (var i=0,len=items.length; i