1 | 'use strict';
|
2 |
|
3 | var path = require('path');
|
4 | var acorn = require('acorn');
|
5 | var escodegen = require('escodegen');
|
6 | var estraverse = require('estraverse');
|
7 | var sourceMap = require('source-map');
|
8 | var mergeSourceMap = require('merge-source-map');
|
9 | var compile = require('./compile');
|
10 | var defaults = require('./defaults');
|
11 | var runtimePath = require.resolve('./runtime');
|
12 |
|
13 | var CONSTS = compile.Compiler.CONSTS;
|
14 | var LOCAL_MODULE = /^\.+\//;
|
15 |
|
16 |
|
17 | var getDefaults = function getDefaults(options) {
|
18 |
|
19 | var setting = {
|
20 | imports: runtimePath,
|
21 | bail: true,
|
22 | cache: false,
|
23 | debug: false,
|
24 |
|
25 | sourceMap: false,
|
26 | sourceRoot: options.sourceRoot
|
27 | };
|
28 |
|
29 | for (var name in options) {
|
30 | setting[name] = options[name];
|
31 | }
|
32 |
|
33 | return defaults.$extend(setting);
|
34 | };
|
35 |
|
36 |
|
37 |
|
38 | var convertFilenameNode = function convertFilenameNode(node, options) {
|
39 | if (node.type === 'Literal') {
|
40 | var resolvePath = options.resolveFilename(node.value, options);
|
41 | var dirname = path.dirname(options.filename);
|
42 | var relativePath = path.relative(dirname, resolvePath);
|
43 |
|
44 | if (LOCAL_MODULE.test(relativePath)) {
|
45 | node.value = relativePath;
|
46 | } else {
|
47 | node.value = './' + relativePath;
|
48 | }
|
49 |
|
50 | delete node.raw;
|
51 | }
|
52 |
|
53 | return node;
|
54 | };
|
55 |
|
56 |
|
57 | var getOldSourceMap = function getOldSourceMap(mappings, _ref) {
|
58 | var sourceRoot = _ref.sourceRoot,
|
59 | source = _ref.source,
|
60 | file = _ref.file;
|
61 |
|
62 | var oldSourceMap = new sourceMap.SourceMapGenerator({
|
63 | file: file,
|
64 | sourceRoot: sourceRoot
|
65 | });
|
66 |
|
67 | mappings.forEach(function (mapping) {
|
68 | mapping.source = source;
|
69 | oldSourceMap.addMapping(mapping);
|
70 | });
|
71 |
|
72 | return oldSourceMap.toJSON();
|
73 | };
|
74 |
|
75 |
|
76 |
|
77 |
|
78 |
|
79 |
|
80 |
|
81 | var precompile = function precompile() {
|
82 | var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
83 |
|
84 | if (typeof options.filename !== 'string') {
|
85 | throw Error('template.precompile(): "options.filename" required');
|
86 | }
|
87 |
|
88 | options = getDefaults(options);
|
89 |
|
90 | var code = null;
|
91 | var sourceMap = null;
|
92 | var ast = null;
|
93 | var imports = options.imports;
|
94 | var functions = [CONSTS.INCLUDE, CONSTS.EXTEND];
|
95 |
|
96 | if (typeof imports !== 'string') {
|
97 | throw Error('template.precompile(): "options.imports" is a file. Example:\n' + 'options: { imports: require.resolve("art-template/lib/runtime") }\n');
|
98 | } else {
|
99 | options.imports = require(imports);
|
100 | }
|
101 |
|
102 | var isLocalModule = LOCAL_MODULE.test(imports);
|
103 | var tplImportsPath = isLocalModule ? imports : path.relative(path.dirname(options.filename), imports);
|
104 | var fn = compile(options);
|
105 |
|
106 | code = '(' + fn.toString() + ')';
|
107 | ast = acorn.parse(code, {
|
108 | locations: options.sourceMap
|
109 | });
|
110 |
|
111 | var extendNode = null;
|
112 | var enter = function enter(node) {
|
113 | if (node.type === 'VariableDeclarator' && functions.indexOf(node.id.name) !== -1) {
|
114 |
|
115 | if (node.id.name === CONSTS.INCLUDE) {
|
116 | node['init'] = {
|
117 | type: 'FunctionExpression',
|
118 | params: [{
|
119 | type: 'Identifier',
|
120 | name: 'content'
|
121 | }],
|
122 | body: {
|
123 | type: 'BlockStatement',
|
124 | body: [{
|
125 | type: 'ExpressionStatement',
|
126 | expression: {
|
127 | type: 'AssignmentExpression',
|
128 | operator: '+=',
|
129 | left: {
|
130 | type: 'Identifier',
|
131 | name: CONSTS.OUT
|
132 | },
|
133 | right: {
|
134 | type: 'Identifier',
|
135 | name: 'content'
|
136 | }
|
137 | }
|
138 | }, {
|
139 | type: 'ReturnStatement',
|
140 | argument: {
|
141 | type: 'Identifier',
|
142 | name: CONSTS.OUT
|
143 | }
|
144 | }]
|
145 | }
|
146 | };
|
147 | return node;
|
148 | } else {
|
149 | this.remove();
|
150 | }
|
151 | } else if (node.type === 'CallExpression' && node.callee.type === 'Identifier' && functions.indexOf(node.callee.name) !== -1) {
|
152 | var replaceNode = void 0;
|
153 | switch (node.callee.name) {
|
154 | case CONSTS.EXTEND:
|
155 | extendNode = convertFilenameNode(node.arguments[0], options);
|
156 | replaceNode = {
|
157 | type: 'AssignmentExpression',
|
158 | operator: '=',
|
159 | left: {
|
160 | type: 'Identifier',
|
161 | name: CONSTS.FROM
|
162 | },
|
163 | right: {
|
164 | type: 'Literal',
|
165 | value: true
|
166 | }
|
167 | };
|
168 |
|
169 | break;
|
170 |
|
171 | case CONSTS.INCLUDE:
|
172 | var filename = node.arguments.shift();
|
173 | var filenameNode = filename.name === CONSTS.FROM ? extendNode : convertFilenameNode(filename, options);
|
174 | var paramNodes = node.arguments.length ? node.arguments : [{
|
175 | type: 'Identifier',
|
176 | name: CONSTS.DATA
|
177 | }];
|
178 |
|
179 | replaceNode = node;
|
180 | replaceNode['arguments'] = [{
|
181 | type: 'CallExpression',
|
182 | callee: {
|
183 | type: 'CallExpression',
|
184 | callee: {
|
185 | type: 'Identifier',
|
186 | name: 'require'
|
187 | },
|
188 | arguments: [filenameNode]
|
189 | },
|
190 | arguments: paramNodes
|
191 | }];
|
192 |
|
193 | break;
|
194 | }
|
195 |
|
196 | return replaceNode;
|
197 | }
|
198 | };
|
199 |
|
200 | ast = estraverse.replace(ast, {
|
201 | enter: enter
|
202 | });
|
203 |
|
204 | if (options.sourceMap) {
|
205 | var sourceRoot = options.sourceRoot;
|
206 | var source = path.relative(sourceRoot, options.filename);
|
207 | var file = path.basename(source);
|
208 | var gen = escodegen.generate(ast, {
|
209 | sourceMap: source,
|
210 | file: file,
|
211 | sourceMapRoot: sourceRoot,
|
212 | sourceMapWithCode: true
|
213 | });
|
214 | code = gen.code;
|
215 |
|
216 | var newSourceMap = gen.map.toJSON();
|
217 | var oldSourceMap = getOldSourceMap(fn.mappings, {
|
218 | sourceRoot: sourceRoot,
|
219 | source: source,
|
220 | file: file
|
221 | });
|
222 | sourceMap = mergeSourceMap(oldSourceMap, newSourceMap);
|
223 | sourceMap.file = file;
|
224 | sourceMap.sourcesContent = fn.sourcesContent;
|
225 | } else {
|
226 | code = escodegen.generate(ast);
|
227 | }
|
228 |
|
229 | code = code.replace(/^\(|\)[;\s]*?$/g, '');
|
230 | code = 'var ' + CONSTS.IMPORTS + ' = require(' + JSON.stringify(tplImportsPath) + ');\n' + 'module.exports = ' + code + ';';
|
231 |
|
232 | return {
|
233 | code: code,
|
234 | ast: ast,
|
235 | sourceMap: sourceMap,
|
236 | toString: function toString() {
|
237 | return code;
|
238 | }
|
239 | };
|
240 | };
|
241 |
|
242 | module.exports = precompile; |
\ | No newline at end of file |