2 * Copyright (C) 2018 Nick Downing <nick@ndcode.org>
3 * SPDX-License-Identifier: MIT
5 * Permission is hereby granted, free of charge, to any person obtaining a copy
6 * of this software and associated documentation files (the "Software"), to
7 * deal in the Software without restriction, including without limitation the
8 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
9 * sell copies of the Software, and to permit persons to whom the Software is
10 * furnished to do so, subject to the following conditions:
12 * The above copyright notice and this permission notice shall be included in
13 * all copies or substantial portions of the Software.
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
24 let assert = require('assert')
25 let astring = require('astring')
26 let transform = require('./transform')
27 let uglify_js = require('@ndcode/uglify-js')
29 let expr_to_tag = (node, context, html_allowed, call_allowed) => {
30 if (node.type === 'Identifier')
31 context.name.push(node.name)
32 else if (node.type === 'Literal')
33 context.name.push(node.value.toString())
34 else if (node.type === 'BinaryExpression' && node.operator === '-') {
35 if (!expr_to_tag(node.left, context, false, false))
37 if (!expr_to_tag(node.right, context, html_allowed, call_allowed))
40 else if (node.type === 'MemberExpression' && !node.computed) {
41 if (!expr_to_tag(node.object, context, false, false))
43 context.tag[context.tag_type].push(context.name.join('-'))
46 if (!expr_to_tag(node.property, context, html_allowed, call_allowed))
49 else if (node.type === 'MemberExpressionHash') {
50 if (!expr_to_tag(node.object, context, false, false))
52 context.tag[context.tag_type].push(context.name.join('-'))
55 if (!expr_to_tag(node.property, context, html_allowed, call_allowed))
58 else if (html_allowed && node.type === 'HTMLExpression') {
59 if (!expr_to_tag(node.tag, context, false, true))
61 context.tag[context.tag_type].push(context.name.join('-'))
62 context.body = node.body
64 else if (call_allowed && node.type === 'CallExpression') {
65 if (!expr_to_tag(node.callee, context, false, false))
67 context.arguments = node.arguments
74 let expr_to_name = (node, name) => {
75 if (node.type === 'Identifier')
77 else if (node.type === 'Literal')
78 name.push(node.value.toString())
79 else if (node.type === 'BinaryExpression' && node.operator === '-') {
80 expr_to_name(node.left, name)
81 expr_to_name(node.right, name)
87 let html_body = (context, st, c) => {
88 assert(context.tag[0].length == 1)
89 let tag = context.tag[0][0]
90 let prefix = '<' + tag
92 if (context.tag[1].length)
93 prefix += ' class="' + context.tag[1].join(' ') + '"'
95 if (context.tag[2].length)
96 prefix += ' id="' + context.tag[2].join(' ') + '"'
99 for (var i = 0; i < context.arguments.length; ++i) {
100 let argument = context.arguments[i]
102 let name_expr, value_expr
104 argument.type === 'AssignmentExpression' &&
105 argument.operator === '='
107 name_expr = argument.left
108 value_expr = c(argument.right, st, 'Expression')
112 value_expr = undefined
116 expr_to_name(name_expr, name)
117 prefix += ' ' + name.join('-')
119 if (value_expr !== undefined) {
121 if (value_expr.type === 'Literal')
125 .replaceAll('&', '&')
126 .replaceAll('"', '"')
133 type: 'BinaryExpression',
134 left: expr === undefined ? expr1 : {
135 type: 'BinaryExpression',
142 type: 'CallExpression',
144 type: 'MemberExpression',
146 type: 'CallExpression',
148 type: 'MemberExpression',
150 type: 'CallExpression',
152 type: 'MemberExpression',
206 let body = c(context.body, st, 'Statement').body
207 let prefix_is_template = false
208 if (tag === 'script') {
209 let program = {type: 'Program', body, sourceType: 'script'}
211 //prefix += astring.generate(program, {indent: ''})
213 let render = uglify_js.minify(
214 uglify_js.AST_Node.from_mozilla_ast(program),
218 output: {interpolate: true}
223 let code = render.code
224 if (render.interpolated) {
225 prefix = prefix.replaceAll('\\', '\\\\').replaceAll('${', '\\${')
226 prefix_is_template = true
229 code = code.replaceAll('\\${', '${').replaceAll('\\\\', '\\')
232 else if (body.length !== 0) {
237 expr = expr === undefined ? expr1 : {
238 type: 'BinaryExpression',
245 type: 'ExpressionStatement',
247 type: 'CallExpression',
249 type: 'MemberExpression',
269 result = result.concat(body)
279 prefix += '</' + tag + '>'
280 if (prefix.length !== 0) {
281 let expr1 = prefix_is_template ?
282 // note: we are cheating a bit here because prefix contains some (${...})
283 // substitutions to be done by the server, and the contents of those are
284 // meant to be given in AST form in expressions list, but we can get away
285 // with it because UglifyJS uses the raw form of the quasis string which
286 // can distinguish between the (${...)} for server and \${...} for client
288 type: 'TemplateLiteral',
292 type: 'TemplateExpression',
294 raw: prefix.replaceAll('`', '\\`')
304 expr = expr === undefined ? expr1 : {
305 type: 'BinaryExpression',
312 type: 'ExpressionStatement',
314 type: 'CallExpression',
316 type: 'MemberExpression',
337 let visitors = Object.assign({}, transform.visitors)
338 let visitors_ExpressionStatement = visitors.ExpressionStatement
339 visitors.ExpressionStatement = (node, st, c) => {
340 if (node.expression.type === 'Literal')
342 type: 'ExpressionStatement',
344 type: 'CallExpression',
346 type: 'MemberExpression',
361 node.expression.value
363 .replaceAll('&', '&')
364 .replaceAll('<', '<')
370 node.expression.type === 'TemplateLiteral' ||
371 node.expression.type === 'TaggedTemplateLiteral'
374 type: 'ExpressionStatement',
376 type: 'CallExpression',
378 type: 'MemberExpression',
391 type: 'CallExpression',
393 type: 'MemberExpression',
395 type: 'CallExpression',
397 type: 'MemberExpression',
398 object: c(node.expression, st, 'Expression'),
437 node.expression.type === 'BinaryExpression' ||
438 node.expression.type === 'MemberExpression' ||
439 node.expression.type === 'HTMLExpression'
441 context = {name: [], tag: [[], [], []], tag_type: 0, arguments: []}
443 expr_to_tag(node.expression, context, true, false) &&
444 context.body !== undefined
446 let body = html_body(context, st, c)
447 return body.length === 1 ? body[0] : {
448 type: 'BlockStatement',
453 return visitors_ExpressionStatement(node, st, c)
455 let visitors_Expression = visitors.Expression
456 visitors.Expression = (node, st, c) => {
458 node.type === 'BinaryExpression' ||
459 node.type === 'MemberExpression' ||
460 node.type === 'HTMLExpression'
462 context = {name: [], tag: [[], [], []], tag_type: 0, arguments: []}
464 expr_to_tag(node, context, true, false) &&
465 context.body !== undefined
468 type: 'CallExpression',
470 type: 'ArrowFunctionExpression',
477 type: 'BlockStatement',
480 type: 'VariableDeclaration',
483 type: 'VariableDeclarator',
489 type: 'ArrayExpression',
497 .concat(html_body(context, st, c))
501 type: 'ReturnStatement',
503 type: 'CallExpression',
505 type: 'MemberExpression',
532 return visitors_Expression(node, st, c)
535 module.exports = visitors