diff --git a/packages/transpiler/babel-preset-inula-jsx/package.json b/packages/transpiler/babel-preset-inula-jsx/package.json index 42721ea7682084d5895014bc3030aee1dc37816c..816266a0f28337d2d581a2c68083d9825cbde040 100644 --- a/packages/transpiler/babel-preset-inula-jsx/package.json +++ b/packages/transpiler/babel-preset-inula-jsx/package.json @@ -22,7 +22,9 @@ "test": "vitest --ui" }, "devDependencies": { + "@babel/generator": "^7.23.6", "@types/babel__core": "^7.20.5", + "@types/babel__generator": "^7.6.8", "@types/node": "^20.10.5", "@vitest/ui": "^1.2.1", "tsup": "^6.7.0", @@ -31,6 +33,9 @@ }, "dependencies": { "@babel/plugin-syntax-jsx": "7.16.7", + "@babel/plugin-syntax-typescript": "^7.23.3", + "@inula/jsx-view-generator": "workspace:*", + "@inula/jsx-view-parser": "workspace:*", "babel-plugin-syntax-typescript-new": "^1.0.0", "minimatch": "^9.0.3" }, diff --git a/packages/transpiler/babel-preset-inula-jsx/src/const.ts b/packages/transpiler/babel-preset-inula-jsx/src/const.ts new file mode 100644 index 0000000000000000000000000000000000000000..1383b408a7ee23f497ff8897e3a94f9edf056d0f --- /dev/null +++ b/packages/transpiler/babel-preset-inula-jsx/src/const.ts @@ -0,0 +1,513 @@ + +export const importMap = [ + 'createElement', + 'setStyle', + 'setAttribute', + 'setDataset', + 'setProperty', + 'setEvent', + 'delegateEvent', + 'addEventListener', + 'watch', + 'insert', + 'createComponent', + 'createText' +].reduce>((acc, cur) => { + acc[cur] = cur; + return acc; +}, {}); + +export const htmlTags = [ + 'a', + 'abbr', + 'address', + 'area', + 'article', + 'aside', + 'audio', + 'b', + 'base', + 'bdi', + 'bdo', + 'blockquote', + 'body', + 'br', + 'button', + 'canvas', + 'caption', + 'cite', + 'code', + 'col', + 'colgroup', + 'data', + 'datalist', + 'dd', + 'del', + 'details', + 'dfn', + 'dialog', + 'div', + 'dl', + 'dt', + 'em', + 'embed', + 'fieldset', + 'figcaption', + 'figure', + 'footer', + 'form', + 'h1', + 'h2', + 'h3', + 'h4', + 'h5', + 'h6', + 'head', + 'header', + 'hgroup', + 'hr', + 'html', + 'i', + 'iframe', + 'img', + 'input', + 'ins', + 'kbd', + 'label', + 'legend', + 'li', + 'link', + 'main', + 'map', + 'mark', + 'menu', + 'meta', + 'meter', + 'nav', + 'noscript', + 'object', + 'ol', + 'optgroup', + 'option', + 'output', + 'p', + 'picture', + 'pre', + 'progress', + 'q', + 'rp', + 'rt', + 'ruby', + 's', + 'samp', + 'script', + 'section', + 'select', + 'slot', + 'small', + 'source', + 'span', + 'strong', + 'style', + 'sub', + 'summary', + 'sup', + 'table', + 'tbody', + 'td', + 'template', + 'textarea', + 'tfoot', + 'th', + 'thead', + 'time', + 'title', + 'tr', + 'track', + 'u', + 'ul', + 'var', + 'video', + 'wbr', + 'acronym', + 'applet', + 'basefont', + 'bgsound', + 'big', + 'blink', + 'center', + 'dir', + 'font', + 'frame', + 'frameset', + 'isindex', + 'keygen', + 'listing', + 'marquee', + 'menuitem', + 'multicol', + 'nextid', + 'nobr', + 'noembed', + 'noframes', + 'param', + 'plaintext', + 'rb', + 'rtc', + 'spacer', + 'strike', + 'tt', + 'xmp', + 'animate', + 'animateMotion', + 'animateTransform', + 'circle', + 'clipPath', + 'defs', + 'desc', + 'ellipse', + 'feBlend', + 'feColorMatrix', + 'feComponentTransfer', + 'feComposite', + 'feConvolveMatrix', + 'feDiffuseLighting', + 'feDisplacementMap', + 'feDistantLight', + 'feDropShadow', + 'feFlood', + 'feFuncA', + 'feFuncB', + 'feFuncG', + 'feFuncR', + 'feGaussianBlur', + 'feImage', + 'feMerge', + 'feMergeNode', + 'feMorphology', + 'feOffset', + 'fePointLight', + 'feSpecularLighting', + 'feSpotLight', + 'feTile', + 'feTurbulence', + 'filter', + 'foreignObject', + 'g', + 'image', + 'line', + 'linearGradient', + 'marker', + 'mask', + 'metadata', + 'mpath', + 'path', + 'pattern', + 'polygon', + 'polyline', + 'radialGradient', + 'rect', + 'set', + 'stop', + 'svg', + 'switch', + 'symbol', + 'text', + 'textPath', + 'tspan', + 'use', + 'view', +]; + +/** + * @brief HTML internal attribute map, can be accessed as js property + */ +export const attributeMap = { + // ---- Other property as attribute + textContent: ['*'], + innerHTML: ['*'], + // ---- Source: https://developer.mozilla.org/zh-CN/docs/Web/HTML/Attributes + accept: ['form', 'input'], + // ---- Original: accept-charset + acceptCharset: ['form'], + accesskey: ['*'], + action: ['form'], + align: [ + 'caption', + 'col', + 'colgroup', + 'hr', + 'iframe', + 'img', + 'table', + 'tbody', + 'td', + 'tfoot', + 'th', + 'thead', + 'tr', + ], + allow: ['iframe'], + alt: ['area', 'img', 'input'], + async: ['script'], + autocapitalize: ['*'], + autocomplete: ['form', 'input', 'select', 'textarea'], + autofocus: ['button', 'input', 'select', 'textarea'], + autoplay: ['audio', 'video'], + background: ['body', 'table', 'td', 'th'], + // ---- Original: base + bgColor: [ + 'body', + 'col', + 'colgroup', + 'marquee', + 'table', + 'tbody', + 'tfoot', + 'td', + 'th', + 'tr', + ], + border: ['img', 'object', 'table'], + buffered: ['audio', 'video'], + capture: ['input'], + charset: ['meta'], + checked: ['input'], + cite: ['blockquote', 'del', 'ins', 'q'], + class: ['*'], + color: ['font', 'hr'], + cols: ['textarea'], + // ---- Original: colspan + colSpan: ['td', 'th'], + content: ['meta'], + // ---- Original: contenteditable + contentEditable: ['*'], + contextmenu: ['*'], + controls: ['audio', 'video'], + coords: ['area'], + crossOrigin: ['audio', 'img', 'link', 'script', 'video'], + csp: ['iframe'], + data: ['object'], + // ---- Original: datetime + dateTime: ['del', 'ins', 'time'], + decoding: ['img'], + default: ['track'], + defer: ['script'], + dir: ['*'], + dirname: ['input', 'textarea'], + disabled: [ + 'button', + 'fieldset', + 'input', + 'optgroup', + 'option', + 'select', + 'textarea', + ], + download: ['a', 'area'], + draggable: ['*'], + enctype: ['form'], + // ---- Original: enterkeyhint + enterKeyHint: ['textarea', 'contenteditable'], + for: ['label', 'output'], + form: [ + 'button', + 'fieldset', + 'input', + 'label', + 'meter', + 'object', + 'output', + 'progress', + 'select', + 'textarea', + ], + // ---- Original: formaction + formAction: ['input', 'button'], + // ---- Original: formenctype + formEnctype: ['button', 'input'], + // ---- Original: formmethod + formMethod: ['button', 'input'], + // ---- Original: formnovalidate + formNoValidate: ['button', 'input'], + // ---- Original: formtarget + formTarget: ['button', 'input'], + headers: ['td', 'th'], + height: ['canvas', 'embed', 'iframe', 'img', 'input', 'object', 'video'], + hidden: ['*'], + high: ['meter'], + href: ['a', 'area', 'base', 'link'], + hreflang: ['a', 'link'], + // ---- Original: http-equiv + httpEquiv: ['meta'], + id: ['*'], + integrity: ['link', 'script'], + // ---- Original: intrinsicsize + intrinsicSize: ['img'], + // ---- Original: inputmode + inputMode: ['textarea', 'contenteditable'], + ismap: ['img'], + // ---- Original: itemprop + itemProp: ['*'], + kind: ['track'], + label: ['optgroup', 'option', 'track'], + lang: ['*'], + language: ['script'], + loading: ['img', 'iframe'], + list: ['input'], + loop: ['audio', 'marquee', 'video'], + low: ['meter'], + manifest: ['html'], + max: ['input', 'meter', 'progress'], + // ---- Original: maxlength + maxLength: ['input', 'textarea'], + // ---- Original: minlength + minLength: ['input', 'textarea'], + media: ['a', 'area', 'link', 'source', 'style'], + method: ['form'], + min: ['input', 'meter'], + multiple: ['input', 'select'], + muted: ['audio', 'video'], + name: [ + 'button', + 'form', + 'fieldset', + 'iframe', + 'input', + 'object', + 'output', + 'select', + 'textarea', + 'map', + 'meta', + 'param', + ], + // ---- Original: novalidate + noValidate: ['form'], + open: ['details', 'dialog'], + optimum: ['meter'], + pattern: ['input'], + ping: ['a', 'area'], + placeholder: ['input', 'textarea'], + // ---- Original: playsinline + playsInline: ['video'], + poster: ['video'], + preload: ['audio', 'video'], + readonly: ['input', 'textarea'], + // ---- Original: referrerpolicy + referrerPolicy: ['a', 'area', 'iframe', 'img', 'link', 'script'], + rel: ['a', 'area', 'link'], + required: ['input', 'select', 'textarea'], + reversed: ['ol'], + role: ['*'], + rows: ['textarea'], + // ---- Original: rowspan + rowSpan: ['td', 'th'], + sandbox: ['iframe'], + scope: ['th'], + scoped: ['style'], + selected: ['option'], + shape: ['a', 'area'], + size: ['input', 'select'], + sizes: ['link', 'img', 'source'], + slot: ['*'], + span: ['col', 'colgroup'], + spellcheck: ['*'], + src: [ + 'audio', + 'embed', + 'iframe', + 'img', + 'input', + 'script', + 'source', + 'track', + 'video', + ], + srcdoc: ['iframe'], + srclang: ['track'], + srcset: ['img', 'source'], + start: ['ol'], + step: ['input'], + style: ['*'], + summary: ['table'], + // ---- Original: tabindex + tabIndex: ['*'], + target: ['a', 'area', 'base', 'form'], + title: ['*'], + translate: ['*'], + type: [ + 'button', + 'input', + 'embed', + 'object', + 'ol', + 'script', + 'source', + 'style', + 'menu', + 'link', + ], + usemap: ['img', 'input', 'object'], + value: [ + 'button', + 'data', + 'input', + 'li', + 'meter', + 'option', + 'progress', + 'param', + 'text' /** extra for TextNode */, + ], + width: ['canvas', 'embed', 'iframe', 'img', 'input', 'object', 'video'], + wrap: ['textarea'], + // --- ARIA attributes + // Source: https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes + ariaAutocomplete: ['*'], + ariaChecked: ['*'], + ariaDisabled: ['*'], + ariaErrorMessage: ['*'], + ariaExpanded: ['*'], + ariaHasPopup: ['*'], + ariaHidden: ['*'], + ariaInvalid: ['*'], + ariaLabel: ['*'], + ariaLevel: ['*'], + ariaModal: ['*'], + ariaMultiline: ['*'], + ariaMultiSelectable: ['*'], + ariaOrientation: ['*'], + ariaPlaceholder: ['*'], + ariaPressed: ['*'], + ariaReadonly: ['*'], + ariaRequired: ['*'], + ariaSelected: ['*'], + ariaSort: ['*'], + ariaValuemax: ['*'], + ariaValuemin: ['*'], + ariaValueNow: ['*'], + ariaValueText: ['*'], + ariaBusy: ['*'], + ariaLive: ['*'], + ariaRelevant: ['*'], + ariaAtomic: ['*'], + ariaDropEffect: ['*'], + ariaGrabbed: ['*'], + ariaActiveDescendant: ['*'], + ariaColCount: ['*'], + ariaColIndex: ['*'], + ariaColSpan: ['*'], + ariaControls: ['*'], + ariaDescribedBy: ['*'], + ariaDescription: ['*'], + ariaDetails: ['*'], + ariaFlowTo: ['*'], + ariaLabelledBy: ['*'], + ariaOwns: ['*'], + ariaPosInset: ['*'], + ariaRowCount: ['*'], + ariaRowIndex: ['*'], + ariaRowSpan: ['*'], + ariaSetSize: ['*'], +}; \ No newline at end of file diff --git a/packages/transpiler/babel-preset-inula-jsx/src/generator.ts b/packages/transpiler/babel-preset-inula-jsx/src/generator.ts deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/packages/transpiler/babel-preset-inula-jsx/src/index.ts b/packages/transpiler/babel-preset-inula-jsx/src/index.ts index 62c85c21bf700b5abf21786dcdde75f6782e4c31..525d863306a7b00daa91d2853b1dd027c07fbcbe 100644 --- a/packages/transpiler/babel-preset-inula-jsx/src/index.ts +++ b/packages/transpiler/babel-preset-inula-jsx/src/index.ts @@ -1,5 +1,3 @@ -import syntaxTypescript from 'babel-plugin-syntax-typescript-new'; -import syntaxJSX from '@babel/plugin-syntax-jsx'; import { InulaOption } from './types'; import type { ConfigAPI, TransformOptions } from '@babel/core'; import inula from './plugin'; @@ -11,8 +9,8 @@ export default function ( ): TransformOptions { return { plugins: [ - [syntaxJSX.default ?? syntaxJSX], - [syntaxTypescript, {isJsx: true}], + ['@babel/plugin-syntax-jsx'], + ['@babel/plugin-syntax-typescript', { isTSX: true }], [inula, options], ], }; diff --git a/packages/transpiler/babel-preset-inula-jsx/src/plugin.ts b/packages/transpiler/babel-preset-inula-jsx/src/plugin.ts index aa6a3fa6ff1d5125fbcb043698dd65baab13e803..c9a8c19fd4e929ffd32dd3f984f0d4722ea5e54e 100644 --- a/packages/transpiler/babel-preset-inula-jsx/src/plugin.ts +++ b/packages/transpiler/babel-preset-inula-jsx/src/plugin.ts @@ -10,13 +10,19 @@ export default function (api: typeof babel, options: InulaOption): PluginObj { name: 'babel-plugin-inula-jsx', visitor: { Program: { - enter(path) { - console.log('babel-plugin-inula-jsx: Program enter'); + enter(path, { filename }) { + pluginProvider.programEnterVisitor(path, filename); }, exit(path) { - console.log('babel-plugin-inula-jsx: Program exit'); + pluginProvider.programExitVisitor(path); }, }, + JSXElement(path) { + pluginProvider.jsxElementVisitor(path); + }, + JSXFragment(path) { + pluginProvider.jsxElementVisitor(path); + } }, }; } \ No newline at end of file diff --git a/packages/transpiler/babel-preset-inula-jsx/src/pluginProvider.ts b/packages/transpiler/babel-preset-inula-jsx/src/pluginProvider.ts index 18efbdc972b9ee8cb3466da4b03d684d84f6ac67..5ccd8f325ef8a1537c640888f5ef26c18e53aa85 100644 --- a/packages/transpiler/babel-preset-inula-jsx/src/pluginProvider.ts +++ b/packages/transpiler/babel-preset-inula-jsx/src/pluginProvider.ts @@ -2,7 +2,9 @@ import type { types as t, NodePath } from '@babel/core'; import type babel from '@babel/core'; import { minimatch } from 'minimatch'; import { InulaOption } from './types'; - +import { generateView } from '@inula/jsx-view-generator'; +import { parseView } from '@inula/jsx-view-parser'; +import { attributeMap, htmlTags, importMap } from './const'; export class PluginProvider { @@ -14,6 +16,12 @@ export class PluginProvider { private readonly includes: string[] private readonly excludes: string[] + private readonly htmlTags + private readonly parseTemplate + private readonly attributeMap + + private programNode: t.Program | undefined + constructor(babelApi: typeof babel, options: InulaOption) { this.babelApi = babelApi; this.t = babelApi.types; @@ -22,6 +30,10 @@ export class PluginProvider { const excludes = options.excludeFiles ?? ['**/{dist,node_modules,lib}']; this.includes = Array.isArray(includes) ? includes : [includes]; this.excludes = Array.isArray(excludes) ? excludes : [excludes]; + + this.htmlTags = options.htmlTags ?? htmlTags; + this.parseTemplate = options.parseTemplate ?? true; + this.attributeMap = options.attributeMap ?? attributeMap; } // ---- Two levels of enter: @@ -30,13 +42,9 @@ export class PluginProvider { private fileEnter = true // ---- File Level - private programNode?: t.Program private allImports: t.ImportDeclaration[] = [] - private didAlterImports = false - private transformedCount = 0 - - // ---- Component Level ---- + private templateIdx = -1 programEnterVisitor( path: NodePath, @@ -56,17 +64,34 @@ export class PluginProvider { return; } this.programNode = path.node; - this.transformedCount = 0; + this.templateIdx = -1; } programExitVisitor(path: NodePath): void { if (!this.fileEnter) return; - + this.fileEnter = false; } - jsxElementVisitor(path: NodePath): void { - + jsxElementVisitor(path: NodePath): void { + if (!this.fileEnter) return; + const viewUnits = parseView(path.node, { + babelApi: this.babelApi, + htmlTags: this.htmlTags, + parseTemplate: this.parseTemplate, + }); + const [templates, viewAst] = generateView(viewUnits, { + babelApi: this.babelApi, + importMap, + attributeMap: this.attributeMap, + }, this.templateIdx); + this.templateIdx += templates.length; + // ---- Add templates to the program + this.programNode!.body.unshift(...templates); + + // ---- Replace the JSXElement with the viewAst + path.replaceWith(viewAst); + path.skip(); } @@ -87,19 +112,6 @@ export class PluginProvider { return false; return true; } - - /** - * @brief Wrap the value in a file - * @param node - * @returns wrapped value - */ - private wrapWithFile(node: t.Expression | t.Statement): t.File { - return this.t.file( - this.t.program([ - this.t.isStatement(node) ? node : this.t.expressionStatement(node), - ]) - ); - } } diff --git a/packages/transpiler/babel-preset-inula-jsx/src/test/entering.test.ts b/packages/transpiler/babel-preset-inula-jsx/src/test/entering.test.ts deleted file mode 100644 index 78b7bde983fbaf9a3a6814af4167d8cb1b1cafc5..0000000000000000000000000000000000000000 --- a/packages/transpiler/babel-preset-inula-jsx/src/test/entering.test.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { describe, expect, it } from 'vitest'; -import { transformInula } from './mock'; - -describe('Entering', () => { - it('should use inula jsx preset in babel', () => { - const code = 'console.log(\'hello world\');'; - expect(transformInula(code)).toBe(code); - }); - -}); \ No newline at end of file diff --git a/packages/transpiler/babel-preset-inula-jsx/src/test/mock.ts b/packages/transpiler/babel-preset-inula-jsx/src/test/mock.ts index a88d0bb5a30264cffb53c561c5e8786a0880af86..d828ed3f85d6ee134872451cb7b406dbebbeab1b 100644 --- a/packages/transpiler/babel-preset-inula-jsx/src/test/mock.ts +++ b/packages/transpiler/babel-preset-inula-jsx/src/test/mock.ts @@ -1,8 +1,22 @@ -import babel, { transform, types as t } from '@babel/core'; +import babel, { transform, types as t, parseSync } from '@babel/core'; import inula from '../'; +import babelJSX from '@babel/plugin-syntax-jsx'; +import generate from '@babel/generator'; +import { expect as ep } from 'vitest'; + +function formatCode(code: string) { + return generate( + parseSync(code, {plugins: [babelJSX]})! + )!.code; +} export function transformInula(code: string) { return transform(code, { - presets: [inula] + presets: [[inula, {'files': '*'}]] })?.code; } + +export function expect(ori: string, expected: string) { + const transformed = transformInula(ori)!; + ep(formatCode(transformed)).toBe(formatCode(expected)); +} \ No newline at end of file diff --git a/packages/transpiler/babel-preset-inula-jsx/src/test/simple.test.ts b/packages/transpiler/babel-preset-inula-jsx/src/test/simple.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..9d1864d523a3999bdb4f0b18f1a2b793caedd769 --- /dev/null +++ b/packages/transpiler/babel-preset-inula-jsx/src/test/simple.test.ts @@ -0,0 +1,80 @@ +import { describe, it } from 'vitest'; +import { expect } from './mock'; + +describe('Entering', () => { + it('should use inula jsx preset in babel', () => { + const code = 'console.log(\'hello world\');'; + expect(code, code); + }); + + it('should transform jsx to inula view', () => { + expect(/*jsx*/` + import A from "inula" + function App() { + return ( +
+ ) + } + `, /*js*/` + import A from "inula"; + function App() { + return (() => { + const $node0 = createElement("div"); + return [$node0]; + })(); + } + `); + }); + + it('should transform jsx to inula view with props', () => { + expect(/*jsx*/` + import A from "inula" + function App() { + return ( +
+ ) + } + `, /*js*/` + import A from "inula"; + function App() { + return (() => { + const $node0 = createElement("div"); + $node0.id = "myDiv"; + return [$node0]; + })(); + } + `); + }); + + it('should transform jsx to inula view with template', () => { + expect(/*jsx*/` + import A from "inula" + function App() { + return ( +
+

ok

+

fine

+
+ ) + } + `, /*js*/` + const $template0 = (() => { + const $node0 = createElement("div"); + const $node1 = createElement("p"); + $node1.textContent = "ok"; + insert($node0, $node1); + const $node2 = createElement("h1"); + $node2.textContent = "fine"; + insert($node0, $node2); + return $node0; + })(); + import A from "inula"; + function App() { + return (() => { + const $node0 = $template0.cloneNode(true); + return [$node0]; + })(); + } + `); + }); +}); \ No newline at end of file diff --git a/packages/transpiler/babel-preset-inula-jsx/src/types.ts b/packages/transpiler/babel-preset-inula-jsx/src/types.ts index c84ba94905ab1d5b41969efd6e0411c3990f90db..51f07da9b46dfb2dea19b9f3f53b7feeb3715067 100644 --- a/packages/transpiler/babel-preset-inula-jsx/src/types.ts +++ b/packages/transpiler/babel-preset-inula-jsx/src/types.ts @@ -10,4 +10,21 @@ export interface InulaOption { * @default ** /{dist,node_modules,lib}/*.{js,ts} */ excludeFiles?: string | string[] + /** + * @brief Using AttributeMap to identify propertyfied attributes + * Reason for adding this: + * `el.prop = xxx` is faster than `el.setAttribute('prop', xxx)` + * @example { href: ["a", "area", "base", "link"], id: ["*"] } + */ + attributeMap?: Record + /** + * @brief Using htmlTags to identify the html tags + * @example ["a", "area", "base", "link"] + */ + htmlTags?: string[] + /** + * @brief Using importMap to identify the import names + * @example { createElement: 'createElement' } + */ + parseTemplate?: boolean } diff --git a/packages/transpiler/jsx-view-generator/dist/index.cjs b/packages/transpiler/jsx-view-generator/dist/index.cjs new file mode 100644 index 0000000000000000000000000000000000000000..0194271917f2645a05c96b8bd239ad6c8c41fe6c --- /dev/null +++ b/packages/transpiler/jsx-view-generator/dist/index.cjs @@ -0,0 +1,2 @@ +"use strict";var v=Object.defineProperty;var V=Object.getOwnPropertyDescriptor;var T=Object.getOwnPropertyNames;var L=Object.prototype.hasOwnProperty;var A=(h,t,e)=>t in h?v(h,t,{enumerable:!0,configurable:!0,writable:!0,value:e}):h[t]=e;var j=(h,t)=>{for(var e in t)v(h,e,{get:t[e],enumerable:!0})},I=(h,t,e,i)=>{if(t&&typeof t=="object"||typeof t=="function")for(let s of T(t))!L.call(h,s)&&s!==e&&v(h,s,{get:()=>t[s],enumerable:!(i=V(t,s))||i.enumerable});return h};var W=h=>I(v({},"__esModule",{value:!0}),h);var U=(h,t,e)=>(A(h,typeof t!="symbol"?t+"":t,e),e);var H={};j(H,{generateNew:()=>x,generateView:()=>u,viewGeneratorMap:()=>S});module.exports=W(H);var d=class{viewUnit;config;t;traverse;elementAttributeMap;importMap;constructor(t,e){this.config=e,this.t=e.babelApi.types,this.traverse=e.babelApi.traverse,this.importMap=e.importMap,this.viewUnit=t,this.elementAttributeMap=e.attributeMap?Object.entries(e.attributeMap).reduce((i,[s,o])=>(o.forEach(r=>{i[r]||(i[r]=[]),i[r].push(s)}),i),{}):{}}initStatements=[];addStatement(...t){this.initStatements.push(...t)}templates=[];addTemplate(...t){this.templates.push(...t)}run(){return""}generate(){return[this.run(),this.initStatements,this.templates]}checkReactive(t){if(this.t.isFunction(t))return!1;let e=!1;return this.traverse(this.wrapWithFile(t),{MemberExpression:i=>{this.t.isIdentifier(i.node.property,{name:"get"})&&(e=!0,i.stop())}}),e}prefixMap={node:"$node",template:"$template"};nodeIdx=-1;geneNodeName(t){return`${this.prefixMap.node}${t??++this.nodeIdx}`}templateIdx=-1;generateTemplateName(){return`${this.prefixMap.template}${++this.templateIdx}`}wrapWithFile(t){return this.t.file(this.t.program([this.t.expressionStatement(t)]))}addWatch(t){return this.t.callExpression(this.t.identifier(this.importMap.watch),[this.t.arrowFunctionExpression([],t)])}createCollector(){let t=[];function e(i){Array.isArray(i)?t.push(...i):i&&t.push(i)}return[t,e]}parseViewProp(t,e){let i=t.value,s=t.viewPropMap,o=Object.fromEntries(Object.entries(s).map(([r,a])=>{let[p,m]=e(a,this.config,this.templateIdx);return this.addTemplate(...p),[r,m]}));return this.traverse(this.wrapWithFile(i),{StringLiteral:r=>{let a=r.node.value;o[a]&&(this.t.isNodesEquivalent(i,r.node)&&(i=o[a].expression),r.replaceWith(o[a]))}}),i}parseProps(t,e){return Object.fromEntries(Object.entries(t).map(([i,s])=>[i,this.parseViewProp(s,e)]))}};var E=class extends d{run(){let{tag:t,props:e,children:i}=this.viewUnit,s=this.parseProps(e,u),[o,r]=this.declareCompNode(t,s,i);return this.addStatement(r),o}declareCompNode(t,e,i){let s=this.geneNodeName(),o=[];if(Object.keys(e).length>0||i.length>0){let r=this.t.objectExpression(Object.entries(e).map(([a,p])=>this.checkReactive(p)?this.t.objectMethod("get",this.t.identifier(a),[],this.t.blockStatement([this.t.returnStatement(p)])):this.t.objectProperty(this.t.identifier(a),p)));if(i.length>0){let a=C(i,this.config);r.properties.push(this.t.objectMethod("get",this.t.identifier("children"),[],a))}o.push(r)}return[s,this.t.variableDeclaration("const",[this.t.variableDeclarator(this.t.identifier(s),this.t.callExpression(this.t.identifier(this.importMap.createComponent),[t,...o]))])]}};var D=class extends d{addHTMLProp(t,e,i,s){let o=this.checkReactive(s),r=this.setDynamicHTMLProp(t,e,i,s,o);return o&&(r=this.addWatch(r)),this.t.expressionStatement(r)}setStyle(t,e){return this.t.callExpression(this.t.identifier(this.importMap.setStyle),[this.t.identifier(t),e])}setDataset(t,e){return this.t.callExpression(this.t.identifier(this.importMap.setDataset),[this.t.identifier(t),e])}setStaticProperty(t,e,i){return this.t.assignmentExpression("=",this.t.memberExpression(this.t.identifier(t),this.t.identifier(e)),i)}setDynamicProperty(t,e,i){return this.t.callExpression(this.t.identifier(this.importMap.setProperty),[this.t.identifier(t),this.t.stringLiteral(e),i])}setStaticAttribute(t,e,i){return this.t.callExpression(this.t.memberExpression(this.t.identifier(t),this.t.identifier("setAttribute")),[this.t.stringLiteral(e),i])}setDynamicAttribute(t,e,i){return this.t.callExpression(this.t.identifier(this.importMap.setAttribute),[this.t.identifier(t),this.t.stringLiteral(e),i])}setDelegatedEvent(t,e,i){return this.t.callExpression(this.t.identifier(this.importMap.delegateEvent),[this.t.identifier(t),this.t.stringLiteral(e),i])}setStaticEvent(t,e,i){return this.t.callExpression(this.t.memberExpression(this.t.identifier(t),this.t.identifier("addEventListener")),[this.t.stringLiteral(e),i])}setDynamicEvent(t,e,i){return this.t.callExpression(this.t.identifier(this.importMap.addEventListener),[this.t.identifier(t),this.t.stringLiteral(e),i])}setDynamicHTMLProp(t,e,i,s,o){if(i==="style")return this.setStyle(t,s);if(i==="dataset")return this.setDataset(t,s);if(i.startsWith("on")){let r=i.slice(2).toLowerCase();return D.DelegatedEvents.has(r)?this.setDelegatedEvent(t,r,s):this[o?"setDynamicEvent":"setStaticEvent"](t,r,s)}return this.isInternalAttribute(e,i)?(i==="class"?i="className":i==="for"&&(i="htmlFor"),this[o?"setDynamicProperty":"setStaticProperty"](t,i,s)):this[o?"setDynamicAttribute":"setStaticAttribute"](t,i,s)}isInternalAttribute(t,e){return this.elementAttributeMap["*"]?.includes(e)||this.elementAttributeMap[t]?.includes(e)}},g=D;U(g,"DelegatedEvents",new Set(["beforeinput","click","dblclick","contextmenu","focusin","focusout","input","keydown","keyup","mousedown","mousemove","mouseout","mouseover","mouseup","pointerdown","pointermove","pointerout","pointerover","pointerup","touchend","touchmove","touchstart"]));var N=class extends g{run(){let{tag:t,props:e,children:i}=this.viewUnit,s=this.parseProps(e,u),[o,r]=this.declareHTMLNode(t);this.addStatement(r);let a=this.t.isStringLiteral(t)?t.value:"ANY";return Object.entries(s).forEach(([p,m])=>{this.addStatement(this.addHTMLProp(o,a,p,m))}),i.forEach(p=>{let[m,n]=x(this,p);this.addStatement(...n),this.addStatement(this.insertChildNode(o,m))}),o}declareHTMLNode(t){let e=this.geneNodeName();return[e,this.t.variableDeclaration("const",[this.t.variableDeclarator(this.t.identifier(e),this.t.callExpression(this.t.identifier("createElement"),[t]))])]}insertChildNode(t,e){return this.t.expressionStatement(this.t.callExpression(this.t.identifier(this.importMap.insert),[this.t.identifier(t),this.t.identifier(e)]))}};var b=class extends g{run(){let{template:t,mutableUnits:e,props:i}=this.viewUnit,s=this.generateTemplate(t),[o,r]=this.declareTemplateNode(s);this.addStatement(r);let a=[];i.forEach(({path:n})=>{a.push(n)}),e.forEach(({path:n})=>{a.push(n.slice(0,-1)),n[n.length-1]!==-1&&a.push(n)});let[p,m]=this.insertElements(a,o);return this.addStatement(...p),i.forEach(({tag:n,path:c,key:l,value:f})=>{let M=m[c.join(".")],P=this.t.isStringLiteral(n)?n.value:"ANY";this.addStatement(this.addHTMLProp(M,P,l,f))}),e.forEach(n=>{let c=n.path,l=m[c.slice(0,-1).join(".")],f=m[c.join(".")];console.log(f,c);let[M,P]=x(this,n);this.addStatement(...P),this.addStatement(this.insertChildNode(l,M,f))}),o}generateTemplate(t){let e=this.generateTemplateName(),[i,s]=x(this,t,!1),o=this.t.returnStatement(this.t.identifier(i));return this.addTemplate(this.t.variableDeclaration("const",[this.t.variableDeclarator(this.t.identifier(e),this.t.callExpression(this.t.arrowFunctionExpression([],this.t.blockStatement([...s,o])),[]))])),e}declareTemplateNode(t){let e=this.geneNodeName();return[e,this.t.variableDeclaration("const",[this.t.variableDeclarator(this.t.identifier(e),this.t.callExpression(this.t.memberExpression(this.t.identifier(t),this.t.identifier("cloneNode")),[this.t.identifier("true")]))])]}insertChildNode(t,e,i){let s=i?[this.t.identifier(i)]:[];return this.t.expressionStatement(this.t.callExpression(this.t.identifier(this.importMap.insert),[this.t.identifier(t),this.t.identifier(e),...s]))}insertElement(t,e,i){let s=this.geneNodeName();if(e.length===0)return this.t.variableDeclaration("const",[this.t.variableDeclarator(this.t.identifier(s),Array.from({length:i}).reduce(n=>this.t.memberExpression(n,this.t.identifier("nextSibling")),this.t.identifier(t)))]);let o=n=>this.t.memberExpression(n,this.t.identifier("firstChild")),r=n=>this.t.memberExpression(o(n),this.t.identifier("nextSibling")),a=n=>this.t.memberExpression(r(n),this.t.identifier("nextSibling")),p=(n,c)=>this.t.memberExpression(this.t.memberExpression(n,this.t.identifier("childNodes")),this.t.numericLiteral(c),!0),m=n=>this.t.memberExpression(n,this.t.identifier("nextSibling"));return this.t.variableDeclaration("const",[this.t.variableDeclarator(this.t.identifier(s),e.reduce((n,c,l)=>{if(l===0&&i>0)for(let f=0;f{let m=b.findBestNodeAndPath(o,p,e),[,n,c]=m,l=m[0];(n.length!==0||c!==0)&&(s(this.insertElement(l,n,c)),l=this.geneNodeName(this.nodeIdx),o[l]=p)});let a=Object.fromEntries(Object.entries(o).map(([p,m])=>[m.join("."),p]));return[i,a]}static pathWithCommonPrefix(t){let e=[...t].sort(),i=[...e];i.forEach(r=>{i.forEach(a=>{if(r!==a){for(let p=0;pr.length!==a.length?r.length-a.length:r[0]-a[0]);return[...new Set(s.map(r=>r.join(".")))].map(r=>r.split(".").filter(Boolean).map(Number))}static findBestNodeAndPath(t,e,i){let s=0,o,r;return Object.entries(t).forEach(([a,p])=>{let m=0,n=p.length;for(let c=0;c0&&c<=3&&(r=[a,m,c])}m===p.length&&m>s&&(o=a,s=m)}),r?[r[0],e.slice(r[1]+1),r[2]]:o?[o,e.slice(s),0]:[i,e,0]}};var w=class extends d{run(){let{content:t}=this.viewUnit,[e,i]=this.declareTextNode(t);return this.addStatement(i),e}declareTextNode(t){let e=this.geneNodeName();return[e,this.t.variableDeclaration("const",[this.t.variableDeclarator(this.t.identifier(e),this.t.callExpression(this.t.identifier(this.importMap.createText),[t]))])]}};var y=class extends d{run(){let{content:t}=this.viewUnit,e=this.parseViewProp(t,u),[i,s]=this.declareExpressionNode(e);return this.addStatement(s),i}declareExpressionNode(t){let e=this.geneNodeName();return[e,this.t.variableDeclaration("const",[this.t.variableDeclarator(this.t.identifier(e),t)])]}};var S={html:N,comp:E,template:b,text:w,exp:y,if:E,env:E};function x(h,t,e=!0){let i=new S[t.type](t,h.config);e&&(i.nodeIdx=h.nodeIdx);let[s,o,r]=i.generate();return e&&(h.nodeIdx=i.nodeIdx),[s,o,r]}function C(h,t){let e=t.babelApi.types,i=[],s=h.flatMap(r=>{let a=new S[r.type](r,t),[p,m]=a.generate();return i.push(p),m}),o=e.returnStatement(e.arrayExpression(i.map(r=>e.identifier(r))));return e.blockStatement(s.concat(o))}function u(h,t,e=-1){let i=t.babelApi.types,s=[],o=[],r=-1,a=h.flatMap(m=>{let n=new S[m.type](m,t);n.templateIdx=e,n.nodeIdx=r;let[c,l,f]=n.generate();return e=n.templateIdx,r=n.nodeIdx,s.push(c),o.push(...f),l}),p=i.returnStatement(i.arrayExpression(s.map(m=>i.identifier(m))));return[o,i.expressionStatement(i.callExpression(i.arrowFunctionExpression([],i.blockStatement(a.concat(p))),[]))]}0&&(module.exports={generateNew,generateView,viewGeneratorMap}); +//# sourceMappingURL=index.cjs.map \ No newline at end of file diff --git a/packages/transpiler/jsx-view-generator/dist/index.cjs.map b/packages/transpiler/jsx-view-generator/dist/index.cjs.map new file mode 100644 index 0000000000000000000000000000000000000000..bcb5bb9ee35d36ebae72cf38b1961b8128b0a08a --- /dev/null +++ b/packages/transpiler/jsx-view-generator/dist/index.cjs.map @@ -0,0 +1 @@ +{"version":3,"sources":["../src/index.ts","../src/HelperGenerators/BaseGenerator.ts","../src/NodeGenerators/CompGenerator.ts","../src/HelperGenerators/HTMLPropGenerator.ts","../src/NodeGenerators/HTMLGenerator.ts","../src/NodeGenerators/TemplateGenerator.ts","../src/NodeGenerators/TextGenerator.ts","../src/NodeGenerators/ExpressionGenerator.ts","../src/generate.ts"],"sourcesContent":["export {generateView, generateNew, viewGeneratorMap} from './generate';\nexport type {ViewGeneratorConfig} from './types';","import { UnitProp, ViewUnit } from '@inula/jsx-view-parser';\nimport { ViewGeneratorConfig } from '../types';\nimport type { types as t, traverse } from '@babel/core';\n\n\nexport default class BaseGenerator {\n readonly viewUnit: ViewUnit\n readonly config: ViewGeneratorConfig\n readonly t\n readonly traverse: typeof traverse\n readonly elementAttributeMap\n readonly importMap\n\n constructor(viewUnit: ViewUnit, config: ViewGeneratorConfig) {\n this.config = config;\n this.t = config.babelApi.types;\n this.traverse = config.babelApi.traverse;\n this.importMap = config.importMap;\n this.viewUnit = viewUnit;\n this.elementAttributeMap = config.attributeMap\n ? Object.entries(config.attributeMap).reduce>(\n (acc, [key, elements]) => {\n elements.forEach(element => {\n if (!acc[element]) acc[element] = [];\n acc[element].push(key);\n });\n return acc;\n },\n {}\n )\n : {};\n }\n\n // ---- Init Statements\n private readonly initStatements: t.Statement[] = []\n addStatement(...statements: t.Statement[]) {\n this.initStatements.push(...statements);\n }\n\n private readonly templates: t.Statement[] = []\n addTemplate(...template: t.Statement[]) {\n this.templates.push(...template);\n }\n\n // ---- Generate ----\n /**\n * @brief To be implemented by the subclass\n */\n run(): string {\n return ''; \n }\n\n generate(): [string, t.Statement[], t.Statement[]]{\n const nodeName = this.run();\n return [nodeName, this.initStatements, this.templates];\n }\n \n // ---- Reactivity ----\n /**\n * @brief Check if the expression is reactive, which satisfies the following conditions:\n * 1. Contains .get() property\n * 2. The whole expression is not a function\n * @param expression \n * @returns \n */\n checkReactive(expression: t.Expression) {\n if (this.t.isFunction(expression)) return false;\n let reactive = false;\n this.traverse(this.wrapWithFile(expression), {\n MemberExpression: path => {\n if (this.t.isIdentifier(path.node.property, { name: 'get' })) {\n reactive = true;\n path.stop();\n }\n }\n });\n return reactive;\n }\n\n // ---- Names ----\n private readonly prefixMap = {\n node: '$node',\n template: '$template',\n }\n\n nodeIdx = -1;\n geneNodeName(idx?: number): string {\n return `${this.prefixMap.node}${idx ?? ++this.nodeIdx}`;\n }\n templateIdx = -1;\n generateTemplateName() {\n return `${this.prefixMap.template}${++this.templateIdx}`;\n }\n\n\n // ---- Utils ----\n /**\n * @brief Wrap the value in a file\n * @param node\n * @returns wrapped value\n */\n wrapWithFile(node: t.Expression): t.File {\n return this.t.file(this.t.program([this.t.expressionStatement(node)]));\n }\n\n addWatch(value: t.Expression) {\n return this.t.callExpression(\n this.t.identifier(this.importMap.watch),\n [this.t.arrowFunctionExpression([], value)]\n );\n }\n\n createCollector(): [t.Statement[], (statement: t.Statement | t.Statement[] | null) => void]{\n const statements: t.Statement[] = [];\n function collect(statement: t.Statement | t.Statement[] | null) {\n if (Array.isArray(statement)) {\n statements.push(...statement);\n } else if (statement) {\n statements.push(statement);\n }\n }\n return [statements, collect];\n }\n\n parseViewProp(prop: UnitProp, generateView: (units: ViewUnit[], config: ViewGeneratorConfig, templateIdx: number) => [t.Statement[], t.ExpressionStatement]): t.Expression {\n let value = prop.value;\n const viewPropMap = prop.viewPropMap;\n const propNodeMap = Object.fromEntries(\n Object.entries(viewPropMap).map(([key, units]) => {\n const [templates, statement] = generateView(units, this.config, this.templateIdx);\n this.addTemplate(...templates);\n return [key, statement];\n })\n );\n\n this.traverse(this.wrapWithFile(value), {\n StringLiteral: path => {\n const key = path.node.value;\n if (propNodeMap[key]) {\n if (this.t.isNodesEquivalent(value, path.node)) {\n value = (propNodeMap[key] as t.ExpressionStatement).expression;\n }\n path.replaceWith(propNodeMap[key]);\n }\n }\n });\n \n return value;\n }\n\n parseProps(props: Record, generateView: (units: ViewUnit[], config: ViewGeneratorConfig) => [t.Statement[], t.ExpressionStatement]) {\n return Object.fromEntries(\n Object.entries(props).map(([key, prop]) => {\n return [key, this.parseViewProp(prop, generateView)];\n })\n );\n }\n}\n","import { CompUnit, ViewUnit } from '@inula/jsx-view-parser';\nimport BaseGenerator from '../HelperGenerators/BaseGenerator';\nimport type { types as t } from '@babel/core';\nimport { generateBlock, generateView } from '../generate';\n\nexport class CompGenerator extends BaseGenerator {\n run(): string {\n const { tag, props: propsWithView, children } = this.viewUnit as CompUnit;\n const props = this.parseProps(propsWithView, generateView);\n\n const [nodeName, statement] = this.declareCompNode(tag, props, children);\n this.addStatement(statement);\n\n return nodeName;\n }\n\n /**\n * @View\n * const $el = createComponent(tag, {\n * ...props\n * }, spreadProps)\n */\n declareCompNode(tag: t.Expression, props: Record, children: ViewUnit[]): [string, t.Statement] {\n const name = this.geneNodeName();\n const nodes = [];\n if (Object.keys(props).length > 0 || children.length > 0) {\n const propNode = this.t.objectExpression(Object.entries(props).map(([key, value]) => {\n const isReactive = this.checkReactive(value);\n if (isReactive) {\n /**\n * @View\n * get reactiveValue() {\n * return value\n * }\n */\n return (\n this.t.objectMethod('get', this.t.identifier(key), [],\n this.t.blockStatement([\n this.t.returnStatement(value)\n ])\n )\n );\n }\n return this.t.objectProperty(\n this.t.identifier(key),\n value\n );\n }));\n if (children.length > 0) {\n const statement = generateBlock(children, this.config);\n propNode.properties.push(\n this.t.objectMethod('get', this.t.identifier('children'), [], statement)\n );\n }\n nodes.push(propNode);\n }\n\n\n return [name, this.t.variableDeclaration('const', [\n this.t.variableDeclarator(\n this.t.identifier(name),\n this.t.callExpression(\n this.t.identifier(this.importMap.createComponent),\n [tag, ...nodes]\n )\n )\n ])];\n }\n}","import BaseGenerator from './BaseGenerator';\nimport type { types as t } from '@babel/core';\n\nexport class HTMLPropGenerator extends BaseGenerator {\n static DelegatedEvents = new Set([\n 'beforeinput',\n 'click',\n 'dblclick',\n 'contextmenu',\n 'focusin',\n 'focusout',\n 'input',\n 'keydown',\n 'keyup',\n 'mousedown',\n 'mousemove',\n 'mouseout',\n 'mouseover',\n 'mouseup',\n 'pointerdown',\n 'pointermove',\n 'pointerout',\n 'pointerover',\n 'pointerup',\n 'touchend',\n 'touchmove',\n 'touchstart',\n ])\n\n addHTMLProp(\n nodeName: string, \n tag: string,\n key: string,\n value: t.Expression,\n ): t.Statement {\n const shouldWatch = this.checkReactive(value);\n let expression = this.setDynamicHTMLProp(nodeName, tag, key, value, shouldWatch);\n if (shouldWatch) expression = this.addWatch(expression);\n\n return this.t.expressionStatement(expression);\n }\n\n /**\n * @View\n * setStyle($node, value)\n */\n private setStyle(\n nodeName: string, \n value: t.Expression,\n ) {\n return this.t.callExpression(\n this.t.identifier(this.importMap.setStyle),\n [this.t.identifier(nodeName), value]\n );\n }\n\n /**\n * @View\n * setDataset($node, value)\n */\n private setDataset(\n nodeName: string, \n value: t.Expression,\n ) {\n return this.t.callExpression(\n this.t.identifier(this.importMap.setDataset),\n [this.t.identifier(nodeName), value]\n );\n }\n\n /**\n * @View\n * $node.key = value\n */\n private setStaticProperty(\n nodeName: string, \n key: string,\n value: t.Expression,\n ) {\n return this.t.assignmentExpression(\n '=',\n this.t.memberExpression(\n this.t.identifier(nodeName),\n this.t.identifier(key),\n ),\n value\n );\n }\n\n /**\n * @View\n * setProperty($node, key, value)\n */\n private setDynamicProperty(\n nodeName: string, \n key: string,\n value: t.Expression,\n ) {\n return this.t.callExpression(\n this.t.identifier(this.importMap.setProperty),\n [this.t.identifier(nodeName), this.t.stringLiteral(key), value]\n );\n }\n\n /**\n * @View\n * $node.setAttribute(key, value)\n */\n private setStaticAttribute(\n nodeName: string, \n key: string,\n value: t.Expression,\n ) {\n return this.t.callExpression(\n this.t.memberExpression(\n this.t.identifier(nodeName),\n this.t.identifier('setAttribute'),\n ),\n [this.t.stringLiteral(key), value]\n );\n }\n\n\n /**\n * @View\n * setAttribute($node, key, value)\n */\n private setDynamicAttribute(\n nodeName: string, \n key: string,\n value: t.Expression,\n ) {\n return this.t.callExpression(\n this.t.identifier(this.importMap.setAttribute),\n [this.t.identifier(nodeName), this.t.stringLiteral(key), value]\n );\n }\n\n /**\n * @View\n * delegateEvent($node, eventName, value)\n */\n private setDelegatedEvent(\n nodeName: string, \n eventName: string,\n value: t.Expression,\n ) {\n return this.t.callExpression(\n this.t.identifier(this.importMap.delegateEvent),\n [this.t.identifier(nodeName), this.t.stringLiteral(eventName), value]\n );\n }\n\n /**\n * @View\n * $node.addEventListener(eventName, value)\n */\n private setStaticEvent(\n nodeName: string, \n eventName: string,\n value: t.Expression,\n ) {\n return this.t.callExpression(\n this.t.memberExpression(\n this.t.identifier(nodeName),\n this.t.identifier('addEventListener'),\n ),\n [this.t.stringLiteral(eventName), value]\n );\n }\n\n /**\n * @View\n * addEventListener($node, eventName, value)\n */\n private setDynamicEvent(\n nodeName: string, \n eventName: string,\n value: t.Expression,\n ) {\n return this.t.callExpression(\n this.t.identifier(this.importMap.addEventListener),\n [this.t.identifier(nodeName), this.t.stringLiteral(eventName), value]\n );\n }\n\n private setDynamicHTMLProp(\n nodeName: string, \n tag: string,\n key: string,\n value: t.Expression,\n dynamic: boolean\n ): t.Expression {\n if (key === 'style') return this.setStyle(nodeName, value);\n if (key === 'dataset') return this.setDataset(nodeName, value);\n if (key.startsWith('on')) {\n const event = key.slice(2).toLowerCase();\n if (HTMLPropGenerator.DelegatedEvents.has(event)) {\n return this.setDelegatedEvent(nodeName, event, value);\n }\n return this[dynamic ? 'setDynamicEvent' : 'setStaticEvent'](nodeName, event, value);\n }\n if (this.isInternalAttribute(tag, key)) {\n if (key === 'class') key = 'className';\n else if (key === 'for') key = 'htmlFor';\n return this[dynamic ? 'setDynamicProperty' : 'setStaticProperty'](nodeName, key, value);\n }\n return this[dynamic ? 'setDynamicAttribute' : 'setStaticAttribute'](nodeName, key, value);\n }\n\n /**\n * @brief Check if the attribute is internal, i.e., can be accessed as js property\n * @param tag\n * @param attribute\n * @returns true if the attribute is internal\n */\n isInternalAttribute(tag: string, attribute: string): boolean {\n return (\n this.elementAttributeMap['*']?.includes(attribute) ||\n this.elementAttributeMap[tag]?.includes(attribute)\n );\n }\n}","import { HTMLUnit } from '@inula/jsx-view-parser';\nimport type { types as t } from '@babel/core';\nimport { HTMLPropGenerator } from '../HelperGenerators/HTMLPropGenerator';\nimport { generateNew, generateView } from '../generate';\n\nexport class HTMLGenerator extends HTMLPropGenerator {\n run(){\n const { tag, props: propsWithView, children } = this.viewUnit as HTMLUnit;\n const props = this.parseProps(propsWithView, generateView);\n const [nodeName, statement] = this.declareHTMLNode(tag);\n this.addStatement(statement);\n\n // ---- Use the tag name to check if the prop is internal for the tag,\n // for dynamic tag, we can't check it, so we just assume it's not internal\n // represent by the \"ANY\" tag name\n const tagName = this.t.isStringLiteral(tag) ? tag.value : 'ANY';\n\n Object.entries(props).forEach(([key, prop]) => {\n this.addStatement(this.addHTMLProp(nodeName, tagName, key, prop));\n });\n\n children.forEach(child => {\n const [childNodeName, childStatements] = generateNew(this, child);\n this.addStatement(...childStatements);\n this.addStatement(this.insertChildNode(nodeName, childNodeName));\n });\n\n return nodeName;\n }\n\n /**\n * @View \n * const $el = createElement(tag)\n */\n declareHTMLNode(tag: t.Expression): [string, t.Statement] {\n const name = this.geneNodeName();\n return [name, this.t.variableDeclaration('const', [\n this.t.variableDeclarator(\n this.t.identifier(name),\n this.t.callExpression(\n this.t.identifier('createElement'),\n [tag]\n )\n )\n ])];\n }\n\n /**\n * @View\n * $insert($el, childNode)\n */\n private insertChildNode(\n parent: string, \n child: string\n ) {\n return this.t.expressionStatement(\n this.t.callExpression(\n this.t.identifier(this.importMap.insert),\n [this.t.identifier(parent), this.t.identifier(child)]\n )\n );\n }\n}","import { HTMLUnit, TemplateUnit } from '@inula/jsx-view-parser';\nimport { HTMLPropGenerator } from '../HelperGenerators/HTMLPropGenerator';\nimport { generateNew } from '../generate';\nimport type { types as t } from '@babel/core';\n\n\nexport class TemplateGenerator extends HTMLPropGenerator{\n run(): string {\n const { template, mutableUnits, props } = this.viewUnit as TemplateUnit;\n\n const templateName = this.generateTemplate(template);\n const [nodeName, statement] = this.declareTemplateNode(templateName);\n this.addStatement(statement);\n\n // ---- Insert elements first\n const paths: number[][] = [];\n props.forEach(({ path }) => {\n paths.push(path);\n });\n mutableUnits.forEach(({ path }) => {\n // ---- ParentPath and NextPath\n paths.push(path.slice(0, -1));\n if (path[path.length - 1] !== -1) paths.push(path);\n });\n const [insertElementStatements, pathNameMap] = this.insertElements(\n paths,\n nodeName\n );\n\n this.addStatement(...insertElementStatements);\n\n // ---- Resolve props\n props.forEach(\n ({\n tag,\n path,\n key,\n value,\n }) => {\n const name = pathNameMap[path.join('.')];\n\n const tagName = this.t.isStringLiteral(tag) ? tag.value : 'ANY';\n this.addStatement(\n this.addHTMLProp(\n name,\n tagName,\n key,\n value,\n )\n );\n }\n );\n\n // ---- Resolve mutable units\n mutableUnits.forEach(unit => {\n const path = unit.path;\n // ---- Find parent htmlElement\n const parentName = pathNameMap[path.slice(0, -1).join('.')];\n const nextName = pathNameMap[path.join('.')];\n console.log(nextName, path);\n const [childName, childStatements] = generateNew(this, unit);\n this.addStatement(...childStatements);\n this.addStatement(this.insertChildNode(parentName, childName, nextName));\n });\n\n\n return nodeName;\n }\n\n private generateTemplate(template: HTMLUnit): string {\n const templateName = this.generateTemplateName();\n const [name, statements] = generateNew(this, template, false);\n const returnStatement = this.t.returnStatement(this.t.identifier(name));\n\n this.addTemplate(\n this.t.variableDeclaration('const', [\n this.t.variableDeclarator(\n this.t.identifier(templateName),\n this.t.callExpression(\n this.t.arrowFunctionExpression([], this.t.blockStatement([...statements, returnStatement]))\n ,[])\n )\n ])\n );\n\n return templateName;\n }\n\n /**\n * @View\n * const $el = template.cloneNode(true)\n */\n private declareTemplateNode(templateName: string): [string, t.Statement]{\n const name = this.geneNodeName();\n return [name, this.t.variableDeclaration('const', [\n this.t.variableDeclarator(\n this.t.identifier(name),\n this.t.callExpression(\n this.t.memberExpression(\n this.t.identifier(templateName),\n this.t.identifier('cloneNode')\n ), [this.t.identifier('true')]\n )\n )\n ])];\n }\n\n /**\n * @View\n * $insert($el, childNode)\n */\n private insertChildNode(\n parent: string, \n child: string,\n nextName: string\n ) {\n const nextNode = nextName ? [this.t.identifier(nextName)] : [];\n return this.t.expressionStatement(\n this.t.callExpression(\n this.t.identifier(this.importMap.insert),\n [this.t.identifier(parent), this.t.identifier(child), ...nextNode]\n )\n );\n }\n\n\n /**\n * @View\n * ${dlNodeName}.firstChild\n * or\n * ${dlNodeName}.firstChild.nextSibling\n * or\n * ...\n * ${dlNodeName}.childNodes[${num}]\n */\n private insertElement(\n dlNodeName: string,\n path: number[],\n offset: number\n ): t.Statement {\n const newNodeName = this.geneNodeName();\n if (path.length === 0) {\n return this.t.variableDeclaration('const', [\n this.t.variableDeclarator(\n this.t.identifier(newNodeName),\n Array.from({ length: offset }).reduce(\n (acc: t.Expression) =>\n this.t.memberExpression(acc, this.t.identifier('nextSibling')),\n this.t.identifier(dlNodeName)\n )\n )\n ]);\n }\n const addFirstChild = (object: t.Expression) =>\n // ---- ${object}.firstChild\n this.t.memberExpression(object, this.t.identifier('firstChild'));\n const addSecondChild = (object: t.Expression) =>\n // ---- ${object}.firstChild.nextSibling\n this.t.memberExpression(\n addFirstChild(object),\n this.t.identifier('nextSibling')\n );\n const addThirdChild = (object: t.Expression) =>\n // ---- ${object}.firstChild.nextSibling.nextSibling\n this.t.memberExpression(\n addSecondChild(object),\n this.t.identifier('nextSibling')\n );\n const addOtherChild = (object: t.Expression, num: number) =>\n // ---- ${object}.childNodes[${num}]\n this.t.memberExpression(\n this.t.memberExpression(object, this.t.identifier('childNodes')),\n this.t.numericLiteral(num),\n true\n );\n const addNextSibling = (object: t.Expression) =>\n // ---- ${object}.nextSibling\n this.t.memberExpression(object, this.t.identifier('nextSibling'));\n\n return this.t.variableDeclaration('const', [\n this.t.variableDeclarator(\n this.t.identifier(newNodeName),\n path.reduce((acc: t.Expression, cur: number, idx) => {\n if (idx === 0 && offset > 0) {\n for (let i = 0; i < offset; i++) acc = addNextSibling(acc);\n }\n if (cur === 0) return addFirstChild(acc);\n if (cur === 1) return addSecondChild(acc);\n if (cur === 2) return addThirdChild(acc);\n return addOtherChild(acc, cur);\n }, this.t.identifier(dlNodeName))\n )\n ]);\n }\n\n /**\n * @brief Insert elements to the template node from the paths\n * @param paths\n * @param dlNodeName\n * @returns\n */\n private insertElements(\n paths: number[][],\n dlNodeName: string\n ): [t.Statement[], Record] {\n const [statements, collect] = this.createCollector();\n const nameMap: Record = { [dlNodeName]: [] };\n\n const commonPrefixPaths = TemplateGenerator.pathWithCommonPrefix(paths);\n\n commonPrefixPaths.forEach(path => {\n const res = TemplateGenerator.findBestNodeAndPath(\n nameMap,\n path,\n dlNodeName\n );\n const [, pat, offset] = res;\n let name = res[0];\n\n if (pat.length !== 0 || offset !== 0) {\n collect(this.insertElement(name, pat, offset));\n name = this.geneNodeName(this.nodeIdx);\n nameMap[name] = path;\n }\n });\n const pathNameMap = Object.fromEntries(\n Object.entries(nameMap).map(([name, path]) => [path.join('.'), name])\n );\n\n return [statements, pathNameMap];\n }\n\n // ---- Path related\n /**\n * @brief Extract common prefix from paths\n * e.g.\n * [0, 1, 2, 3] + [0, 1, 2, 4] => [0, 1, 2], [0, 1, 2, 3], [0, 1, 2, 4]\n * [0, 1, 2] is the common prefix\n * @param paths\n * @returns paths with common prefix\n */\n private static pathWithCommonPrefix(paths: number[][]): number[][] {\n const allPaths = [...paths].sort();\n const ps = [...allPaths];\n ps.forEach(path0 => {\n ps.forEach(path1 => {\n if (path0 === path1) return;\n for (let i = 0; i < path0.length; i++) {\n if (path0[i] !== path1[i]) {\n if (i !== 0) {\n allPaths.push(path0.slice(0, i));\n }\n break;\n }\n }\n });\n });\n\n // ---- Sort by length and then by first element, small to large\n const sortedPaths = allPaths.sort((a, b) => {\n if (a.length !== b.length) return a.length - b.length;\n return a[0] - b[0];\n });\n\n // ---- Deduplicate\n const deduplicatedPaths = [\n ...new Set(sortedPaths.map(path => path.join('.'))),\n ].map(path => path.split('.').filter(Boolean).map(Number));\n\n return deduplicatedPaths;\n }\n\n /**\n * @brief Find the best node name and path for the given path by looking into the nameMap.\n * If there's a full match, return the name and an empty path\n * If there's a partly match, return the name and the remaining path\n * If there's a nextSibling match, return the name and the remaining path with sibling offset\n * @param nameMap\n * @param path\n * @param defaultName\n * @returns [name, path, siblingOffset]\n */\n private static findBestNodeAndPath(\n nameMap: Record,\n path: number[],\n defaultName: string\n ): [string, number[], number] {\n let bestMatchCount = 0;\n let bestMatchName: string | undefined;\n let bestHalfMatch: [string, number, number] | undefined;\n Object.entries(nameMap).forEach(([name, pat]) => {\n let matchCount = 0;\n const pathLength = pat.length;\n for (let i = 0; i < pathLength; i++) {\n if (pat[i] === path[i]) matchCount++;\n }\n // console.log(name, matchCount, pathLength - 1, pat);\n if (matchCount === pathLength - 1) {\n const offset = path[pathLength - 1] - pat[pathLength - 1];\n if (offset > 0 && offset <= 3) {\n bestHalfMatch = [name, matchCount, offset];\n }\n }\n if (matchCount !== pat.length) return;\n if (matchCount > bestMatchCount) {\n bestMatchName = name;\n bestMatchCount = matchCount;\n }\n });\n if (bestHalfMatch) {\n return [\n bestHalfMatch[0],\n path.slice(bestHalfMatch[1] + 1),\n bestHalfMatch[2],\n ];\n }\n if (!bestMatchName) {\n return [defaultName, path, 0];\n }\n return [bestMatchName, path.slice(bestMatchCount), 0];\n }\n}","import { TextUnit } from '@inula/jsx-view-parser';\nimport BaseGenerator from '../HelperGenerators/BaseGenerator';\nimport type { types as t } from '@babel/core';\n\nexport class TextGenerator extends BaseGenerator {\n run() {\n const { content } = this.viewUnit as TextUnit;\n const [nodeName, statement] = this.declareTextNode(content);\n this.addStatement(statement);\n return nodeName;\n }\n\n declareTextNode(content: t.Literal): [string, t.Statement] {\n const name = this.geneNodeName();\n return [name, this.t.variableDeclaration('const', [\n this.t.variableDeclarator(\n this.t.identifier(name),\n this.t.callExpression(\n this.t.identifier(this.importMap.createText),\n [content]\n )\n )\n ])];\n }\n}","import { ExpUnit } from '@inula/jsx-view-parser';\nimport BaseGenerator from '../HelperGenerators/BaseGenerator';\nimport type { types as t } from '@babel/core';\nimport { generateView } from '../generate';\n\nexport class ExpressionGenerator extends BaseGenerator {\n run() {\n const { content: contentWithProp } = this.viewUnit as ExpUnit;\n const content = this.parseViewProp(contentWithProp, generateView);\n const [nodeName, statement] = this.declareExpressionNode(content);\n this.addStatement(statement);\n return nodeName;\n }\n\n\n declareExpressionNode(expression: t.Expression): [string, t.Statement] {\n const name = this.geneNodeName();\n return [name, this.t.variableDeclaration('const', [\n this.t.variableDeclarator(\n this.t.identifier(name),\n expression\n )\n ])];\n }\n}","import { ViewUnit } from '@inula/jsx-view-parser';\nimport { CompGenerator } from './NodeGenerators/CompGenerator';\nimport { HTMLGenerator } from './NodeGenerators/HTMLGenerator';\nimport { ViewGeneratorConfig } from './types';\nimport type { types as t } from '@babel/core';\nimport { TemplateGenerator } from './NodeGenerators/TemplateGenerator';\nimport { TextGenerator } from './NodeGenerators/TextGenerator';\nimport { ExpressionGenerator } from './NodeGenerators/ExpressionGenerator';\n\nexport const viewGeneratorMap = {\n html: HTMLGenerator,\n comp: CompGenerator,\n template: TemplateGenerator,\n text: TextGenerator,\n exp: ExpressionGenerator,\n if: CompGenerator,\n env: CompGenerator,\n} as const;\n\n\nexport function generateNew(oldGenerator: any, viewUnit: ViewUnit, resetIdx = true): [string, t.Statement[], t.Statement[]]{\n const generator = new viewGeneratorMap[viewUnit.type](viewUnit, oldGenerator.config);\n if (resetIdx) generator.nodeIdx = oldGenerator.nodeIdx;\n const [name, statements, templates] = generator.generate();\n if (resetIdx) oldGenerator.nodeIdx = generator.nodeIdx;\n return [name, statements, templates];\n}\n\nexport function generateBlock(viewUnits: ViewUnit[], config: ViewGeneratorConfig) {\n const t = config.babelApi.types;\n const names: string[] = [];\n const statements = viewUnits.flatMap(viewUnit => {\n const generator = new viewGeneratorMap[viewUnit.type](viewUnit, config);\n const [name, statements] = generator.generate();\n names.push(name);\n return statements;\n });\n\n const returnStatement = t.returnStatement(t.arrayExpression(names.map(name => t.identifier(name))));\n return (\n t.blockStatement(statements.concat(returnStatement))\n );\n}\n\nexport function generateView(viewUnits: ViewUnit[], config: ViewGeneratorConfig, templateIdx=-1): [t.Statement[], t.ExpressionStatement] {\n const t = config.babelApi.types;\n const names: string[] = [];\n const allTemplates: t.Statement[] = [];\n let nodeIdx = -1;\n const statements = viewUnits.flatMap(viewUnit => {\n const generator = new viewGeneratorMap[viewUnit.type](viewUnit, config);\n generator.templateIdx = templateIdx;\n generator.nodeIdx = nodeIdx;\n const [name, statements, templates] = generator.generate();\n templateIdx = generator.templateIdx;\n nodeIdx = generator.nodeIdx;\n names.push(name);\n allTemplates.push(...templates);\n return statements;\n });\n\n const returnStatement = t.returnStatement(t.arrayExpression(names.map(name => t.identifier(name))));\n\n return [allTemplates, (\n t.expressionStatement(\n t.callExpression(\n t.arrowFunctionExpression([], t.blockStatement(statements.concat(returnStatement))),\n []\n )\n )\n )];\n}"],"mappings":"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","names":["src_exports","__export","generateNew","generateView","viewGeneratorMap","__toCommonJS","BaseGenerator","viewUnit","config","acc","key","elements","element","statements","template","expression","reactive","path","idx","node","value","collect","statement","prop","generateView","viewPropMap","propNodeMap","units","templates","props","CompGenerator","BaseGenerator","tag","propsWithView","children","props","generateView","nodeName","statement","name","nodes","propNode","key","value","generateBlock","_HTMLPropGenerator","BaseGenerator","nodeName","tag","key","value","shouldWatch","expression","eventName","dynamic","event","attribute","HTMLPropGenerator","__publicField","HTMLGenerator","HTMLPropGenerator","tag","propsWithView","children","props","generateView","nodeName","statement","tagName","key","prop","child","childNodeName","childStatements","generateNew","name","parent","TemplateGenerator","HTMLPropGenerator","template","mutableUnits","props","templateName","nodeName","statement","paths","path","insertElementStatements","pathNameMap","tag","key","value","name","tagName","unit","parentName","nextName","childName","childStatements","generateNew","statements","returnStatement","parent","child","nextNode","dlNodeName","offset","newNodeName","acc","addFirstChild","object","addSecondChild","addThirdChild","addOtherChild","num","addNextSibling","cur","idx","i","collect","nameMap","res","pat","allPaths","ps","path0","path1","sortedPaths","a","b","defaultName","bestMatchCount","bestMatchName","bestHalfMatch","matchCount","pathLength","TextGenerator","BaseGenerator","content","nodeName","statement","name","ExpressionGenerator","BaseGenerator","contentWithProp","content","generateView","nodeName","statement","expression","name","viewGeneratorMap","HTMLGenerator","CompGenerator","TemplateGenerator","TextGenerator","ExpressionGenerator","generateNew","oldGenerator","viewUnit","resetIdx","generator","name","statements","templates","generateBlock","viewUnits","config","t","names","returnStatement","generateView","templateIdx","allTemplates","nodeIdx"]} \ No newline at end of file diff --git a/packages/transpiler/jsx-view-generator/dist/index.d.ts b/packages/transpiler/jsx-view-generator/dist/index.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..e21dbd93227c477968ad7fdee2acd8eafd023819 --- /dev/null +++ b/packages/transpiler/jsx-view-generator/dist/index.d.ts @@ -0,0 +1,217 @@ +import { ViewUnit, UnitProp } from '@inula/jsx-view-parser'; +import Babel, { types, traverse } from '@babel/core'; + +interface ViewGeneratorConfig { + babelApi: typeof Babel; + importMap: Record; + /** + * @brief Using AttributeMap to identify propertyfied attributes + * Reason for adding this: + * `el.prop = xxx` is faster than `el.setAttribute('prop', xxx)` + * @example { href: ["a", "area", "base", "link"], id: ["*"] } + */ + attributeMap?: Record; +} + +declare class BaseGenerator { + readonly viewUnit: ViewUnit; + readonly config: ViewGeneratorConfig; + readonly t: typeof types; + readonly traverse: typeof traverse; + readonly elementAttributeMap: Record; + readonly importMap: Record; + constructor(viewUnit: ViewUnit, config: ViewGeneratorConfig); + private readonly initStatements; + addStatement(...statements: types.Statement[]): void; + private readonly templates; + addTemplate(...template: types.Statement[]): void; + /** + * @brief To be implemented by the subclass + */ + run(): string; + generate(): [string, types.Statement[], types.Statement[]]; + /** + * @brief Check if the expression is reactive, which satisfies the following conditions: + * 1. Contains .get() property + * 2. The whole expression is not a function + * @param expression + * @returns + */ + checkReactive(expression: types.Expression): boolean; + private readonly prefixMap; + nodeIdx: number; + geneNodeName(idx?: number): string; + templateIdx: number; + generateTemplateName(): string; + /** + * @brief Wrap the value in a file + * @param node + * @returns wrapped value + */ + wrapWithFile(node: types.Expression): types.File; + addWatch(value: types.Expression): types.CallExpression; + createCollector(): [types.Statement[], (statement: types.Statement | types.Statement[] | null) => void]; + parseViewProp(prop: UnitProp, generateView: (units: ViewUnit[], config: ViewGeneratorConfig, templateIdx: number) => [types.Statement[], types.ExpressionStatement]): types.Expression; + parseProps(props: Record, generateView: (units: ViewUnit[], config: ViewGeneratorConfig) => [types.Statement[], types.ExpressionStatement]): { + [k: string]: types.Expression; + }; +} + +declare class CompGenerator extends BaseGenerator { + run(): string; + /** + * @View + * const $el = createComponent(tag, { + * ...props + * }, spreadProps) + */ + declareCompNode(tag: types.Expression, props: Record, children: ViewUnit[]): [string, types.Statement]; +} + +declare class HTMLPropGenerator extends BaseGenerator { + static DelegatedEvents: Set; + addHTMLProp(nodeName: string, tag: string, key: string, value: types.Expression): types.Statement; + /** + * @View + * setStyle($node, value) + */ + private setStyle; + /** + * @View + * setDataset($node, value) + */ + private setDataset; + /** + * @View + * $node.key = value + */ + private setStaticProperty; + /** + * @View + * setProperty($node, key, value) + */ + private setDynamicProperty; + /** + * @View + * $node.setAttribute(key, value) + */ + private setStaticAttribute; + /** + * @View + * setAttribute($node, key, value) + */ + private setDynamicAttribute; + /** + * @View + * delegateEvent($node, eventName, value) + */ + private setDelegatedEvent; + /** + * @View + * $node.addEventListener(eventName, value) + */ + private setStaticEvent; + /** + * @View + * addEventListener($node, eventName, value) + */ + private setDynamicEvent; + private setDynamicHTMLProp; + /** + * @brief Check if the attribute is internal, i.e., can be accessed as js property + * @param tag + * @param attribute + * @returns true if the attribute is internal + */ + isInternalAttribute(tag: string, attribute: string): boolean; +} + +declare class HTMLGenerator extends HTMLPropGenerator { + run(): string; + /** + * @View + * const $el = createElement(tag) + */ + declareHTMLNode(tag: types.Expression): [string, types.Statement]; + /** + * @View + * $insert($el, childNode) + */ + private insertChildNode; +} + +declare class TemplateGenerator extends HTMLPropGenerator { + run(): string; + private generateTemplate; + /** + * @View + * const $el = template.cloneNode(true) + */ + private declareTemplateNode; + /** + * @View + * $insert($el, childNode) + */ + private insertChildNode; + /** + * @View + * ${dlNodeName}.firstChild + * or + * ${dlNodeName}.firstChild.nextSibling + * or + * ... + * ${dlNodeName}.childNodes[${num}] + */ + private insertElement; + /** + * @brief Insert elements to the template node from the paths + * @param paths + * @param dlNodeName + * @returns + */ + private insertElements; + /** + * @brief Extract common prefix from paths + * e.g. + * [0, 1, 2, 3] + [0, 1, 2, 4] => [0, 1, 2], [0, 1, 2, 3], [0, 1, 2, 4] + * [0, 1, 2] is the common prefix + * @param paths + * @returns paths with common prefix + */ + private static pathWithCommonPrefix; + /** + * @brief Find the best node name and path for the given path by looking into the nameMap. + * If there's a full match, return the name and an empty path + * If there's a partly match, return the name and the remaining path + * If there's a nextSibling match, return the name and the remaining path with sibling offset + * @param nameMap + * @param path + * @param defaultName + * @returns [name, path, siblingOffset] + */ + private static findBestNodeAndPath; +} + +declare class TextGenerator extends BaseGenerator { + run(): string; + declareTextNode(content: types.Literal): [string, types.Statement]; +} + +declare class ExpressionGenerator extends BaseGenerator { + run(): string; + declareExpressionNode(expression: types.Expression): [string, types.Statement]; +} + +declare const viewGeneratorMap: { + readonly html: typeof HTMLGenerator; + readonly comp: typeof CompGenerator; + readonly template: typeof TemplateGenerator; + readonly text: typeof TextGenerator; + readonly exp: typeof ExpressionGenerator; + readonly if: typeof CompGenerator; + readonly env: typeof CompGenerator; +}; +declare function generateNew(oldGenerator: any, viewUnit: ViewUnit, resetIdx?: boolean): [string, types.Statement[], types.Statement[]]; +declare function generateView(viewUnits: ViewUnit[], config: ViewGeneratorConfig, templateIdx?: number): [types.Statement[], types.ExpressionStatement]; + +export { ViewGeneratorConfig, generateNew, generateView, viewGeneratorMap }; diff --git a/packages/transpiler/jsx-view-generator/dist/index.js b/packages/transpiler/jsx-view-generator/dist/index.js new file mode 100644 index 0000000000000000000000000000000000000000..03d87c29c47b7d859d4c34ca75f311d6acace533 --- /dev/null +++ b/packages/transpiler/jsx-view-generator/dist/index.js @@ -0,0 +1,2 @@ +var C=Object.defineProperty;var V=(c,t,e)=>t in c?C(c,t,{enumerable:!0,configurable:!0,writable:!0,value:e}):c[t]=e;var D=(c,t,e)=>(V(c,typeof t!="symbol"?t+"":t,e),e);var d=class{viewUnit;config;t;traverse;elementAttributeMap;importMap;constructor(t,e){this.config=e,this.t=e.babelApi.types,this.traverse=e.babelApi.traverse,this.importMap=e.importMap,this.viewUnit=t,this.elementAttributeMap=e.attributeMap?Object.entries(e.attributeMap).reduce((i,[s,o])=>(o.forEach(r=>{i[r]||(i[r]=[]),i[r].push(s)}),i),{}):{}}initStatements=[];addStatement(...t){this.initStatements.push(...t)}templates=[];addTemplate(...t){this.templates.push(...t)}run(){return""}generate(){return[this.run(),this.initStatements,this.templates]}checkReactive(t){if(this.t.isFunction(t))return!1;let e=!1;return this.traverse(this.wrapWithFile(t),{MemberExpression:i=>{this.t.isIdentifier(i.node.property,{name:"get"})&&(e=!0,i.stop())}}),e}prefixMap={node:"$node",template:"$template"};nodeIdx=-1;geneNodeName(t){return`${this.prefixMap.node}${t??++this.nodeIdx}`}templateIdx=-1;generateTemplateName(){return`${this.prefixMap.template}${++this.templateIdx}`}wrapWithFile(t){return this.t.file(this.t.program([this.t.expressionStatement(t)]))}addWatch(t){return this.t.callExpression(this.t.identifier(this.importMap.watch),[this.t.arrowFunctionExpression([],t)])}createCollector(){let t=[];function e(i){Array.isArray(i)?t.push(...i):i&&t.push(i)}return[t,e]}parseViewProp(t,e){let i=t.value,s=t.viewPropMap,o=Object.fromEntries(Object.entries(s).map(([r,a])=>{let[p,m]=e(a,this.config,this.templateIdx);return this.addTemplate(...p),[r,m]}));return this.traverse(this.wrapWithFile(i),{StringLiteral:r=>{let a=r.node.value;o[a]&&(this.t.isNodesEquivalent(i,r.node)&&(i=o[a].expression),r.replaceWith(o[a]))}}),i}parseProps(t,e){return Object.fromEntries(Object.entries(t).map(([i,s])=>[i,this.parseViewProp(s,e)]))}};var b=class extends d{run(){let{tag:t,props:e,children:i}=this.viewUnit,s=this.parseProps(e,u),[o,r]=this.declareCompNode(t,s,i);return this.addStatement(r),o}declareCompNode(t,e,i){let s=this.geneNodeName(),o=[];if(Object.keys(e).length>0||i.length>0){let r=this.t.objectExpression(Object.entries(e).map(([a,p])=>this.checkReactive(p)?this.t.objectMethod("get",this.t.identifier(a),[],this.t.blockStatement([this.t.returnStatement(p)])):this.t.objectProperty(this.t.identifier(a),p)));if(i.length>0){let a=U(i,this.config);r.properties.push(this.t.objectMethod("get",this.t.identifier("children"),[],a))}o.push(r)}return[s,this.t.variableDeclaration("const",[this.t.variableDeclarator(this.t.identifier(s),this.t.callExpression(this.t.identifier(this.importMap.createComponent),[t,...o]))])]}};var P=class extends d{addHTMLProp(t,e,i,s){let o=this.checkReactive(s),r=this.setDynamicHTMLProp(t,e,i,s,o);return o&&(r=this.addWatch(r)),this.t.expressionStatement(r)}setStyle(t,e){return this.t.callExpression(this.t.identifier(this.importMap.setStyle),[this.t.identifier(t),e])}setDataset(t,e){return this.t.callExpression(this.t.identifier(this.importMap.setDataset),[this.t.identifier(t),e])}setStaticProperty(t,e,i){return this.t.assignmentExpression("=",this.t.memberExpression(this.t.identifier(t),this.t.identifier(e)),i)}setDynamicProperty(t,e,i){return this.t.callExpression(this.t.identifier(this.importMap.setProperty),[this.t.identifier(t),this.t.stringLiteral(e),i])}setStaticAttribute(t,e,i){return this.t.callExpression(this.t.memberExpression(this.t.identifier(t),this.t.identifier("setAttribute")),[this.t.stringLiteral(e),i])}setDynamicAttribute(t,e,i){return this.t.callExpression(this.t.identifier(this.importMap.setAttribute),[this.t.identifier(t),this.t.stringLiteral(e),i])}setDelegatedEvent(t,e,i){return this.t.callExpression(this.t.identifier(this.importMap.delegateEvent),[this.t.identifier(t),this.t.stringLiteral(e),i])}setStaticEvent(t,e,i){return this.t.callExpression(this.t.memberExpression(this.t.identifier(t),this.t.identifier("addEventListener")),[this.t.stringLiteral(e),i])}setDynamicEvent(t,e,i){return this.t.callExpression(this.t.identifier(this.importMap.addEventListener),[this.t.identifier(t),this.t.stringLiteral(e),i])}setDynamicHTMLProp(t,e,i,s,o){if(i==="style")return this.setStyle(t,s);if(i==="dataset")return this.setDataset(t,s);if(i.startsWith("on")){let r=i.slice(2).toLowerCase();return P.DelegatedEvents.has(r)?this.setDelegatedEvent(t,r,s):this[o?"setDynamicEvent":"setStaticEvent"](t,r,s)}return this.isInternalAttribute(e,i)?(i==="class"?i="className":i==="for"&&(i="htmlFor"),this[o?"setDynamicProperty":"setStaticProperty"](t,i,s)):this[o?"setDynamicAttribute":"setStaticAttribute"](t,i,s)}isInternalAttribute(t,e){return this.elementAttributeMap["*"]?.includes(e)||this.elementAttributeMap[t]?.includes(e)}},g=P;D(g,"DelegatedEvents",new Set(["beforeinput","click","dblclick","contextmenu","focusin","focusout","input","keydown","keyup","mousedown","mousemove","mouseout","mouseover","mouseup","pointerdown","pointermove","pointerout","pointerover","pointerup","touchend","touchmove","touchstart"]));var S=class extends g{run(){let{tag:t,props:e,children:i}=this.viewUnit,s=this.parseProps(e,u),[o,r]=this.declareHTMLNode(t);this.addStatement(r);let a=this.t.isStringLiteral(t)?t.value:"ANY";return Object.entries(s).forEach(([p,m])=>{this.addStatement(this.addHTMLProp(o,a,p,m))}),i.forEach(p=>{let[m,n]=E(this,p);this.addStatement(...n),this.addStatement(this.insertChildNode(o,m))}),o}declareHTMLNode(t){let e=this.geneNodeName();return[e,this.t.variableDeclaration("const",[this.t.variableDeclarator(this.t.identifier(e),this.t.callExpression(this.t.identifier("createElement"),[t]))])]}insertChildNode(t,e){return this.t.expressionStatement(this.t.callExpression(this.t.identifier(this.importMap.insert),[this.t.identifier(t),this.t.identifier(e)]))}};var x=class extends g{run(){let{template:t,mutableUnits:e,props:i}=this.viewUnit,s=this.generateTemplate(t),[o,r]=this.declareTemplateNode(s);this.addStatement(r);let a=[];i.forEach(({path:n})=>{a.push(n)}),e.forEach(({path:n})=>{a.push(n.slice(0,-1)),n[n.length-1]!==-1&&a.push(n)});let[p,m]=this.insertElements(a,o);return this.addStatement(...p),i.forEach(({tag:n,path:h,key:l,value:f})=>{let y=m[h.join(".")],M=this.t.isStringLiteral(n)?n.value:"ANY";this.addStatement(this.addHTMLProp(y,M,l,f))}),e.forEach(n=>{let h=n.path,l=m[h.slice(0,-1).join(".")],f=m[h.join(".")];console.log(f,h);let[y,M]=E(this,n);this.addStatement(...M),this.addStatement(this.insertChildNode(l,y,f))}),o}generateTemplate(t){let e=this.generateTemplateName(),[i,s]=E(this,t,!1),o=this.t.returnStatement(this.t.identifier(i));return this.addTemplate(this.t.variableDeclaration("const",[this.t.variableDeclarator(this.t.identifier(e),this.t.callExpression(this.t.arrowFunctionExpression([],this.t.blockStatement([...s,o])),[]))])),e}declareTemplateNode(t){let e=this.geneNodeName();return[e,this.t.variableDeclaration("const",[this.t.variableDeclarator(this.t.identifier(e),this.t.callExpression(this.t.memberExpression(this.t.identifier(t),this.t.identifier("cloneNode")),[this.t.identifier("true")]))])]}insertChildNode(t,e,i){let s=i?[this.t.identifier(i)]:[];return this.t.expressionStatement(this.t.callExpression(this.t.identifier(this.importMap.insert),[this.t.identifier(t),this.t.identifier(e),...s]))}insertElement(t,e,i){let s=this.geneNodeName();if(e.length===0)return this.t.variableDeclaration("const",[this.t.variableDeclarator(this.t.identifier(s),Array.from({length:i}).reduce(n=>this.t.memberExpression(n,this.t.identifier("nextSibling")),this.t.identifier(t)))]);let o=n=>this.t.memberExpression(n,this.t.identifier("firstChild")),r=n=>this.t.memberExpression(o(n),this.t.identifier("nextSibling")),a=n=>this.t.memberExpression(r(n),this.t.identifier("nextSibling")),p=(n,h)=>this.t.memberExpression(this.t.memberExpression(n,this.t.identifier("childNodes")),this.t.numericLiteral(h),!0),m=n=>this.t.memberExpression(n,this.t.identifier("nextSibling"));return this.t.variableDeclaration("const",[this.t.variableDeclarator(this.t.identifier(s),e.reduce((n,h,l)=>{if(l===0&&i>0)for(let f=0;f{let m=x.findBestNodeAndPath(o,p,e),[,n,h]=m,l=m[0];(n.length!==0||h!==0)&&(s(this.insertElement(l,n,h)),l=this.geneNodeName(this.nodeIdx),o[l]=p)});let a=Object.fromEntries(Object.entries(o).map(([p,m])=>[m.join("."),p]));return[i,a]}static pathWithCommonPrefix(t){let e=[...t].sort(),i=[...e];i.forEach(r=>{i.forEach(a=>{if(r!==a){for(let p=0;pr.length!==a.length?r.length-a.length:r[0]-a[0]);return[...new Set(s.map(r=>r.join(".")))].map(r=>r.split(".").filter(Boolean).map(Number))}static findBestNodeAndPath(t,e,i){let s=0,o,r;return Object.entries(t).forEach(([a,p])=>{let m=0,n=p.length;for(let h=0;h0&&h<=3&&(r=[a,m,h])}m===p.length&&m>s&&(o=a,s=m)}),r?[r[0],e.slice(r[1]+1),r[2]]:o?[o,e.slice(s),0]:[i,e,0]}};var v=class extends d{run(){let{content:t}=this.viewUnit,[e,i]=this.declareTextNode(t);return this.addStatement(i),e}declareTextNode(t){let e=this.geneNodeName();return[e,this.t.variableDeclaration("const",[this.t.variableDeclarator(this.t.identifier(e),this.t.callExpression(this.t.identifier(this.importMap.createText),[t]))])]}};var N=class extends d{run(){let{content:t}=this.viewUnit,e=this.parseViewProp(t,u),[i,s]=this.declareExpressionNode(e);return this.addStatement(s),i}declareExpressionNode(t){let e=this.geneNodeName();return[e,this.t.variableDeclaration("const",[this.t.variableDeclarator(this.t.identifier(e),t)])]}};var w={html:S,comp:b,template:x,text:v,exp:N,if:b,env:b};function E(c,t,e=!0){let i=new w[t.type](t,c.config);e&&(i.nodeIdx=c.nodeIdx);let[s,o,r]=i.generate();return e&&(c.nodeIdx=i.nodeIdx),[s,o,r]}function U(c,t){let e=t.babelApi.types,i=[],s=c.flatMap(r=>{let a=new w[r.type](r,t),[p,m]=a.generate();return i.push(p),m}),o=e.returnStatement(e.arrayExpression(i.map(r=>e.identifier(r))));return e.blockStatement(s.concat(o))}function u(c,t,e=-1){let i=t.babelApi.types,s=[],o=[],r=-1,a=c.flatMap(m=>{let n=new w[m.type](m,t);n.templateIdx=e,n.nodeIdx=r;let[h,l,f]=n.generate();return e=n.templateIdx,r=n.nodeIdx,s.push(h),o.push(...f),l}),p=i.returnStatement(i.arrayExpression(s.map(m=>i.identifier(m))));return[o,i.expressionStatement(i.callExpression(i.arrowFunctionExpression([],i.blockStatement(a.concat(p))),[]))]}export{E as generateNew,u as generateView,w as viewGeneratorMap}; +//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/packages/transpiler/jsx-view-generator/dist/index.js.map b/packages/transpiler/jsx-view-generator/dist/index.js.map new file mode 100644 index 0000000000000000000000000000000000000000..e9b625a7351b2c214b6212454c8670a3f7bb67a7 --- /dev/null +++ b/packages/transpiler/jsx-view-generator/dist/index.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["../src/HelperGenerators/BaseGenerator.ts","../src/NodeGenerators/CompGenerator.ts","../src/HelperGenerators/HTMLPropGenerator.ts","../src/NodeGenerators/HTMLGenerator.ts","../src/NodeGenerators/TemplateGenerator.ts","../src/NodeGenerators/TextGenerator.ts","../src/NodeGenerators/ExpressionGenerator.ts","../src/generate.ts"],"sourcesContent":["import { UnitProp, ViewUnit } from '@inula/jsx-view-parser';\nimport { ViewGeneratorConfig } from '../types';\nimport type { types as t, traverse } from '@babel/core';\n\n\nexport default class BaseGenerator {\n readonly viewUnit: ViewUnit\n readonly config: ViewGeneratorConfig\n readonly t\n readonly traverse: typeof traverse\n readonly elementAttributeMap\n readonly importMap\n\n constructor(viewUnit: ViewUnit, config: ViewGeneratorConfig) {\n this.config = config;\n this.t = config.babelApi.types;\n this.traverse = config.babelApi.traverse;\n this.importMap = config.importMap;\n this.viewUnit = viewUnit;\n this.elementAttributeMap = config.attributeMap\n ? Object.entries(config.attributeMap).reduce>(\n (acc, [key, elements]) => {\n elements.forEach(element => {\n if (!acc[element]) acc[element] = [];\n acc[element].push(key);\n });\n return acc;\n },\n {}\n )\n : {};\n }\n\n // ---- Init Statements\n private readonly initStatements: t.Statement[] = []\n addStatement(...statements: t.Statement[]) {\n this.initStatements.push(...statements);\n }\n\n private readonly templates: t.Statement[] = []\n addTemplate(...template: t.Statement[]) {\n this.templates.push(...template);\n }\n\n // ---- Generate ----\n /**\n * @brief To be implemented by the subclass\n */\n run(): string {\n return ''; \n }\n\n generate(): [string, t.Statement[], t.Statement[]]{\n const nodeName = this.run();\n return [nodeName, this.initStatements, this.templates];\n }\n \n // ---- Reactivity ----\n /**\n * @brief Check if the expression is reactive, which satisfies the following conditions:\n * 1. Contains .get() property\n * 2. The whole expression is not a function\n * @param expression \n * @returns \n */\n checkReactive(expression: t.Expression) {\n if (this.t.isFunction(expression)) return false;\n let reactive = false;\n this.traverse(this.wrapWithFile(expression), {\n MemberExpression: path => {\n if (this.t.isIdentifier(path.node.property, { name: 'get' })) {\n reactive = true;\n path.stop();\n }\n }\n });\n return reactive;\n }\n\n // ---- Names ----\n private readonly prefixMap = {\n node: '$node',\n template: '$template',\n }\n\n nodeIdx = -1;\n geneNodeName(idx?: number): string {\n return `${this.prefixMap.node}${idx ?? ++this.nodeIdx}`;\n }\n templateIdx = -1;\n generateTemplateName() {\n return `${this.prefixMap.template}${++this.templateIdx}`;\n }\n\n\n // ---- Utils ----\n /**\n * @brief Wrap the value in a file\n * @param node\n * @returns wrapped value\n */\n wrapWithFile(node: t.Expression): t.File {\n return this.t.file(this.t.program([this.t.expressionStatement(node)]));\n }\n\n addWatch(value: t.Expression) {\n return this.t.callExpression(\n this.t.identifier(this.importMap.watch),\n [this.t.arrowFunctionExpression([], value)]\n );\n }\n\n createCollector(): [t.Statement[], (statement: t.Statement | t.Statement[] | null) => void]{\n const statements: t.Statement[] = [];\n function collect(statement: t.Statement | t.Statement[] | null) {\n if (Array.isArray(statement)) {\n statements.push(...statement);\n } else if (statement) {\n statements.push(statement);\n }\n }\n return [statements, collect];\n }\n\n parseViewProp(prop: UnitProp, generateView: (units: ViewUnit[], config: ViewGeneratorConfig, templateIdx: number) => [t.Statement[], t.ExpressionStatement]): t.Expression {\n let value = prop.value;\n const viewPropMap = prop.viewPropMap;\n const propNodeMap = Object.fromEntries(\n Object.entries(viewPropMap).map(([key, units]) => {\n const [templates, statement] = generateView(units, this.config, this.templateIdx);\n this.addTemplate(...templates);\n return [key, statement];\n })\n );\n\n this.traverse(this.wrapWithFile(value), {\n StringLiteral: path => {\n const key = path.node.value;\n if (propNodeMap[key]) {\n if (this.t.isNodesEquivalent(value, path.node)) {\n value = (propNodeMap[key] as t.ExpressionStatement).expression;\n }\n path.replaceWith(propNodeMap[key]);\n }\n }\n });\n \n return value;\n }\n\n parseProps(props: Record, generateView: (units: ViewUnit[], config: ViewGeneratorConfig) => [t.Statement[], t.ExpressionStatement]) {\n return Object.fromEntries(\n Object.entries(props).map(([key, prop]) => {\n return [key, this.parseViewProp(prop, generateView)];\n })\n );\n }\n}\n","import { CompUnit, ViewUnit } from '@inula/jsx-view-parser';\nimport BaseGenerator from '../HelperGenerators/BaseGenerator';\nimport type { types as t } from '@babel/core';\nimport { generateBlock, generateView } from '../generate';\n\nexport class CompGenerator extends BaseGenerator {\n run(): string {\n const { tag, props: propsWithView, children } = this.viewUnit as CompUnit;\n const props = this.parseProps(propsWithView, generateView);\n\n const [nodeName, statement] = this.declareCompNode(tag, props, children);\n this.addStatement(statement);\n\n return nodeName;\n }\n\n /**\n * @View\n * const $el = createComponent(tag, {\n * ...props\n * }, spreadProps)\n */\n declareCompNode(tag: t.Expression, props: Record, children: ViewUnit[]): [string, t.Statement] {\n const name = this.geneNodeName();\n const nodes = [];\n if (Object.keys(props).length > 0 || children.length > 0) {\n const propNode = this.t.objectExpression(Object.entries(props).map(([key, value]) => {\n const isReactive = this.checkReactive(value);\n if (isReactive) {\n /**\n * @View\n * get reactiveValue() {\n * return value\n * }\n */\n return (\n this.t.objectMethod('get', this.t.identifier(key), [],\n this.t.blockStatement([\n this.t.returnStatement(value)\n ])\n )\n );\n }\n return this.t.objectProperty(\n this.t.identifier(key),\n value\n );\n }));\n if (children.length > 0) {\n const statement = generateBlock(children, this.config);\n propNode.properties.push(\n this.t.objectMethod('get', this.t.identifier('children'), [], statement)\n );\n }\n nodes.push(propNode);\n }\n\n\n return [name, this.t.variableDeclaration('const', [\n this.t.variableDeclarator(\n this.t.identifier(name),\n this.t.callExpression(\n this.t.identifier(this.importMap.createComponent),\n [tag, ...nodes]\n )\n )\n ])];\n }\n}","import BaseGenerator from './BaseGenerator';\nimport type { types as t } from '@babel/core';\n\nexport class HTMLPropGenerator extends BaseGenerator {\n static DelegatedEvents = new Set([\n 'beforeinput',\n 'click',\n 'dblclick',\n 'contextmenu',\n 'focusin',\n 'focusout',\n 'input',\n 'keydown',\n 'keyup',\n 'mousedown',\n 'mousemove',\n 'mouseout',\n 'mouseover',\n 'mouseup',\n 'pointerdown',\n 'pointermove',\n 'pointerout',\n 'pointerover',\n 'pointerup',\n 'touchend',\n 'touchmove',\n 'touchstart',\n ])\n\n addHTMLProp(\n nodeName: string, \n tag: string,\n key: string,\n value: t.Expression,\n ): t.Statement {\n const shouldWatch = this.checkReactive(value);\n let expression = this.setDynamicHTMLProp(nodeName, tag, key, value, shouldWatch);\n if (shouldWatch) expression = this.addWatch(expression);\n\n return this.t.expressionStatement(expression);\n }\n\n /**\n * @View\n * setStyle($node, value)\n */\n private setStyle(\n nodeName: string, \n value: t.Expression,\n ) {\n return this.t.callExpression(\n this.t.identifier(this.importMap.setStyle),\n [this.t.identifier(nodeName), value]\n );\n }\n\n /**\n * @View\n * setDataset($node, value)\n */\n private setDataset(\n nodeName: string, \n value: t.Expression,\n ) {\n return this.t.callExpression(\n this.t.identifier(this.importMap.setDataset),\n [this.t.identifier(nodeName), value]\n );\n }\n\n /**\n * @View\n * $node.key = value\n */\n private setStaticProperty(\n nodeName: string, \n key: string,\n value: t.Expression,\n ) {\n return this.t.assignmentExpression(\n '=',\n this.t.memberExpression(\n this.t.identifier(nodeName),\n this.t.identifier(key),\n ),\n value\n );\n }\n\n /**\n * @View\n * setProperty($node, key, value)\n */\n private setDynamicProperty(\n nodeName: string, \n key: string,\n value: t.Expression,\n ) {\n return this.t.callExpression(\n this.t.identifier(this.importMap.setProperty),\n [this.t.identifier(nodeName), this.t.stringLiteral(key), value]\n );\n }\n\n /**\n * @View\n * $node.setAttribute(key, value)\n */\n private setStaticAttribute(\n nodeName: string, \n key: string,\n value: t.Expression,\n ) {\n return this.t.callExpression(\n this.t.memberExpression(\n this.t.identifier(nodeName),\n this.t.identifier('setAttribute'),\n ),\n [this.t.stringLiteral(key), value]\n );\n }\n\n\n /**\n * @View\n * setAttribute($node, key, value)\n */\n private setDynamicAttribute(\n nodeName: string, \n key: string,\n value: t.Expression,\n ) {\n return this.t.callExpression(\n this.t.identifier(this.importMap.setAttribute),\n [this.t.identifier(nodeName), this.t.stringLiteral(key), value]\n );\n }\n\n /**\n * @View\n * delegateEvent($node, eventName, value)\n */\n private setDelegatedEvent(\n nodeName: string, \n eventName: string,\n value: t.Expression,\n ) {\n return this.t.callExpression(\n this.t.identifier(this.importMap.delegateEvent),\n [this.t.identifier(nodeName), this.t.stringLiteral(eventName), value]\n );\n }\n\n /**\n * @View\n * $node.addEventListener(eventName, value)\n */\n private setStaticEvent(\n nodeName: string, \n eventName: string,\n value: t.Expression,\n ) {\n return this.t.callExpression(\n this.t.memberExpression(\n this.t.identifier(nodeName),\n this.t.identifier('addEventListener'),\n ),\n [this.t.stringLiteral(eventName), value]\n );\n }\n\n /**\n * @View\n * addEventListener($node, eventName, value)\n */\n private setDynamicEvent(\n nodeName: string, \n eventName: string,\n value: t.Expression,\n ) {\n return this.t.callExpression(\n this.t.identifier(this.importMap.addEventListener),\n [this.t.identifier(nodeName), this.t.stringLiteral(eventName), value]\n );\n }\n\n private setDynamicHTMLProp(\n nodeName: string, \n tag: string,\n key: string,\n value: t.Expression,\n dynamic: boolean\n ): t.Expression {\n if (key === 'style') return this.setStyle(nodeName, value);\n if (key === 'dataset') return this.setDataset(nodeName, value);\n if (key.startsWith('on')) {\n const event = key.slice(2).toLowerCase();\n if (HTMLPropGenerator.DelegatedEvents.has(event)) {\n return this.setDelegatedEvent(nodeName, event, value);\n }\n return this[dynamic ? 'setDynamicEvent' : 'setStaticEvent'](nodeName, event, value);\n }\n if (this.isInternalAttribute(tag, key)) {\n if (key === 'class') key = 'className';\n else if (key === 'for') key = 'htmlFor';\n return this[dynamic ? 'setDynamicProperty' : 'setStaticProperty'](nodeName, key, value);\n }\n return this[dynamic ? 'setDynamicAttribute' : 'setStaticAttribute'](nodeName, key, value);\n }\n\n /**\n * @brief Check if the attribute is internal, i.e., can be accessed as js property\n * @param tag\n * @param attribute\n * @returns true if the attribute is internal\n */\n isInternalAttribute(tag: string, attribute: string): boolean {\n return (\n this.elementAttributeMap['*']?.includes(attribute) ||\n this.elementAttributeMap[tag]?.includes(attribute)\n );\n }\n}","import { HTMLUnit } from '@inula/jsx-view-parser';\nimport type { types as t } from '@babel/core';\nimport { HTMLPropGenerator } from '../HelperGenerators/HTMLPropGenerator';\nimport { generateNew, generateView } from '../generate';\n\nexport class HTMLGenerator extends HTMLPropGenerator {\n run(){\n const { tag, props: propsWithView, children } = this.viewUnit as HTMLUnit;\n const props = this.parseProps(propsWithView, generateView);\n const [nodeName, statement] = this.declareHTMLNode(tag);\n this.addStatement(statement);\n\n // ---- Use the tag name to check if the prop is internal for the tag,\n // for dynamic tag, we can't check it, so we just assume it's not internal\n // represent by the \"ANY\" tag name\n const tagName = this.t.isStringLiteral(tag) ? tag.value : 'ANY';\n\n Object.entries(props).forEach(([key, prop]) => {\n this.addStatement(this.addHTMLProp(nodeName, tagName, key, prop));\n });\n\n children.forEach(child => {\n const [childNodeName, childStatements] = generateNew(this, child);\n this.addStatement(...childStatements);\n this.addStatement(this.insertChildNode(nodeName, childNodeName));\n });\n\n return nodeName;\n }\n\n /**\n * @View \n * const $el = createElement(tag)\n */\n declareHTMLNode(tag: t.Expression): [string, t.Statement] {\n const name = this.geneNodeName();\n return [name, this.t.variableDeclaration('const', [\n this.t.variableDeclarator(\n this.t.identifier(name),\n this.t.callExpression(\n this.t.identifier('createElement'),\n [tag]\n )\n )\n ])];\n }\n\n /**\n * @View\n * $insert($el, childNode)\n */\n private insertChildNode(\n parent: string, \n child: string\n ) {\n return this.t.expressionStatement(\n this.t.callExpression(\n this.t.identifier(this.importMap.insert),\n [this.t.identifier(parent), this.t.identifier(child)]\n )\n );\n }\n}","import { HTMLUnit, TemplateUnit } from '@inula/jsx-view-parser';\nimport { HTMLPropGenerator } from '../HelperGenerators/HTMLPropGenerator';\nimport { generateNew } from '../generate';\nimport type { types as t } from '@babel/core';\n\n\nexport class TemplateGenerator extends HTMLPropGenerator{\n run(): string {\n const { template, mutableUnits, props } = this.viewUnit as TemplateUnit;\n\n const templateName = this.generateTemplate(template);\n const [nodeName, statement] = this.declareTemplateNode(templateName);\n this.addStatement(statement);\n\n // ---- Insert elements first\n const paths: number[][] = [];\n props.forEach(({ path }) => {\n paths.push(path);\n });\n mutableUnits.forEach(({ path }) => {\n // ---- ParentPath and NextPath\n paths.push(path.slice(0, -1));\n if (path[path.length - 1] !== -1) paths.push(path);\n });\n const [insertElementStatements, pathNameMap] = this.insertElements(\n paths,\n nodeName\n );\n\n this.addStatement(...insertElementStatements);\n\n // ---- Resolve props\n props.forEach(\n ({\n tag,\n path,\n key,\n value,\n }) => {\n const name = pathNameMap[path.join('.')];\n\n const tagName = this.t.isStringLiteral(tag) ? tag.value : 'ANY';\n this.addStatement(\n this.addHTMLProp(\n name,\n tagName,\n key,\n value,\n )\n );\n }\n );\n\n // ---- Resolve mutable units\n mutableUnits.forEach(unit => {\n const path = unit.path;\n // ---- Find parent htmlElement\n const parentName = pathNameMap[path.slice(0, -1).join('.')];\n const nextName = pathNameMap[path.join('.')];\n console.log(nextName, path);\n const [childName, childStatements] = generateNew(this, unit);\n this.addStatement(...childStatements);\n this.addStatement(this.insertChildNode(parentName, childName, nextName));\n });\n\n\n return nodeName;\n }\n\n private generateTemplate(template: HTMLUnit): string {\n const templateName = this.generateTemplateName();\n const [name, statements] = generateNew(this, template, false);\n const returnStatement = this.t.returnStatement(this.t.identifier(name));\n\n this.addTemplate(\n this.t.variableDeclaration('const', [\n this.t.variableDeclarator(\n this.t.identifier(templateName),\n this.t.callExpression(\n this.t.arrowFunctionExpression([], this.t.blockStatement([...statements, returnStatement]))\n ,[])\n )\n ])\n );\n\n return templateName;\n }\n\n /**\n * @View\n * const $el = template.cloneNode(true)\n */\n private declareTemplateNode(templateName: string): [string, t.Statement]{\n const name = this.geneNodeName();\n return [name, this.t.variableDeclaration('const', [\n this.t.variableDeclarator(\n this.t.identifier(name),\n this.t.callExpression(\n this.t.memberExpression(\n this.t.identifier(templateName),\n this.t.identifier('cloneNode')\n ), [this.t.identifier('true')]\n )\n )\n ])];\n }\n\n /**\n * @View\n * $insert($el, childNode)\n */\n private insertChildNode(\n parent: string, \n child: string,\n nextName: string\n ) {\n const nextNode = nextName ? [this.t.identifier(nextName)] : [];\n return this.t.expressionStatement(\n this.t.callExpression(\n this.t.identifier(this.importMap.insert),\n [this.t.identifier(parent), this.t.identifier(child), ...nextNode]\n )\n );\n }\n\n\n /**\n * @View\n * ${dlNodeName}.firstChild\n * or\n * ${dlNodeName}.firstChild.nextSibling\n * or\n * ...\n * ${dlNodeName}.childNodes[${num}]\n */\n private insertElement(\n dlNodeName: string,\n path: number[],\n offset: number\n ): t.Statement {\n const newNodeName = this.geneNodeName();\n if (path.length === 0) {\n return this.t.variableDeclaration('const', [\n this.t.variableDeclarator(\n this.t.identifier(newNodeName),\n Array.from({ length: offset }).reduce(\n (acc: t.Expression) =>\n this.t.memberExpression(acc, this.t.identifier('nextSibling')),\n this.t.identifier(dlNodeName)\n )\n )\n ]);\n }\n const addFirstChild = (object: t.Expression) =>\n // ---- ${object}.firstChild\n this.t.memberExpression(object, this.t.identifier('firstChild'));\n const addSecondChild = (object: t.Expression) =>\n // ---- ${object}.firstChild.nextSibling\n this.t.memberExpression(\n addFirstChild(object),\n this.t.identifier('nextSibling')\n );\n const addThirdChild = (object: t.Expression) =>\n // ---- ${object}.firstChild.nextSibling.nextSibling\n this.t.memberExpression(\n addSecondChild(object),\n this.t.identifier('nextSibling')\n );\n const addOtherChild = (object: t.Expression, num: number) =>\n // ---- ${object}.childNodes[${num}]\n this.t.memberExpression(\n this.t.memberExpression(object, this.t.identifier('childNodes')),\n this.t.numericLiteral(num),\n true\n );\n const addNextSibling = (object: t.Expression) =>\n // ---- ${object}.nextSibling\n this.t.memberExpression(object, this.t.identifier('nextSibling'));\n\n return this.t.variableDeclaration('const', [\n this.t.variableDeclarator(\n this.t.identifier(newNodeName),\n path.reduce((acc: t.Expression, cur: number, idx) => {\n if (idx === 0 && offset > 0) {\n for (let i = 0; i < offset; i++) acc = addNextSibling(acc);\n }\n if (cur === 0) return addFirstChild(acc);\n if (cur === 1) return addSecondChild(acc);\n if (cur === 2) return addThirdChild(acc);\n return addOtherChild(acc, cur);\n }, this.t.identifier(dlNodeName))\n )\n ]);\n }\n\n /**\n * @brief Insert elements to the template node from the paths\n * @param paths\n * @param dlNodeName\n * @returns\n */\n private insertElements(\n paths: number[][],\n dlNodeName: string\n ): [t.Statement[], Record] {\n const [statements, collect] = this.createCollector();\n const nameMap: Record = { [dlNodeName]: [] };\n\n const commonPrefixPaths = TemplateGenerator.pathWithCommonPrefix(paths);\n\n commonPrefixPaths.forEach(path => {\n const res = TemplateGenerator.findBestNodeAndPath(\n nameMap,\n path,\n dlNodeName\n );\n const [, pat, offset] = res;\n let name = res[0];\n\n if (pat.length !== 0 || offset !== 0) {\n collect(this.insertElement(name, pat, offset));\n name = this.geneNodeName(this.nodeIdx);\n nameMap[name] = path;\n }\n });\n const pathNameMap = Object.fromEntries(\n Object.entries(nameMap).map(([name, path]) => [path.join('.'), name])\n );\n\n return [statements, pathNameMap];\n }\n\n // ---- Path related\n /**\n * @brief Extract common prefix from paths\n * e.g.\n * [0, 1, 2, 3] + [0, 1, 2, 4] => [0, 1, 2], [0, 1, 2, 3], [0, 1, 2, 4]\n * [0, 1, 2] is the common prefix\n * @param paths\n * @returns paths with common prefix\n */\n private static pathWithCommonPrefix(paths: number[][]): number[][] {\n const allPaths = [...paths].sort();\n const ps = [...allPaths];\n ps.forEach(path0 => {\n ps.forEach(path1 => {\n if (path0 === path1) return;\n for (let i = 0; i < path0.length; i++) {\n if (path0[i] !== path1[i]) {\n if (i !== 0) {\n allPaths.push(path0.slice(0, i));\n }\n break;\n }\n }\n });\n });\n\n // ---- Sort by length and then by first element, small to large\n const sortedPaths = allPaths.sort((a, b) => {\n if (a.length !== b.length) return a.length - b.length;\n return a[0] - b[0];\n });\n\n // ---- Deduplicate\n const deduplicatedPaths = [\n ...new Set(sortedPaths.map(path => path.join('.'))),\n ].map(path => path.split('.').filter(Boolean).map(Number));\n\n return deduplicatedPaths;\n }\n\n /**\n * @brief Find the best node name and path for the given path by looking into the nameMap.\n * If there's a full match, return the name and an empty path\n * If there's a partly match, return the name and the remaining path\n * If there's a nextSibling match, return the name and the remaining path with sibling offset\n * @param nameMap\n * @param path\n * @param defaultName\n * @returns [name, path, siblingOffset]\n */\n private static findBestNodeAndPath(\n nameMap: Record,\n path: number[],\n defaultName: string\n ): [string, number[], number] {\n let bestMatchCount = 0;\n let bestMatchName: string | undefined;\n let bestHalfMatch: [string, number, number] | undefined;\n Object.entries(nameMap).forEach(([name, pat]) => {\n let matchCount = 0;\n const pathLength = pat.length;\n for (let i = 0; i < pathLength; i++) {\n if (pat[i] === path[i]) matchCount++;\n }\n // console.log(name, matchCount, pathLength - 1, pat);\n if (matchCount === pathLength - 1) {\n const offset = path[pathLength - 1] - pat[pathLength - 1];\n if (offset > 0 && offset <= 3) {\n bestHalfMatch = [name, matchCount, offset];\n }\n }\n if (matchCount !== pat.length) return;\n if (matchCount > bestMatchCount) {\n bestMatchName = name;\n bestMatchCount = matchCount;\n }\n });\n if (bestHalfMatch) {\n return [\n bestHalfMatch[0],\n path.slice(bestHalfMatch[1] + 1),\n bestHalfMatch[2],\n ];\n }\n if (!bestMatchName) {\n return [defaultName, path, 0];\n }\n return [bestMatchName, path.slice(bestMatchCount), 0];\n }\n}","import { TextUnit } from '@inula/jsx-view-parser';\nimport BaseGenerator from '../HelperGenerators/BaseGenerator';\nimport type { types as t } from '@babel/core';\n\nexport class TextGenerator extends BaseGenerator {\n run() {\n const { content } = this.viewUnit as TextUnit;\n const [nodeName, statement] = this.declareTextNode(content);\n this.addStatement(statement);\n return nodeName;\n }\n\n declareTextNode(content: t.Literal): [string, t.Statement] {\n const name = this.geneNodeName();\n return [name, this.t.variableDeclaration('const', [\n this.t.variableDeclarator(\n this.t.identifier(name),\n this.t.callExpression(\n this.t.identifier(this.importMap.createText),\n [content]\n )\n )\n ])];\n }\n}","import { ExpUnit } from '@inula/jsx-view-parser';\nimport BaseGenerator from '../HelperGenerators/BaseGenerator';\nimport type { types as t } from '@babel/core';\nimport { generateView } from '../generate';\n\nexport class ExpressionGenerator extends BaseGenerator {\n run() {\n const { content: contentWithProp } = this.viewUnit as ExpUnit;\n const content = this.parseViewProp(contentWithProp, generateView);\n const [nodeName, statement] = this.declareExpressionNode(content);\n this.addStatement(statement);\n return nodeName;\n }\n\n\n declareExpressionNode(expression: t.Expression): [string, t.Statement] {\n const name = this.geneNodeName();\n return [name, this.t.variableDeclaration('const', [\n this.t.variableDeclarator(\n this.t.identifier(name),\n expression\n )\n ])];\n }\n}","import { ViewUnit } from '@inula/jsx-view-parser';\nimport { CompGenerator } from './NodeGenerators/CompGenerator';\nimport { HTMLGenerator } from './NodeGenerators/HTMLGenerator';\nimport { ViewGeneratorConfig } from './types';\nimport type { types as t } from '@babel/core';\nimport { TemplateGenerator } from './NodeGenerators/TemplateGenerator';\nimport { TextGenerator } from './NodeGenerators/TextGenerator';\nimport { ExpressionGenerator } from './NodeGenerators/ExpressionGenerator';\n\nexport const viewGeneratorMap = {\n html: HTMLGenerator,\n comp: CompGenerator,\n template: TemplateGenerator,\n text: TextGenerator,\n exp: ExpressionGenerator,\n if: CompGenerator,\n env: CompGenerator,\n} as const;\n\n\nexport function generateNew(oldGenerator: any, viewUnit: ViewUnit, resetIdx = true): [string, t.Statement[], t.Statement[]]{\n const generator = new viewGeneratorMap[viewUnit.type](viewUnit, oldGenerator.config);\n if (resetIdx) generator.nodeIdx = oldGenerator.nodeIdx;\n const [name, statements, templates] = generator.generate();\n if (resetIdx) oldGenerator.nodeIdx = generator.nodeIdx;\n return [name, statements, templates];\n}\n\nexport function generateBlock(viewUnits: ViewUnit[], config: ViewGeneratorConfig) {\n const t = config.babelApi.types;\n const names: string[] = [];\n const statements = viewUnits.flatMap(viewUnit => {\n const generator = new viewGeneratorMap[viewUnit.type](viewUnit, config);\n const [name, statements] = generator.generate();\n names.push(name);\n return statements;\n });\n\n const returnStatement = t.returnStatement(t.arrayExpression(names.map(name => t.identifier(name))));\n return (\n t.blockStatement(statements.concat(returnStatement))\n );\n}\n\nexport function generateView(viewUnits: ViewUnit[], config: ViewGeneratorConfig, templateIdx=-1): [t.Statement[], t.ExpressionStatement] {\n const t = config.babelApi.types;\n const names: string[] = [];\n const allTemplates: t.Statement[] = [];\n let nodeIdx = -1;\n const statements = viewUnits.flatMap(viewUnit => {\n const generator = new viewGeneratorMap[viewUnit.type](viewUnit, config);\n generator.templateIdx = templateIdx;\n generator.nodeIdx = nodeIdx;\n const [name, statements, templates] = generator.generate();\n templateIdx = generator.templateIdx;\n nodeIdx = generator.nodeIdx;\n names.push(name);\n allTemplates.push(...templates);\n return statements;\n });\n\n const returnStatement = t.returnStatement(t.arrayExpression(names.map(name => t.identifier(name))));\n\n return [allTemplates, (\n t.expressionStatement(\n t.callExpression(\n t.arrowFunctionExpression([], t.blockStatement(statements.concat(returnStatement))),\n []\n )\n )\n )];\n}"],"mappings":"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","names":["BaseGenerator","viewUnit","config","acc","key","elements","element","statements","template","expression","reactive","path","idx","node","value","collect","statement","prop","generateView","viewPropMap","propNodeMap","units","templates","props","CompGenerator","BaseGenerator","tag","propsWithView","children","props","generateView","nodeName","statement","name","nodes","propNode","key","value","generateBlock","_HTMLPropGenerator","BaseGenerator","nodeName","tag","key","value","shouldWatch","expression","eventName","dynamic","event","attribute","HTMLPropGenerator","__publicField","HTMLGenerator","HTMLPropGenerator","tag","propsWithView","children","props","generateView","nodeName","statement","tagName","key","prop","child","childNodeName","childStatements","generateNew","name","parent","TemplateGenerator","HTMLPropGenerator","template","mutableUnits","props","templateName","nodeName","statement","paths","path","insertElementStatements","pathNameMap","tag","key","value","name","tagName","unit","parentName","nextName","childName","childStatements","generateNew","statements","returnStatement","parent","child","nextNode","dlNodeName","offset","newNodeName","acc","addFirstChild","object","addSecondChild","addThirdChild","addOtherChild","num","addNextSibling","cur","idx","i","collect","nameMap","res","pat","allPaths","ps","path0","path1","sortedPaths","a","b","defaultName","bestMatchCount","bestMatchName","bestHalfMatch","matchCount","pathLength","TextGenerator","BaseGenerator","content","nodeName","statement","name","ExpressionGenerator","BaseGenerator","contentWithProp","content","generateView","nodeName","statement","expression","name","viewGeneratorMap","HTMLGenerator","CompGenerator","TemplateGenerator","TextGenerator","ExpressionGenerator","generateNew","oldGenerator","viewUnit","resetIdx","generator","name","statements","templates","generateBlock","viewUnits","config","t","names","returnStatement","generateView","templateIdx","allTemplates","nodeIdx"]} \ No newline at end of file diff --git a/packages/transpiler/jsx-view-generator/package.json b/packages/transpiler/jsx-view-generator/package.json new file mode 100755 index 0000000000000000000000000000000000000000..260f231d634288b14392fe49d6049d09a01f2430 --- /dev/null +++ b/packages/transpiler/jsx-view-generator/package.json @@ -0,0 +1,47 @@ +{ + "name": "@inula/jsx-view-generator", + "version": "0.0.0", + "description": "Inula view generator", + "author": { + "name": "IanDx", + "email": "iandxssxx@gmail.com" + }, + "keywords": [ + "inula" + ], + "files": [ + "dist" + ], + "type": "module", + "main": "dist/index.cjs", + "module": "dist/index.js", + "typings": "dist/index.d.ts", + "scripts": { + "build": "tsup --sourcemap", + "test": "vitest --ui" + }, + "devDependencies": { + "@babel/core": "^7.20.12", + "@babel/generator": "^7.23.6", + "@babel/plugin-syntax-jsx": "7.16.7", + "@inula/jsx-view-parser": "workspace:*", + "@types/babel__core": "^7.20.5", + "@types/babel__generator": "^7.6.8", + "@vitest/ui": "^0.34.5", + "tsup": "^6.7.0", + "typescript": "^5.3.2", + "vitest": "^0.34.5" + }, + "tsup": { + "entry": [ + "src/index.ts" + ], + "format": [ + "cjs", + "esm" + ], + "clean": true, + "dts": true, + "minify": true + } +} diff --git a/packages/transpiler/jsx-view-generator/src/HelperGenerators/BaseGenerator.ts b/packages/transpiler/jsx-view-generator/src/HelperGenerators/BaseGenerator.ts new file mode 100644 index 0000000000000000000000000000000000000000..81c960feae7a57dca7ed3b1de9af347e949fb1ce --- /dev/null +++ b/packages/transpiler/jsx-view-generator/src/HelperGenerators/BaseGenerator.ts @@ -0,0 +1,158 @@ +import { UnitProp, ViewUnit } from '@inula/jsx-view-parser'; +import { ViewGeneratorConfig } from '../types'; +import type { types as t, traverse } from '@babel/core'; + + +export default class BaseGenerator { + readonly viewUnit: ViewUnit + readonly config: ViewGeneratorConfig + readonly t + readonly traverse: typeof traverse + readonly elementAttributeMap + readonly importMap + + constructor(viewUnit: ViewUnit, config: ViewGeneratorConfig) { + this.config = config; + this.t = config.babelApi.types; + this.traverse = config.babelApi.traverse; + this.importMap = config.importMap; + this.viewUnit = viewUnit; + this.elementAttributeMap = config.attributeMap + ? Object.entries(config.attributeMap).reduce>( + (acc, [key, elements]) => { + elements.forEach(element => { + if (!acc[element]) acc[element] = []; + acc[element].push(key); + }); + return acc; + }, + {} + ) + : {}; + } + + // ---- Init Statements + private readonly initStatements: t.Statement[] = [] + addStatement(...statements: t.Statement[]) { + this.initStatements.push(...statements); + } + + private readonly templates: t.Statement[] = [] + addTemplate(...template: t.Statement[]) { + this.templates.push(...template); + } + + // ---- Generate ---- + /** + * @brief To be implemented by the subclass + */ + run(): string { + return ''; + } + + generate(): [string, t.Statement[], t.Statement[]]{ + const nodeName = this.run(); + return [nodeName, this.initStatements, this.templates]; + } + + // ---- Reactivity ---- + /** + * @brief Check if the expression is reactive, which satisfies the following conditions: + * 1. Contains .get() property + * 2. The whole expression is not a function + * @param expression + * @returns + */ + checkReactive(expression: t.Expression) { + if (this.t.isFunction(expression)) return false; + let reactive = false; + this.traverse(this.wrapWithFile(expression), { + MemberExpression: path => { + if (this.t.isIdentifier(path.node.property, { name: 'get' })) { + reactive = true; + path.stop(); + } + } + }); + return reactive; + } + + // ---- Names ---- + private readonly prefixMap = { + node: '$node', + template: '$template', + } + + nodeIdx = -1; + geneNodeName(idx?: number): string { + return `${this.prefixMap.node}${idx ?? ++this.nodeIdx}`; + } + templateIdx = -1; + generateTemplateName() { + return `${this.prefixMap.template}${++this.templateIdx}`; + } + + + // ---- Utils ---- + /** + * @brief Wrap the value in a file + * @param node + * @returns wrapped value + */ + wrapWithFile(node: t.Expression): t.File { + return this.t.file(this.t.program([this.t.expressionStatement(node)])); + } + + addWatch(value: t.Expression) { + return this.t.callExpression( + this.t.identifier(this.importMap.watch), + [this.t.arrowFunctionExpression([], value)] + ); + } + + createCollector(): [t.Statement[], (statement: t.Statement | t.Statement[] | null) => void]{ + const statements: t.Statement[] = []; + function collect(statement: t.Statement | t.Statement[] | null) { + if (Array.isArray(statement)) { + statements.push(...statement); + } else if (statement) { + statements.push(statement); + } + } + return [statements, collect]; + } + + parseViewProp(prop: UnitProp, generateView: (units: ViewUnit[], config: ViewGeneratorConfig, templateIdx: number) => [t.Statement[], t.ExpressionStatement]): t.Expression { + let value = prop.value; + const viewPropMap = prop.viewPropMap; + const propNodeMap = Object.fromEntries( + Object.entries(viewPropMap).map(([key, units]) => { + const [templates, statement] = generateView(units, this.config, this.templateIdx); + this.addTemplate(...templates); + return [key, statement]; + }) + ); + + this.traverse(this.wrapWithFile(value), { + StringLiteral: path => { + const key = path.node.value; + if (propNodeMap[key]) { + if (this.t.isNodesEquivalent(value, path.node)) { + value = (propNodeMap[key] as t.ExpressionStatement).expression; + } + path.replaceWith(propNodeMap[key]); + } + } + }); + + return value; + } + + parseProps(props: Record, generateView: (units: ViewUnit[], config: ViewGeneratorConfig) => [t.Statement[], t.ExpressionStatement]) { + return Object.fromEntries( + Object.entries(props).map(([key, prop]) => { + return [key, this.parseViewProp(prop, generateView)]; + }) + ); + } +} diff --git a/packages/transpiler/jsx-view-generator/src/HelperGenerators/HTMLPropGenerator.ts b/packages/transpiler/jsx-view-generator/src/HelperGenerators/HTMLPropGenerator.ts new file mode 100644 index 0000000000000000000000000000000000000000..7d413cfa57c8ad561b21de6872628589e7de3354 --- /dev/null +++ b/packages/transpiler/jsx-view-generator/src/HelperGenerators/HTMLPropGenerator.ts @@ -0,0 +1,223 @@ +import BaseGenerator from './BaseGenerator'; +import type { types as t } from '@babel/core'; + +export class HTMLPropGenerator extends BaseGenerator { + static DelegatedEvents = new Set([ + 'beforeinput', + 'click', + 'dblclick', + 'contextmenu', + 'focusin', + 'focusout', + 'input', + 'keydown', + 'keyup', + 'mousedown', + 'mousemove', + 'mouseout', + 'mouseover', + 'mouseup', + 'pointerdown', + 'pointermove', + 'pointerout', + 'pointerover', + 'pointerup', + 'touchend', + 'touchmove', + 'touchstart', + ]) + + addHTMLProp( + nodeName: string, + tag: string, + key: string, + value: t.Expression, + ): t.Statement { + const shouldWatch = this.checkReactive(value); + let expression = this.setDynamicHTMLProp(nodeName, tag, key, value, shouldWatch); + if (shouldWatch) expression = this.addWatch(expression); + + return this.t.expressionStatement(expression); + } + + /** + * @View + * setStyle($node, value) + */ + private setStyle( + nodeName: string, + value: t.Expression, + ) { + return this.t.callExpression( + this.t.identifier(this.importMap.setStyle), + [this.t.identifier(nodeName), value] + ); + } + + /** + * @View + * setDataset($node, value) + */ + private setDataset( + nodeName: string, + value: t.Expression, + ) { + return this.t.callExpression( + this.t.identifier(this.importMap.setDataset), + [this.t.identifier(nodeName), value] + ); + } + + /** + * @View + * $node.key = value + */ + private setStaticProperty( + nodeName: string, + key: string, + value: t.Expression, + ) { + return this.t.assignmentExpression( + '=', + this.t.memberExpression( + this.t.identifier(nodeName), + this.t.identifier(key), + ), + value + ); + } + + /** + * @View + * setProperty($node, key, value) + */ + private setDynamicProperty( + nodeName: string, + key: string, + value: t.Expression, + ) { + return this.t.callExpression( + this.t.identifier(this.importMap.setProperty), + [this.t.identifier(nodeName), this.t.stringLiteral(key), value] + ); + } + + /** + * @View + * $node.setAttribute(key, value) + */ + private setStaticAttribute( + nodeName: string, + key: string, + value: t.Expression, + ) { + return this.t.callExpression( + this.t.memberExpression( + this.t.identifier(nodeName), + this.t.identifier('setAttribute'), + ), + [this.t.stringLiteral(key), value] + ); + } + + + /** + * @View + * setAttribute($node, key, value) + */ + private setDynamicAttribute( + nodeName: string, + key: string, + value: t.Expression, + ) { + return this.t.callExpression( + this.t.identifier(this.importMap.setAttribute), + [this.t.identifier(nodeName), this.t.stringLiteral(key), value] + ); + } + + /** + * @View + * delegateEvent($node, eventName, value) + */ + private setDelegatedEvent( + nodeName: string, + eventName: string, + value: t.Expression, + ) { + return this.t.callExpression( + this.t.identifier(this.importMap.delegateEvent), + [this.t.identifier(nodeName), this.t.stringLiteral(eventName), value] + ); + } + + /** + * @View + * $node.addEventListener(eventName, value) + */ + private setStaticEvent( + nodeName: string, + eventName: string, + value: t.Expression, + ) { + return this.t.callExpression( + this.t.memberExpression( + this.t.identifier(nodeName), + this.t.identifier('addEventListener'), + ), + [this.t.stringLiteral(eventName), value] + ); + } + + /** + * @View + * addEventListener($node, eventName, value) + */ + private setDynamicEvent( + nodeName: string, + eventName: string, + value: t.Expression, + ) { + return this.t.callExpression( + this.t.identifier(this.importMap.addEventListener), + [this.t.identifier(nodeName), this.t.stringLiteral(eventName), value] + ); + } + + private setDynamicHTMLProp( + nodeName: string, + tag: string, + key: string, + value: t.Expression, + dynamic: boolean + ): t.Expression { + if (key === 'style') return this.setStyle(nodeName, value); + if (key === 'dataset') return this.setDataset(nodeName, value); + if (key.startsWith('on')) { + const event = key.slice(2).toLowerCase(); + if (HTMLPropGenerator.DelegatedEvents.has(event)) { + return this.setDelegatedEvent(nodeName, event, value); + } + return this[dynamic ? 'setDynamicEvent' : 'setStaticEvent'](nodeName, event, value); + } + if (this.isInternalAttribute(tag, key)) { + if (key === 'class') key = 'className'; + else if (key === 'for') key = 'htmlFor'; + return this[dynamic ? 'setDynamicProperty' : 'setStaticProperty'](nodeName, key, value); + } + return this[dynamic ? 'setDynamicAttribute' : 'setStaticAttribute'](nodeName, key, value); + } + + /** + * @brief Check if the attribute is internal, i.e., can be accessed as js property + * @param tag + * @param attribute + * @returns true if the attribute is internal + */ + isInternalAttribute(tag: string, attribute: string): boolean { + return ( + this.elementAttributeMap['*']?.includes(attribute) || + this.elementAttributeMap[tag]?.includes(attribute) + ); + } +} \ No newline at end of file diff --git a/packages/transpiler/jsx-view-generator/src/NodeGenerators/CompGenerator.ts b/packages/transpiler/jsx-view-generator/src/NodeGenerators/CompGenerator.ts new file mode 100644 index 0000000000000000000000000000000000000000..e582b9051eda4f99635c528b3757b7c4d4ce926a --- /dev/null +++ b/packages/transpiler/jsx-view-generator/src/NodeGenerators/CompGenerator.ts @@ -0,0 +1,69 @@ +import { CompUnit, ViewUnit } from '@inula/jsx-view-parser'; +import BaseGenerator from '../HelperGenerators/BaseGenerator'; +import type { types as t } from '@babel/core'; +import { generateBlock, generateView } from '../generate'; + +export class CompGenerator extends BaseGenerator { + run(): string { + const { tag, props: propsWithView, children } = this.viewUnit as CompUnit; + const props = this.parseProps(propsWithView, generateView); + + const [nodeName, statement] = this.declareCompNode(tag, props, children); + this.addStatement(statement); + + return nodeName; + } + + /** + * @View + * const $el = createComponent(tag, { + * ...props + * }, spreadProps) + */ + declareCompNode(tag: t.Expression, props: Record, children: ViewUnit[]): [string, t.Statement] { + const name = this.geneNodeName(); + const nodes = []; + if (Object.keys(props).length > 0 || children.length > 0) { + const propNode = this.t.objectExpression(Object.entries(props).map(([key, value]) => { + const isReactive = this.checkReactive(value); + if (isReactive) { + /** + * @View + * get reactiveValue() { + * return value + * } + */ + return ( + this.t.objectMethod('get', this.t.identifier(key), [], + this.t.blockStatement([ + this.t.returnStatement(value) + ]) + ) + ); + } + return this.t.objectProperty( + this.t.identifier(key), + value + ); + })); + if (children.length > 0) { + const statement = generateBlock(children, this.config); + propNode.properties.push( + this.t.objectMethod('get', this.t.identifier('children'), [], statement) + ); + } + nodes.push(propNode); + } + + + return [name, this.t.variableDeclaration('const', [ + this.t.variableDeclarator( + this.t.identifier(name), + this.t.callExpression( + this.t.identifier(this.importMap.createComponent), + [tag, ...nodes] + ) + ) + ])]; + } +} \ No newline at end of file diff --git a/packages/transpiler/jsx-view-generator/src/NodeGenerators/ExpressionGenerator.ts b/packages/transpiler/jsx-view-generator/src/NodeGenerators/ExpressionGenerator.ts new file mode 100644 index 0000000000000000000000000000000000000000..976f21668285aa4633aa467408e6f45b6ae06316 --- /dev/null +++ b/packages/transpiler/jsx-view-generator/src/NodeGenerators/ExpressionGenerator.ts @@ -0,0 +1,25 @@ +import { ExpUnit } from '@inula/jsx-view-parser'; +import BaseGenerator from '../HelperGenerators/BaseGenerator'; +import type { types as t } from '@babel/core'; +import { generateView } from '../generate'; + +export class ExpressionGenerator extends BaseGenerator { + run() { + const { content: contentWithProp } = this.viewUnit as ExpUnit; + const content = this.parseViewProp(contentWithProp, generateView); + const [nodeName, statement] = this.declareExpressionNode(content); + this.addStatement(statement); + return nodeName; + } + + + declareExpressionNode(expression: t.Expression): [string, t.Statement] { + const name = this.geneNodeName(); + return [name, this.t.variableDeclaration('const', [ + this.t.variableDeclarator( + this.t.identifier(name), + expression + ) + ])]; + } +} \ No newline at end of file diff --git a/packages/transpiler/jsx-view-generator/src/NodeGenerators/HTMLGenerator.ts b/packages/transpiler/jsx-view-generator/src/NodeGenerators/HTMLGenerator.ts new file mode 100644 index 0000000000000000000000000000000000000000..f6124e6a95cdd7d7c97fe4ff37f21abbace44dc9 --- /dev/null +++ b/packages/transpiler/jsx-view-generator/src/NodeGenerators/HTMLGenerator.ts @@ -0,0 +1,63 @@ +import { HTMLUnit } from '@inula/jsx-view-parser'; +import type { types as t } from '@babel/core'; +import { HTMLPropGenerator } from '../HelperGenerators/HTMLPropGenerator'; +import { generateNew, generateView } from '../generate'; + +export class HTMLGenerator extends HTMLPropGenerator { + run(){ + const { tag, props: propsWithView, children } = this.viewUnit as HTMLUnit; + const props = this.parseProps(propsWithView, generateView); + const [nodeName, statement] = this.declareHTMLNode(tag); + this.addStatement(statement); + + // ---- Use the tag name to check if the prop is internal for the tag, + // for dynamic tag, we can't check it, so we just assume it's not internal + // represent by the "ANY" tag name + const tagName = this.t.isStringLiteral(tag) ? tag.value : 'ANY'; + + Object.entries(props).forEach(([key, prop]) => { + this.addStatement(this.addHTMLProp(nodeName, tagName, key, prop)); + }); + + children.forEach(child => { + const [childNodeName, childStatements] = generateNew(this, child); + this.addStatement(...childStatements); + this.addStatement(this.insertChildNode(nodeName, childNodeName)); + }); + + return nodeName; + } + + /** + * @View + * const $el = createElement(tag) + */ + declareHTMLNode(tag: t.Expression): [string, t.Statement] { + const name = this.geneNodeName(); + return [name, this.t.variableDeclaration('const', [ + this.t.variableDeclarator( + this.t.identifier(name), + this.t.callExpression( + this.t.identifier('createElement'), + [tag] + ) + ) + ])]; + } + + /** + * @View + * $insert($el, childNode) + */ + private insertChildNode( + parent: string, + child: string + ) { + return this.t.expressionStatement( + this.t.callExpression( + this.t.identifier(this.importMap.insert), + [this.t.identifier(parent), this.t.identifier(child)] + ) + ); + } +} \ No newline at end of file diff --git a/packages/transpiler/jsx-view-generator/src/NodeGenerators/TemplateGenerator.ts b/packages/transpiler/jsx-view-generator/src/NodeGenerators/TemplateGenerator.ts new file mode 100644 index 0000000000000000000000000000000000000000..db84dbdccedb3f590997e428cb34b433e78b2282 --- /dev/null +++ b/packages/transpiler/jsx-view-generator/src/NodeGenerators/TemplateGenerator.ts @@ -0,0 +1,322 @@ +import { HTMLUnit, TemplateUnit } from '@inula/jsx-view-parser'; +import { HTMLPropGenerator } from '../HelperGenerators/HTMLPropGenerator'; +import { generateNew } from '../generate'; +import type { types as t } from '@babel/core'; + + +export class TemplateGenerator extends HTMLPropGenerator{ + run(): string { + const { template, mutableUnits, props } = this.viewUnit as TemplateUnit; + + const templateName = this.generateTemplate(template); + const [nodeName, statement] = this.declareTemplateNode(templateName); + this.addStatement(statement); + + // ---- Insert elements first + const paths: number[][] = []; + props.forEach(({ path }) => { + paths.push(path); + }); + mutableUnits.forEach(({ path }) => { + // ---- ParentPath and NextPath + paths.push(path.slice(0, -1)); + if (path[path.length - 1] !== -1) paths.push(path); + }); + const [insertElementStatements, pathNameMap] = this.insertElements( + paths, + nodeName + ); + + this.addStatement(...insertElementStatements); + + // ---- Resolve props + props.forEach( + ({ + tag, + path, + key, + value, + }) => { + const name = pathNameMap[path.join('.')]; + + const tagName = this.t.isStringLiteral(tag) ? tag.value : 'ANY'; + this.addStatement( + this.addHTMLProp( + name, + tagName, + key, + value, + ) + ); + } + ); + + // ---- Resolve mutable units + mutableUnits.forEach(unit => { + const path = unit.path; + // ---- Find parent htmlElement + const parentName = pathNameMap[path.slice(0, -1).join('.')]; + const nextName = pathNameMap[path.join('.')]; + console.log(nextName, path); + const [childName, childStatements] = generateNew(this, unit); + this.addStatement(...childStatements); + this.addStatement(this.insertChildNode(parentName, childName, nextName)); + }); + + + return nodeName; + } + + private generateTemplate(template: HTMLUnit): string { + const templateName = this.generateTemplateName(); + const [name, statements] = generateNew(this, template, false); + const returnStatement = this.t.returnStatement(this.t.identifier(name)); + + this.addTemplate( + this.t.variableDeclaration('const', [ + this.t.variableDeclarator( + this.t.identifier(templateName), + this.t.callExpression( + this.t.arrowFunctionExpression([], this.t.blockStatement([...statements, returnStatement])) + ,[]) + ) + ]) + ); + + return templateName; + } + + /** + * @View + * const $el = template.cloneNode(true) + */ + private declareTemplateNode(templateName: string): [string, t.Statement]{ + const name = this.geneNodeName(); + return [name, this.t.variableDeclaration('const', [ + this.t.variableDeclarator( + this.t.identifier(name), + this.t.callExpression( + this.t.memberExpression( + this.t.identifier(templateName), + this.t.identifier('cloneNode') + ), [this.t.identifier('true')] + ) + ) + ])]; + } + + /** + * @View + * $insert($el, childNode) + */ + private insertChildNode( + parent: string, + child: string, + nextName: string + ) { + const nextNode = nextName ? [this.t.identifier(nextName)] : []; + return this.t.expressionStatement( + this.t.callExpression( + this.t.identifier(this.importMap.insert), + [this.t.identifier(parent), this.t.identifier(child), ...nextNode] + ) + ); + } + + + /** + * @View + * ${dlNodeName}.firstChild + * or + * ${dlNodeName}.firstChild.nextSibling + * or + * ... + * ${dlNodeName}.childNodes[${num}] + */ + private insertElement( + dlNodeName: string, + path: number[], + offset: number + ): t.Statement { + const newNodeName = this.geneNodeName(); + if (path.length === 0) { + return this.t.variableDeclaration('const', [ + this.t.variableDeclarator( + this.t.identifier(newNodeName), + Array.from({ length: offset }).reduce( + (acc: t.Expression) => + this.t.memberExpression(acc, this.t.identifier('nextSibling')), + this.t.identifier(dlNodeName) + ) + ) + ]); + } + const addFirstChild = (object: t.Expression) => + // ---- ${object}.firstChild + this.t.memberExpression(object, this.t.identifier('firstChild')); + const addSecondChild = (object: t.Expression) => + // ---- ${object}.firstChild.nextSibling + this.t.memberExpression( + addFirstChild(object), + this.t.identifier('nextSibling') + ); + const addThirdChild = (object: t.Expression) => + // ---- ${object}.firstChild.nextSibling.nextSibling + this.t.memberExpression( + addSecondChild(object), + this.t.identifier('nextSibling') + ); + const addOtherChild = (object: t.Expression, num: number) => + // ---- ${object}.childNodes[${num}] + this.t.memberExpression( + this.t.memberExpression(object, this.t.identifier('childNodes')), + this.t.numericLiteral(num), + true + ); + const addNextSibling = (object: t.Expression) => + // ---- ${object}.nextSibling + this.t.memberExpression(object, this.t.identifier('nextSibling')); + + return this.t.variableDeclaration('const', [ + this.t.variableDeclarator( + this.t.identifier(newNodeName), + path.reduce((acc: t.Expression, cur: number, idx) => { + if (idx === 0 && offset > 0) { + for (let i = 0; i < offset; i++) acc = addNextSibling(acc); + } + if (cur === 0) return addFirstChild(acc); + if (cur === 1) return addSecondChild(acc); + if (cur === 2) return addThirdChild(acc); + return addOtherChild(acc, cur); + }, this.t.identifier(dlNodeName)) + ) + ]); + } + + /** + * @brief Insert elements to the template node from the paths + * @param paths + * @param dlNodeName + * @returns + */ + private insertElements( + paths: number[][], + dlNodeName: string + ): [t.Statement[], Record] { + const [statements, collect] = this.createCollector(); + const nameMap: Record = { [dlNodeName]: [] }; + + const commonPrefixPaths = TemplateGenerator.pathWithCommonPrefix(paths); + + commonPrefixPaths.forEach(path => { + const res = TemplateGenerator.findBestNodeAndPath( + nameMap, + path, + dlNodeName + ); + const [, pat, offset] = res; + let name = res[0]; + + if (pat.length !== 0 || offset !== 0) { + collect(this.insertElement(name, pat, offset)); + name = this.geneNodeName(this.nodeIdx); + nameMap[name] = path; + } + }); + const pathNameMap = Object.fromEntries( + Object.entries(nameMap).map(([name, path]) => [path.join('.'), name]) + ); + + return [statements, pathNameMap]; + } + + // ---- Path related + /** + * @brief Extract common prefix from paths + * e.g. + * [0, 1, 2, 3] + [0, 1, 2, 4] => [0, 1, 2], [0, 1, 2, 3], [0, 1, 2, 4] + * [0, 1, 2] is the common prefix + * @param paths + * @returns paths with common prefix + */ + private static pathWithCommonPrefix(paths: number[][]): number[][] { + const allPaths = [...paths].sort(); + const ps = [...allPaths]; + ps.forEach(path0 => { + ps.forEach(path1 => { + if (path0 === path1) return; + for (let i = 0; i < path0.length; i++) { + if (path0[i] !== path1[i]) { + if (i !== 0) { + allPaths.push(path0.slice(0, i)); + } + break; + } + } + }); + }); + + // ---- Sort by length and then by first element, small to large + const sortedPaths = allPaths.sort((a, b) => { + if (a.length !== b.length) return a.length - b.length; + return a[0] - b[0]; + }); + + // ---- Deduplicate + const deduplicatedPaths = [ + ...new Set(sortedPaths.map(path => path.join('.'))), + ].map(path => path.split('.').filter(Boolean).map(Number)); + + return deduplicatedPaths; + } + + /** + * @brief Find the best node name and path for the given path by looking into the nameMap. + * If there's a full match, return the name and an empty path + * If there's a partly match, return the name and the remaining path + * If there's a nextSibling match, return the name and the remaining path with sibling offset + * @param nameMap + * @param path + * @param defaultName + * @returns [name, path, siblingOffset] + */ + private static findBestNodeAndPath( + nameMap: Record, + path: number[], + defaultName: string + ): [string, number[], number] { + let bestMatchCount = 0; + let bestMatchName: string | undefined; + let bestHalfMatch: [string, number, number] | undefined; + Object.entries(nameMap).forEach(([name, pat]) => { + let matchCount = 0; + const pathLength = pat.length; + for (let i = 0; i < pathLength; i++) { + if (pat[i] === path[i]) matchCount++; + } + // console.log(name, matchCount, pathLength - 1, pat); + if (matchCount === pathLength - 1) { + const offset = path[pathLength - 1] - pat[pathLength - 1]; + if (offset > 0 && offset <= 3) { + bestHalfMatch = [name, matchCount, offset]; + } + } + if (matchCount !== pat.length) return; + if (matchCount > bestMatchCount) { + bestMatchName = name; + bestMatchCount = matchCount; + } + }); + if (bestHalfMatch) { + return [ + bestHalfMatch[0], + path.slice(bestHalfMatch[1] + 1), + bestHalfMatch[2], + ]; + } + if (!bestMatchName) { + return [defaultName, path, 0]; + } + return [bestMatchName, path.slice(bestMatchCount), 0]; + } +} \ No newline at end of file diff --git a/packages/transpiler/jsx-view-generator/src/NodeGenerators/TextGenerator.ts b/packages/transpiler/jsx-view-generator/src/NodeGenerators/TextGenerator.ts new file mode 100644 index 0000000000000000000000000000000000000000..4abe02b2b5de384791ce073e4d2f19aea5e244c2 --- /dev/null +++ b/packages/transpiler/jsx-view-generator/src/NodeGenerators/TextGenerator.ts @@ -0,0 +1,25 @@ +import { TextUnit } from '@inula/jsx-view-parser'; +import BaseGenerator from '../HelperGenerators/BaseGenerator'; +import type { types as t } from '@babel/core'; + +export class TextGenerator extends BaseGenerator { + run() { + const { content } = this.viewUnit as TextUnit; + const [nodeName, statement] = this.declareTextNode(content); + this.addStatement(statement); + return nodeName; + } + + declareTextNode(content: t.Literal): [string, t.Statement] { + const name = this.geneNodeName(); + return [name, this.t.variableDeclaration('const', [ + this.t.variableDeclarator( + this.t.identifier(name), + this.t.callExpression( + this.t.identifier(this.importMap.createText), + [content] + ) + ) + ])]; + } +} \ No newline at end of file diff --git a/packages/transpiler/jsx-view-generator/src/generate.ts b/packages/transpiler/jsx-view-generator/src/generate.ts new file mode 100644 index 0000000000000000000000000000000000000000..301409df1b57fdb4e64f312689a8c6a9012b3c06 --- /dev/null +++ b/packages/transpiler/jsx-view-generator/src/generate.ts @@ -0,0 +1,72 @@ +import { ViewUnit } from '@inula/jsx-view-parser'; +import { CompGenerator } from './NodeGenerators/CompGenerator'; +import { HTMLGenerator } from './NodeGenerators/HTMLGenerator'; +import { ViewGeneratorConfig } from './types'; +import type { types as t } from '@babel/core'; +import { TemplateGenerator } from './NodeGenerators/TemplateGenerator'; +import { TextGenerator } from './NodeGenerators/TextGenerator'; +import { ExpressionGenerator } from './NodeGenerators/ExpressionGenerator'; + +export const viewGeneratorMap = { + html: HTMLGenerator, + comp: CompGenerator, + template: TemplateGenerator, + text: TextGenerator, + exp: ExpressionGenerator, + if: CompGenerator, + env: CompGenerator, +} as const; + + +export function generateNew(oldGenerator: any, viewUnit: ViewUnit, resetIdx = true): [string, t.Statement[], t.Statement[]]{ + const generator = new viewGeneratorMap[viewUnit.type](viewUnit, oldGenerator.config); + if (resetIdx) generator.nodeIdx = oldGenerator.nodeIdx; + const [name, statements, templates] = generator.generate(); + if (resetIdx) oldGenerator.nodeIdx = generator.nodeIdx; + return [name, statements, templates]; +} + +export function generateBlock(viewUnits: ViewUnit[], config: ViewGeneratorConfig) { + const t = config.babelApi.types; + const names: string[] = []; + const statements = viewUnits.flatMap(viewUnit => { + const generator = new viewGeneratorMap[viewUnit.type](viewUnit, config); + const [name, statements] = generator.generate(); + names.push(name); + return statements; + }); + + const returnStatement = t.returnStatement(t.arrayExpression(names.map(name => t.identifier(name)))); + return ( + t.blockStatement(statements.concat(returnStatement)) + ); +} + +export function generateView(viewUnits: ViewUnit[], config: ViewGeneratorConfig, templateIdx=-1): [t.Statement[], t.ExpressionStatement] { + const t = config.babelApi.types; + const names: string[] = []; + const allTemplates: t.Statement[] = []; + let nodeIdx = -1; + const statements = viewUnits.flatMap(viewUnit => { + const generator = new viewGeneratorMap[viewUnit.type](viewUnit, config); + generator.templateIdx = templateIdx; + generator.nodeIdx = nodeIdx; + const [name, statements, templates] = generator.generate(); + templateIdx = generator.templateIdx; + nodeIdx = generator.nodeIdx; + names.push(name); + allTemplates.push(...templates); + return statements; + }); + + const returnStatement = t.returnStatement(t.arrayExpression(names.map(name => t.identifier(name)))); + + return [allTemplates, ( + t.expressionStatement( + t.callExpression( + t.arrowFunctionExpression([], t.blockStatement(statements.concat(returnStatement))), + [] + ) + ) + )]; +} \ No newline at end of file diff --git a/packages/transpiler/jsx-view-generator/src/index.ts b/packages/transpiler/jsx-view-generator/src/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..be5fe8fb3237f67d84c4ea3c7fc6b1c506067645 --- /dev/null +++ b/packages/transpiler/jsx-view-generator/src/index.ts @@ -0,0 +1,2 @@ +export {generateView, generateNew, viewGeneratorMap} from './generate'; +export type {ViewGeneratorConfig} from './types'; \ No newline at end of file diff --git a/packages/transpiler/jsx-view-generator/src/test/comp.test.ts b/packages/transpiler/jsx-view-generator/src/test/comp.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..450cc17a5aa45f134344a05e1823dbd5ea36b2ac --- /dev/null +++ b/packages/transpiler/jsx-view-generator/src/test/comp.test.ts @@ -0,0 +1,82 @@ +import { describe, it } from 'vitest'; +import { expectView } from './mock'; + + +describe('Comp', () => { + it('should generate a Component', () => { + expectView(/*jsx*/` + + `, /*js*/ ` + const $node0 = createComponent(Comp) + `); + }); + + it('should generate a Component with props', () => { + expectView(/*jsx*/` + + `, /*js*/` + const $node0 = createComponent(Comp, { + prop1: "value1", + prop2: value2 + }) + `); + }); + + it('should generate a Component with children', () => { + expectView(/*jsx*/` + +
+
+ `, /*js*/` + const $node0 = createComponent(Comp, { + get children() { + const $node0 = createElement("div") + return [$node0] + } + }) + `); + }); + + it('should generate a Component with props and children', () => { + expectView(/*jsx*/` + +
+
+ `, /*js*/` + const $node0 = createComponent(Comp, { + prop1: "value1", + prop2: value2, + get children() { + const $node0 = createElement("div") + return [$node0] + } + }) + `); + }); + + it('should generate a Component with reactive props', () => { + expectView(/*jsx*/` + + `, /*js*/` + const $node0 = createComponent(Comp, { + get prop1() { + return value.get() + } + }) + `); + }); + + it('should generate a Component with render/view props', () => { + expectView(/*jsx*/` + ok}/> + `, /*js*/` + const $node0 = createComponent(Comp, { + render: (() => { + const $node0 = createElement("div") + $node0.textContent = "ok" + return [$node0] + })() + }) + `); + }); +}); diff --git a/packages/transpiler/jsx-view-generator/src/test/const.ts b/packages/transpiler/jsx-view-generator/src/test/const.ts new file mode 100644 index 0000000000000000000000000000000000000000..1383b408a7ee23f497ff8897e3a94f9edf056d0f --- /dev/null +++ b/packages/transpiler/jsx-view-generator/src/test/const.ts @@ -0,0 +1,513 @@ + +export const importMap = [ + 'createElement', + 'setStyle', + 'setAttribute', + 'setDataset', + 'setProperty', + 'setEvent', + 'delegateEvent', + 'addEventListener', + 'watch', + 'insert', + 'createComponent', + 'createText' +].reduce>((acc, cur) => { + acc[cur] = cur; + return acc; +}, {}); + +export const htmlTags = [ + 'a', + 'abbr', + 'address', + 'area', + 'article', + 'aside', + 'audio', + 'b', + 'base', + 'bdi', + 'bdo', + 'blockquote', + 'body', + 'br', + 'button', + 'canvas', + 'caption', + 'cite', + 'code', + 'col', + 'colgroup', + 'data', + 'datalist', + 'dd', + 'del', + 'details', + 'dfn', + 'dialog', + 'div', + 'dl', + 'dt', + 'em', + 'embed', + 'fieldset', + 'figcaption', + 'figure', + 'footer', + 'form', + 'h1', + 'h2', + 'h3', + 'h4', + 'h5', + 'h6', + 'head', + 'header', + 'hgroup', + 'hr', + 'html', + 'i', + 'iframe', + 'img', + 'input', + 'ins', + 'kbd', + 'label', + 'legend', + 'li', + 'link', + 'main', + 'map', + 'mark', + 'menu', + 'meta', + 'meter', + 'nav', + 'noscript', + 'object', + 'ol', + 'optgroup', + 'option', + 'output', + 'p', + 'picture', + 'pre', + 'progress', + 'q', + 'rp', + 'rt', + 'ruby', + 's', + 'samp', + 'script', + 'section', + 'select', + 'slot', + 'small', + 'source', + 'span', + 'strong', + 'style', + 'sub', + 'summary', + 'sup', + 'table', + 'tbody', + 'td', + 'template', + 'textarea', + 'tfoot', + 'th', + 'thead', + 'time', + 'title', + 'tr', + 'track', + 'u', + 'ul', + 'var', + 'video', + 'wbr', + 'acronym', + 'applet', + 'basefont', + 'bgsound', + 'big', + 'blink', + 'center', + 'dir', + 'font', + 'frame', + 'frameset', + 'isindex', + 'keygen', + 'listing', + 'marquee', + 'menuitem', + 'multicol', + 'nextid', + 'nobr', + 'noembed', + 'noframes', + 'param', + 'plaintext', + 'rb', + 'rtc', + 'spacer', + 'strike', + 'tt', + 'xmp', + 'animate', + 'animateMotion', + 'animateTransform', + 'circle', + 'clipPath', + 'defs', + 'desc', + 'ellipse', + 'feBlend', + 'feColorMatrix', + 'feComponentTransfer', + 'feComposite', + 'feConvolveMatrix', + 'feDiffuseLighting', + 'feDisplacementMap', + 'feDistantLight', + 'feDropShadow', + 'feFlood', + 'feFuncA', + 'feFuncB', + 'feFuncG', + 'feFuncR', + 'feGaussianBlur', + 'feImage', + 'feMerge', + 'feMergeNode', + 'feMorphology', + 'feOffset', + 'fePointLight', + 'feSpecularLighting', + 'feSpotLight', + 'feTile', + 'feTurbulence', + 'filter', + 'foreignObject', + 'g', + 'image', + 'line', + 'linearGradient', + 'marker', + 'mask', + 'metadata', + 'mpath', + 'path', + 'pattern', + 'polygon', + 'polyline', + 'radialGradient', + 'rect', + 'set', + 'stop', + 'svg', + 'switch', + 'symbol', + 'text', + 'textPath', + 'tspan', + 'use', + 'view', +]; + +/** + * @brief HTML internal attribute map, can be accessed as js property + */ +export const attributeMap = { + // ---- Other property as attribute + textContent: ['*'], + innerHTML: ['*'], + // ---- Source: https://developer.mozilla.org/zh-CN/docs/Web/HTML/Attributes + accept: ['form', 'input'], + // ---- Original: accept-charset + acceptCharset: ['form'], + accesskey: ['*'], + action: ['form'], + align: [ + 'caption', + 'col', + 'colgroup', + 'hr', + 'iframe', + 'img', + 'table', + 'tbody', + 'td', + 'tfoot', + 'th', + 'thead', + 'tr', + ], + allow: ['iframe'], + alt: ['area', 'img', 'input'], + async: ['script'], + autocapitalize: ['*'], + autocomplete: ['form', 'input', 'select', 'textarea'], + autofocus: ['button', 'input', 'select', 'textarea'], + autoplay: ['audio', 'video'], + background: ['body', 'table', 'td', 'th'], + // ---- Original: base + bgColor: [ + 'body', + 'col', + 'colgroup', + 'marquee', + 'table', + 'tbody', + 'tfoot', + 'td', + 'th', + 'tr', + ], + border: ['img', 'object', 'table'], + buffered: ['audio', 'video'], + capture: ['input'], + charset: ['meta'], + checked: ['input'], + cite: ['blockquote', 'del', 'ins', 'q'], + class: ['*'], + color: ['font', 'hr'], + cols: ['textarea'], + // ---- Original: colspan + colSpan: ['td', 'th'], + content: ['meta'], + // ---- Original: contenteditable + contentEditable: ['*'], + contextmenu: ['*'], + controls: ['audio', 'video'], + coords: ['area'], + crossOrigin: ['audio', 'img', 'link', 'script', 'video'], + csp: ['iframe'], + data: ['object'], + // ---- Original: datetime + dateTime: ['del', 'ins', 'time'], + decoding: ['img'], + default: ['track'], + defer: ['script'], + dir: ['*'], + dirname: ['input', 'textarea'], + disabled: [ + 'button', + 'fieldset', + 'input', + 'optgroup', + 'option', + 'select', + 'textarea', + ], + download: ['a', 'area'], + draggable: ['*'], + enctype: ['form'], + // ---- Original: enterkeyhint + enterKeyHint: ['textarea', 'contenteditable'], + for: ['label', 'output'], + form: [ + 'button', + 'fieldset', + 'input', + 'label', + 'meter', + 'object', + 'output', + 'progress', + 'select', + 'textarea', + ], + // ---- Original: formaction + formAction: ['input', 'button'], + // ---- Original: formenctype + formEnctype: ['button', 'input'], + // ---- Original: formmethod + formMethod: ['button', 'input'], + // ---- Original: formnovalidate + formNoValidate: ['button', 'input'], + // ---- Original: formtarget + formTarget: ['button', 'input'], + headers: ['td', 'th'], + height: ['canvas', 'embed', 'iframe', 'img', 'input', 'object', 'video'], + hidden: ['*'], + high: ['meter'], + href: ['a', 'area', 'base', 'link'], + hreflang: ['a', 'link'], + // ---- Original: http-equiv + httpEquiv: ['meta'], + id: ['*'], + integrity: ['link', 'script'], + // ---- Original: intrinsicsize + intrinsicSize: ['img'], + // ---- Original: inputmode + inputMode: ['textarea', 'contenteditable'], + ismap: ['img'], + // ---- Original: itemprop + itemProp: ['*'], + kind: ['track'], + label: ['optgroup', 'option', 'track'], + lang: ['*'], + language: ['script'], + loading: ['img', 'iframe'], + list: ['input'], + loop: ['audio', 'marquee', 'video'], + low: ['meter'], + manifest: ['html'], + max: ['input', 'meter', 'progress'], + // ---- Original: maxlength + maxLength: ['input', 'textarea'], + // ---- Original: minlength + minLength: ['input', 'textarea'], + media: ['a', 'area', 'link', 'source', 'style'], + method: ['form'], + min: ['input', 'meter'], + multiple: ['input', 'select'], + muted: ['audio', 'video'], + name: [ + 'button', + 'form', + 'fieldset', + 'iframe', + 'input', + 'object', + 'output', + 'select', + 'textarea', + 'map', + 'meta', + 'param', + ], + // ---- Original: novalidate + noValidate: ['form'], + open: ['details', 'dialog'], + optimum: ['meter'], + pattern: ['input'], + ping: ['a', 'area'], + placeholder: ['input', 'textarea'], + // ---- Original: playsinline + playsInline: ['video'], + poster: ['video'], + preload: ['audio', 'video'], + readonly: ['input', 'textarea'], + // ---- Original: referrerpolicy + referrerPolicy: ['a', 'area', 'iframe', 'img', 'link', 'script'], + rel: ['a', 'area', 'link'], + required: ['input', 'select', 'textarea'], + reversed: ['ol'], + role: ['*'], + rows: ['textarea'], + // ---- Original: rowspan + rowSpan: ['td', 'th'], + sandbox: ['iframe'], + scope: ['th'], + scoped: ['style'], + selected: ['option'], + shape: ['a', 'area'], + size: ['input', 'select'], + sizes: ['link', 'img', 'source'], + slot: ['*'], + span: ['col', 'colgroup'], + spellcheck: ['*'], + src: [ + 'audio', + 'embed', + 'iframe', + 'img', + 'input', + 'script', + 'source', + 'track', + 'video', + ], + srcdoc: ['iframe'], + srclang: ['track'], + srcset: ['img', 'source'], + start: ['ol'], + step: ['input'], + style: ['*'], + summary: ['table'], + // ---- Original: tabindex + tabIndex: ['*'], + target: ['a', 'area', 'base', 'form'], + title: ['*'], + translate: ['*'], + type: [ + 'button', + 'input', + 'embed', + 'object', + 'ol', + 'script', + 'source', + 'style', + 'menu', + 'link', + ], + usemap: ['img', 'input', 'object'], + value: [ + 'button', + 'data', + 'input', + 'li', + 'meter', + 'option', + 'progress', + 'param', + 'text' /** extra for TextNode */, + ], + width: ['canvas', 'embed', 'iframe', 'img', 'input', 'object', 'video'], + wrap: ['textarea'], + // --- ARIA attributes + // Source: https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes + ariaAutocomplete: ['*'], + ariaChecked: ['*'], + ariaDisabled: ['*'], + ariaErrorMessage: ['*'], + ariaExpanded: ['*'], + ariaHasPopup: ['*'], + ariaHidden: ['*'], + ariaInvalid: ['*'], + ariaLabel: ['*'], + ariaLevel: ['*'], + ariaModal: ['*'], + ariaMultiline: ['*'], + ariaMultiSelectable: ['*'], + ariaOrientation: ['*'], + ariaPlaceholder: ['*'], + ariaPressed: ['*'], + ariaReadonly: ['*'], + ariaRequired: ['*'], + ariaSelected: ['*'], + ariaSort: ['*'], + ariaValuemax: ['*'], + ariaValuemin: ['*'], + ariaValueNow: ['*'], + ariaValueText: ['*'], + ariaBusy: ['*'], + ariaLive: ['*'], + ariaRelevant: ['*'], + ariaAtomic: ['*'], + ariaDropEffect: ['*'], + ariaGrabbed: ['*'], + ariaActiveDescendant: ['*'], + ariaColCount: ['*'], + ariaColIndex: ['*'], + ariaColSpan: ['*'], + ariaControls: ['*'], + ariaDescribedBy: ['*'], + ariaDescription: ['*'], + ariaDetails: ['*'], + ariaFlowTo: ['*'], + ariaLabelledBy: ['*'], + ariaOwns: ['*'], + ariaPosInset: ['*'], + ariaRowCount: ['*'], + ariaRowIndex: ['*'], + ariaRowSpan: ['*'], + ariaSetSize: ['*'], +}; \ No newline at end of file diff --git a/packages/transpiler/jsx-view-generator/src/test/exp.test.ts b/packages/transpiler/jsx-view-generator/src/test/exp.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..d9585f3c22afa514977897669fc614b73ba7eedf --- /dev/null +++ b/packages/transpiler/jsx-view-generator/src/test/exp.test.ts @@ -0,0 +1,14 @@ +import { describe, it } from 'vitest'; +import { expectView } from './mock'; + + +describe('Expression', () => { + it('should generate a expression Node', () => { + expectView(/*jsx*/` + <>{expr} + `, /*js*/` + const $node0 = expr + `); + }); +}); + \ No newline at end of file diff --git a/packages/transpiler/view-parser/src/test/global.d.ts b/packages/transpiler/jsx-view-generator/src/test/global.d.ts similarity index 100% rename from packages/transpiler/view-parser/src/test/global.d.ts rename to packages/transpiler/jsx-view-generator/src/test/global.d.ts diff --git a/packages/transpiler/jsx-view-generator/src/test/html.test.ts b/packages/transpiler/jsx-view-generator/src/test/html.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..c47145bdd67b2e3b253d3b8f5a7e36fadcb44184 --- /dev/null +++ b/packages/transpiler/jsx-view-generator/src/test/html.test.ts @@ -0,0 +1,128 @@ +import { describe, it } from 'vitest'; +import { expectView } from './mock'; + +describe('HTML', () => { + it('should generate a div element', () => { + expectView(/*jsx*/` +
+ `, /*js*/ ` + const $node0 = createElement("div") + `); + }); + + // ---- Static Props + it('should generate a div element with a static id', () => { + expectView(/*jsx*/` +
+ `, /*js*/` + const $node0 = createElement("div") + $node0.id = "myDiv" + `); + }); + + it('should generate a div element with a static class', () => { + expectView(/*jsx*/` +
+ `, /*js*/` + const $node0 = createElement("div") + $node0.className = "myClass" + `); + }); + + it('should generate a div element with a static for', () => { + expectView(/*jsx*/` + + `, /*js*/` + const $node0 = createElement("label") + $node0.htmlFor = "myFor" + `); + }); + + it('should generate a div element with a static style', () => { + expectView(/*jsx*/` +
+ `, /*js*/` + const $node0 = createElement("div") + setStyle($node0, "color: red") + `); + }); + + it('should generate a div element with a static dataset', () => { + expectView(/*jsx*/` +
+ `, /*js*/` + const $node0 = createElement("div") + setDataset($node0, myDataset) + `); + }); + + it('should generate a div element with a static event', () => { + expectView(/*jsx*/` +
+ `, /*js*/` + const $node0 = createElement("div") + delegateEvent($node0, "click", myFunction) + `); + }); + + it('should generate a div element with a static custom event', () => { + expectView(/*jsx*/` +
+ `, /*js*/` + const $node0 = createElement("div") + $node0.addEventListener("myevent", myFunction) + `); + }); + + // ---- Dynamic Props + it('should generate a div element with a dynamic id', () => { + expectView(/*jsx*/` +
+ `, /*js*/` + const $node0 = createElement("div") + watch(() => setProperty($node0, "id", count.get())) + `); + }); + + it('should generate a div element with a dynamic style', () => { + expectView(/*jsx*/` +
+ `, /*js*/` + const $node0 = createElement("div") + watch(() => setStyle($node0, styleList[count.get()])) + `); + }); + + it('should generate a div element with a dynamic dataset', () => { + expectView(/*jsx*/` +
+ `, /*js*/` + const $node0 = createElement("div") + watch(() => setDataset($node0, datasetList[count.get()])) + `); + }); + + it('should generate a div element with a dynamic event', () => { + expectView(/*jsx*/` +
{ console.log(count.get()) }}>
+ `, /*js*/` + const $node0 = createElement("div") + delegateEvent( + $node0, "click", + () => { console.log(count.get()) } + ) + `); + }); + + it('should insert a element into a div', () => { + expectView(/*jsx*/` +
+ +
+ `, /*js*/` + const $node0 = createElement("div") + const $node1 = createComponent(Comp) + insert($node0, $node1) + `); + }); +}); diff --git a/packages/transpiler/jsx-view-generator/src/test/mock.ts b/packages/transpiler/jsx-view-generator/src/test/mock.ts new file mode 100644 index 0000000000000000000000000000000000000000..ff8629e2a81f78f4d6757a2113aebe0bd102dbca --- /dev/null +++ b/packages/transpiler/jsx-view-generator/src/test/mock.ts @@ -0,0 +1,64 @@ +import { ViewGeneratorConfig } from '../types'; +import babel, { parseSync, types as t } from '@babel/core'; +import { attributeMap, htmlTags, importMap } from './const'; +import { AllowedJSXNode, ViewParserConfig, parseView } from '@inula/jsx-view-parser'; +import babelJSX from '@babel/plugin-syntax-jsx'; +import { generateView as gV } from '..'; +import { expect } from 'vitest'; +import generate from '@babel/generator'; + +export const parserConfig: ViewParserConfig = { + babelApi: babel, + htmlTags, +}; + +export function parseCode(code: string) { + return ( + parseSync(code, {plugins: [babelJSX]})!.program + .body[0] as t.ExpressionStatement + ).expression as AllowedJSXNode; +} + +export const generatorConfig: ViewGeneratorConfig = { + babelApi: babel, + importMap, + attributeMap, +}; + +export function generateView(code: string) { + const viewUnits = parseView(parseCode(code), parserConfig); + return gV(viewUnits, generatorConfig); +} + +function formatCode(code: string) { + return generate( + parseSync(code, {plugins: [babelJSX]})! + )!.code; +} + +export function expectView(code: string, expected: string, expectedTemplates?: string[]) { + const [templates, viewAst] = generateView(code); + const statements = (((viewAst.expression as t.CallExpression) + .callee as t.ArrowFunctionExpression) + .body as t.BlockStatement) + .body.slice(0, -1); + const viewCode = generate( + t.file(t.program(statements)), + )!.code!; + + const expectedCode = formatCode(expected); + + expect(viewCode).toBe(expectedCode); + if (expectedTemplates) { + const templateCode = templates.map(template => generate(template).code); + expectedTemplates = expectedTemplates.map(formatCode); + expect(templateCode).toEqual(expectedTemplates.map(formatCode)); + + } +} + +export function js(strings: TemplateStringsArray, ...values: any[]) { + return strings.reduce((acc, cur, i) => { + return acc + cur + (values[i] || ''); + }, ''); +} \ No newline at end of file diff --git a/packages/transpiler/jsx-view-generator/src/test/template.test.ts b/packages/transpiler/jsx-view-generator/src/test/template.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..9378fea878df2401eb11face9ce8587dd267586b --- /dev/null +++ b/packages/transpiler/jsx-view-generator/src/test/template.test.ts @@ -0,0 +1,302 @@ +import { describe, it } from 'vitest'; +import { expectView } from './mock'; + + +describe('Template', () => { + it('should generate a Template', () => { + expectView(/*jsx*/` +
+

ok

+

fine

+
+ `, /*js*/` + const $node0 = $template0.cloneNode(true) + `, [ + /*js*/` + const $template0 = (() => { + const $node0 = createElement("div") + const $node1 = createElement("p") + $node1.textContent = "ok" + insert($node0, $node1) + const $node2 = createElement("h1") + $node2.textContent = "fine" + insert($node0, $node2) + return $node0 + })() + ` + ]); + }); + + it('should generate a Template with props', () => { + expectView(/*jsx*/` +
+

ok

+

fine

+
+ `, /*js*/` + const $node0 = $template0.cloneNode(true) + `, [ + /*js*/` + const $template0 = (() => { + const $node0 = createElement("div") + $node0.id = "myDiv" + const $node1 = createElement("p") + $node1.className = "ok" + $node1.textContent = "ok" + insert($node0, $node1) + const $node2 = createElement("h1") + $node2.textContent = "fine" + insert($node0, $node2) + return $node0 + })() + ` + ]); + }); + + it('should generate a Template with dynamic props', () => { + expectView(/*jsx*/` +
+

+

+
+ `, /*js*/` + const $node0 = $template0.cloneNode(true) + const $node1 = $node0.firstChild + $node0.id = id + $node1.className = cls + `, [ + /*js*/` + const $template0 = (() => { + const $node0 = createElement("div") + const $node1 = createElement("p") + insert($node0, $node1) + const $node2 = createElement("h1") + insert($node0, $node2) + return $node0 + })() + ` + ]); + }); + + it('should generate a Template with reactive props', () => { + expectView(/*jsx*/` +
+

+

+
+ `, /*js*/` + const $node0 = $template0.cloneNode(true) + const $node1 = $node0.firstChild + watch(() => setProperty($node0, "id", id.get())) + watch(() => setProperty($node1, "className", cls.get())) + `, [ + /*js*/` + const $template0 = (() => { + const $node0 = createElement("div") + const $node1 = createElement("p") + insert($node0, $node1) + const $node2 = createElement("h1") + insert($node0, $node2) + return $node0 + })() + ` + ]); + }); + + it('should generate multiple Template', () => { + expectView(/*jsx*/` + <> +
+

ok

+

fine

+
+
+
second temp
+

ok

+

fine

+
+ + `, /*js*/` + const $node0 = $template0.cloneNode(true) + const $node1 = $template1.cloneNode(true) + `, [ + /*js*/` + const $template0 = (() => { + const $node0 = createElement("div") + const $node1 = createElement("p") + $node1.textContent = "ok" + insert($node0, $node1) + const $node2 = createElement("h1") + $node2.textContent = "fine" + insert($node0, $node2) + return $node0 + })() + `, + /*js*/` + const $template1 = (() => { + const $node0 = createElement("section") + const $node1 = createElement("div") + $node1.textContent = "second temp" + insert($node0, $node1) + const $node2 = createElement("p") + $node2.textContent = "ok" + insert($node0, $node2) + const $node3 = createElement("h1") + $node3.textContent = "fine" + insert($node0, $node3) + return $node0 + })() + ` + ]); + }); + + it('should generate a Template with Components at end', () => { + expectView(/*jsx*/` +
+

+ +

+ `, /*js*/` + const $node0 = $template0.cloneNode(true) + const $node1 = createComponent(Comp) + insert($node0, $node1) + `, [ + /*js*/` + const $template0 = (() => { + const $node0 = createElement("div") + const $node1 = createElement("p") + insert($node0, $node1) + return $node0 + })() + ` + ]); + }); + + it('should generate a Template with Components in the middle', () => { + expectView(/*jsx*/` +
+

+ +

+
+ `, /*js*/` + const $node0 = $template0.cloneNode(true) + const $node1 = $node0.firstChild.nextSibling; + const $node2 = createComponent(Comp); + insert($node0, $node2, $node1); + `, [ + /*js*/` + const $template0 = (() => { + const $node0 = createElement("div") + const $node1 = createElement("p") + insert($node0, $node1) + const $node2 = createElement("section") + insert($node0, $node2) + return $node0 + })() + ` + ]); + }); + + it('should generate a Template with a very very complex structure', () => { + expectView(/*jsx*/` + <> +
+

+ +

+
{console.log(prop.get())}}>second temp
+

ok

+

fine

+
+
+
+

+ +

+
second temp
+
+
+
+
+

stop here

+
+
+
+
+
+
+ + `, /*js*/` + const $node0 = $template0.cloneNode(true) + const $node1 = $node0.firstChild.nextSibling; + const $node2 = $node1.firstChild; + const $node3 = $node2.nextSibling; + const $node4 = $node3.nextSibling; + delegateEvent($node2, "click", () => { + console.log(prop.get()); + }); + $node3.className = cls; + watch(() => setProperty($node4, "id", id.get())); + const $node5 = createComponent(Comp, { + myProp: prop, + get reactiveProp() { + return prop.get(); + } + }); + insert($node0, $node5, $node1); + const $node6 = $template1.cloneNode(true); + const $node7 = $node6.firstChild.nextSibling; + const $node8 = $node7.firstChild.nextSibling; + watch(() => setProperty($node8, "id", id.get())); + const $node9 = createComponent(Comp); + insert($node6, $node9, $node7); + `, [ + /*js*/` + const $template0 = (() => { + const $node0 = createElement("div") + const $node1 = createElement("p") + insert($node0, $node1) + const $node2 = createElement("section") + const $node3 = createElement("div") + $node3.textContent = "second temp" + insert($node2, $node3) + const $node4 = createElement("p") + $node4.textContent = "ok" + insert($node2, $node4) + const $node5 = createElement("h1") + $node5.textContent = "fine" + insert($node2, $node5) + insert($node0, $node2) + return $node0 + })() + `, /*js*/` + const $template1 = (() => { + const $node0 = createElement("div") + const $node1 = createElement("p") + $node1.className = "pp" + insert($node0, $node1) + const $node2 = createElement("section"); + const $node3 = createElement("div"); + $node3.textContent = "second temp"; + insert($node2, $node3); + const $node4 = createElement("div"); + $node4.className = "very deep"; + const $node5 = createElement("div"); + $node5.className = "nono"; + const $node6 = createElement("div"); + const $node7 = createElement("div"); + const $node8 = createElement("p"); + $node8.textContent = "stop here"; + insert($node7, $node8); + insert($node6, $node7); + insert($node5, $node6); + insert($node4, $node5); + insert($node2, $node4); + insert($node0, $node2); + return $node0 + })() + ` + ]); + }); +}); diff --git a/packages/transpiler/jsx-view-generator/src/test/text.test.ts b/packages/transpiler/jsx-view-generator/src/test/text.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..ce76b934f86c05d2fd283bacc05b2b3904ef4507 --- /dev/null +++ b/packages/transpiler/jsx-view-generator/src/test/text.test.ts @@ -0,0 +1,46 @@ +import { describe, it } from 'vitest'; +import { expectView } from './mock'; + + +describe('Text', () => { + it('should generate a text node', () => { + expectView(/*jsx*/` + <>hello world + `, /*js*/` + const $node0 = createText("hello world") + `); + }); + + it('should generate a text node with a boolean expression', () => { + expectView(/*jsx*/` + <>{true} + `, /*js*/` + const $node0 = createText(true) + `); + }); + + it('should generate a text node with a number expression', () => { + expectView(/*jsx*/` + <>{1} + `, /*js*/` + const $node0 = createText(1) + `); + }); + + it('should generate a text node with a null expression', () => { + expectView(/*jsx*/` + <>{null} + `, /*js*/` + const $node0 = createText(null) + `); + }); + + it('should generate a text node with a string literal expression', () => { + expectView(/*jsx*/` + <>{"hello world"} + `, /*js*/` + const $node0 = createText("hello world") + `); + }); +}); + \ No newline at end of file diff --git a/packages/transpiler/jsx-view-generator/src/types.ts b/packages/transpiler/jsx-view-generator/src/types.ts new file mode 100644 index 0000000000000000000000000000000000000000..85cf6964e6e35883b5dc3fb42d17e6029ec9bc2c --- /dev/null +++ b/packages/transpiler/jsx-view-generator/src/types.ts @@ -0,0 +1,14 @@ +import type Babel from '@babel/core'; + +export interface ViewGeneratorConfig { + babelApi: typeof Babel; + importMap: Record; + + /** + * @brief Using AttributeMap to identify propertyfied attributes + * Reason for adding this: + * `el.prop = xxx` is faster than `el.setAttribute('prop', xxx)` + * @example { href: ["a", "area", "base", "link"], id: ["*"] } + */ + attributeMap?: Record +} \ No newline at end of file diff --git a/packages/transpiler/view-parser/tsconfig.json b/packages/transpiler/jsx-view-generator/tsconfig.json similarity index 100% rename from packages/transpiler/view-parser/tsconfig.json rename to packages/transpiler/jsx-view-generator/tsconfig.json diff --git a/packages/transpiler/jsx-view-parser/dist/index.cjs b/packages/transpiler/jsx-view-parser/dist/index.cjs new file mode 100644 index 0000000000000000000000000000000000000000..4f61c13ed7a6b560221edc09fb6e5a8a1c97eca8 --- /dev/null +++ b/packages/transpiler/jsx-view-parser/dist/index.cjs @@ -0,0 +1,2 @@ +"use strict";var c=Object.defineProperty;var f=Object.getOwnPropertyDescriptor;var g=Object.getOwnPropertyNames;var v=Object.prototype.hasOwnProperty;var u=(o,t)=>{for(var e in t)c(o,e,{get:t[e],enumerable:!0})},T=(o,t,e,r)=>{if(t&&typeof t=="object"||typeof t=="function")for(let i of g(t))!v.call(o,i)&&i!==e&&c(o,i,{get:()=>t[i],enumerable:!(r=f(t,i))||r.enumerable});return o};var d=o=>T(c({},"__esModule",{value:!0}),o);var y={};u(y,{parseView:()=>w});module.exports=d(y);var h=class{htmlNamespace="html";htmlTagNamespace="tag";compTagNamespace="comp";envTagName="env";ifTagName="if";elseIfTagName="else-if";elseTagName="else";customHTMLProps=["ref"];config;htmlTags;willParseTemplate;t;traverse;viewUnits=[];constructor(t){this.config=t,this.t=t.babelApi.types,this.traverse=t.babelApi.traverse,this.htmlTags=t.htmlTags,this.willParseTemplate=t.parseTemplate??!0}parse(t){return this.t.isJSXText(t)?this.parseText(t):this.t.isJSXExpressionContainer(t)?this.parseExpression(t.expression):this.t.isJSXElement(t)?this.parseElement(t):this.t.isJSXFragment(t)&&t.children.forEach(e=>{this.parse(e)}),this.viewUnits}parseText(t){let e=t.value.trim();e&&this.viewUnits.push({type:"text",content:this.t.stringLiteral(e)})}parseExpression(t){if(!this.t.isJSXEmptyExpression(t)){if(this.t.isLiteral(t)&&!this.t.isTemplateLiteral(t)){this.viewUnits.push({type:"text",content:t});return}this.viewUnits.push({type:"exp",content:this.parseProp(t)})}}parseElement(t){let e,r,i=t.openingElement.name;if(this.t.isJSXIdentifier(i)){let n=i.name;if([this.ifTagName,this.elseIfTagName,this.elseTagName].includes(n))return this.parseIf(t);if(n===this.envTagName)return this.parseEnv(t);this.htmlTags.includes(n)?(e="html",r=this.t.stringLiteral(n)):(e="comp",r=this.t.identifier(n))}else if(this.t.isJSXMemberExpression(i)){e="comp";let n=m=>this.t.isJSXMemberExpression(m.object)?this.t.memberExpression(n(m.object),this.t.identifier(m.property.name)):this.t.memberExpression(this.t.identifier(m.object.name),this.t.identifier(m.property.name));r=n(i)}else{let n=i.namespace.name;switch(n){case this.compTagNamespace:e="comp",r=this.t.identifier(i.name.name);break;case this.htmlNamespace:e="html",r=this.t.stringLiteral(i.name.name);break;case this.htmlTagNamespace:e="html",r=this.t.identifier(i.name.name);break;default:e="html",r=this.t.stringLiteral(`${n}:${i.name.name}`);break}}let s=t.openingElement.attributes,p=Object.fromEntries(s.map(n=>this.parseJSXProp(n))),l=t.children.map(n=>this.parseView(n)).flat(),a={type:e,tag:r,props:p,children:l};if(a.type==="html"&&l.length===1&&l[0].type==="text"){let n=l[0];a={...a,children:[],props:{...a.props,textContent:{value:n.content,viewPropMap:{}}}}}a.type==="html"&&(a=this.transformTemplate(a)),this.viewUnits.push(a)}parseEnv(t){let e=t.openingElement.attributes,r=Object.fromEntries(e.map(s=>this.parseJSXProp(s))),i=t.children.map(s=>this.parseView(s)).flat();this.viewUnits.push({type:"env",props:r,children:i})}parseIf(t){let e=t.openingElement.name.name;if(e===this.elseTagName){let s=this.viewUnits[this.viewUnits.length-1];if(!s||s.type!=="if")throw new Error(`Missing if for ${e}`);s.branches.push({condition:this.t.booleanLiteral(!0),children:t.children.map(p=>this.parseView(p)).flat()});return}let r=t.openingElement.attributes.filter(s=>this.t.isJSXAttribute(s)&&s.name.name==="cond")[0];if(!r)throw new Error(`Missing condition for ${e}`);if(!this.t.isJSXAttribute(r))throw new Error(`JSXSpreadAttribute is not supported for ${e} condition`);if(!this.t.isJSXExpressionContainer(r.value)||!this.t.isExpression(r.value.expression))throw new Error(`Invalid condition for ${e}`);if(e===this.ifTagName){this.viewUnits.push({type:"if",branches:[{condition:r.value.expression,children:t.children.map(s=>this.parseView(s)).flat()}]});return}let i=this.viewUnits[this.viewUnits.length-1];if(!i||i.type!=="if")throw new Error(`Missing if for ${e}`);i.branches.push({condition:r.value.expression,children:t.children.map(s=>this.parseView(s)).flat()})}parseJSXProp(t){if(this.t.isJSXAttribute(t)){let e,r;this.t.isJSXNamespacedName(t.name)?(e=t.name.name.name,r=t.name.namespace.name):e=t.name.name;let i=this.t.isJSXExpressionContainer(t.value)?t.value.expression:t.value;return this.t.isJSXEmptyExpression(i)&&(i=void 0),[e,this.parseProp(i,r)]}return["*spread*",this.parseProp(t.argument)]}parseProp(t,e){if(!t)return{value:this.t.booleanLiteral(!0),viewPropMap:{}};let r={},i=s=>{let p=this.uid(),l=s.node;r[p]=this.parseView(l);let a=this.t.stringLiteral(p);l===t&&(t=a),s.replaceWith(a),s.skip()};return this.traverse(this.wrapWithFile(t),{JSXElement:i,JSXFragment:i}),{value:t,viewPropMap:r,specifier:e}}transformTemplate(t){return!this.willParseTemplate||!this.isHTMLTemplate(t)?t:(t=t,{type:"template",template:this.generateTemplate(t),mutableUnits:this.generateMutableUnits(t),props:this.parseTemplateProps(t)})}generateTemplate(t){let e=Object.fromEntries(this.filterTemplateProps(Object.entries(t.props??[]).filter(([,i])=>this.isStaticProp(i)&&!(this.t.isBooleanLiteral(i.value)&&!i.value.value)))),r=[];return t.children&&(r=t.children.map(i=>{if(i.type==="text")return i;if(i.type==="html"&&this.t.isStringLiteral(i.tag))return this.generateTemplate(i)}).filter(Boolean)),{type:"html",tag:t.tag,props:e,children:r}}generateMutableUnits(t){let e=[],r=(i,s=[])=>{let p=i.children?.filter(a=>a.type==="html"&&this.t.isStringLiteral(a.tag)||a.type==="text").length,l=-1;i.children?.forEach(a=>{if(!(a.type==="html"&&this.t.isStringLiteral(a.tag))&&a.type!=="text"){let n=l+1>=p?-1:l+1;e.push({path:[...s,n],...this.transformTemplate(a)})}else l++}),i.children?.filter(a=>a.type==="html"&&this.t.isStringLiteral(a.tag)).forEach((a,n)=>{r(a,[...s,n])})};return r(t),e}parseTemplateProps(t){let e=[],r=(i,s)=>{i.props&&Object.entries(i.props).filter(([,p])=>!this.isStaticProp(p)).forEach(([p,l])=>{e.push({tag:i.tag,name:i.tag.value,key:p,path:s,value:l.value})}),i.children?.filter(p=>p.type==="html"&&this.t.isStringLiteral(p.tag)).forEach((p,l)=>{r(p,[...s,l])})};return r(t,[]),e}isHTMLTemplate(t){return t.type==="html"&&this.t.isStringLiteral(t.tag)&&!!t.children?.some(e=>e.type==="html"&&this.t.isStringLiteral(e.tag))}isStaticProp(t){return this.t.isStringLiteral(t.value)||this.t.isNumericLiteral(t.value)||this.t.isBooleanLiteral(t.value)||this.t.isNullLiteral(t.value)}filterTemplateProps(t){return t.filter(([e])=>!e.startsWith("on")).filter(([e])=>!this.customHTMLProps.includes(e))}parseView(t){return new h({...this.config,parseTemplate:!1}).parse(t)}wrapWithFile(t){return this.t.file(this.t.program([this.t.expressionStatement(t)]))}uid(){return Math.random().toString(36).slice(2)}};function w(o,t){return new h(t).parse(o)}0&&(module.exports={parseView}); +//# sourceMappingURL=index.cjs.map \ No newline at end of file diff --git a/packages/transpiler/jsx-view-parser/dist/index.cjs.map b/packages/transpiler/jsx-view-parser/dist/index.cjs.map new file mode 100644 index 0000000000000000000000000000000000000000..66dbe19f29ee9cfaa0c695aaf566f75f4736904e --- /dev/null +++ b/packages/transpiler/jsx-view-parser/dist/index.cjs.map @@ -0,0 +1 @@ +{"version":3,"sources":["../src/index.ts","../src/parser.ts"],"sourcesContent":["import { ViewParser } from './parser';\nimport type { ViewUnit, ViewParserConfig, AllowedJSXNode } from './types';\n\n/**\n * @brief Generate view units from a babel ast\n * @param statement\n * @param config\n * @returns ViewUnit[]\n */\nexport function parseView(\n node: AllowedJSXNode,\n config: ViewParserConfig\n): ViewUnit[] {\n return new ViewParser(config).parse(node);\n}\n\nexport type * from './types';\n","import type { NodePath, types as t, traverse as tr } from '@babel/core';\nimport type {\n UnitProp,\n ViewUnit,\n ViewParserConfig,\n AllowedJSXNode,\n HTMLUnit,\n TextUnit,\n MutableUnit,\n TemplateProp,\n} from './types';\n\nexport class ViewParser {\n // ---- Namespace and tag name\n private readonly htmlNamespace: string = 'html'\n private readonly htmlTagNamespace: string = 'tag'\n private readonly compTagNamespace: string = 'comp'\n private readonly envTagName: string = 'env'\n private readonly ifTagName: string = 'if'\n private readonly elseIfTagName: string = 'else-if'\n private readonly elseTagName: string = 'else'\n private readonly customHTMLProps: string[] = ['ref']\n\n private readonly config: ViewParserConfig\n private readonly htmlTags: string[]\n private readonly willParseTemplate: boolean\n\n private readonly t: typeof t\n private readonly traverse: typeof tr\n\n private readonly viewUnits: ViewUnit[] = []\n\n /**\n * @brief Constructor\n * @param config\n */\n constructor(config: ViewParserConfig) {\n this.config = config;\n this.t = config.babelApi.types;\n this.traverse = config.babelApi.traverse;\n this.htmlTags = config.htmlTags;\n this.willParseTemplate = config.parseTemplate ?? true;\n }\n\n /**\n * @brief Parse the node into view units\n * @param node\n * @returns ViewUnit[]\n */\n parse(node: AllowedJSXNode): ViewUnit[] {\n if (this.t.isJSXText(node)) this.parseText(node);\n else if (this.t.isJSXExpressionContainer(node)) this.parseExpression(node.expression);\n else if (this.t.isJSXElement(node)) this.parseElement(node);\n else if (this.t.isJSXFragment(node)) {\n node.children.forEach(child => {\n this.parse(child);\n });\n }\n \n return this.viewUnits;\n }\n\n /**\n * @brief Parse JSXText\n * @param node\n */\n private parseText(node: t.JSXText): void {\n const text = node.value.trim();\n if (!text) return;\n this.viewUnits.push({\n type: 'text',\n content: this.t.stringLiteral(text),\n });\n }\n\n /**\n * @brief Parse JSXExpressionContainer\n * @param node\n */\n private parseExpression(node: t.Expression | t.JSXEmptyExpression): void {\n if (this.t.isJSXEmptyExpression(node)) return;\n if (this.t.isLiteral(node) && !this.t.isTemplateLiteral(node)) {\n // ---- Treat literal as text except template literal\n // Cuz template literal may have viewProp inside like:\n // <>{i18n`hello ${}`}\n this.viewUnits.push({\n type: 'text',\n content: node\n });\n return; \n }\n this.viewUnits.push({\n type: 'exp',\n content: this.parseProp(node),\n });\n }\n\n /**\n * @brief Parse JSXElement\n * @param node \n */\n private parseElement(node: t.JSXElement): void {\n let type: 'html' | 'comp';\n let tag: t.Expression;\n\n // ---- Parse tag and type\n const openingName = node.openingElement.name;\n if (this.t.isJSXIdentifier(openingName)) {\n // ---- Opening name is a JSXIdentifier, e.g.,
\n const name = openingName.name;\n // ---- Specially parse if and env\n if ([this.ifTagName, this.elseIfTagName, this.elseTagName].includes(name)) \n return this.parseIf(node);\n if (name === this.envTagName) return this.parseEnv(node);\n else if (this.htmlTags.includes(name)) {\n type = 'html';\n tag = this.t.stringLiteral(name);\n } else {\n // ---- If the name is not in htmlTags, treat it as a comp\n type = 'comp';\n tag = this.t.identifier(name);\n }\n } else if (this.t.isJSXMemberExpression(openingName)) {\n // ---- Opening name is a JSXMemberExpression, e.g., \n // Treat it as a comp and set the tag as the opening name\n type = 'comp';\n // ---- Turn JSXMemberExpression into MemberExpression recursively\n const toMemberExpression = (node: t.JSXMemberExpression): t.MemberExpression => {\n if (this.t.isJSXMemberExpression(node.object)) {\n return this.t.memberExpression(\n toMemberExpression(node.object),\n this.t.identifier(node.property.name)\n );\n }\n return this.t.memberExpression(\n this.t.identifier(node.object.name),\n this.t.identifier(node.property.name)\n );\n };\n tag = toMemberExpression(openingName);\n } else {\n // ---- isJSXNamespacedName\n const namespace = openingName.namespace.name;\n switch (namespace) {\n case this.compTagNamespace:\n // ---- If the namespace is the same as the compTagNamespace, treat it as a comp\n // and set the tag as an identifier\n // e.g., => [\"comp\", div]\n // this means you've declared a component named \"div\" and force it to be a comp instead an html\n type = 'comp';\n tag = this.t.identifier(openingName.name.name);\n break;\n case this.htmlNamespace:\n // ---- If the namespace is the same as the htmlTagNamespace, treat it as an html\n // and set the tag as a string literal\n // e.g., => [\"html\", \"MyWebComponent\"]\n // the tag will be treated as a string, i.e., \n type = 'html';\n tag = this.t.stringLiteral(openingName.name.name);\n break;\n case this.htmlTagNamespace:\n // ---- If the namespace is the same as the htmlTagNamespace, treat it as an html\n // and set the tag as an identifier\n // e.g., => [\"html\", variable]\n // this unit will be htmlUnit and the html string tag is stored in \"variable\"\n type = 'html';\n tag = this.t.identifier(openingName.name.name);\n break;\n default:\n // ---- Otherwise, treat it as an html tag and make the tag as the namespace:name\n type = 'html';\n tag = this.t.stringLiteral(`${namespace}:${openingName.name.name}`);\n break;\n }\n }\n\n // ---- Parse the props\n const props = node.openingElement.attributes;\n const propMap: Record = Object.fromEntries(\n props.map(prop => this.parseJSXProp(prop))\n );\n\n // ---- Parse the children\n const childUnits = node.children.map(child => this.parseView(child)).flat();\n\n let unit: ViewUnit = { type, tag, props: propMap, children: childUnits };\n \n if (unit.type === 'html' && childUnits.length === 1 && childUnits[0].type === 'text') {\n // ---- If the html unit only has one text child, merge the text into the html unit\n const text = childUnits[0] as TextUnit;\n unit = {\n ...unit,\n children: [],\n props: {\n ...unit.props,\n textContent: {\n value: text.content,\n viewPropMap: {},\n },\n },\n };\n }\n\n if (unit.type === 'html') unit = this.transformTemplate(unit);\n\n this.viewUnits.push(unit);\n }\n\n /**\n * @brief Parse EnvUnit\n * @param node\n */\n private parseEnv(node: t.JSXElement): void {\n const props = node.openingElement.attributes;\n const propMap: Record = Object.fromEntries(\n props.map(prop => this.parseJSXProp(prop))\n );\n const children = node.children.map(child => this.parseView(child)).flat();\n this.viewUnits.push({\n type: 'env',\n props: propMap,\n children,\n });\n }\n\n private parseIf(node: t.JSXElement): void {\n const name = (node.openingElement.name as t.JSXIdentifier).name;\n // ---- else\n if (name === this.elseTagName) {\n const lastUnit = this.viewUnits[this.viewUnits.length - 1];\n if (!lastUnit || lastUnit.type !== 'if') throw new Error(`Missing if for ${name}`);\n lastUnit.branches.push({\n condition: this.t.booleanLiteral(true),\n children: node.children.map(child => this.parseView(child)).flat(),\n });\n return;\n }\n\n const condition = node.openingElement.attributes.filter(attr => \n this.t.isJSXAttribute(attr) && attr.name.name === 'cond'\n )[0];\n if (!condition) throw new Error(`Missing condition for ${name}`);\n if (!this.t.isJSXAttribute(condition)) throw new Error(`JSXSpreadAttribute is not supported for ${name} condition`);\n if (!this.t.isJSXExpressionContainer(condition.value) || !this.t.isExpression(condition.value.expression))\n throw new Error(`Invalid condition for ${name}`);\n \n // ---- if\n if (name === this.ifTagName) {\n this.viewUnits.push({\n type: 'if',\n branches: [{\n condition: condition.value.expression,\n children: node.children.map(child => this.parseView(child)).flat(),\n }],\n });\n return;\n } \n\n // ---- else-if\n const lastUnit = this.viewUnits[this.viewUnits.length - 1];\n if (!lastUnit || lastUnit.type !== 'if') \n throw new Error(`Missing if for ${name}`);\n\n lastUnit.branches.push({\n condition: condition.value.expression,\n children: node.children.map(child => this.parseView(child)).flat(),\n });\n }\n\n /**\n * @brief Parse JSXAttribute or JSXSpreadAttribute into UnitProp,\n * considering both namespace and expression\n * @param prop \n * @returns [propName, propValue]\n */\n private parseJSXProp(prop: t.JSXAttribute | t.JSXSpreadAttribute): [string, UnitProp] {\n if (this.t.isJSXAttribute(prop)) {\n let propName: string, specifier: string | undefined;\n if (this.t.isJSXNamespacedName(prop.name)) {\n // ---- If the prop name is a JSXNamespacedName, e.g., bind:value\n // give it a special tag\n propName = prop.name.name.name;\n specifier = prop.name.namespace.name;\n } else {\n propName = prop.name.name;\n }\n let value = this.t.isJSXExpressionContainer(prop.value) ? prop.value.expression : prop.value;\n if (this.t.isJSXEmptyExpression(value)) value = undefined;\n return [propName, this.parseProp(value, specifier)];\n }\n // ---- Use *spread* as the propName to avoid conflict with other props\n return ['*spread*', this.parseProp(prop.argument)];\n }\n\n /**\n * @brief Parse the prop node into UnitProp\n * @param propNode\n * @param specifier\n * @returns UnitProp\n */\n private parseProp(propNode: t.Expression | undefined | null, specifier?: string): UnitProp {\n // ---- If there is no propNode, set the default prop as true\n if (!propNode) {\n return {\n value: this.t.booleanLiteral(true),\n viewPropMap: {},\n };\n }\n\n // ---- Collect sub jsx nodes as Prop\n const viewPropMap: Record = {};\n const parseViewProp = (innerPath: NodePath): void => {\n const id = this.uid();\n const node = innerPath.node;\n viewPropMap[id] = this.parseView(node);\n const newNode = this.t.stringLiteral(id);\n if (node === propNode) {\n // ---- If the node is the propNode, replace it with the new node\n propNode = newNode;\n }\n // ---- Replace the node and skip the inner path\n innerPath.replaceWith(newNode);\n innerPath.skip();\n };\n\n // ---- Apply the parseViewProp to JSXElement and JSXFragment\n this.traverse(this.wrapWithFile(propNode), {\n JSXElement: parseViewProp,\n JSXFragment: parseViewProp,\n });\n\n return {\n value: propNode,\n viewPropMap,\n specifier,\n };\n }\n\n transformTemplate(unit: ViewUnit): ViewUnit {\n if (!this.willParseTemplate) return unit;\n if (!this.isHTMLTemplate(unit)) return unit;\n unit = unit as HTMLUnit;\n return {\n type: 'template',\n template: this.generateTemplate(unit),\n mutableUnits: this.generateMutableUnits(unit),\n props: this.parseTemplateProps(unit),\n };\n }\n\n /**\n * @brief Generate the entire HTMLUnit\n * @param unit\n * @returns HTMLUnit\n */\n private generateTemplate(unit: HTMLUnit): HTMLUnit {\n const staticProps = Object.fromEntries(this.filterTemplateProps(\n // ---- Get all the static props\n Object.entries(unit.props ?? []).filter(\n ([, prop]) =>\n this.isStaticProp(prop) &&\n // ---- Filter out props with false values\n !(this.t.isBooleanLiteral(prop.value) && !prop.value.value)\n )\n ));\n\n let children: (HTMLUnit | TextUnit)[] = [];\n if (unit.children) {\n children = unit.children\n .map(unit => {\n if (unit.type === 'text') return unit;\n if (unit.type === 'html' && this.t.isStringLiteral(unit.tag)) {\n return this.generateTemplate(unit);\n }\n })\n .filter(Boolean) as (HTMLUnit | TextUnit)[];\n }\n return {\n type: 'html',\n tag: unit.tag,\n props: staticProps,\n children,\n };\n }\n\n /**\n * @brief Collect all the mutable nodes in a static HTMLUnit\n * We use this function to collect mutable nodes' path and props,\n * so that in the generator, we know which position to insert the mutable nodes\n * @param htmlUnit\n * @returns mutable particles\n */\n private generateMutableUnits(htmlUnit: HTMLUnit): MutableUnit[] {\n const mutableUnits: MutableUnit[] = [];\n\n const generateMutableUnit = (unit: HTMLUnit, path: number[] = []) => {\n const maxHtmlIdx = unit.children?.filter(\n child => (child.type === 'html' && this.t.isStringLiteral(child.tag)) ||\n child.type === 'text'\n ).length;\n let htmlIdx = -1;\n // ---- Generate mutable unit for current HTMLUnit\n unit.children?.forEach((child) => {\n if (\n !(child.type === 'html' && this.t.isStringLiteral(child.tag)) &&\n !(child.type === 'text')\n ) {\n const idx = htmlIdx + 1 >= maxHtmlIdx ? -1 : htmlIdx + 1;\n mutableUnits.push({\n path: [...path, idx],\n ...this.transformTemplate(child),\n });\n } else {\n htmlIdx++;\n }\n });\n // ---- Recursively generate mutable units for static HTMLUnit children\n unit.children\n ?.filter(\n child => child.type === 'html' && this.t.isStringLiteral(child.tag)\n )\n .forEach((child, idx) => {\n generateMutableUnit(child as HTMLUnit, [...path, idx]);\n });\n };\n generateMutableUnit(htmlUnit);\n\n return mutableUnits;\n }\n\n /**\n * @brief Collect all the props in a static HTMLUnit or its nested HTMLUnit children\n * Just like the mutable nodes, props are also equipped with path,\n * so that we know which HTML ChildNode to insert the props\n * @param htmlUnit\n * @returns props\n */\n private parseTemplateProps(htmlUnit: HTMLUnit): TemplateProp[] {\n const templateProps: TemplateProp[] = [];\n const generateVariableProp = (unit: HTMLUnit, path: number[]) => {\n // ---- Generate all non-static(string/number/boolean) props for current HTMLUnit\n // to be inserted further in the generator\n unit.props && Object.entries(unit.props)\n .filter(([, prop]) => !this.isStaticProp(prop))\n .forEach(([key, prop]) => {\n templateProps.push({\n tag: unit.tag,\n name: (unit.tag as t.StringLiteral).value,\n key,\n path,\n value: prop.value,\n });\n });\n // ---- Recursively generate props for static HTMLUnit children\n unit.children\n ?.filter(child => child.type === 'html' && this.t.isStringLiteral(child.tag))\n .forEach((child, idx) => {\n generateVariableProp(child as HTMLUnit, [...path, idx]);\n });\n };\n generateVariableProp(htmlUnit, []);\n\n return templateProps;\n }\n\n /**\n * @brief Check if a ViewUnit is a static HTMLUnit that can be parsed into a template\n * Must satisfy:\n * 1. type is html\n * 2. tag is a string literal, i.e., non-dynamic tag\n * 3. has at least one child that is a static HTMLUnit,\n * or else just call a createElement function, no need for template clone\n * @param viewUnit\n * @returns is a static HTMLUnit\n */\n private isHTMLTemplate(viewUnit: ViewUnit): boolean {\n return (\n viewUnit.type === 'html' &&\n this.t.isStringLiteral(viewUnit.tag) &&\n !!viewUnit.children?.some(\n child => child.type === 'html' && this.t.isStringLiteral(child.tag)\n )\n );\n }\n\n private isStaticProp(prop: UnitProp): boolean {\n return (\n this.t.isStringLiteral(prop.value) ||\n this.t.isNumericLiteral(prop.value) ||\n this.t.isBooleanLiteral(prop.value) ||\n this.t.isNullLiteral(prop.value)\n );\n }\n\n /**\n * @brief Filter out some props that are not needed in the template,\n * these are all special props to be parsed differently in the generator\n * @param props\n * @returns filtered props\n */\n private filterTemplateProps(\n props: Array<[string, T]>\n ): Array<[string, T]> {\n return (\n props\n // ---- Filter out event listeners\n .filter(([key]) => !key.startsWith('on'))\n // ---- Filter out specific props\n .filter(([key]) => !this.customHTMLProps.includes(key))\n );\n }\n\n /**\n * @brief Parse the view by duplicating current parser's classRootPath, statements and htmlTags\n * @param statements\n * @returns ViewUnit[]\n */\n private parseView(node: AllowedJSXNode): ViewUnit[] {\n return new ViewParser({...this.config, parseTemplate:false}).parse(node);\n }\n \n\n /**\n * @brief Wrap the value in a file\n * @param node\n * @returns wrapped value\n */\n private wrapWithFile(node: t.Expression): t.File {\n return this.t.file(this.t.program([this.t.expressionStatement(node)]));\n }\n\n /**\n * @brief Generate a unique id\n * @returns a unique id\n */\n private uid(): string {\n return Math.random().toString(36).slice(2);\n }\n}\n"],"mappings":"yaAAA,IAAAA,EAAA,GAAAC,EAAAD,EAAA,eAAAE,IAAA,eAAAC,EAAAH,GCYO,IAAMI,EAAN,KAAiB,CAEL,cAAwB,OACxB,iBAA2B,MAC3B,iBAA2B,OAC3B,WAAqB,MACrB,UAAoB,KACpB,cAAwB,UACxB,YAAsB,OACtB,gBAA4B,CAAC,KAAK,EAElC,OACA,SACA,kBAEA,EACA,SAEA,UAAwB,CAAC,EAM1C,YAAYC,EAA0B,CACpC,KAAK,OAASA,EACd,KAAK,EAAIA,EAAO,SAAS,MACzB,KAAK,SAAWA,EAAO,SAAS,SAChC,KAAK,SAAWA,EAAO,SACvB,KAAK,kBAAoBA,EAAO,eAAiB,EACnD,CAOA,MAAMC,EAAkC,CACtC,OAAI,KAAK,EAAE,UAAUA,CAAI,EAAG,KAAK,UAAUA,CAAI,EACtC,KAAK,EAAE,yBAAyBA,CAAI,EAAG,KAAK,gBAAgBA,EAAK,UAAU,EAC3E,KAAK,EAAE,aAAaA,CAAI,EAAG,KAAK,aAAaA,CAAI,EACjD,KAAK,EAAE,cAAcA,CAAI,GAChCA,EAAK,SAAS,QAAQC,GAAS,CAC7B,KAAK,MAAMA,CAAK,CAClB,CAAC,EAGI,KAAK,SACd,CAMQ,UAAUD,EAAuB,CACvC,IAAME,EAAOF,EAAK,MAAM,KAAK,EACxBE,GACL,KAAK,UAAU,KAAK,CAClB,KAAM,OACN,QAAS,KAAK,EAAE,cAAcA,CAAI,CACpC,CAAC,CACH,CAMQ,gBAAgBF,EAAiD,CACvE,GAAI,MAAK,EAAE,qBAAqBA,CAAI,EACpC,IAAI,KAAK,EAAE,UAAUA,CAAI,GAAK,CAAC,KAAK,EAAE,kBAAkBA,CAAI,EAAG,CAI7D,KAAK,UAAU,KAAK,CAClB,KAAM,OACN,QAASA,CACX,CAAC,EACD,OAEF,KAAK,UAAU,KAAK,CAClB,KAAM,MACN,QAAS,KAAK,UAAUA,CAAI,CAC9B,CAAC,EACH,CAMQ,aAAaA,EAA0B,CAC7C,IAAIG,EACAC,EAGEC,EAAcL,EAAK,eAAe,KACxC,GAAI,KAAK,EAAE,gBAAgBK,CAAW,EAAG,CAEvC,IAAMC,EAAOD,EAAY,KAEzB,GAAI,CAAC,KAAK,UAAW,KAAK,cAAe,KAAK,WAAW,EAAE,SAASC,CAAI,EACtE,OAAO,KAAK,QAAQN,CAAI,EAC1B,GAAIM,IAAS,KAAK,WAAY,OAAO,KAAK,SAASN,CAAI,EAC9C,KAAK,SAAS,SAASM,CAAI,GAClCH,EAAO,OACPC,EAAM,KAAK,EAAE,cAAcE,CAAI,IAG/BH,EAAO,OACPC,EAAM,KAAK,EAAE,WAAWE,CAAI,WAErB,KAAK,EAAE,sBAAsBD,CAAW,EAAG,CAGpDF,EAAO,OAEP,IAAMI,EAAsBP,GACtB,KAAK,EAAE,sBAAsBA,EAAK,MAAM,EACnC,KAAK,EAAE,iBACZO,EAAmBP,EAAK,MAAM,EAC9B,KAAK,EAAE,WAAWA,EAAK,SAAS,IAAI,CACtC,EAEK,KAAK,EAAE,iBACZ,KAAK,EAAE,WAAWA,EAAK,OAAO,IAAI,EAClC,KAAK,EAAE,WAAWA,EAAK,SAAS,IAAI,CACtC,EAEFI,EAAMG,EAAmBF,CAAW,MAC/B,CAEL,IAAMG,EAAYH,EAAY,UAAU,KACxC,OAAQG,EAAW,CACjB,KAAK,KAAK,iBAKRL,EAAO,OACPC,EAAM,KAAK,EAAE,WAAWC,EAAY,KAAK,IAAI,EAC7C,MACF,KAAK,KAAK,cAKRF,EAAO,OACPC,EAAM,KAAK,EAAE,cAAcC,EAAY,KAAK,IAAI,EAChD,MACF,KAAK,KAAK,iBAKRF,EAAO,OACPC,EAAM,KAAK,EAAE,WAAWC,EAAY,KAAK,IAAI,EAC7C,MACF,QAEEF,EAAO,OACPC,EAAM,KAAK,EAAE,cAAc,GAAGI,KAAaH,EAAY,KAAK,MAAM,EAClE,KACJ,EAIF,IAAMI,EAAQT,EAAK,eAAe,WAC5BU,EAAoC,OAAO,YAC/CD,EAAM,IAAIE,GAAQ,KAAK,aAAaA,CAAI,CAAC,CAC3C,EAGMC,EAAaZ,EAAK,SAAS,IAAIC,GAAS,KAAK,UAAUA,CAAK,CAAC,EAAE,KAAK,EAEtEY,EAAiB,CAAE,KAAAV,EAAM,IAAAC,EAAK,MAAOM,EAAS,SAAUE,CAAW,EAEvE,GAAIC,EAAK,OAAS,QAAUD,EAAW,SAAW,GAAKA,EAAW,CAAC,EAAE,OAAS,OAAQ,CAEpF,IAAMV,EAAOU,EAAW,CAAC,EACzBC,EAAO,CACL,GAAGA,EACH,SAAU,CAAC,EACX,MAAO,CACL,GAAGA,EAAK,MACR,YAAa,CACX,MAAOX,EAAK,QACZ,YAAa,CAAC,CAChB,CACF,CACF,EAGEW,EAAK,OAAS,SAAQA,EAAO,KAAK,kBAAkBA,CAAI,GAE5D,KAAK,UAAU,KAAKA,CAAI,CAC1B,CAMQ,SAASb,EAA0B,CACzC,IAAMS,EAAQT,EAAK,eAAe,WAC5BU,EAAoC,OAAO,YAC/CD,EAAM,IAAIE,GAAQ,KAAK,aAAaA,CAAI,CAAC,CAC3C,EACMG,EAAWd,EAAK,SAAS,IAAIC,GAAS,KAAK,UAAUA,CAAK,CAAC,EAAE,KAAK,EACxE,KAAK,UAAU,KAAK,CAClB,KAAM,MACN,MAAOS,EACP,SAAAI,CACF,CAAC,CACH,CAEQ,QAAQd,EAA0B,CACxC,IAAMM,EAAQN,EAAK,eAAe,KAAyB,KAE3D,GAAIM,IAAS,KAAK,YAAa,CAC7B,IAAMS,EAAW,KAAK,UAAU,KAAK,UAAU,OAAS,CAAC,EACzD,GAAI,CAACA,GAAYA,EAAS,OAAS,KAAM,MAAM,IAAI,MAAM,kBAAkBT,GAAM,EACjFS,EAAS,SAAS,KAAK,CACrB,UAAW,KAAK,EAAE,eAAe,EAAI,EACrC,SAAUf,EAAK,SAAS,IAAIC,GAAS,KAAK,UAAUA,CAAK,CAAC,EAAE,KAAK,CACnE,CAAC,EACD,OAGF,IAAMe,EAAYhB,EAAK,eAAe,WAAW,OAAOiB,GACtD,KAAK,EAAE,eAAeA,CAAI,GAAKA,EAAK,KAAK,OAAS,MACpD,EAAE,CAAC,EACH,GAAI,CAACD,EAAW,MAAM,IAAI,MAAM,yBAAyBV,GAAM,EAC/D,GAAI,CAAC,KAAK,EAAE,eAAeU,CAAS,EAAG,MAAM,IAAI,MAAM,2CAA2CV,aAAgB,EAClH,GAAI,CAAC,KAAK,EAAE,yBAAyBU,EAAU,KAAK,GAAK,CAAC,KAAK,EAAE,aAAaA,EAAU,MAAM,UAAU,EACtG,MAAM,IAAI,MAAM,yBAAyBV,GAAM,EAGjD,GAAIA,IAAS,KAAK,UAAW,CAC3B,KAAK,UAAU,KAAK,CAClB,KAAM,KACN,SAAU,CAAC,CACT,UAAWU,EAAU,MAAM,WAC3B,SAAUhB,EAAK,SAAS,IAAIC,GAAS,KAAK,UAAUA,CAAK,CAAC,EAAE,KAAK,CACnE,CAAC,CACH,CAAC,EACD,OAIF,IAAMc,EAAW,KAAK,UAAU,KAAK,UAAU,OAAS,CAAC,EACzD,GAAI,CAACA,GAAYA,EAAS,OAAS,KACjC,MAAM,IAAI,MAAM,kBAAkBT,GAAM,EAE1CS,EAAS,SAAS,KAAK,CACrB,UAAWC,EAAU,MAAM,WAC3B,SAAUhB,EAAK,SAAS,IAAIC,GAAS,KAAK,UAAUA,CAAK,CAAC,EAAE,KAAK,CACnE,CAAC,CACH,CAQQ,aAAaU,EAAiE,CACpF,GAAI,KAAK,EAAE,eAAeA,CAAI,EAAG,CAC/B,IAAIO,EAAkBC,EAClB,KAAK,EAAE,oBAAoBR,EAAK,IAAI,GAGtCO,EAAWP,EAAK,KAAK,KAAK,KAC1BQ,EAAYR,EAAK,KAAK,UAAU,MAEhCO,EAAWP,EAAK,KAAK,KAEvB,IAAIS,EAAQ,KAAK,EAAE,yBAAyBT,EAAK,KAAK,EAAIA,EAAK,MAAM,WAAaA,EAAK,MACvF,OAAI,KAAK,EAAE,qBAAqBS,CAAK,IAAGA,EAAQ,QACzC,CAACF,EAAU,KAAK,UAAUE,EAAOD,CAAS,CAAC,EAGpD,MAAO,CAAC,WAAY,KAAK,UAAUR,EAAK,QAAQ,CAAC,CACnD,CAQQ,UAAUU,EAA2CF,EAA8B,CAEzF,GAAI,CAACE,EACH,MAAO,CACL,MAAO,KAAK,EAAE,eAAe,EAAI,EACjC,YAAa,CAAC,CAChB,EAIF,IAAMC,EAA0C,CAAC,EAC3CC,EAAiBC,GAA0D,CAC/E,IAAMC,EAAK,KAAK,IAAI,EACdzB,EAAOwB,EAAU,KACvBF,EAAYG,CAAE,EAAI,KAAK,UAAUzB,CAAI,EACrC,IAAM0B,EAAU,KAAK,EAAE,cAAcD,CAAE,EACnCzB,IAASqB,IAEXA,EAAWK,GAGbF,EAAU,YAAYE,CAAO,EAC7BF,EAAU,KAAK,CACjB,EAGA,YAAK,SAAS,KAAK,aAAaH,CAAQ,EAAG,CACzC,WAAYE,EACZ,YAAaA,CACf,CAAC,EAEM,CACL,MAAOF,EACP,YAAAC,EACA,UAAAH,CACF,CACF,CAEA,kBAAkBN,EAA0B,CAE1C,MADI,CAAC,KAAK,mBACN,CAAC,KAAK,eAAeA,CAAI,EAAUA,GACvCA,EAAOA,EACA,CACL,KAAM,WACN,SAAU,KAAK,iBAAiBA,CAAI,EACpC,aAAc,KAAK,qBAAqBA,CAAI,EAC5C,MAAO,KAAK,mBAAmBA,CAAI,CACrC,EACF,CAOQ,iBAAiBA,EAA0B,CACjD,IAAMc,EAAc,OAAO,YAAY,KAAK,oBAE1C,OAAO,QAAQd,EAAK,OAAS,CAAC,CAAC,EAAE,OAC/B,CAAC,CAAC,CAAEF,CAAI,IACN,KAAK,aAAaA,CAAI,GAEtB,EAAE,KAAK,EAAE,iBAAiBA,EAAK,KAAK,GAAK,CAACA,EAAK,MAAM,MACzD,CACF,CAAC,EAEGG,EAAoC,CAAC,EACzC,OAAID,EAAK,WACPC,EAAWD,EAAK,SACb,IAAIA,GAAQ,CACX,GAAIA,EAAK,OAAS,OAAQ,OAAOA,EACjC,GAAIA,EAAK,OAAS,QAAU,KAAK,EAAE,gBAAgBA,EAAK,GAAG,EACzD,OAAO,KAAK,iBAAiBA,CAAI,CAErC,CAAC,EACA,OAAO,OAAO,GAEZ,CACL,KAAM,OACN,IAAKA,EAAK,IACV,MAAOc,EACP,SAAAb,CACF,CACF,CASQ,qBAAqBc,EAAmC,CAC9D,IAAMC,EAA8B,CAAC,EAE/BC,EAAsB,CAACjB,EAAgBkB,EAAiB,CAAC,IAAM,CACnE,IAAMC,EAAanB,EAAK,UAAU,OAChCZ,GAAUA,EAAM,OAAS,QAAU,KAAK,EAAE,gBAAgBA,EAAM,GAAG,GACnEA,EAAM,OAAS,MACjB,EAAE,OACEgC,EAAU,GAEdpB,EAAK,UAAU,QAASZ,GAAU,CAChC,GACE,EAAEA,EAAM,OAAS,QAAU,KAAK,EAAE,gBAAgBA,EAAM,GAAG,IACzDA,EAAM,OAAS,OACjB,CACA,IAAMiC,EAAMD,EAAU,GAAKD,EAAa,GAAKC,EAAU,EACvDJ,EAAa,KAAK,CAChB,KAAM,CAAC,GAAGE,EAAMG,CAAG,EACnB,GAAG,KAAK,kBAAkBjC,CAAK,CACjC,CAAC,OAEDgC,GAEJ,CAAC,EAEDpB,EAAK,UACD,OACAZ,GAASA,EAAM,OAAS,QAAU,KAAK,EAAE,gBAAgBA,EAAM,GAAG,CACpE,EACC,QAAQ,CAACA,EAAOiC,IAAQ,CACvBJ,EAAoB7B,EAAmB,CAAC,GAAG8B,EAAMG,CAAG,CAAC,CACvD,CAAC,CACL,EACA,OAAAJ,EAAoBF,CAAQ,EAErBC,CACT,CASQ,mBAAmBD,EAAoC,CAC7D,IAAMO,EAAgC,CAAC,EACjCC,EAAuB,CAACvB,EAAgBkB,IAAmB,CAG/DlB,EAAK,OAAS,OAAO,QAAQA,EAAK,KAAK,EACpC,OAAO,CAAC,CAAC,CAAEF,CAAI,IAAM,CAAC,KAAK,aAAaA,CAAI,CAAC,EAC7C,QAAQ,CAAC,CAAC0B,EAAK1B,CAAI,IAAM,CACxBwB,EAAc,KAAK,CACjB,IAAKtB,EAAK,IACV,KAAOA,EAAK,IAAwB,MACpC,IAAAwB,EACA,KAAAN,EACA,MAAOpB,EAAK,KACd,CAAC,CACH,CAAC,EAEHE,EAAK,UACD,OAAOZ,GAASA,EAAM,OAAS,QAAU,KAAK,EAAE,gBAAgBA,EAAM,GAAG,CAAC,EAC3E,QAAQ,CAACA,EAAOiC,IAAQ,CACvBE,EAAqBnC,EAAmB,CAAC,GAAG8B,EAAMG,CAAG,CAAC,CACxD,CAAC,CACL,EACA,OAAAE,EAAqBR,EAAU,CAAC,CAAC,EAE1BO,CACT,CAYQ,eAAeG,EAA6B,CAClD,OACEA,EAAS,OAAS,QAClB,KAAK,EAAE,gBAAgBA,EAAS,GAAG,GACnC,CAAC,CAACA,EAAS,UAAU,KACnBrC,GAASA,EAAM,OAAS,QAAU,KAAK,EAAE,gBAAgBA,EAAM,GAAG,CACpE,CAEJ,CAEQ,aAAaU,EAAyB,CAC5C,OACE,KAAK,EAAE,gBAAgBA,EAAK,KAAK,GACjC,KAAK,EAAE,iBAAiBA,EAAK,KAAK,GAClC,KAAK,EAAE,iBAAiBA,EAAK,KAAK,GAClC,KAAK,EAAE,cAAcA,EAAK,KAAK,CAEnC,CAQQ,oBACNF,EACoB,CACpB,OACEA,EAEG,OAAO,CAAC,CAAC4B,CAAG,IAAM,CAACA,EAAI,WAAW,IAAI,CAAC,EAEvC,OAAO,CAAC,CAACA,CAAG,IAAM,CAAC,KAAK,gBAAgB,SAASA,CAAG,CAAC,CAE5D,CAOQ,UAAUrC,EAAkC,CAClD,OAAO,IAAIF,EAAW,CAAC,GAAG,KAAK,OAAQ,cAAc,EAAK,CAAC,EAAE,MAAME,CAAI,CACzE,CAQQ,aAAaA,EAA4B,CAC/C,OAAO,KAAK,EAAE,KAAK,KAAK,EAAE,QAAQ,CAAC,KAAK,EAAE,oBAAoBA,CAAI,CAAC,CAAC,CAAC,CACvE,CAMQ,KAAc,CACpB,OAAO,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,MAAM,CAAC,CAC3C,CACF,EDjhBO,SAASuC,EACdC,EACAC,EACY,CACZ,OAAQ,IAAIC,EAAWD,CAAM,EAAE,MAAMD,CAAI,CAC3C","names":["src_exports","__export","parseView","__toCommonJS","ViewParser","config","node","child","text","type","tag","openingName","name","toMemberExpression","namespace","props","propMap","prop","childUnits","unit","children","lastUnit","condition","attr","propName","specifier","value","propNode","viewPropMap","parseViewProp","innerPath","id","newNode","staticProps","htmlUnit","mutableUnits","generateMutableUnit","path","maxHtmlIdx","htmlIdx","idx","templateProps","generateVariableProp","key","viewUnit","parseView","node","config","ViewParser"]} \ No newline at end of file diff --git a/packages/transpiler/jsx-view-parser/dist/index.d.ts b/packages/transpiler/jsx-view-parser/dist/index.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..4712c6b14586e64dbe1976ea26f19def41d2cd2e --- /dev/null +++ b/packages/transpiler/jsx-view-parser/dist/index.d.ts @@ -0,0 +1,73 @@ +import Babel, { types } from '@babel/core'; + +interface UnitProp { + value: types.Expression; + viewPropMap: Record; + specifier?: string; +} +interface TextUnit { + type: 'text'; + content: types.Literal; +} +type MutableUnit = ViewUnit & { + path: number[]; +}; +interface TemplateProp { + tag: types.Expression; + name: string; + key: string; + path: number[]; + value: types.Expression; +} +interface TemplateUnit { + type: 'template'; + template: HTMLUnit; + mutableUnits: MutableUnit[]; + props: TemplateProp[]; +} +interface HTMLUnit { + type: 'html'; + tag: types.Expression; + props: Record; + children: ViewUnit[]; +} +interface CompUnit { + type: 'comp'; + tag: types.Expression; + props: Record; + children: ViewUnit[]; +} +interface IfBranch { + condition: types.Expression; + children: ViewUnit[]; +} +interface IfUnit { + type: 'if'; + branches: IfBranch[]; +} +interface ExpUnit { + type: 'exp'; + content: UnitProp; +} +interface EnvUnit { + type: 'env'; + props: Record; + children: ViewUnit[]; +} +type ViewUnit = TextUnit | HTMLUnit | CompUnit | IfUnit | ExpUnit | EnvUnit | TemplateUnit; +interface ViewParserConfig { + babelApi: typeof Babel; + htmlTags: string[]; + parseTemplate?: boolean; +} +type AllowedJSXNode = types.JSXElement | types.JSXFragment | types.JSXText | types.JSXExpressionContainer | types.JSXSpreadChild; + +/** + * @brief Generate view units from a babel ast + * @param statement + * @param config + * @returns ViewUnit[] + */ +declare function parseView(node: AllowedJSXNode, config: ViewParserConfig): ViewUnit[]; + +export { AllowedJSXNode, CompUnit, EnvUnit, ExpUnit, HTMLUnit, IfBranch, IfUnit, MutableUnit, TemplateProp, TemplateUnit, TextUnit, UnitProp, ViewParserConfig, ViewUnit, parseView }; diff --git a/packages/transpiler/jsx-view-parser/dist/index.js b/packages/transpiler/jsx-view-parser/dist/index.js new file mode 100644 index 0000000000000000000000000000000000000000..73087f869154dcd9836dc276b368fb2030c8cfc2 --- /dev/null +++ b/packages/transpiler/jsx-view-parser/dist/index.js @@ -0,0 +1,2 @@ +var o=class{htmlNamespace="html";htmlTagNamespace="tag";compTagNamespace="comp";envTagName="env";ifTagName="if";elseIfTagName="else-if";elseTagName="else";customHTMLProps=["ref"];config;htmlTags;willParseTemplate;t;traverse;viewUnits=[];constructor(t){this.config=t,this.t=t.babelApi.types,this.traverse=t.babelApi.traverse,this.htmlTags=t.htmlTags,this.willParseTemplate=t.parseTemplate??!0}parse(t){return this.t.isJSXText(t)?this.parseText(t):this.t.isJSXExpressionContainer(t)?this.parseExpression(t.expression):this.t.isJSXElement(t)?this.parseElement(t):this.t.isJSXFragment(t)&&t.children.forEach(e=>{this.parse(e)}),this.viewUnits}parseText(t){let e=t.value.trim();e&&this.viewUnits.push({type:"text",content:this.t.stringLiteral(e)})}parseExpression(t){if(!this.t.isJSXEmptyExpression(t)){if(this.t.isLiteral(t)&&!this.t.isTemplateLiteral(t)){this.viewUnits.push({type:"text",content:t});return}this.viewUnits.push({type:"exp",content:this.parseProp(t)})}}parseElement(t){let e,r,i=t.openingElement.name;if(this.t.isJSXIdentifier(i)){let n=i.name;if([this.ifTagName,this.elseIfTagName,this.elseTagName].includes(n))return this.parseIf(t);if(n===this.envTagName)return this.parseEnv(t);this.htmlTags.includes(n)?(e="html",r=this.t.stringLiteral(n)):(e="comp",r=this.t.identifier(n))}else if(this.t.isJSXMemberExpression(i)){e="comp";let n=h=>this.t.isJSXMemberExpression(h.object)?this.t.memberExpression(n(h.object),this.t.identifier(h.property.name)):this.t.memberExpression(this.t.identifier(h.object.name),this.t.identifier(h.property.name));r=n(i)}else{let n=i.namespace.name;switch(n){case this.compTagNamespace:e="comp",r=this.t.identifier(i.name.name);break;case this.htmlNamespace:e="html",r=this.t.stringLiteral(i.name.name);break;case this.htmlTagNamespace:e="html",r=this.t.identifier(i.name.name);break;default:e="html",r=this.t.stringLiteral(`${n}:${i.name.name}`);break}}let s=t.openingElement.attributes,p=Object.fromEntries(s.map(n=>this.parseJSXProp(n))),l=t.children.map(n=>this.parseView(n)).flat(),a={type:e,tag:r,props:p,children:l};if(a.type==="html"&&l.length===1&&l[0].type==="text"){let n=l[0];a={...a,children:[],props:{...a.props,textContent:{value:n.content,viewPropMap:{}}}}}a.type==="html"&&(a=this.transformTemplate(a)),this.viewUnits.push(a)}parseEnv(t){let e=t.openingElement.attributes,r=Object.fromEntries(e.map(s=>this.parseJSXProp(s))),i=t.children.map(s=>this.parseView(s)).flat();this.viewUnits.push({type:"env",props:r,children:i})}parseIf(t){let e=t.openingElement.name.name;if(e===this.elseTagName){let s=this.viewUnits[this.viewUnits.length-1];if(!s||s.type!=="if")throw new Error(`Missing if for ${e}`);s.branches.push({condition:this.t.booleanLiteral(!0),children:t.children.map(p=>this.parseView(p)).flat()});return}let r=t.openingElement.attributes.filter(s=>this.t.isJSXAttribute(s)&&s.name.name==="cond")[0];if(!r)throw new Error(`Missing condition for ${e}`);if(!this.t.isJSXAttribute(r))throw new Error(`JSXSpreadAttribute is not supported for ${e} condition`);if(!this.t.isJSXExpressionContainer(r.value)||!this.t.isExpression(r.value.expression))throw new Error(`Invalid condition for ${e}`);if(e===this.ifTagName){this.viewUnits.push({type:"if",branches:[{condition:r.value.expression,children:t.children.map(s=>this.parseView(s)).flat()}]});return}let i=this.viewUnits[this.viewUnits.length-1];if(!i||i.type!=="if")throw new Error(`Missing if for ${e}`);i.branches.push({condition:r.value.expression,children:t.children.map(s=>this.parseView(s)).flat()})}parseJSXProp(t){if(this.t.isJSXAttribute(t)){let e,r;this.t.isJSXNamespacedName(t.name)?(e=t.name.name.name,r=t.name.namespace.name):e=t.name.name;let i=this.t.isJSXExpressionContainer(t.value)?t.value.expression:t.value;return this.t.isJSXEmptyExpression(i)&&(i=void 0),[e,this.parseProp(i,r)]}return["*spread*",this.parseProp(t.argument)]}parseProp(t,e){if(!t)return{value:this.t.booleanLiteral(!0),viewPropMap:{}};let r={},i=s=>{let p=this.uid(),l=s.node;r[p]=this.parseView(l);let a=this.t.stringLiteral(p);l===t&&(t=a),s.replaceWith(a),s.skip()};return this.traverse(this.wrapWithFile(t),{JSXElement:i,JSXFragment:i}),{value:t,viewPropMap:r,specifier:e}}transformTemplate(t){return!this.willParseTemplate||!this.isHTMLTemplate(t)?t:(t=t,{type:"template",template:this.generateTemplate(t),mutableUnits:this.generateMutableUnits(t),props:this.parseTemplateProps(t)})}generateTemplate(t){let e=Object.fromEntries(this.filterTemplateProps(Object.entries(t.props??[]).filter(([,i])=>this.isStaticProp(i)&&!(this.t.isBooleanLiteral(i.value)&&!i.value.value)))),r=[];return t.children&&(r=t.children.map(i=>{if(i.type==="text")return i;if(i.type==="html"&&this.t.isStringLiteral(i.tag))return this.generateTemplate(i)}).filter(Boolean)),{type:"html",tag:t.tag,props:e,children:r}}generateMutableUnits(t){let e=[],r=(i,s=[])=>{let p=i.children?.filter(a=>a.type==="html"&&this.t.isStringLiteral(a.tag)||a.type==="text").length,l=-1;i.children?.forEach(a=>{if(!(a.type==="html"&&this.t.isStringLiteral(a.tag))&&a.type!=="text"){let n=l+1>=p?-1:l+1;e.push({path:[...s,n],...this.transformTemplate(a)})}else l++}),i.children?.filter(a=>a.type==="html"&&this.t.isStringLiteral(a.tag)).forEach((a,n)=>{r(a,[...s,n])})};return r(t),e}parseTemplateProps(t){let e=[],r=(i,s)=>{i.props&&Object.entries(i.props).filter(([,p])=>!this.isStaticProp(p)).forEach(([p,l])=>{e.push({tag:i.tag,name:i.tag.value,key:p,path:s,value:l.value})}),i.children?.filter(p=>p.type==="html"&&this.t.isStringLiteral(p.tag)).forEach((p,l)=>{r(p,[...s,l])})};return r(t,[]),e}isHTMLTemplate(t){return t.type==="html"&&this.t.isStringLiteral(t.tag)&&!!t.children?.some(e=>e.type==="html"&&this.t.isStringLiteral(e.tag))}isStaticProp(t){return this.t.isStringLiteral(t.value)||this.t.isNumericLiteral(t.value)||this.t.isBooleanLiteral(t.value)||this.t.isNullLiteral(t.value)}filterTemplateProps(t){return t.filter(([e])=>!e.startsWith("on")).filter(([e])=>!this.customHTMLProps.includes(e))}parseView(t){return new o({...this.config,parseTemplate:!1}).parse(t)}wrapWithFile(t){return this.t.file(this.t.program([this.t.expressionStatement(t)]))}uid(){return Math.random().toString(36).slice(2)}};function g(m,t){return new o(t).parse(m)}export{g as parseView}; +//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/packages/transpiler/jsx-view-parser/dist/index.js.map b/packages/transpiler/jsx-view-parser/dist/index.js.map new file mode 100644 index 0000000000000000000000000000000000000000..4e63b85a261548de11d2633d1def10c6822ee3fe --- /dev/null +++ b/packages/transpiler/jsx-view-parser/dist/index.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["../src/parser.ts","../src/index.ts"],"sourcesContent":["import type { NodePath, types as t, traverse as tr } from '@babel/core';\nimport type {\n UnitProp,\n ViewUnit,\n ViewParserConfig,\n AllowedJSXNode,\n HTMLUnit,\n TextUnit,\n MutableUnit,\n TemplateProp,\n} from './types';\n\nexport class ViewParser {\n // ---- Namespace and tag name\n private readonly htmlNamespace: string = 'html'\n private readonly htmlTagNamespace: string = 'tag'\n private readonly compTagNamespace: string = 'comp'\n private readonly envTagName: string = 'env'\n private readonly ifTagName: string = 'if'\n private readonly elseIfTagName: string = 'else-if'\n private readonly elseTagName: string = 'else'\n private readonly customHTMLProps: string[] = ['ref']\n\n private readonly config: ViewParserConfig\n private readonly htmlTags: string[]\n private readonly willParseTemplate: boolean\n\n private readonly t: typeof t\n private readonly traverse: typeof tr\n\n private readonly viewUnits: ViewUnit[] = []\n\n /**\n * @brief Constructor\n * @param config\n */\n constructor(config: ViewParserConfig) {\n this.config = config;\n this.t = config.babelApi.types;\n this.traverse = config.babelApi.traverse;\n this.htmlTags = config.htmlTags;\n this.willParseTemplate = config.parseTemplate ?? true;\n }\n\n /**\n * @brief Parse the node into view units\n * @param node\n * @returns ViewUnit[]\n */\n parse(node: AllowedJSXNode): ViewUnit[] {\n if (this.t.isJSXText(node)) this.parseText(node);\n else if (this.t.isJSXExpressionContainer(node)) this.parseExpression(node.expression);\n else if (this.t.isJSXElement(node)) this.parseElement(node);\n else if (this.t.isJSXFragment(node)) {\n node.children.forEach(child => {\n this.parse(child);\n });\n }\n \n return this.viewUnits;\n }\n\n /**\n * @brief Parse JSXText\n * @param node\n */\n private parseText(node: t.JSXText): void {\n const text = node.value.trim();\n if (!text) return;\n this.viewUnits.push({\n type: 'text',\n content: this.t.stringLiteral(text),\n });\n }\n\n /**\n * @brief Parse JSXExpressionContainer\n * @param node\n */\n private parseExpression(node: t.Expression | t.JSXEmptyExpression): void {\n if (this.t.isJSXEmptyExpression(node)) return;\n if (this.t.isLiteral(node) && !this.t.isTemplateLiteral(node)) {\n // ---- Treat literal as text except template literal\n // Cuz template literal may have viewProp inside like:\n // <>{i18n`hello ${}`}\n this.viewUnits.push({\n type: 'text',\n content: node\n });\n return; \n }\n this.viewUnits.push({\n type: 'exp',\n content: this.parseProp(node),\n });\n }\n\n /**\n * @brief Parse JSXElement\n * @param node \n */\n private parseElement(node: t.JSXElement): void {\n let type: 'html' | 'comp';\n let tag: t.Expression;\n\n // ---- Parse tag and type\n const openingName = node.openingElement.name;\n if (this.t.isJSXIdentifier(openingName)) {\n // ---- Opening name is a JSXIdentifier, e.g.,
\n const name = openingName.name;\n // ---- Specially parse if and env\n if ([this.ifTagName, this.elseIfTagName, this.elseTagName].includes(name)) \n return this.parseIf(node);\n if (name === this.envTagName) return this.parseEnv(node);\n else if (this.htmlTags.includes(name)) {\n type = 'html';\n tag = this.t.stringLiteral(name);\n } else {\n // ---- If the name is not in htmlTags, treat it as a comp\n type = 'comp';\n tag = this.t.identifier(name);\n }\n } else if (this.t.isJSXMemberExpression(openingName)) {\n // ---- Opening name is a JSXMemberExpression, e.g., \n // Treat it as a comp and set the tag as the opening name\n type = 'comp';\n // ---- Turn JSXMemberExpression into MemberExpression recursively\n const toMemberExpression = (node: t.JSXMemberExpression): t.MemberExpression => {\n if (this.t.isJSXMemberExpression(node.object)) {\n return this.t.memberExpression(\n toMemberExpression(node.object),\n this.t.identifier(node.property.name)\n );\n }\n return this.t.memberExpression(\n this.t.identifier(node.object.name),\n this.t.identifier(node.property.name)\n );\n };\n tag = toMemberExpression(openingName);\n } else {\n // ---- isJSXNamespacedName\n const namespace = openingName.namespace.name;\n switch (namespace) {\n case this.compTagNamespace:\n // ---- If the namespace is the same as the compTagNamespace, treat it as a comp\n // and set the tag as an identifier\n // e.g., => [\"comp\", div]\n // this means you've declared a component named \"div\" and force it to be a comp instead an html\n type = 'comp';\n tag = this.t.identifier(openingName.name.name);\n break;\n case this.htmlNamespace:\n // ---- If the namespace is the same as the htmlTagNamespace, treat it as an html\n // and set the tag as a string literal\n // e.g., => [\"html\", \"MyWebComponent\"]\n // the tag will be treated as a string, i.e., \n type = 'html';\n tag = this.t.stringLiteral(openingName.name.name);\n break;\n case this.htmlTagNamespace:\n // ---- If the namespace is the same as the htmlTagNamespace, treat it as an html\n // and set the tag as an identifier\n // e.g., => [\"html\", variable]\n // this unit will be htmlUnit and the html string tag is stored in \"variable\"\n type = 'html';\n tag = this.t.identifier(openingName.name.name);\n break;\n default:\n // ---- Otherwise, treat it as an html tag and make the tag as the namespace:name\n type = 'html';\n tag = this.t.stringLiteral(`${namespace}:${openingName.name.name}`);\n break;\n }\n }\n\n // ---- Parse the props\n const props = node.openingElement.attributes;\n const propMap: Record = Object.fromEntries(\n props.map(prop => this.parseJSXProp(prop))\n );\n\n // ---- Parse the children\n const childUnits = node.children.map(child => this.parseView(child)).flat();\n\n let unit: ViewUnit = { type, tag, props: propMap, children: childUnits };\n \n if (unit.type === 'html' && childUnits.length === 1 && childUnits[0].type === 'text') {\n // ---- If the html unit only has one text child, merge the text into the html unit\n const text = childUnits[0] as TextUnit;\n unit = {\n ...unit,\n children: [],\n props: {\n ...unit.props,\n textContent: {\n value: text.content,\n viewPropMap: {},\n },\n },\n };\n }\n\n if (unit.type === 'html') unit = this.transformTemplate(unit);\n\n this.viewUnits.push(unit);\n }\n\n /**\n * @brief Parse EnvUnit\n * @param node\n */\n private parseEnv(node: t.JSXElement): void {\n const props = node.openingElement.attributes;\n const propMap: Record = Object.fromEntries(\n props.map(prop => this.parseJSXProp(prop))\n );\n const children = node.children.map(child => this.parseView(child)).flat();\n this.viewUnits.push({\n type: 'env',\n props: propMap,\n children,\n });\n }\n\n private parseIf(node: t.JSXElement): void {\n const name = (node.openingElement.name as t.JSXIdentifier).name;\n // ---- else\n if (name === this.elseTagName) {\n const lastUnit = this.viewUnits[this.viewUnits.length - 1];\n if (!lastUnit || lastUnit.type !== 'if') throw new Error(`Missing if for ${name}`);\n lastUnit.branches.push({\n condition: this.t.booleanLiteral(true),\n children: node.children.map(child => this.parseView(child)).flat(),\n });\n return;\n }\n\n const condition = node.openingElement.attributes.filter(attr => \n this.t.isJSXAttribute(attr) && attr.name.name === 'cond'\n )[0];\n if (!condition) throw new Error(`Missing condition for ${name}`);\n if (!this.t.isJSXAttribute(condition)) throw new Error(`JSXSpreadAttribute is not supported for ${name} condition`);\n if (!this.t.isJSXExpressionContainer(condition.value) || !this.t.isExpression(condition.value.expression))\n throw new Error(`Invalid condition for ${name}`);\n \n // ---- if\n if (name === this.ifTagName) {\n this.viewUnits.push({\n type: 'if',\n branches: [{\n condition: condition.value.expression,\n children: node.children.map(child => this.parseView(child)).flat(),\n }],\n });\n return;\n } \n\n // ---- else-if\n const lastUnit = this.viewUnits[this.viewUnits.length - 1];\n if (!lastUnit || lastUnit.type !== 'if') \n throw new Error(`Missing if for ${name}`);\n\n lastUnit.branches.push({\n condition: condition.value.expression,\n children: node.children.map(child => this.parseView(child)).flat(),\n });\n }\n\n /**\n * @brief Parse JSXAttribute or JSXSpreadAttribute into UnitProp,\n * considering both namespace and expression\n * @param prop \n * @returns [propName, propValue]\n */\n private parseJSXProp(prop: t.JSXAttribute | t.JSXSpreadAttribute): [string, UnitProp] {\n if (this.t.isJSXAttribute(prop)) {\n let propName: string, specifier: string | undefined;\n if (this.t.isJSXNamespacedName(prop.name)) {\n // ---- If the prop name is a JSXNamespacedName, e.g., bind:value\n // give it a special tag\n propName = prop.name.name.name;\n specifier = prop.name.namespace.name;\n } else {\n propName = prop.name.name;\n }\n let value = this.t.isJSXExpressionContainer(prop.value) ? prop.value.expression : prop.value;\n if (this.t.isJSXEmptyExpression(value)) value = undefined;\n return [propName, this.parseProp(value, specifier)];\n }\n // ---- Use *spread* as the propName to avoid conflict with other props\n return ['*spread*', this.parseProp(prop.argument)];\n }\n\n /**\n * @brief Parse the prop node into UnitProp\n * @param propNode\n * @param specifier\n * @returns UnitProp\n */\n private parseProp(propNode: t.Expression | undefined | null, specifier?: string): UnitProp {\n // ---- If there is no propNode, set the default prop as true\n if (!propNode) {\n return {\n value: this.t.booleanLiteral(true),\n viewPropMap: {},\n };\n }\n\n // ---- Collect sub jsx nodes as Prop\n const viewPropMap: Record = {};\n const parseViewProp = (innerPath: NodePath): void => {\n const id = this.uid();\n const node = innerPath.node;\n viewPropMap[id] = this.parseView(node);\n const newNode = this.t.stringLiteral(id);\n if (node === propNode) {\n // ---- If the node is the propNode, replace it with the new node\n propNode = newNode;\n }\n // ---- Replace the node and skip the inner path\n innerPath.replaceWith(newNode);\n innerPath.skip();\n };\n\n // ---- Apply the parseViewProp to JSXElement and JSXFragment\n this.traverse(this.wrapWithFile(propNode), {\n JSXElement: parseViewProp,\n JSXFragment: parseViewProp,\n });\n\n return {\n value: propNode,\n viewPropMap,\n specifier,\n };\n }\n\n transformTemplate(unit: ViewUnit): ViewUnit {\n if (!this.willParseTemplate) return unit;\n if (!this.isHTMLTemplate(unit)) return unit;\n unit = unit as HTMLUnit;\n return {\n type: 'template',\n template: this.generateTemplate(unit),\n mutableUnits: this.generateMutableUnits(unit),\n props: this.parseTemplateProps(unit),\n };\n }\n\n /**\n * @brief Generate the entire HTMLUnit\n * @param unit\n * @returns HTMLUnit\n */\n private generateTemplate(unit: HTMLUnit): HTMLUnit {\n const staticProps = Object.fromEntries(this.filterTemplateProps(\n // ---- Get all the static props\n Object.entries(unit.props ?? []).filter(\n ([, prop]) =>\n this.isStaticProp(prop) &&\n // ---- Filter out props with false values\n !(this.t.isBooleanLiteral(prop.value) && !prop.value.value)\n )\n ));\n\n let children: (HTMLUnit | TextUnit)[] = [];\n if (unit.children) {\n children = unit.children\n .map(unit => {\n if (unit.type === 'text') return unit;\n if (unit.type === 'html' && this.t.isStringLiteral(unit.tag)) {\n return this.generateTemplate(unit);\n }\n })\n .filter(Boolean) as (HTMLUnit | TextUnit)[];\n }\n return {\n type: 'html',\n tag: unit.tag,\n props: staticProps,\n children,\n };\n }\n\n /**\n * @brief Collect all the mutable nodes in a static HTMLUnit\n * We use this function to collect mutable nodes' path and props,\n * so that in the generator, we know which position to insert the mutable nodes\n * @param htmlUnit\n * @returns mutable particles\n */\n private generateMutableUnits(htmlUnit: HTMLUnit): MutableUnit[] {\n const mutableUnits: MutableUnit[] = [];\n\n const generateMutableUnit = (unit: HTMLUnit, path: number[] = []) => {\n const maxHtmlIdx = unit.children?.filter(\n child => (child.type === 'html' && this.t.isStringLiteral(child.tag)) ||\n child.type === 'text'\n ).length;\n let htmlIdx = -1;\n // ---- Generate mutable unit for current HTMLUnit\n unit.children?.forEach((child) => {\n if (\n !(child.type === 'html' && this.t.isStringLiteral(child.tag)) &&\n !(child.type === 'text')\n ) {\n const idx = htmlIdx + 1 >= maxHtmlIdx ? -1 : htmlIdx + 1;\n mutableUnits.push({\n path: [...path, idx],\n ...this.transformTemplate(child),\n });\n } else {\n htmlIdx++;\n }\n });\n // ---- Recursively generate mutable units for static HTMLUnit children\n unit.children\n ?.filter(\n child => child.type === 'html' && this.t.isStringLiteral(child.tag)\n )\n .forEach((child, idx) => {\n generateMutableUnit(child as HTMLUnit, [...path, idx]);\n });\n };\n generateMutableUnit(htmlUnit);\n\n return mutableUnits;\n }\n\n /**\n * @brief Collect all the props in a static HTMLUnit or its nested HTMLUnit children\n * Just like the mutable nodes, props are also equipped with path,\n * so that we know which HTML ChildNode to insert the props\n * @param htmlUnit\n * @returns props\n */\n private parseTemplateProps(htmlUnit: HTMLUnit): TemplateProp[] {\n const templateProps: TemplateProp[] = [];\n const generateVariableProp = (unit: HTMLUnit, path: number[]) => {\n // ---- Generate all non-static(string/number/boolean) props for current HTMLUnit\n // to be inserted further in the generator\n unit.props && Object.entries(unit.props)\n .filter(([, prop]) => !this.isStaticProp(prop))\n .forEach(([key, prop]) => {\n templateProps.push({\n tag: unit.tag,\n name: (unit.tag as t.StringLiteral).value,\n key,\n path,\n value: prop.value,\n });\n });\n // ---- Recursively generate props for static HTMLUnit children\n unit.children\n ?.filter(child => child.type === 'html' && this.t.isStringLiteral(child.tag))\n .forEach((child, idx) => {\n generateVariableProp(child as HTMLUnit, [...path, idx]);\n });\n };\n generateVariableProp(htmlUnit, []);\n\n return templateProps;\n }\n\n /**\n * @brief Check if a ViewUnit is a static HTMLUnit that can be parsed into a template\n * Must satisfy:\n * 1. type is html\n * 2. tag is a string literal, i.e., non-dynamic tag\n * 3. has at least one child that is a static HTMLUnit,\n * or else just call a createElement function, no need for template clone\n * @param viewUnit\n * @returns is a static HTMLUnit\n */\n private isHTMLTemplate(viewUnit: ViewUnit): boolean {\n return (\n viewUnit.type === 'html' &&\n this.t.isStringLiteral(viewUnit.tag) &&\n !!viewUnit.children?.some(\n child => child.type === 'html' && this.t.isStringLiteral(child.tag)\n )\n );\n }\n\n private isStaticProp(prop: UnitProp): boolean {\n return (\n this.t.isStringLiteral(prop.value) ||\n this.t.isNumericLiteral(prop.value) ||\n this.t.isBooleanLiteral(prop.value) ||\n this.t.isNullLiteral(prop.value)\n );\n }\n\n /**\n * @brief Filter out some props that are not needed in the template,\n * these are all special props to be parsed differently in the generator\n * @param props\n * @returns filtered props\n */\n private filterTemplateProps(\n props: Array<[string, T]>\n ): Array<[string, T]> {\n return (\n props\n // ---- Filter out event listeners\n .filter(([key]) => !key.startsWith('on'))\n // ---- Filter out specific props\n .filter(([key]) => !this.customHTMLProps.includes(key))\n );\n }\n\n /**\n * @brief Parse the view by duplicating current parser's classRootPath, statements and htmlTags\n * @param statements\n * @returns ViewUnit[]\n */\n private parseView(node: AllowedJSXNode): ViewUnit[] {\n return new ViewParser({...this.config, parseTemplate:false}).parse(node);\n }\n \n\n /**\n * @brief Wrap the value in a file\n * @param node\n * @returns wrapped value\n */\n private wrapWithFile(node: t.Expression): t.File {\n return this.t.file(this.t.program([this.t.expressionStatement(node)]));\n }\n\n /**\n * @brief Generate a unique id\n * @returns a unique id\n */\n private uid(): string {\n return Math.random().toString(36).slice(2);\n }\n}\n","import { ViewParser } from './parser';\nimport type { ViewUnit, ViewParserConfig, AllowedJSXNode } from './types';\n\n/**\n * @brief Generate view units from a babel ast\n * @param statement\n * @param config\n * @returns ViewUnit[]\n */\nexport function parseView(\n node: AllowedJSXNode,\n config: ViewParserConfig\n): ViewUnit[] {\n return new ViewParser(config).parse(node);\n}\n\nexport type * from './types';\n"],"mappings":"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","names":["ViewParser","config","node","child","text","type","tag","openingName","name","toMemberExpression","namespace","props","propMap","prop","childUnits","unit","children","lastUnit","condition","attr","propName","specifier","value","propNode","viewPropMap","parseViewProp","innerPath","id","newNode","staticProps","htmlUnit","mutableUnits","generateMutableUnit","path","maxHtmlIdx","htmlIdx","idx","templateProps","generateVariableProp","key","viewUnit","parseView","node","config","ViewParser"]} \ No newline at end of file diff --git a/packages/transpiler/view-parser/package.json b/packages/transpiler/jsx-view-parser/package.json similarity index 95% rename from packages/transpiler/view-parser/package.json rename to packages/transpiler/jsx-view-parser/package.json index afe36eaad0607c330d42376bcaa29893ba33fb17..8f3389c7122c1bf5772dd2c53201c95bd8775256 100755 --- a/packages/transpiler/view-parser/package.json +++ b/packages/transpiler/jsx-view-parser/package.json @@ -1,5 +1,5 @@ { - "name": "@inula/view-parser", + "name": "@inula/jsx-view-parser", "version": "0.0.0", "description": "Inula jsx parser", "author": { diff --git a/packages/transpiler/view-parser/src/index.ts b/packages/transpiler/jsx-view-parser/src/index.ts similarity index 88% rename from packages/transpiler/view-parser/src/index.ts rename to packages/transpiler/jsx-view-parser/src/index.ts index 17a88c01007ddf603079526c0524129ad5aa1db4..e98e634d1937f6bddfaadba5f41e4cf0bcc190e2 100644 --- a/packages/transpiler/view-parser/src/index.ts +++ b/packages/transpiler/jsx-view-parser/src/index.ts @@ -11,7 +11,7 @@ export function parseView( node: AllowedJSXNode, config: ViewParserConfig ): ViewUnit[] { - return new ViewParser(config).parse(node); + return new ViewParser(config).parse(node); } export type * from './types'; diff --git a/packages/transpiler/view-parser/src/parser.ts b/packages/transpiler/jsx-view-parser/src/parser.ts similarity index 92% rename from packages/transpiler/view-parser/src/parser.ts rename to packages/transpiler/jsx-view-parser/src/parser.ts index 3b4222be67eb8f5b18fd885a3ce088ea8d751ac3..c27eb92fc0ffdcaf48f2c33cf3655d4e8c8ab753 100644 --- a/packages/transpiler/view-parser/src/parser.ts +++ b/packages/transpiler/jsx-view-parser/src/parser.ts @@ -184,8 +184,24 @@ export class ViewParser { const childUnits = node.children.map(child => this.parseView(child)).flat(); let unit: ViewUnit = { type, tag, props: propMap, children: childUnits }; - if (unit.type === 'html' && this.willParseTemplate) - unit = this.transformTemplate(unit); + + if (unit.type === 'html' && childUnits.length === 1 && childUnits[0].type === 'text') { + // ---- If the html unit only has one text child, merge the text into the html unit + const text = childUnits[0] as TextUnit; + unit = { + ...unit, + children: [], + props: { + ...unit.props, + textContent: { + value: text.content, + viewPropMap: {}, + }, + }, + }; + } + + if (unit.type === 'html') unit = this.transformTemplate(unit); this.viewUnits.push(unit); } @@ -320,7 +336,8 @@ export class ViewParser { }; } - private transformTemplate(unit: ViewUnit): ViewUnit { + transformTemplate(unit: ViewUnit): ViewUnit { + if (!this.willParseTemplate) return unit; if (!this.isHTMLTemplate(unit)) return unit; unit = unit as HTMLUnit; return { @@ -347,7 +364,7 @@ export class ViewParser { ) )); - let children: (HTMLUnit | TextUnit)[] | undefined; + let children: (HTMLUnit | TextUnit)[] = []; if (unit.children) { children = unit.children .map(unit => { @@ -375,17 +392,26 @@ export class ViewParser { */ private generateMutableUnits(htmlUnit: HTMLUnit): MutableUnit[] { const mutableUnits: MutableUnit[] = []; + const generateMutableUnit = (unit: HTMLUnit, path: number[] = []) => { + const maxHtmlIdx = unit.children?.filter( + child => (child.type === 'html' && this.t.isStringLiteral(child.tag)) || + child.type === 'text' + ).length; + let htmlIdx = -1; // ---- Generate mutable unit for current HTMLUnit - unit.children?.forEach((child, idx) => { + unit.children?.forEach((child) => { if ( !(child.type === 'html' && this.t.isStringLiteral(child.tag)) && !(child.type === 'text') ) { + const idx = htmlIdx + 1 >= maxHtmlIdx ? -1 : htmlIdx + 1; mutableUnits.push({ path: [...path, idx], ...this.transformTemplate(child), }); + } else { + htmlIdx++; } }); // ---- Recursively generate mutable units for static HTMLUnit children @@ -418,6 +444,7 @@ export class ViewParser { .filter(([, prop]) => !this.isStaticProp(prop)) .forEach(([key, prop]) => { templateProps.push({ + tag: unit.tag, name: (unit.tag as t.StringLiteral).value, key, path, @@ -426,9 +453,9 @@ export class ViewParser { }); // ---- Recursively generate props for static HTMLUnit children unit.children - ?.forEach((child, idx) => { - if (child.type !== 'html') return; - generateVariableProp(child, [...path, idx]); + ?.filter(child => child.type === 'html' && this.t.isStringLiteral(child.tag)) + .forEach((child, idx) => { + generateVariableProp(child as HTMLUnit, [...path, idx]); }); }; generateVariableProp(htmlUnit, []); @@ -489,7 +516,7 @@ export class ViewParser { * @returns ViewUnit[] */ private parseView(node: AllowedJSXNode): ViewUnit[] { - return new ViewParser(this.config).parse(node); + return new ViewParser({...this.config, parseTemplate:false}).parse(node); } diff --git a/packages/transpiler/view-parser/src/test/ElementUnit.test.ts b/packages/transpiler/jsx-view-parser/src/test/ElementUnit.test.ts similarity index 100% rename from packages/transpiler/view-parser/src/test/ElementUnit.test.ts rename to packages/transpiler/jsx-view-parser/src/test/ElementUnit.test.ts diff --git a/packages/transpiler/view-parser/src/test/ExpUnit.test.ts b/packages/transpiler/jsx-view-parser/src/test/ExpUnit.test.ts similarity index 100% rename from packages/transpiler/view-parser/src/test/ExpUnit.test.ts rename to packages/transpiler/jsx-view-parser/src/test/ExpUnit.test.ts diff --git a/packages/transpiler/view-parser/src/test/IfUnit.test.ts b/packages/transpiler/jsx-view-parser/src/test/IfUnit.test.ts similarity index 100% rename from packages/transpiler/view-parser/src/test/IfUnit.test.ts rename to packages/transpiler/jsx-view-parser/src/test/IfUnit.test.ts diff --git a/packages/transpiler/view-parser/src/test/TemplateUnit.test.ts b/packages/transpiler/jsx-view-parser/src/test/TemplateUnit.test.ts similarity index 100% rename from packages/transpiler/view-parser/src/test/TemplateUnit.test.ts rename to packages/transpiler/jsx-view-parser/src/test/TemplateUnit.test.ts diff --git a/packages/transpiler/view-parser/src/test/TextUnit.test.ts b/packages/transpiler/jsx-view-parser/src/test/TextUnit.test.ts similarity index 100% rename from packages/transpiler/view-parser/src/test/TextUnit.test.ts rename to packages/transpiler/jsx-view-parser/src/test/TextUnit.test.ts diff --git a/packages/transpiler/jsx-view-parser/src/test/global.d.ts b/packages/transpiler/jsx-view-parser/src/test/global.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..96c0761c875f1e68a6014f1f2d51fcd963e3d220 --- /dev/null +++ b/packages/transpiler/jsx-view-parser/src/test/global.d.ts @@ -0,0 +1 @@ +declare module "@babel/plugin-syntax-jsx" \ No newline at end of file diff --git a/packages/transpiler/view-parser/src/test/mock.ts b/packages/transpiler/jsx-view-parser/src/test/mock.ts similarity index 100% rename from packages/transpiler/view-parser/src/test/mock.ts rename to packages/transpiler/jsx-view-parser/src/test/mock.ts diff --git a/packages/transpiler/view-parser/src/types.ts b/packages/transpiler/jsx-view-parser/src/types.ts similarity index 85% rename from packages/transpiler/view-parser/src/types.ts rename to packages/transpiler/jsx-view-parser/src/types.ts index 4b961e4ceb23a4b51e55e606d0df7e3ecab13df9..c473d6a52af3ad97edd42b42185c2a3d7d4b1660 100644 --- a/packages/transpiler/view-parser/src/types.ts +++ b/packages/transpiler/jsx-view-parser/src/types.ts @@ -12,9 +12,10 @@ export interface TextUnit { content: t.Literal } -export type MutableUnit = ViewUnit & { path: number[] } +export type MutableUnit = ViewUnit & { path: number[]} export interface TemplateProp { + tag: t.Expression name: string key: string path: number[] @@ -31,17 +32,15 @@ export interface TemplateUnit { export interface HTMLUnit { type: 'html' tag: t.Expression - content?: UnitProp - props?: Record - children?: ViewUnit[] + props: Record + children: ViewUnit[] } export interface CompUnit { type: 'comp' tag: t.Expression - content?: UnitProp - props?: Record - children?: ViewUnit[] + props: Record + children: ViewUnit[] } export interface IfBranch { diff --git a/packages/transpiler/jsx-view-parser/tsconfig.json b/packages/transpiler/jsx-view-parser/tsconfig.json new file mode 100644 index 0000000000000000000000000000000000000000..e0932d7828a8e6b8f5b2c7752a24057e6461adf0 --- /dev/null +++ b/packages/transpiler/jsx-view-parser/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "target": "ESNext", + "module": "ESNext", + "lib": ["ESNext", "DOM"], + "moduleResolution": "Node", + "strict": true, + "esModuleInterop": true + }, + "ts-node": { + "esm": true + } +} \ No newline at end of file