UNPKG

@builder.io/mitosis

Version:

Write components once, run everywhere. Compiles to Vue, React, Solid, and Liquid. Import code from Figma and Builder.io

576 lines (575 loc) 19.7 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.iteratorProperty = exports.lastProperty = exports.isStatement = exports.iif = exports.arrowFnValue = exports.arrowFnBlock = exports.invoke = exports.quote = exports.Imports = exports.Symbol = exports.SrcBuilder = exports.File = void 0; const parser_typescript_1 = __importDefault(require("prettier/parser-typescript")); const standalone_1 = require("prettier/standalone"); const event_handlers_1 = require("../../helpers/event-handlers"); const html_tags_1 = require("../../constants/html_tags"); const builder_1 = require("../../parsers/builder"); const stable_serialize_1 = require("./helpers/stable-serialize"); class File { get module() { return this.filename.substr(0, this.filename.lastIndexOf('.')); } get path() { return this.filename; } get contents() { return this.toString(); } constructor(filename, options, qwikModule, qrlPrefix) { this.imports = new Imports(); this.exports = new Map(); this.filename = filename; this.options = options; this.src = new SrcBuilder(this, this.options); this.qwikModule = qwikModule; this.qrlPrefix = qrlPrefix; } import(module, symbol, as) { return this.imports.get(module, symbol, as); } toQrlChunk() { return quote(this.qrlPrefix + this.module + '.js'); } exportConst(name, value, locallyVisible = false) { if (this.exports.has(name)) return; this.exports.set(name, this.src.isModule ? name : 'exports.' + name); this.src.const(name, value, true, locallyVisible); } exportDefault(symbolName) { if (this.options.isPretty) { this.src.emit('\n\n'); } if (this.options.isModule) { this.src.emit('export default ', symbolName, ';'); } else { this.src.emit('module.exports=', symbolName, ';'); } } toString() { const srcImports = new SrcBuilder(this, this.options); const imports = this.imports.imports; const modules = Array.from(imports.keys()).sort(); modules.forEach((module) => { if (module == '<SELF>') return; const symbolMap = imports.get(module); const symbols = Array.from(symbolMap.values()); symbols.sort(symbolSort); if (removeExt(module) !== removeExt(this.qrlPrefix + this.filename)) { srcImports.import(module, symbols); } }); let source = srcImports.toString() + this.src.toString(); if (this.options.isPretty) { try { source = (0, standalone_1.format)(source, { parser: 'typescript', plugins: [ 'prettier/parser-postcss', parser_typescript_1.default, 'prettier-plugin-organize-imports', ], htmlWhitespaceSensitivity: 'ignore', }); } catch (e) { throw new Error(e + '\n' + '========================================================================\n' + source + '\n\n========================================================================'); } } return source; } } exports.File = File; function symbolSort(a, b) { return a.importName < b.importName ? -1 : a.importName === b.importName ? 0 : 1; } function removeExt(filename) { const indx = filename.lastIndexOf('.'); return indx == -1 ? filename : filename.substr(0, indx); } class SrcBuilder { constructor(file, options) { this.buf = []; this.jsxDepth = 0; /** * Used to signal that we are generating code for Builder. * * In builder the `<For/>` iteration places the value on the state. */ this.isBuilder = false; this.file = file; this.isTypeScript = options.isTypeScript; this.isModule = options.isModule; this.isJSX = options.isJSX; this.isBuilder = options.isBuilder; } import(module, symbols) { if (this.isModule) { this.emit('import'); if (symbols.length === 1 && symbols[0].importName === 'default') { this.emit(' ', symbols[0].localName, ' '); } else { this.emit('{'); symbols.forEach((symbol) => { if (symbol.importName === symbol.localName) { this.emit(symbol.importName); } else { this.emit(symbol.importName, ' as ', symbol.localName); } this.emit(','); }); this.emit('}'); } this.emit('from', quote(module), ';'); } else { symbols.forEach((symbol) => { this.const(symbol.localName, function () { this.emit(invoke('require', [quote(module)])); if (symbol.importName !== 'default') { this.emit('.', symbol.importName); } }); }); } if (this.file.options.isPretty) { this.emit('\n\n'); } return this; } emit(...values) { for (let i = 0; i < values.length; i++) { const value = values[i]; if (typeof value == 'function') { value.call(this); } else if (value === null) { this.push('null'); } else if (value === undefined) { this.push('undefined'); } else if (typeof value == 'string') { this.push(value); } else if (typeof value == 'number') { this.push(String(value)); } else if (typeof value == 'boolean') { this.push(String(value)); } else if (Array.isArray(value)) { this.emitList(value); } else if (typeof value == 'object') { this.emit('{'); let separator = false; for (const key in value) { if (Object.prototype.hasOwnProperty.call(value, key)) { if (separator) { this.emit(','); } this.emit(possiblyQuotePropertyName(key)).emit(':', value[key]); separator = true; } } this.emit('}'); } else { throw new Error('Unexpected value: ' + value); } } return this; } push(value) { if (value.startsWith(')') || value.startsWith(':') || value.startsWith(']') || value.startsWith('}') || value.startsWith(',') || value.startsWith('?')) { // clear last ',' or ';'; let index = this.buf.length - 1; let ch = this.buf[index]; if (ch.endsWith(',') || ch.endsWith(';')) { ch = ch.substring(0, ch.length - 1); this.buf[index] = ch; } } this.buf.push(value); } emitList(values, sep = ',') { let separator = false; for (const value of values) { if (separator) { this.emit(sep); } this.emit(value); separator = true; } return this; } const(name, value, export_ = false, locallyVisible = false) { if (export_) { this.emit(this.isModule ? 'export const ' : (locallyVisible ? 'const ' + name + '=' : '') + 'exports.'); } else { this.emit('const '); } this.emit(name); if (value !== undefined) { this.emit('=', value); } this.emit(';'); return this; } type(def) { if (this.isTypeScript) { this.emit(':', def); } return this; } typeParameters(typeParameters) { if (this.isTypeScript && typeParameters && typeParameters.length) { this.emit('<', typeParameters, '>'); } } jsxExpression(expression) { const previousJsxDepth = this.jsxDepth; try { if (previousJsxDepth) { this.jsxDepth = 0; this.isJSX && this.emit('{'); } expression.apply(this); } finally { if (previousJsxDepth) { this.isJSX && this.emit('}'); } this.jsxDepth = previousJsxDepth; } } jsxBegin(symbol, props, bindings) { this.jsxDepth++; const self = this; if (symbol == 'div' && ('href' in props || 'href' in bindings)) { // HACK: if we contain href then we are `a` not `div` symbol = 'a'; } if (this.isJSX) { this.emit('<' + symbol); } else { this.emit('h(', literalTagName(symbol), ',{'); } for (const key in props) { if (Object.prototype.hasOwnProperty.call(props, key) && !ignoreKey(key) && !Object.prototype.hasOwnProperty.call(bindings, key)) { emitJsxProp(key, quote(props[key])); } } for (const rawKey in bindings) { if (bindings[rawKey].type === 'spread') { if (this.isJSX) { this.emit('{...', bindings[rawKey].code, '}'); } else { this.emit('...', bindings[rawKey].code); } } else if (Object.prototype.hasOwnProperty.call(bindings, rawKey) && !ignoreKey(rawKey)) { let binding = bindings[rawKey]; binding = binding && typeof binding == 'object' && 'code' in binding ? binding.code : binding; if (rawKey === 'class' && props.class) { // special case for classes as they can have both static and dynamic binding binding = quote(props.class + ' ') + '+' + binding; } let key = lastProperty(rawKey); if (isEvent(key)) { key = key + '$'; binding = `${this.file.import(this.file.qwikModule, '$').localName}((event)=>${binding})`; } else if (!binding && rawKey in props) { binding = quote(props[rawKey]); } else if (binding != null && binding === props[key]) { // HACK: workaround for the fact that sometimes the `bindings` have string literals // We assume that when the binding content equals prop content. binding = quote(binding); } else if (typeof binding == 'string' && isStatement(binding)) { binding = iif(binding); } if (key === 'hide' || key === 'show') { let [truthy, falsy] = key == 'hide' ? ['"none"', '"inherit"'] : ['"inherit"', '"none"']; emitJsxProp('style', function () { this.emit('{display:', binding, '?', truthy, ':', falsy, '}'); }); } else { emitJsxProp(key, binding); } } } if (this.isJSX) { if (!this.isSelfClosingTag(symbol)) { this.emit('>'); } } else { this.emit('},'); } function emitJsxProp(key, value) { if (value) { if (key === 'innerHTML') key = 'dangerouslySetInnerHTML'; if (key === 'dataSet') return; // ignore if (self.isJSX) { if (key.includes(':') && value === '""') { self.emit(' ', key); return; } self.emit(' ', key, '='); if (typeof value == 'string' && value.startsWith('"') && value.endsWith('"')) { self.emit(value); } else { self.emit('{', value, '}'); } } else { self.emit(possiblyQuotePropertyName(key), ':', value, ','); } } } } isSelfClosingTag(symbol) { return html_tags_1.SELF_CLOSING_HTML_TAGS.has(String(symbol)); } jsxEnd(symbol) { if (this.isJSX) { if (this.isSelfClosingTag(symbol)) { this.emit(' />'); } else { this.emit('</', symbol, '>'); } } else { this.emit('),'); } this.jsxDepth--; } jsxBeginFragment(symbol) { this.jsxDepth++; if (this.isJSX) { this.emit('<>'); } else { this.emit('h(', symbol.localName, ',null,'); } } jsxEndFragment() { this.jsxDepth--; if (this.isJSX) { this.emit('</>'); } else { this.emit(')'); } } jsxTextBinding(exp) { if (this.isJSX) { this.emit('{', exp, '}'); } else { this.emit(exp); } } toString() { return this.buf.join(''); } } exports.SrcBuilder = SrcBuilder; function isEvent(name) { return (0, event_handlers_1.checkIsEvent)(name) && isUppercase(name.charAt(2)) && !name.endsWith('$'); } function isUppercase(ch) { return ch == ch.toUpperCase(); } class Symbol { constructor(importName, localName) { this.importName = importName; this.localName = localName; } } exports.Symbol = Symbol; class Imports { constructor() { this.imports = new Map(); } get(moduleName, symbolName, asVar) { let importSymbols = this.imports.get(moduleName); if (!importSymbols) { importSymbols = new Map(); this.imports.set(moduleName, importSymbols); } let symbol = importSymbols.get(symbolName); if (!symbol) { symbol = new Symbol(symbolName, asVar || symbolName); importSymbols.set(symbolName, symbol); } return symbol; } hasImport(localName) { for (const symbolMap of Array.from(this.imports.values())) { for (const symbol of Array.from(symbolMap.values())) { if (symbol.localName === localName) { return true; } } } return false; } } exports.Imports = Imports; function ignoreKey(key) { return (key.startsWith('$') || key.startsWith('_') || key == 'code' || key == '' || key.indexOf('.') !== -1); } function possiblyQuotePropertyName(key) { return /^\w[\w\d]*$/.test(key) ? key : quote(key); } function quote(text) { const string = (0, stable_serialize_1.stableJSONserialize)(text); // So \u2028 is a line separator character and prettier treats it as such // https://www.fileformat.info/info/unicode/char/2028/index.htm // That means it can't be inside of a string, so we replace it with `\\u2028`. // (see double `\\` vs `\`) const parts = string.split('\u2028'); return parts.join('\\u2028'); } exports.quote = quote; function invoke(symbol, args, typeParameters) { return function () { this.emit(typeof symbol == 'string' ? symbol : symbol.localName); this.typeParameters(typeParameters); this.emit('(', args, ')'); }; } exports.invoke = invoke; function arrowFnBlock(args, statements, argTypes) { return function () { this.emit('('); for (let i = 0; i < args.length; i++) { const arg = args[i]; const type = argTypes && argTypes[i]; this.emit(arg); if (type && this.file.options.isTypeScript) { this.emit(':', type); } this.emit(','); } this.emit(')=>{').emitList(statements, ';').emit('}'); }; } exports.arrowFnBlock = arrowFnBlock; function arrowFnValue(args, expression) { return function () { this.emit('(', args, ')=>', expression); }; } exports.arrowFnValue = arrowFnValue; const _virtual_index = '_virtual_index;'; const return_virtual_index = 'return _virtual_index;'; function iif(code) { if (!code) return; code = code.trim(); if (code.endsWith(_virtual_index) && !code.endsWith(return_virtual_index)) { code = code.substr(0, code.length - _virtual_index.length) + return_virtual_index; } if (code.indexOf('export') !== -1) { code = (0, builder_1.convertExportDefaultToReturn)(code); } return function () { code && this.emit('(()=>{', code, '})()'); }; } exports.iif = iif; const LOWER_CASE = 'a'.charCodeAt(0) - 1; function literalTagName(symbol) { if (typeof symbol == 'string' && symbol.charCodeAt(0) > LOWER_CASE && symbol.indexOf('.') === -1) { return quote(symbol); } return symbol; } /** * Returns `true` if the code is a statement (rather than expression). * * Code is an expression if it is a list of identifiers all connected with a valid separator * identifier: [a-z_$](a-z0-9_$)* * separator: [()[]{}.-+/*,] * * it is not 100% but a good enough approximation */ function isStatement(code) { // remove trailing `!` as it is used to mark a non-null assertion in TS // it messes up the logic afterwards if (code.endsWith('!')) { code = code.substr(0, code.length - 1); } code = code.trim(); if ((code.startsWith('(') && code.endsWith(')')) || (code.startsWith('{') && code.endsWith('}'))) { // Code starting with `(` is most likely an IFF and hence is an expression. return false; } const codeNoStrings = code.replace(STRING_LITERAL, 'STRING_LITERAL'); const identifiers = codeNoStrings.split(EXPRESSION_SEPARATORS); const filteredIdentifiers = identifiers.filter((i) => { i = i.trim(); return i && !i.match(EXPRESSION_IDENTIFIER); }); return filteredIdentifiers.length !== 0; } exports.isStatement = isStatement; // https://regexr.com/6cppf const STRING_LITERAL = /(["'`])((\\{2})*|((\n|.)*?[^\\](\\{2})*))\1/g; // https://regexr.com/6cpk4 const EXPRESSION_SEPARATORS = /[()\[\]{}.\?:\-+/*,|&]+/; // https://regexr.com/6cpka const EXPRESSION_IDENTIFIER = /^\s*[a-zA-Z0-9_$]+\s*$/; function lastProperty(expr) { const parts = expr.split('.'); return parts[parts.length - 1]; } exports.lastProperty = lastProperty; function iteratorProperty(expr) { return lastProperty(expr) + 'Item'; } exports.iteratorProperty = iteratorProperty;