[6a3a178] | 1 | "use strict";
|
---|
| 2 | /**
|
---|
| 3 | * @license
|
---|
| 4 | * Copyright Google LLC All Rights Reserved.
|
---|
| 5 | *
|
---|
| 6 | * Use of this source code is governed by an MIT-style license that can be
|
---|
| 7 | * found in the LICENSE file at https://angular.io/license
|
---|
| 8 | */
|
---|
| 9 | Object.defineProperty(exports, "__esModule", { value: true });
|
---|
| 10 | exports.template = exports.templateParser = void 0;
|
---|
| 11 | const source_map_1 = require("source-map");
|
---|
| 12 | // Matches <%= expr %>. This does not support structural JavaScript (for/if/...).
|
---|
| 13 | const kInterpolateRe = /<%=([\s\S]+?)%>/g;
|
---|
| 14 | // Matches <%# text %>. It's a comment and will be entirely ignored.
|
---|
| 15 | const kCommentRe = /<%#([\s\S]+?)%>/g;
|
---|
| 16 | // Used to match template delimiters.
|
---|
| 17 | // <%- expr %>: HTML escape the value.
|
---|
| 18 | // <% ... %>: Structural template code.
|
---|
| 19 | const kEscapeRe = /<%-([\s\S]+?)%>/g;
|
---|
| 20 | const kEvaluateRe = /<%([\s\S]+?)%>/g;
|
---|
| 21 | /** Used to map characters to HTML entities. */
|
---|
| 22 | const kHtmlEscapes = {
|
---|
| 23 | '&': '&',
|
---|
| 24 | '<': '<',
|
---|
| 25 | '>': '>',
|
---|
| 26 | '"': '"',
|
---|
| 27 | "'": ''',
|
---|
| 28 | '`': '`',
|
---|
| 29 | };
|
---|
| 30 | // Used to match HTML entities and HTML characters.
|
---|
| 31 | const reUnescapedHtml = new RegExp(`[${Object.keys(kHtmlEscapes).join('')}]`, 'g');
|
---|
| 32 | function _positionFor(content, offset) {
|
---|
| 33 | let line = 1;
|
---|
| 34 | let column = 0;
|
---|
| 35 | for (let i = 0; i < offset - 1; i++) {
|
---|
| 36 | if (content[i] == '\n') {
|
---|
| 37 | line++;
|
---|
| 38 | column = 0;
|
---|
| 39 | }
|
---|
| 40 | else {
|
---|
| 41 | column++;
|
---|
| 42 | }
|
---|
| 43 | }
|
---|
| 44 | return {
|
---|
| 45 | line,
|
---|
| 46 | column,
|
---|
| 47 | };
|
---|
| 48 | }
|
---|
| 49 | /**
|
---|
| 50 | * Given a source text (and a fileName), returns a TemplateAst.
|
---|
| 51 | */
|
---|
| 52 | function templateParser(sourceText, fileName) {
|
---|
| 53 | const children = [];
|
---|
| 54 | // Compile the regexp to match each delimiter.
|
---|
| 55 | const reExpressions = [kEscapeRe, kCommentRe, kInterpolateRe, kEvaluateRe];
|
---|
| 56 | const reDelimiters = RegExp(reExpressions.map((x) => x.source).join('|') + '|$', 'g');
|
---|
| 57 | const parsed = sourceText.split(reDelimiters);
|
---|
| 58 | let offset = 0;
|
---|
| 59 | // Optimization that uses the fact that the end of a node is always the beginning of the next
|
---|
| 60 | // node, so we keep the positioning of the nodes in memory.
|
---|
| 61 | let start = _positionFor(sourceText, offset);
|
---|
| 62 | let end;
|
---|
| 63 | const increment = reExpressions.length + 1;
|
---|
| 64 | for (let i = 0; i < parsed.length; i += increment) {
|
---|
| 65 | const [content, escape, comment, interpolate, evaluate] = parsed.slice(i, i + increment);
|
---|
| 66 | if (content) {
|
---|
| 67 | end = _positionFor(sourceText, offset + content.length);
|
---|
| 68 | offset += content.length;
|
---|
| 69 | children.push({ kind: 'content', content, start, end });
|
---|
| 70 | start = end;
|
---|
| 71 | }
|
---|
| 72 | if (escape) {
|
---|
| 73 | end = _positionFor(sourceText, offset + escape.length + 5);
|
---|
| 74 | offset += escape.length + 5;
|
---|
| 75 | children.push({ kind: 'escape', expression: escape, start, end });
|
---|
| 76 | start = end;
|
---|
| 77 | }
|
---|
| 78 | if (comment) {
|
---|
| 79 | end = _positionFor(sourceText, offset + comment.length + 5);
|
---|
| 80 | offset += comment.length + 5;
|
---|
| 81 | children.push({ kind: 'comment', text: comment, start, end });
|
---|
| 82 | start = end;
|
---|
| 83 | }
|
---|
| 84 | if (interpolate) {
|
---|
| 85 | end = _positionFor(sourceText, offset + interpolate.length + 5);
|
---|
| 86 | offset += interpolate.length + 5;
|
---|
| 87 | children.push({
|
---|
| 88 | kind: 'interpolate',
|
---|
| 89 | expression: interpolate,
|
---|
| 90 | start,
|
---|
| 91 | end,
|
---|
| 92 | });
|
---|
| 93 | start = end;
|
---|
| 94 | }
|
---|
| 95 | if (evaluate) {
|
---|
| 96 | end = _positionFor(sourceText, offset + evaluate.length + 5);
|
---|
| 97 | offset += evaluate.length + 5;
|
---|
| 98 | children.push({ kind: 'evaluate', expression: evaluate, start, end });
|
---|
| 99 | start = end;
|
---|
| 100 | }
|
---|
| 101 | }
|
---|
| 102 | return {
|
---|
| 103 | fileName,
|
---|
| 104 | content: sourceText,
|
---|
| 105 | children,
|
---|
| 106 | };
|
---|
| 107 | }
|
---|
| 108 | exports.templateParser = templateParser;
|
---|
| 109 | /**
|
---|
| 110 | * Fastest implementation of the templating algorithm. It only add strings and does not bother
|
---|
| 111 | * with source maps.
|
---|
| 112 | */
|
---|
| 113 | function templateFast(ast, options) {
|
---|
| 114 | const module = options && options.module ? 'module.exports.default =' : '';
|
---|
| 115 | const reHtmlEscape = reUnescapedHtml.source.replace(/[']/g, "\\\\\\'");
|
---|
| 116 | return `
|
---|
| 117 | return ${module} function(obj) {
|
---|
| 118 | obj || (obj = {});
|
---|
| 119 | let __t;
|
---|
| 120 | let __p = '';
|
---|
| 121 | const __escapes = ${JSON.stringify(kHtmlEscapes)};
|
---|
| 122 | const __escapesre = new RegExp('${reHtmlEscape}', 'g');
|
---|
| 123 |
|
---|
| 124 | const __e = function(s) {
|
---|
| 125 | return s ? s.replace(__escapesre, function(key) { return __escapes[key]; }) : '';
|
---|
| 126 | };
|
---|
| 127 | with (obj) {
|
---|
| 128 | ${ast.children
|
---|
| 129 | .map((node) => {
|
---|
| 130 | switch (node.kind) {
|
---|
| 131 | case 'content':
|
---|
| 132 | return `__p += ${JSON.stringify(node.content)};`;
|
---|
| 133 | case 'interpolate':
|
---|
| 134 | return `__p += ((__t = (${node.expression})) == null) ? '' : __t;`;
|
---|
| 135 | case 'escape':
|
---|
| 136 | return `__p += __e(${node.expression});`;
|
---|
| 137 | case 'evaluate':
|
---|
| 138 | return node.expression;
|
---|
| 139 | }
|
---|
| 140 | })
|
---|
| 141 | .join('\n')}
|
---|
| 142 | }
|
---|
| 143 |
|
---|
| 144 | return __p;
|
---|
| 145 | };
|
---|
| 146 | `;
|
---|
| 147 | }
|
---|
| 148 | /**
|
---|
| 149 | * Templating algorithm with source map support. The map is outputted as //# sourceMapUrl=...
|
---|
| 150 | */
|
---|
| 151 | function templateWithSourceMap(ast, options) {
|
---|
| 152 | const sourceUrl = ast.fileName;
|
---|
| 153 | const module = options && options.module ? 'module.exports.default =' : '';
|
---|
| 154 | const reHtmlEscape = reUnescapedHtml.source.replace(/[']/g, "\\\\\\'");
|
---|
| 155 | const preamble = new source_map_1.SourceNode(1, 0, sourceUrl, '').add(new source_map_1.SourceNode(1, 0, sourceUrl, [
|
---|
| 156 | `return ${module} function(obj) {\n`,
|
---|
| 157 | ' obj || (obj = {});\n',
|
---|
| 158 | ' let __t;\n',
|
---|
| 159 | ' let __p = "";\n',
|
---|
| 160 | ` const __escapes = ${JSON.stringify(kHtmlEscapes)};\n`,
|
---|
| 161 | ` const __escapesre = new RegExp('${reHtmlEscape}', 'g');\n`,
|
---|
| 162 | `\n`,
|
---|
| 163 | ` const __e = function(s) { `,
|
---|
| 164 | ` return s ? s.replace(__escapesre, function(key) { return __escapes[key]; }) : '';`,
|
---|
| 165 | ` };\n`,
|
---|
| 166 | ` with (obj) {\n`,
|
---|
| 167 | ]));
|
---|
| 168 | const end = ast.children.length
|
---|
| 169 | ? ast.children[ast.children.length - 1].end
|
---|
| 170 | : { line: 0, column: 0 };
|
---|
| 171 | const nodes = ast.children
|
---|
| 172 | .reduce((chunk, node) => {
|
---|
| 173 | let code = '';
|
---|
| 174 | switch (node.kind) {
|
---|
| 175 | case 'content':
|
---|
| 176 | code = [
|
---|
| 177 | new source_map_1.SourceNode(node.start.line, node.start.column, sourceUrl, '__p = __p'),
|
---|
| 178 | ...node.content.split('\n').map((line, i, arr) => {
|
---|
| 179 | return new source_map_1.SourceNode(node.start.line + i, i == 0 ? node.start.column : 0, sourceUrl, '\n + ' + JSON.stringify(line + (i == arr.length - 1 ? '' : '\n')));
|
---|
| 180 | }),
|
---|
| 181 | new source_map_1.SourceNode(node.end.line, node.end.column, sourceUrl, ';\n'),
|
---|
| 182 | ];
|
---|
| 183 | break;
|
---|
| 184 | case 'interpolate':
|
---|
| 185 | code = [
|
---|
| 186 | new source_map_1.SourceNode(node.start.line, node.start.column, sourceUrl, '__p += ((__t = '),
|
---|
| 187 | ...node.expression.split('\n').map((line, i, arr) => {
|
---|
| 188 | return new source_map_1.SourceNode(node.start.line + i, i == 0 ? node.start.column : 0, sourceUrl, line + (i == arr.length - 1 ? '' : '\n'));
|
---|
| 189 | }),
|
---|
| 190 | new source_map_1.SourceNode(node.end.line, node.end.column, sourceUrl, ') == null ? "" : __t);\n'),
|
---|
| 191 | ];
|
---|
| 192 | break;
|
---|
| 193 | case 'escape':
|
---|
| 194 | code = [
|
---|
| 195 | new source_map_1.SourceNode(node.start.line, node.start.column, sourceUrl, '__p += __e('),
|
---|
| 196 | ...node.expression.split('\n').map((line, i, arr) => {
|
---|
| 197 | return new source_map_1.SourceNode(node.start.line + i, i == 0 ? node.start.column : 0, sourceUrl, line + (i == arr.length - 1 ? '' : '\n'));
|
---|
| 198 | }),
|
---|
| 199 | new source_map_1.SourceNode(node.end.line, node.end.column, sourceUrl, ');\n'),
|
---|
| 200 | ];
|
---|
| 201 | break;
|
---|
| 202 | case 'evaluate':
|
---|
| 203 | code = [
|
---|
| 204 | ...node.expression.split('\n').map((line, i, arr) => {
|
---|
| 205 | return new source_map_1.SourceNode(node.start.line + i, i == 0 ? node.start.column : 0, sourceUrl, line + (i == arr.length - 1 ? '' : '\n'));
|
---|
| 206 | }),
|
---|
| 207 | new source_map_1.SourceNode(node.end.line, node.end.column, sourceUrl, '\n'),
|
---|
| 208 | ];
|
---|
| 209 | break;
|
---|
| 210 | }
|
---|
| 211 | return chunk.add(new source_map_1.SourceNode(node.start.line, node.start.column, sourceUrl, code));
|
---|
| 212 | }, preamble)
|
---|
| 213 | .add(new source_map_1.SourceNode(end.line, end.column, sourceUrl, [' };\n', '\n', ' return __p;\n', '}\n']));
|
---|
| 214 | const code = nodes.toStringWithSourceMap({
|
---|
| 215 | file: sourceUrl,
|
---|
| 216 | sourceRoot: (options && options.sourceRoot) || '.',
|
---|
| 217 | });
|
---|
| 218 | // Set the source content in the source map, otherwise the sourceUrl is not enough
|
---|
| 219 | // to find the content.
|
---|
| 220 | code.map.setSourceContent(sourceUrl, ast.content);
|
---|
| 221 | return (code.code +
|
---|
| 222 | '\n//# sourceMappingURL=data:application/json;base64,' +
|
---|
| 223 | Buffer.from(code.map.toString()).toString('base64'));
|
---|
| 224 | }
|
---|
| 225 | /**
|
---|
| 226 | * An equivalent of EJS templates, which is based on John Resig's `tmpl` implementation
|
---|
| 227 | * (http://ejohn.org/blog/javascript-micro-templating/) and Laura Doktorova's doT.js
|
---|
| 228 | * (https://github.com/olado/doT).
|
---|
| 229 | *
|
---|
| 230 | * This version differs from lodash by removing support from ES6 quasi-literals, and making the
|
---|
| 231 | * code slightly simpler to follow. It also does not depend on any third party, which is nice.
|
---|
| 232 | *
|
---|
| 233 | * Finally, it supports SourceMap, if you ever need to debug, which is super nice.
|
---|
| 234 | *
|
---|
| 235 | * @param content The template content.
|
---|
| 236 | * @param options Optional Options. See TemplateOptions for more description.
|
---|
| 237 | * @return {(input: T) => string} A function that accept an input object and returns the content
|
---|
| 238 | * of the template with the input applied.
|
---|
| 239 | */
|
---|
| 240 | function template(content, options) {
|
---|
| 241 | const sourceUrl = (options && options.sourceURL) || 'ejs';
|
---|
| 242 | const ast = templateParser(content, sourceUrl);
|
---|
| 243 | let source;
|
---|
| 244 | // If there's no need for source map support, we revert back to the fast implementation.
|
---|
| 245 | if (options && options.sourceMap) {
|
---|
| 246 | source = templateWithSourceMap(ast, options);
|
---|
| 247 | }
|
---|
| 248 | else {
|
---|
| 249 | source = templateFast(ast, options);
|
---|
| 250 | }
|
---|
| 251 | // We pass a dummy module in case the module option is passed. If `module: true` is passed, we
|
---|
| 252 | // need to only use the source, not the function itself. Otherwise expect a module object to be
|
---|
| 253 | // passed, and we use that one.
|
---|
| 254 | const fn = Function('module', source);
|
---|
| 255 | const module = options && options.module ? (options.module === true ? { exports: {} } : options.module) : null;
|
---|
| 256 | const result = fn(module);
|
---|
| 257 | // Provide the compiled function's source by its `toString` method or
|
---|
| 258 | // the `source` property as a convenience for inlining compiled templates.
|
---|
| 259 | result.source = source;
|
---|
| 260 | return result;
|
---|
| 261 | }
|
---|
| 262 | exports.template = template;
|
---|