From 5395b21e668b228450178561352604d820687063 Mon Sep 17 00:00:00 2001 From: Marty Nelson Date: Wed, 13 Mar 2024 11:17:31 -0700 Subject: [PATCH 01/23] copy TemplateGenerator and make getTargets and bind function --- packages/compiler/compiler.js | 22 +- packages/compiler/compiler.test.js | 38 +++ packages/compiler/source-maps.test.js | 46 +-- packages/compiler/transform/BlindGenerator.js | 268 ++++++++++++++++++ packages/compiler/transform/bind.test.js | 42 +++ 5 files changed, 385 insertions(+), 31 deletions(-) create mode 100644 packages/compiler/transform/BlindGenerator.js create mode 100644 packages/compiler/transform/bind.test.js diff --git a/packages/compiler/compiler.js b/packages/compiler/compiler.js index 64da7ee..5ecd364 100644 --- a/packages/compiler/compiler.js +++ b/packages/compiler/compiler.js @@ -33,24 +33,30 @@ export function parse(code, options = {}) { } // generate = ast --> code + html -export function generate(ast, config) { - const file = config?.sourceFile || 'module.jsx'; +export function generateWith(generator, ast, config) { + const file = config?.sourceFile || 'script.js'; const sourceMap = new SourceMapGenerator({ file }); - const generator = new TemplateGenerator(); - - let code = astring(ast, { + const code = astring(ast, { ...config, generator, sourceMap, }); - const { templates } = generator; - return { code, - templates, map: sourceMap.toJSON(), // exposed for testing _sourceMap: sourceMap, }; } + +export function generate(ast, config) { + const generator = new TemplateGenerator(); + const generated = generateWith(generator, ast, config); + const { templates } = generator; + + return { + ...generated, + templates, + }; +} diff --git a/packages/compiler/compiler.test.js b/packages/compiler/compiler.test.js index 6b03f26..00e75f8 100644 --- a/packages/compiler/compiler.test.js +++ b/packages/compiler/compiler.test.js @@ -15,6 +15,44 @@ const compile = input => { }; describe('JSX dom literals', () => { + + test.only('Hello Azoth', ({ expect }) => { + const input = `const t =

+ Hello {"Azoth"} +

;`; + + const { code, templates } = compile(input); + + expect(code).toMatchInlineSnapshot(` + "import { __compose } from 'azoth/runtime'; + import { ta516887159 } from 'virtual:azoth-templates?id=a516887159'; + const t = (() => { + const __root = ta516887159()[0]; + const __child1 = __root.childNodes[1]; + __root.className = ("className"); + __compose(__child1, "Azoth"); + return __root; + })(); + " + `); + expect(templates).toMatchInlineSnapshot(` + [ + { + "html": "

+ Hello +

", + "id": "a516887159", + "imports": [ + "compose", + ], + "isDomFragment": false, + "isEmpty": false, + "isStatic": false, + }, + ] + `); + }); + test('complex template structure with props and child nodes', ({ expect }) => { const input = `const t =

{"felix"}

diff --git a/packages/compiler/source-maps.test.js b/packages/compiler/source-maps.test.js index 4adf77a..59dd680 100644 --- a/packages/compiler/source-maps.test.js +++ b/packages/compiler/source-maps.test.js @@ -24,7 +24,7 @@ test('static one line', ({ expect }) => { "name": "t", "originalColumn": 6, "originalLine": 1, - "source": "module.jsx", + "source": "script.js", }, { "generatedColumn": 10, @@ -32,7 +32,7 @@ test('static one line', ({ expect }) => { "name": undefined, "originalColumn": 10, "originalLine": 1, - "source": "module.jsx", + "source": "script.js", }, { "generatedColumn": 23, @@ -40,7 +40,7 @@ test('static one line', ({ expect }) => { "name": undefined, "originalColumn": 10, "originalLine": 1, - "source": "module.jsx", + "source": "script.js", }, ] `); @@ -68,7 +68,7 @@ test('{...} one line', ({ expect }) => { "name": undefined, "originalColumn": 0, "originalLine": 1, - "source": "module.jsx", + "source": "script.js", }, { "generatedColumn": 30, @@ -76,7 +76,7 @@ test('{...} one line', ({ expect }) => { "name": undefined, "originalColumn": 0, "originalLine": 1, - "source": "module.jsx", + "source": "script.js", }, { "generatedColumn": 19, @@ -84,7 +84,7 @@ test('{...} one line', ({ expect }) => { "name": "div", "originalColumn": 1, "originalLine": 1, - "source": "module.jsx", + "source": "script.js", }, { "generatedColumn": 36, @@ -92,7 +92,7 @@ test('{...} one line', ({ expect }) => { "name": undefined, "originalColumn": 11, "originalLine": 1, - "source": "module.jsx", + "source": "script.js", }, { "generatedColumn": 2, @@ -100,7 +100,7 @@ test('{...} one line', ({ expect }) => { "name": undefined, "originalColumn": 11, "originalLine": 1, - "source": "module.jsx", + "source": "script.js", }, { "generatedColumn": 12, @@ -108,7 +108,7 @@ test('{...} one line', ({ expect }) => { "name": undefined, "originalColumn": 11, "originalLine": 1, - "source": "module.jsx", + "source": "script.js", }, { "generatedColumn": 22, @@ -116,7 +116,7 @@ test('{...} one line', ({ expect }) => { "name": "place", "originalColumn": 12, "originalLine": 1, - "source": "module.jsx", + "source": "script.js", }, { "generatedColumn": 9, @@ -124,7 +124,7 @@ test('{...} one line', ({ expect }) => { "name": undefined, "originalColumn": 0, "originalLine": 1, - "source": "module.jsx", + "source": "script.js", }, ] `); @@ -149,7 +149,7 @@ test('static three line', ({ expect }) => { "name": "t", "originalColumn": 6, "originalLine": 1, - "source": "module.jsx", + "source": "script.js", }, { "generatedColumn": 10, @@ -157,7 +157,7 @@ test('static three line', ({ expect }) => { "name": undefined, "originalColumn": 10, "originalLine": 1, - "source": "module.jsx", + "source": "script.js", }, { "generatedColumn": 23, @@ -165,7 +165,7 @@ test('static three line', ({ expect }) => { "name": undefined, "originalColumn": 10, "originalLine": 1, - "source": "module.jsx", + "source": "script.js", }, ] `); @@ -195,7 +195,7 @@ test('{...} three line', ({ expect }) => { "name": "t", "originalColumn": 6, "originalLine": 1, - "source": "module.jsx", + "source": "script.js", }, { "generatedColumn": 17, @@ -203,7 +203,7 @@ test('{...} three line', ({ expect }) => { "name": undefined, "originalColumn": 10, "originalLine": 1, - "source": "module.jsx", + "source": "script.js", }, { "generatedColumn": 30, @@ -211,7 +211,7 @@ test('{...} three line', ({ expect }) => { "name": undefined, "originalColumn": 10, "originalLine": 1, - "source": "module.jsx", + "source": "script.js", }, { "generatedColumn": 19, @@ -219,7 +219,7 @@ test('{...} three line', ({ expect }) => { "name": "div", "originalColumn": 11, "originalLine": 1, - "source": "module.jsx", + "source": "script.js", }, { "generatedColumn": 36, @@ -227,7 +227,7 @@ test('{...} three line', ({ expect }) => { "name": undefined, "originalColumn": 14, "originalLine": 2, - "source": "module.jsx", + "source": "script.js", }, { "generatedColumn": 2, @@ -235,7 +235,7 @@ test('{...} three line', ({ expect }) => { "name": undefined, "originalColumn": 14, "originalLine": 2, - "source": "module.jsx", + "source": "script.js", }, { "generatedColumn": 12, @@ -243,7 +243,7 @@ test('{...} three line', ({ expect }) => { "name": undefined, "originalColumn": 14, "originalLine": 2, - "source": "module.jsx", + "source": "script.js", }, { "generatedColumn": 22, @@ -251,7 +251,7 @@ test('{...} three line', ({ expect }) => { "name": "place", "originalColumn": 15, "originalLine": 2, - "source": "module.jsx", + "source": "script.js", }, { "generatedColumn": 9, @@ -259,7 +259,7 @@ test('{...} three line', ({ expect }) => { "name": undefined, "originalColumn": 10, "originalLine": 1, - "source": "module.jsx", + "source": "script.js", }, ] `); diff --git a/packages/compiler/transform/BlindGenerator.js b/packages/compiler/transform/BlindGenerator.js new file mode 100644 index 0000000..20b46c5 --- /dev/null +++ b/packages/compiler/transform/BlindGenerator.js @@ -0,0 +1,268 @@ +import { Generator, writeNextLine } from './GeneratorBase.js'; +import { isValidESIdentifier } from 'is-valid-es-identifier'; +import { generate as astring } from 'astring'; +import { generateWith } from '../compiler.js'; + +const OPENING_PROP = { + JSXElement: 'openingElement', + JSXFragment: 'openingFragment', +}; + +const IS_OPENING = { + JSXOpeningElement: true, + JSXOpeningFragment: true, +}; + +export class BindGenerator extends Generator { + static generate(template) { + const generator = new BindGenerator(template); + return generateWith(generator, template.node); + } + + constructor(template) { + super(); + this.template = template; + } + + JSXFragment(_node, state) { + this.JSXTemplate(state); + } + + JSXElement(_node, state) { + this.JSXTemplate(state); + } + + JSXTemplate(state) { + const { template } = this; + // Short-circuit templates + const { isStatic, bindings, node: root } = template; + const { isComponent } = root; + if(isStatic || isComponent) { + if(isComponent) this.CreateElement(root, state); + else if(isStatic) this.StaticRoot(template, state); + return; + } + + this.GetTargets(template, state); + + + this.Bindings(template, state); + + + } + + // process javascript in {...} exprs, + // supports nested template: recursive processing ftw! + JSXExpressionContainer({ expression }, state) { + this[expression.type](expression, state); + } + + JSXIdentifier(identifier, state) { + state.write(identifier.name, identifier); + } + + StaticRoot(template, state) { + this.TemplateRenderer(template, state); + if(!template.isEmpty) state.write(`[0]`, template.node); // dom root + } + + TemplateRenderer({ id, isEmpty, isDomFragment, node }, state) { + if(isEmpty) { + state.write('null', node); + return; + } + state.write(`t${id}`, node); + state.write(`(`); + if(isDomFragment) state.write('true'); + state.write(`)`); + } + + GetTargets(template, state) { + const { boundElements, bindings, node } = template; + + const hasTargets = !!boundElements.length; + const params = hasTargets ? `root, targets` : `root`; + state.write(`function getTargets(${params}) {`); + state.indentLevel++; + + // target variables + for(let i = 0; i < boundElements.length; i++) { + const boundElement = boundElements[i]; + const opening = boundElement.openingElement || boundElement.openFragment; + writeNextLine(state); + state.write(`const target${i} = `); + state.write(`targets[${i}]`, opening?.name); + state.write(`;`); + } + + const returnValues = []; + for(let i = 0; i < bindings.length; i++) { + const { element, type, index, node } = bindings[i]; + const { queryIndex } = element; + const varName = queryIndex === -1 ? `root` : `target${queryIndex}`; + if(type !== 'child') { + returnValues.push(varName); + continue; + } + + let opening = null; + if(IS_OPENING[element.type]) opening = element; + else { + const prop = OPENING_PROP[element.type]; + if(prop) opening = element[prop]; + else { + throw new TypeError(`Unexpected binding node type "${node.type}"`); + } + } + + const childVar = `child${i}`; + returnValues.push(childVar); + writeNextLine(state); + state.write(`const ${childVar} = `); + state.write(`${varName}.childNodes`, opening.name); + state.write(`[${index}]`, node); + state.write(`;`); + } + + writeNextLine(state); + state.write(`return [${returnValues.join(', ')}];`); + + state.indentLevel--; + writeNextLine(state); + state.write(`}`); + state.write(state.lineEnd); + writeNextLine(state); + } + + Bindings(template, state) { + const { boundElements, bindings, node } = template; + + + const params = []; + for(let i = 0; i < bindings.length; i++) { + params.push(`p${i}`); + } + state.write(`function apply(${params.join(', ')}) {`); + state.indentLevel++; + writeNextLine(state); + + const returnStatement = template.node.returnStatement; + + + // template service renderer call + const hasTargets = !!boundElements.length; + + const vars = ['root']; + for(let i = 0; i < bindings.length; i++) { + vars.push(`t${i}`); + } + + state.write(`const [${vars.join(', ')}] = getTargets();`); + + // bindings + for(let i = 0; i < bindings.length; i++) { + const { element, type, node, expr } = bindings[i]; + writeNextLine(state); + + if(!this[expr.type]) { + throw new TypeError(`Unexpected Binding expression AST type "${expr.type}"`); + } + + if(node.isComponent) { + this.ComposeElement(node, expr, i, state); + continue; + } + if(type === 'child') { + this.Compose(node, expr, i, state); + continue; + } + if(type === 'prop') { + this.BindingProp(node, expr, i, element, state); + continue; + } + + const message = `Unexpected binding type "${type}", expected "child" or "prop"`; + throw new Error(message); + } + + state.indentLevel--; + writeNextLine(state); + state.write(`}`); + state.write(state.lineEnd); + } + + Compose(node, expr, index, state) { + state.write(`compose(`, node); + state.write(`t${index}, `, node); + state.write(`p${index}`, expr); + // this[expr.type](expr, state); + state.write(`);`); + } + + ComposeElement(node, expr, index, state) { + state.write(`composeElement(`, node); + state.write(`t${index}, `); + this.CompleteElement(node, expr, state); + state.write(`);`); + } + + CreateElement(node, state) { + state.write(`createElement(`, node); + this.CompleteElement(node, node.componentExpr, state); + state.write(`)`); + } + + CompleteElement({ props, slotFragment }, expr, state) { + this[expr.type](expr, state); + if(props?.length) { + this.ComponentProps(props, state); + } + else if(slotFragment) state.write(`, null`); + + if(slotFragment) { + state.write(', '); + this.JSXTemplate(slotFragment, state); + } + } + + ComponentProps(props, state) { + state.write(`, {`); + for(let i = 0; i < props.length; i++) { + const { node, expr } = props[i]; + // TODO: Dom lookup, JS .prop v['prop'], etc. + // refactor with code below + state.write(` `); + state.write(node.name.name, node.name); + state.write(`: `); + this[expr.type](expr, state); + state.write(`,`); + } + state.write(` }`); + } + + BindingProp(node, expr, index, element, state) { + const { queryIndex, openingElement, openingFragment } = element; + const varName = queryIndex === -1 ? `root` : `target${queryIndex}`; + const opening = openingElement ?? openingFragment; + state.write(`t${index}`, opening.name); + // TODO: more property validation + const identity = node.name; + const propName = identity.name; + // TODO: refactor with component props + if(isValidESIdentifier(propName)) { + state.write(`.`); + state.write(propName, node.name); + } + else { + state.write(`["`, node.name); + state.write(propName, node.name); + state.write(`"]`); + } + + /* expression */ + state.write(` = `); + // this[expr.type](expr, state); + state.write(`p${index}`, expr); + state.write(`;`); + } +} diff --git a/packages/compiler/transform/bind.test.js b/packages/compiler/transform/bind.test.js new file mode 100644 index 0000000..18f9b15 --- /dev/null +++ b/packages/compiler/transform/bind.test.js @@ -0,0 +1,42 @@ +/* eslint-disable no-undef */ +import { BindGenerator } from './BlindGenerator.js'; +import { parse, generate as _generate } from '../compiler.js'; +import { describe, test } from 'vitest'; + +function preParse(input) { + const ast = parse(input); + return _generate(ast); +} +describe('Bind Generator', () => { + + test('Hello Bind', ({ expect }) => { + // const input = `const t =

yo

;`; + const input = `const t =

+ Hello hey {"Azoth"}! +

;`; + + const initial = preParse(input); + const template = initial.templates[0]; + expect(template.node.type).toBe('JSXElement'); + + const { code } = BindGenerator.generate(template); + + expect(code).toMatchInlineSnapshot(` + "function getTargets(root, targets) { + const target0 = targets[0]; + const child1 = target0.childNodes[1]; + return [root, child1]; + } + + function apply(p0, p1) { + const [root, t0, t1] = getTargets(); + t0.className = p0; + compose(t1, p1); + } + " + `); + + + }); + +}); \ No newline at end of file From 79c4944e83d8e7b38ef2524380758918de8a2afb Mon Sep 17 00:00:00 2001 From: Marty Nelson Date: Wed, 13 Mar 2024 11:17:33 -0700 Subject: [PATCH 02/23] Copy TemplateGenerator and make getTargets and bind functions --- docs/get-started.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/docs/get-started.md b/docs/get-started.md index fc82f20..5cc0f79 100644 --- a/docs/get-started.md +++ b/docs/get-started.md @@ -1,2 +1,25 @@ # Get Started + + + +## Developer Value Proposition: + +It is easier to reason about rendering with present data at multiple times + +Easier to reason about multiple data projection rendering channels + + + + + + +## Data projection rendering + +Simple to think about: +- all inputs present and accounted for +- structuring and presentation components +- html only +- no component reentry + + From fa99fa24f591651d2d926b3c0ac3db3cd4da7bc1 Mon Sep 17 00:00:00 2001 From: Marty Nelson Date: Fri, 15 Mar 2024 07:08:24 -0700 Subject: [PATCH 03/23] leaner functions --- packages/compiler/transform/Analyzer.js | 5 +- packages/compiler/transform/BlindGenerator.js | 96 +++++++++---------- packages/compiler/transform/Template.js | 2 +- packages/compiler/transform/bind.test.js | 13 ++- 4 files changed, 57 insertions(+), 59 deletions(-) diff --git a/packages/compiler/transform/Analyzer.js b/packages/compiler/transform/Analyzer.js index d9fc5ff..8792f25 100644 --- a/packages/compiler/transform/Analyzer.js +++ b/packages/compiler/transform/Analyzer.js @@ -92,6 +92,7 @@ export class Analyzer { #bind(type, node, expr, index) { const element = this.#elements.current; + element.isRoot = element === this.#root; const binding = { element, @@ -106,6 +107,8 @@ export class Analyzer { if(type !== 'prop') { throw new TypeError(`Unexpected binding type "${type}", expected "prop"`); } + + // early exit! components get bindings as props element.props.push(binding); return; } @@ -116,7 +119,7 @@ export class Analyzer { this.#imports.add('compose'); } - if(element === this.#root) { + if(element.isRoot) { // root can't be a "target", it gets a -1 queryIndex // to signal bound template root (either el or fragment) element.queryIndex = -1; diff --git a/packages/compiler/transform/BlindGenerator.js b/packages/compiler/transform/BlindGenerator.js index 20b46c5..37fa480 100644 --- a/packages/compiler/transform/BlindGenerator.js +++ b/packages/compiler/transform/BlindGenerator.js @@ -1,17 +1,27 @@ import { Generator, writeNextLine } from './GeneratorBase.js'; import { isValidESIdentifier } from 'is-valid-es-identifier'; -import { generate as astring } from 'astring'; import { generateWith } from '../compiler.js'; -const OPENING_PROP = { +const OPENING_TYPES = { + JSXOpeningElement: true, + JSXOpeningFragment: true, +}; + +const OPENING_PROP_BY_TYPE = { JSXElement: 'openingElement', JSXFragment: 'openingFragment', }; -const IS_OPENING = { - JSXOpeningElement: true, - JSXOpeningFragment: true, -}; +function getOpening(element) { + const { type } = element; + if(OPENING_TYPES[type]) return element; + + const prop = OPENING_PROP_BY_TYPE[type]; + if(prop) return element[prop]; + + throw new TypeError(`Unexpected binding element type "${element.type}"`); +} + export class BindGenerator extends Generator { static generate(template) { @@ -35,7 +45,7 @@ export class BindGenerator extends Generator { JSXTemplate(state) { const { template } = this; // Short-circuit templates - const { isStatic, bindings, node: root } = template; + const { isStatic, node: root } = template; const { isComponent } = root; if(isStatic || isComponent) { if(isComponent) this.CreateElement(root, state); @@ -43,16 +53,10 @@ export class BindGenerator extends Generator { return; } - this.GetTargets(template, state); - - + this.DomTargets(template, state); this.Bindings(template, state); - - } - // process javascript in {...} exprs, - // supports nested template: recursive processing ftw! JSXExpressionContainer({ expression }, state) { this[expression.type](expression, state); } @@ -73,59 +77,50 @@ export class BindGenerator extends Generator { } state.write(`t${id}`, node); state.write(`(`); + // TODO: this should go in template module if(isDomFragment) state.write('true'); state.write(`)`); } - GetTargets(template, state) { - const { boundElements, bindings, node } = template; + DomTargets(template, state) { + const { boundElements, bindings, isBoundRoot } = template; + const { length: elLength } = boundElements; - const hasTargets = !!boundElements.length; - const params = hasTargets ? `root, targets` : `root`; - state.write(`function getTargets(${params}) {`); - state.indentLevel++; + const ROOT = 'r'; + const TARGET = 't'; - // target variables - for(let i = 0; i < boundElements.length; i++) { - const boundElement = boundElements[i]; - const opening = boundElement.openingElement || boundElement.openFragment; - writeNextLine(state); - state.write(`const target${i} = `); - state.write(`targets[${i}]`, opening?.name); - state.write(`;`); + state.write(`function getTargets(`); + if(isBoundRoot) state.write(ROOT); + if(elLength) { + const targets = []; + for(let i = 0; i < elLength; i++) { + targets.push(`${TARGET}${i}`); + } + state.write(`, [${targets.join(', ')}]`); } + state.write(') {'); - const returnValues = []; + state.indentLevel++; + writeNextLine(state); + + state.write(`return [`); for(let i = 0; i < bindings.length; i++) { const { element, type, index, node } = bindings[i]; - const { queryIndex } = element; - const varName = queryIndex === -1 ? `root` : `target${queryIndex}`; + const { isRoot, queryIndex } = element; + const varName = isRoot ? ROOT : `${TARGET}${queryIndex}`; + + if(i !== 0) state.write(', '); + if(type !== 'child') { - returnValues.push(varName); + state.write(`${varName}`); continue; } - let opening = null; - if(IS_OPENING[element.type]) opening = element; - else { - const prop = OPENING_PROP[element.type]; - if(prop) opening = element[prop]; - else { - throw new TypeError(`Unexpected binding node type "${node.type}"`); - } - } - - const childVar = `child${i}`; - returnValues.push(childVar); - writeNextLine(state); - state.write(`const ${childVar} = `); + const opening = getOpening(element, node); state.write(`${varName}.childNodes`, opening.name); state.write(`[${index}]`, node); - state.write(`;`); } - - writeNextLine(state); - state.write(`return [${returnValues.join(', ')}];`); + state.write(`];`); state.indentLevel--; writeNextLine(state); @@ -266,3 +261,4 @@ export class BindGenerator extends Generator { state.write(`;`); } } + diff --git a/packages/compiler/transform/Template.js b/packages/compiler/transform/Template.js index a099a67..9419d6b 100644 --- a/packages/compiler/transform/Template.js +++ b/packages/compiler/transform/Template.js @@ -28,7 +28,7 @@ export class Template { if(node.isComponent && bindings.length) { throw new Error('Unexpected component binding length'); } - + this.isBoundRoot = node.queryIndex === -1; this.isDomFragment = node.isJSXFragment; this.isEmpty = node.isComponent || (node.isJSXFragment && node.children.length === 0); diff --git a/packages/compiler/transform/bind.test.js b/packages/compiler/transform/bind.test.js index 18f9b15..10c8510 100644 --- a/packages/compiler/transform/bind.test.js +++ b/packages/compiler/transform/bind.test.js @@ -12,7 +12,7 @@ describe('Bind Generator', () => { test('Hello Bind', ({ expect }) => { // const input = `const t =

yo

;`; const input = `const t =

- Hello hey {"Azoth"}! + {"Greeting"} hey {"Azoth"}!

;`; const initial = preParse(input); @@ -22,16 +22,15 @@ describe('Bind Generator', () => { const { code } = BindGenerator.generate(template); expect(code).toMatchInlineSnapshot(` - "function getTargets(root, targets) { - const target0 = targets[0]; - const child1 = target0.childNodes[1]; - return [root, child1]; + "function getTargets(r, [t0]) { + return [r, r.childNodes[1], t0.childNodes[1]]; } - function apply(p0, p1) { - const [root, t0, t1] = getTargets(); + function apply(p0, p1, p2) { + const [root, t0, t1, t2] = getTargets(); t0.className = p0; compose(t1, p1); + compose(t2, p2); } " `); From 200ea0b1da32c5f23365ef04751f31ac98c02581 Mon Sep 17 00:00:00 2001 From: Marty Nelson Date: Fri, 15 Mar 2024 15:36:11 -0700 Subject: [PATCH 04/23] work out details of inject updating --- jsconfig.json | 5 ++ packages/compiler/transform/bind.test.js | 22 ++++++ packages/runtime/renderer/magic.test.js | 98 ++++++++++++++++++++++++ packages/runtime/renderer/renderer.js | 8 +- 4 files changed, 132 insertions(+), 1 deletion(-) create mode 100644 jsconfig.json create mode 100644 packages/runtime/renderer/magic.test.js diff --git a/jsconfig.json b/jsconfig.json new file mode 100644 index 0000000..e0fd815 --- /dev/null +++ b/jsconfig.json @@ -0,0 +1,5 @@ +{ + "compilerOptions": { + "module": "ES6", + } +} \ No newline at end of file diff --git a/packages/compiler/transform/bind.test.js b/packages/compiler/transform/bind.test.js index 10c8510..2ecd3f8 100644 --- a/packages/compiler/transform/bind.test.js +++ b/packages/compiler/transform/bind.test.js @@ -34,6 +34,28 @@ describe('Bind Generator', () => { } " `); + }); + + test('Hello Bind', ({ expect }) => { + // const input = `const t =

yo

;`; + const input = `name =>

{name}

`; + const initial = preParse(input); + const template = initial.templates[0]; + expect(template.node.type).toBe('JSXElement'); + + const { code } = BindGenerator.generate(template); + + expect(code).toMatchInlineSnapshot(` + "function getTargets(r) { + return [r.childNodes[0]]; + } + + function apply(p0) { + const [root, t0] = getTargets(); + compose(t0, p0); + } + " + `); }); diff --git a/packages/runtime/renderer/magic.test.js b/packages/runtime/renderer/magic.test.js new file mode 100644 index 0000000..628a1a7 --- /dev/null +++ b/packages/runtime/renderer/magic.test.js @@ -0,0 +1,98 @@ +import { test } from 'vitest'; +import { compose } from '../compose/compose.js'; +import { makeRenderer, getBoundElements } from './renderer.js'; + +// template generated artifacts +const source = makeRenderer('id', `

`); + +let injectableRoot = null; +function injectRender(dom, callback) { + injectableRoot = dom; + callback(); + injectableRoot = null; +} + +function getBindTargets(r, boundEls) { + return [r.childNodes[0]]; +} +const makeBind = targets => { + const t0 = targets[0]; + return p0 => { + compose(t0, p0); + }; +}; + +const map = new Map(); + +const makeTemplate = (source) => { + let bind = null; + let root = injectableRoot; + // TODO: test injectable is right template id + + if(root) bind = map.get(root); + if(!bind) { + const result = root + ? [root, getBoundElements(root)] + : source(); + root = result[0]; + const nodes = getBindTargets(root, result[1]); + bind = makeBind(nodes); + map.set(root, bind); + } + + return [root, bind]; +}; + +function render123(p0) { + const [root, bind] = makeTemplate(source); + bind(p0); + return root; +} + +class Controller { + static for(renderFn) { + return new this(renderFn); + } + constructor(renderFn) { + this.renderFn = renderFn; + } + render(props) { + return this.renderFn(props); + } + update(dom, props) { + injectRender(dom, () => this.renderFn(props)); + } +} + +class Updater extends Controller { + #dom = null; + render(props) { + return this.#dom = super.render(props); + } + update(props) { + super.update(this.#dom, props); + } +} + +test('controller creates or injects', ({ expect }) => { + const controller = Controller.for(name => render123(name)); + + let dom1 = controller.render('felix'); + let dom2 = controller.render('duchess'); + expect(dom1.outerHTML).toMatchInlineSnapshot(`"

felix

"`); + expect(dom2.outerHTML).toMatchInlineSnapshot(`"

duchess

"`); + + controller.update(dom1, 'garfield'); + controller.update(dom2, 'stimpy'); + expect(dom1.outerHTML).toMatchInlineSnapshot(`"

garfield

"`); + expect(dom2.outerHTML).toMatchInlineSnapshot(`"

stimpy

"`); +}); + +test('update remembers dom', ({ expect }) => { + const updater = Updater.for(name => render123(name)); + const dom = updater.render('felix'); + expect(dom.outerHTML).toMatchInlineSnapshot(`"

felix

"`); + + updater.update('duchess'); + expect(dom.outerHTML).toMatchInlineSnapshot(`"

duchess

"`); +}); diff --git a/packages/runtime/renderer/renderer.js b/packages/runtime/renderer/renderer.js index a2c42c7..6553e07 100644 --- a/packages/runtime/renderer/renderer.js +++ b/packages/runtime/renderer/renderer.js @@ -29,13 +29,19 @@ function rendererFactory(id, node, isFragment) { return render; } +const QUERY_SELECTOR = '[data-bind]'; + function renderer(fragment, isFragment) { if(!isFragment) fragment = fragment.firstElementChild; // TODO: malformed fragments...necessary? return function render() { const clone = fragment.cloneNode(true); - const targets = clone.querySelectorAll('[data-bind]'); + const targets = clone.querySelectorAll(QUERY_SELECTOR); return [clone, targets]; }; +} + +export function getBoundElements(dom) { + return dom.querySelectorAll(QUERY_SELECTOR); } \ No newline at end of file From bbf8ab69753c8b6098973f0a5daec13ad769f252 Mon Sep 17 00:00:00 2001 From: Marty Nelson Date: Fri, 15 Mar 2024 19:43:56 -0700 Subject: [PATCH 05/23] new render --- jsconfig.json | 1 + .../{BlindGenerator.js => BindGenerator.js} | 83 +++++++++++-------- packages/compiler/transform/bind.test.js | 59 +++++++------ packages/runtime/renderer/magic.test.js | 29 ++----- packages/runtime/renderer/renderer.js | 24 +++++- 5 files changed, 112 insertions(+), 84 deletions(-) rename packages/compiler/transform/{BlindGenerator.js => BindGenerator.js} (79%) diff --git a/jsconfig.json b/jsconfig.json index e0fd815..98862fb 100644 --- a/jsconfig.json +++ b/jsconfig.json @@ -1,5 +1,6 @@ { "compilerOptions": { "module": "ES6", + "strict": true } } \ No newline at end of file diff --git a/packages/compiler/transform/BlindGenerator.js b/packages/compiler/transform/BindGenerator.js similarity index 79% rename from packages/compiler/transform/BlindGenerator.js rename to packages/compiler/transform/BindGenerator.js index 37fa480..f2975cf 100644 --- a/packages/compiler/transform/BlindGenerator.js +++ b/packages/compiler/transform/BindGenerator.js @@ -22,6 +22,10 @@ function getOpening(element) { throw new TypeError(`Unexpected binding element type "${element.type}"`); } +const ROOT = 'r'; +const TARGETS = 'ts'; +const TARGET = 't'; +const VALUE = 'v'; export class BindGenerator extends Generator { static generate(template) { @@ -53,8 +57,9 @@ export class BindGenerator extends Generator { return; } - this.DomTargets(template, state); + this.Targets(template, state); this.Bindings(template, state); + this.Render(template, state); } JSXExpressionContainer({ expression }, state) { @@ -82,22 +87,13 @@ export class BindGenerator extends Generator { state.write(`)`); } - DomTargets(template, state) { + Targets(template, state) { const { boundElements, bindings, isBoundRoot } = template; const { length: elLength } = boundElements; - const ROOT = 'r'; - const TARGET = 't'; - - state.write(`function getTargets(`); - if(isBoundRoot) state.write(ROOT); - if(elLength) { - const targets = []; - for(let i = 0; i < elLength; i++) { - targets.push(`${TARGET}${i}`); - } - state.write(`, [${targets.join(', ')}]`); - } + state.write(`function targets(`); + state.write(ROOT); + if(elLength) state.write(`, ${TARGETS}`); state.write(') {'); state.indentLevel++; @@ -107,7 +103,7 @@ export class BindGenerator extends Generator { for(let i = 0; i < bindings.length; i++) { const { element, type, index, node } = bindings[i]; const { isRoot, queryIndex } = element; - const varName = isRoot ? ROOT : `${TARGET}${queryIndex}`; + const varName = isRoot ? ROOT : `${TARGETS}[${queryIndex}]`; if(i !== 0) state.write(', '); @@ -126,35 +122,51 @@ export class BindGenerator extends Generator { writeNextLine(state); state.write(`}`); state.write(state.lineEnd); - writeNextLine(state); } - Bindings(template, state) { - const { boundElements, bindings, node } = template; - + Render(template, state) { + const { bindings } = template; const params = []; for(let i = 0; i < bindings.length; i++) { - params.push(`p${i}`); + params.push(`${VALUE}${i}`); } - state.write(`function apply(${params.join(', ')}) {`); + + state.write(`function render(${params.join(', ')}) {`); state.indentLevel++; writeNextLine(state); - const returnStatement = template.node.returnStatement; + state.write(`const [root, bind] = makeTemplate(source);`); + writeNextLine(state); + state.write(`bind(${params.join(', ')});`); + writeNextLine(state); + state.write(`return root;`); + + state.indentLevel--; + writeNextLine(state); + state.write(`}`); + state.write(state.lineEnd); + } + Bindings(template, state) { + const { bindings } = template; - // template service renderer call - const hasTargets = !!boundElements.length; + state.write(`function bind(${TARGETS}) {`); + state.indentLevel++; + writeNextLine(state); - const vars = ['root']; + const targets = []; + const params = []; for(let i = 0; i < bindings.length; i++) { - vars.push(`t${i}`); + targets.push(`${TARGET}${i} = ${TARGETS}[${i}]`); + params.push(`${VALUE}${i}`); } - state.write(`const [${vars.join(', ')}] = getTargets();`); + state.write(`const ${targets.join(', ')};`); + writeNextLine(state); + state.write(`return (${params.join(', ')}) => {`); + state.indentLevel++; - // bindings for(let i = 0; i < bindings.length; i++) { const { element, type, node, expr } = bindings[i]; writeNextLine(state); @@ -180,23 +192,27 @@ export class BindGenerator extends Generator { throw new Error(message); } + state.indentLevel--; + writeNextLine(state); + state.write(`};`); state.indentLevel--; writeNextLine(state); state.write(`}`); + state.write(state.lineEnd); } Compose(node, expr, index, state) { state.write(`compose(`, node); - state.write(`t${index}, `, node); - state.write(`p${index}`, expr); + state.write(`${TARGET}${index}, `, node); + state.write(`${VALUE}${index}`, expr); // this[expr.type](expr, state); state.write(`);`); } ComposeElement(node, expr, index, state) { state.write(`composeElement(`, node); - state.write(`t${index}, `); + state.write(`${TARGET}${index}, `); this.CompleteElement(node, expr, state); state.write(`);`); } @@ -237,9 +253,8 @@ export class BindGenerator extends Generator { BindingProp(node, expr, index, element, state) { const { queryIndex, openingElement, openingFragment } = element; - const varName = queryIndex === -1 ? `root` : `target${queryIndex}`; const opening = openingElement ?? openingFragment; - state.write(`t${index}`, opening.name); + state.write(`${TARGET}${index}`, opening.name); // TODO: more property validation const identity = node.name; const propName = identity.name; @@ -257,7 +272,7 @@ export class BindGenerator extends Generator { /* expression */ state.write(` = `); // this[expr.type](expr, state); - state.write(`p${index}`, expr); + state.write(`${VALUE}${index}`, expr); state.write(`;`); } } diff --git a/packages/compiler/transform/bind.test.js b/packages/compiler/transform/bind.test.js index 2ecd3f8..fb925da 100644 --- a/packages/compiler/transform/bind.test.js +++ b/packages/compiler/transform/bind.test.js @@ -1,5 +1,5 @@ /* eslint-disable no-undef */ -import { BindGenerator } from './BlindGenerator.js'; +import { BindGenerator } from './BindGenerator.js'; import { parse, generate as _generate } from '../compiler.js'; import { describe, test } from 'vitest'; @@ -9,12 +9,9 @@ function preParse(input) { } describe('Bind Generator', () => { - test('Hello Bind', ({ expect }) => { + test('Hello bind', ({ expect }) => { // const input = `const t =

yo

;`; - const input = `const t =

- {"Greeting"} hey {"Azoth"}! -

;`; - + const input = `name =>

{name}

`; const initial = preParse(input); const template = initial.templates[0]; expect(template.node.type).toBe('JSXElement'); @@ -22,23 +19,30 @@ describe('Bind Generator', () => { const { code } = BindGenerator.generate(template); expect(code).toMatchInlineSnapshot(` - "function getTargets(r, [t0]) { - return [r, r.childNodes[1], t0.childNodes[1]]; + "function targets(r) { + return [r.childNodes[0]]; } - - function apply(p0, p1, p2) { - const [root, t0, t1, t2] = getTargets(); - t0.className = p0; - compose(t1, p1); - compose(t2, p2); + function bind(ts) { + const t0 = ts[0]; + return (v0) => { + compose(t0, v0); + }; + } + function render(v0) { + const [root, bind] = makeTemplate(source); + bind(v0); + return root; } " `); }); - test('Hello Bind', ({ expect }) => { + test('Bind children and props', ({ expect }) => { // const input = `const t =

yo

;`; - const input = `name =>

{name}

`; + const input = `const t =

+ {"Greeting"} hey {"Azoth"}! +

;`; + const initial = preParse(input); const template = initial.templates[0]; expect(template.node.type).toBe('JSXElement'); @@ -46,18 +50,23 @@ describe('Bind Generator', () => { const { code } = BindGenerator.generate(template); expect(code).toMatchInlineSnapshot(` - "function getTargets(r) { - return [r.childNodes[0]]; + "function targets(r, ts) { + return [r, r.childNodes[1], ts[0].childNodes[1]]; } - - function apply(p0) { - const [root, t0] = getTargets(); - compose(t0, p0); + function bind(ts) { + const t0 = ts[0], t1 = ts[1], t2 = ts[2]; + return (v0, v1, v2) => { + t0.className = v0; + compose(t1, v1); + compose(t2, v2); + }; + } + function render(v0, v1, v2) { + const [root, bind] = makeTemplate(source); + bind(v0, v1, v2); + return root; } " `); - - }); - }); \ No newline at end of file diff --git a/packages/runtime/renderer/magic.test.js b/packages/runtime/renderer/magic.test.js index 628a1a7..e85d6ee 100644 --- a/packages/runtime/renderer/magic.test.js +++ b/packages/runtime/renderer/magic.test.js @@ -1,6 +1,6 @@ import { test } from 'vitest'; import { compose } from '../compose/compose.js'; -import { makeRenderer, getBoundElements } from './renderer.js'; +import { makeRenderer, getBoundElements, makeTemplate } from './renderer.js'; // template generated artifacts const source = makeRenderer('id', `

`); @@ -12,9 +12,10 @@ function injectRender(dom, callback) { injectableRoot = null; } -function getBindTargets(r, boundEls) { +function getTargets(r, boundEls) { return [r.childNodes[0]]; } + const makeBind = targets => { const t0 = targets[0]; return p0 => { @@ -22,33 +23,13 @@ const makeBind = targets => { }; }; -const map = new Map(); - -const makeTemplate = (source) => { - let bind = null; - let root = injectableRoot; - // TODO: test injectable is right template id - - if(root) bind = map.get(root); - if(!bind) { - const result = root - ? [root, getBoundElements(root)] - : source(); - root = result[0]; - const nodes = getBindTargets(root, result[1]); - bind = makeBind(nodes); - map.set(root, bind); - } - - return [root, bind]; -}; - function render123(p0) { - const [root, bind] = makeTemplate(source); + const [root, bind] = makeTemplate(source, getTargets, makeBind); bind(p0); return root; } + class Controller { static for(renderFn) { return new this(renderFn); diff --git a/packages/runtime/renderer/renderer.js b/packages/runtime/renderer/renderer.js index 6553e07..ac7d7ea 100644 --- a/packages/runtime/renderer/renderer.js +++ b/packages/runtime/renderer/renderer.js @@ -44,4 +44,26 @@ function renderer(fragment, isFragment) { export function getBoundElements(dom) { return dom.querySelectorAll(QUERY_SELECTOR); -} \ No newline at end of file +} + + +const map = new Map(); + +export function makeTemplate(source, targets, makeBind) { + let bind = null; + let root = injectableRoot; + // TODO: test injectable is right template id + + if(root) bind = map.get(root); + if(!bind) { + const result = root + ? [root, getBoundElements(root)] + : source(); + root = result[0]; + const nodes = targets(root, result[1]); + bind = makeBind(nodes); + map.set(root, bind); + } + + return [root, bind]; +}; \ No newline at end of file From 04595e885dc6ccc0dfae0d38af0b8dd96e5628a2 Mon Sep 17 00:00:00 2001 From: Marty Nelson Date: Fri, 15 Mar 2024 19:44:46 -0700 Subject: [PATCH 06/23] oops, broken test --- packages/runtime/renderer/magic.test.js | 27 +++++++++++++++++++++---- packages/runtime/renderer/renderer.js | 4 ++-- 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/packages/runtime/renderer/magic.test.js b/packages/runtime/renderer/magic.test.js index e85d6ee..2c8c7f7 100644 --- a/packages/runtime/renderer/magic.test.js +++ b/packages/runtime/renderer/magic.test.js @@ -1,6 +1,6 @@ import { test } from 'vitest'; import { compose } from '../compose/compose.js'; -import { makeRenderer, getBoundElements, makeTemplate } from './renderer.js'; +import { makeRenderer, getBoundElements } from './renderer.js'; // template generated artifacts const source = makeRenderer('id', `

`); @@ -12,10 +12,9 @@ function injectRender(dom, callback) { injectableRoot = null; } -function getTargets(r, boundEls) { +function getBindTargets(r, boundEls) { return [r.childNodes[0]]; } - const makeBind = targets => { const t0 = targets[0]; return p0 => { @@ -24,11 +23,31 @@ const makeBind = targets => { }; function render123(p0) { - const [root, bind] = makeTemplate(source, getTargets, makeBind); + const [root, bind] = makeTemplate(source); bind(p0); return root; } +const map = new Map(); + +const makeTemplate = (source) => { + let bind = null; + let root = injectableRoot; + // TODO: test injectable is right template id + + if(root) bind = map.get(root); + if(!bind) { + const result = root + ? [root, getBoundElements(root)] + : source(); + root = result[0]; + const nodes = getBindTargets(root, result[1]); + bind = makeBind(nodes); + map.set(root, bind); + } + + return [root, bind]; +}; class Controller { static for(renderFn) { diff --git a/packages/runtime/renderer/renderer.js b/packages/runtime/renderer/renderer.js index ac7d7ea..5aa61bd 100644 --- a/packages/runtime/renderer/renderer.js +++ b/packages/runtime/renderer/renderer.js @@ -47,8 +47,8 @@ export function getBoundElements(dom) { } +/* const map = new Map(); - export function makeTemplate(source, targets, makeBind) { let bind = null; let root = injectableRoot; @@ -66,4 +66,4 @@ export function makeTemplate(source, targets, makeBind) { } return [root, bind]; -}; \ No newline at end of file +};*/ \ No newline at end of file From a63005b780f25227cf000602fa7c0aa4897e18c2 Mon Sep 17 00:00:00 2001 From: Marty Nelson Date: Fri, 15 Mar 2024 22:16:22 -0700 Subject: [PATCH 07/23] basic string child node render and update! --- packages/runtime/renderer/magic.test.js | 164 ++++++++++++++---------- packages/runtime/renderer/renderer.js | 74 +++++++++-- 2 files changed, 158 insertions(+), 80 deletions(-) diff --git a/packages/runtime/renderer/magic.test.js b/packages/runtime/renderer/magic.test.js index 2c8c7f7..3044438 100644 --- a/packages/runtime/renderer/magic.test.js +++ b/packages/runtime/renderer/magic.test.js @@ -1,20 +1,15 @@ -import { test } from 'vitest'; +import { describe, test } from 'vitest'; import { compose } from '../compose/compose.js'; -import { makeRenderer, getBoundElements } from './renderer.js'; +import { makeRenderer, makeTemplate, Controller, Updater, makeStringRenderer } from './renderer.js'; // template generated artifacts const source = makeRenderer('id', `

`); +const stringSource = makeStringRenderer('id', [`

`, `

`]); -let injectableRoot = null; -function injectRender(dom, callback) { - injectableRoot = dom; - callback(); - injectableRoot = null; -} - -function getBindTargets(r, boundEls) { +function getTargets(r, boundEls) { return [r.childNodes[0]]; } + const makeBind = targets => { const t0 = targets[0]; return p0 => { @@ -22,77 +17,106 @@ const makeBind = targets => { }; }; +function getStringTargets(r, boundEls) { + return [boundEls[0]]; +} + +const makeStringBind = targets => { + const t0 = targets[0]; + return p0 => { + t0[0] = p0; + }; +}; function render123(p0) { - const [root, bind] = makeTemplate(source); + const [root, bind] = makeTemplate( + source, + getTargets, + makeBind + ); bind(p0); return root; } +function renderString(p0) { + const [root, bind] = makeTemplate( + stringSource, + getStringTargets, + makeStringBind + ); + bind(p0); + return root; +} +/* +const NameTag = Controller.for(({ greeting, name }) => { + const Greeting = Controller.for(greeting => {greeting}); -const map = new Map(); - -const makeTemplate = (source) => { - let bind = null; - let root = injectableRoot; - // TODO: test injectable is right template id - - if(root) bind = map.get(root); - if(!bind) { - const result = root - ? [root, getBoundElements(root)] - : source(); - root = result[0]; - const nodes = getBindTargets(root, result[1]); - bind = makeBind(nodes); - map.set(root, bind); - } - - return [root, bind]; -}; + return

+ {name} +

; +}); -class Controller { - static for(renderFn) { - return new this(renderFn); - } - constructor(renderFn) { - this.renderFn = renderFn; - } - render(props) { - return this.renderFn(props); - } - update(dom, props) { - injectRender(dom, () => this.renderFn(props)); - } -} +const Hello = Controller.for(name =>

{name}

); +*/ +describe('string render', () => { + const flatRender = node => node.flat().join(''); + test('Controller.for', ({ expect }) => { + const controller = Controller.for(name => renderString(name)); -class Updater extends Controller { - #dom = null; - render(props) { - return this.#dom = super.render(props); - } - update(props) { - super.update(this.#dom, props); - } -} + let node1 = controller.render('felix'); + let node2 = controller.render('duchess'); + expect(flatRender(node1)).toMatchInlineSnapshot( + `"

felix

"` + ); + expect(flatRender(node2)).toMatchInlineSnapshot( + `"

duchess

"` + ); -test('controller creates or injects', ({ expect }) => { - const controller = Controller.for(name => render123(name)); + controller.update(node1, 'garfield'); + controller.update(node2, 'stimpy'); + expect(flatRender(node1)).toMatchInlineSnapshot( + `"

garfield

"` + ); + expect(flatRender(node2)).toMatchInlineSnapshot( + `"

stimpy

"` + ); + }); - let dom1 = controller.render('felix'); - let dom2 = controller.render('duchess'); - expect(dom1.outerHTML).toMatchInlineSnapshot(`"

felix

"`); - expect(dom2.outerHTML).toMatchInlineSnapshot(`"

duchess

"`); + test('Updater.for', ({ expect }) => { + const updater = Updater.for(name => renderString(name)); + const node = updater.render('felix'); + expect(flatRender(node)).toMatchInlineSnapshot( + `"

felix

"` + ); + + updater.update('duchess'); + expect(flatRender(node)).toMatchInlineSnapshot( + `"

duchess

"` + ); + }); - controller.update(dom1, 'garfield'); - controller.update(dom2, 'stimpy'); - expect(dom1.outerHTML).toMatchInlineSnapshot(`"

garfield

"`); - expect(dom2.outerHTML).toMatchInlineSnapshot(`"

stimpy

"`); }); +describe('dom render', () => { + + test('Controller.for', ({ expect }) => { + const controller = Controller.for(name => render123(name)); + + let node1 = controller.render('felix'); + let node2 = controller.render('duchess'); + expect(node1.outerHTML).toMatchInlineSnapshot(`"

felix

"`); + expect(node2.outerHTML).toMatchInlineSnapshot(`"

duchess

"`); + + controller.update(node1, 'garfield'); + controller.update(node2, 'stimpy'); + expect(node1.outerHTML).toMatchInlineSnapshot(`"

garfield

"`); + expect(node2.outerHTML).toMatchInlineSnapshot(`"

stimpy

"`); + }); + + test('Updater.for', ({ expect }) => { + const updater = Updater.for(name => render123(name)); + const node = updater.render('felix'); + expect(node.outerHTML).toMatchInlineSnapshot(`"

felix

"`); -test('update remembers dom', ({ expect }) => { - const updater = Updater.for(name => render123(name)); - const dom = updater.render('felix'); - expect(dom.outerHTML).toMatchInlineSnapshot(`"

felix

"`); + updater.update('duchess'); + expect(node.outerHTML).toMatchInlineSnapshot(`"

duchess

"`); + }); - updater.update('duchess'); - expect(dom.outerHTML).toMatchInlineSnapshot(`"

duchess

"`); }); diff --git a/packages/runtime/renderer/renderer.js b/packages/runtime/renderer/renderer.js index 5aa61bd..717575a 100644 --- a/packages/runtime/renderer/renderer.js +++ b/packages/runtime/renderer/renderer.js @@ -4,6 +4,21 @@ export function clearTemplates() { templates.clear(); } +export function makeStringRenderer(id, html, isFragment = false) { + return () => { + const root = []; + const targets = []; + for(let i = 0; i < html.length; i++) { + root.push(html[i]); + if(i === html.length - 1) break; + const target = []; + targets.push(target); + root.push(target); + } + return [root, targets]; + }; +} + export function makeRenderer(id, html, isFragment = false) { if(templates.has(id)) return templates.get(id); @@ -47,23 +62,62 @@ export function getBoundElements(dom) { } -/* + const map = new Map(); + + +export const injectable = []; +export function inject(node, callback) { + injectable.push(node); + callback(); + const popped = injectable.pop(); + if(popped !== node) { + // TODO: display html like object for compose + throw new Error('Injectable stack error'); + } +} + + export function makeTemplate(source, targets, makeBind) { let bind = null; - let root = injectableRoot; + let node = injectable.at(-1); // peek! // TODO: test injectable is right template id - if(root) bind = map.get(root); + if(node) bind = map.get(node); if(!bind) { - const result = root - ? [root, getBoundElements(root)] + const result = node + ? [node, getBoundElements(node)] : source(); - root = result[0]; - const nodes = targets(root, result[1]); + node = result[0]; + const nodes = targets(node, result[1]); bind = makeBind(nodes); - map.set(root, bind); + map.set(node, bind); } - return [root, bind]; -};*/ \ No newline at end of file + return [node, bind]; +} + +export class Controller { + static for(renderFn) { + return new this(renderFn); + } + constructor(renderFn) { + this.renderFn = renderFn; + } + render(props) { + return this.renderFn(props); + } + update(node, props) { + inject(node, () => this.renderFn(props)); + } +} + +export class Updater extends Controller { + #dom = null; + render(props) { + return this.#dom = super.render(props); + } + update(props) { + super.update(this.#dom, props); + } +} \ No newline at end of file From fb3a452730b7ba9a21bd5bb1c62738ce36254e05 Mon Sep 17 00:00:00 2001 From: Marty Nelson Date: Sat, 16 Mar 2024 07:44:04 -0700 Subject: [PATCH 08/23] start refactoring dom/string renderers --- packages/compiler/transform/Analyzer.js | 7 +- packages/compiler/transform/Stack.js | 19 ----- packages/runtime/package.json | 2 +- packages/runtime/renderer/index.js | 1 + packages/runtime/renderer/magic.test.js | 22 ++++-- packages/runtime/renderer/renderer.dom.js | 47 +++++++++++++ packages/runtime/renderer/renderer.js | 84 +++++------------------ 7 files changed, 86 insertions(+), 96 deletions(-) delete mode 100644 packages/compiler/transform/Stack.js create mode 100644 packages/runtime/renderer/index.js create mode 100644 packages/runtime/renderer/renderer.dom.js diff --git a/packages/compiler/transform/Analyzer.js b/packages/compiler/transform/Analyzer.js index 8792f25..0a40832 100644 --- a/packages/compiler/transform/Analyzer.js +++ b/packages/compiler/transform/Analyzer.js @@ -1,4 +1,3 @@ -import { Stack } from './Stack.js'; import { Template } from './Template.js'; import { voidElements } from './html.js'; @@ -14,7 +13,7 @@ const BINDING_ATTR = { const byOrder = (a, b) => a.order - b.order; export class Analyzer { - #elements = new Stack(); + #elements = []; // stack #documentOrder = 0; #boundElements = new Set(); #bindings = []; @@ -91,7 +90,7 @@ export class Analyzer { } #bind(type, node, expr, index) { - const element = this.#elements.current; + const element = this.#elements.at(-1); // peek element.isRoot = element === this.#root; const binding = { @@ -261,5 +260,3 @@ function trimChildren(node) { node.children = trimmed; } } - - diff --git a/packages/compiler/transform/Stack.js b/packages/compiler/transform/Stack.js deleted file mode 100644 index e4bdbba..0000000 --- a/packages/compiler/transform/Stack.js +++ /dev/null @@ -1,19 +0,0 @@ -export class Stack { - #current = null; - #stack = []; - - get current() { - return this.#current; - } - - push(context) { - this.#current = context; - this.#stack.push(context); - } - - pop() { - const context = this.#stack.pop(); - this.#current = this.#stack.at(-1); - return context; - } -} diff --git a/packages/runtime/package.json b/packages/runtime/package.json index 8f3ecd1..a13632e 100644 --- a/packages/runtime/package.json +++ b/packages/runtime/package.json @@ -27,7 +27,7 @@ ], "exports": { "./compose": "./compose/compose.js", - "./renderer": "./renderer/renderer.js" + "./renderer": "./renderer/index.js" }, "devDependencies": { "test-utils": "workspace:^" diff --git a/packages/runtime/renderer/index.js b/packages/runtime/renderer/index.js new file mode 100644 index 0000000..0b844f3 --- /dev/null +++ b/packages/runtime/renderer/index.js @@ -0,0 +1 @@ +export { makeRenderer, rendererById } from './renderer.dom.js'; \ No newline at end of file diff --git a/packages/runtime/renderer/magic.test.js b/packages/runtime/renderer/magic.test.js index 3044438..1160ba2 100644 --- a/packages/runtime/renderer/magic.test.js +++ b/packages/runtime/renderer/magic.test.js @@ -1,6 +1,15 @@ import { describe, test } from 'vitest'; import { compose } from '../compose/compose.js'; -import { makeRenderer, makeTemplate, Controller, Updater, makeStringRenderer } from './renderer.js'; +import { + makeRenderer, + getBoundElements, +} from './renderer.dom.js'; +import { + makeTemplate, + makeStringRenderer, + Controller, + Updater, +} from './renderer.js'; // template generated artifacts const source = makeRenderer('id', `

`); @@ -27,11 +36,12 @@ const makeStringBind = targets => { t0[0] = p0; }; }; -function render123(p0) { +function renderDOM(p0) { const [root, bind] = makeTemplate( source, getTargets, - makeBind + makeBind, + getBoundElements, ); bind(p0); return root; @@ -58,7 +68,7 @@ const Hello = Controller.for(name =>

{name}

); */ describe('string render', () => { const flatRender = node => node.flat().join(''); - test('Controller.for', ({ expect }) => { + test.only('Controller.for', ({ expect }) => { const controller = Controller.for(name => renderString(name)); let node1 = controller.render('felix'); @@ -97,7 +107,7 @@ describe('string render', () => { describe('dom render', () => { test('Controller.for', ({ expect }) => { - const controller = Controller.for(name => render123(name)); + const controller = Controller.for(name => renderDOM(name)); let node1 = controller.render('felix'); let node2 = controller.render('duchess'); @@ -111,7 +121,7 @@ describe('dom render', () => { }); test('Updater.for', ({ expect }) => { - const updater = Updater.for(name => render123(name)); + const updater = Updater.for(name => renderDOM(name)); const node = updater.render('felix'); expect(node.outerHTML).toMatchInlineSnapshot(`"

felix

"`); diff --git a/packages/runtime/renderer/renderer.dom.js b/packages/runtime/renderer/renderer.dom.js new file mode 100644 index 0000000..3b6fec1 --- /dev/null +++ b/packages/runtime/renderer/renderer.dom.js @@ -0,0 +1,47 @@ +const templates = new Map(); + +export function clearTemplates() { + templates.clear(); +} + +export function makeRenderer(id, html, isFragment = false) { + if(templates.has(id)) return templates.get(id); + + const template = document.createElement('template'); + template.innerHTML = html; + return rendererFactory(id, template.content, isFragment); +} + +export function rendererById(id, isFragment = false) { + if(templates.has(id)) return templates.get(id); + + const templateEl = document.getElementById(id); + if(!templateEl) { + throw new Error(`No template with id "${id}"`); + } + + return rendererFactory(id, templateEl.content, isFragment); +} + +function rendererFactory(id, node, isFragment) { + const render = renderer(node, isFragment); + templates.set(id, render); + return render; +} + +const QUERY_SELECTOR = '[data-bind]'; + +function renderer(fragment, isFragment) { + if(!isFragment) fragment = fragment.firstElementChild; + // TODO: malformed fragments...necessary? + + return function render() { + const clone = fragment.cloneNode(true); + const targets = clone.querySelectorAll(QUERY_SELECTOR); + return [clone, targets]; + }; +} + +export function getBoundElements(dom) { + return dom.querySelectorAll(QUERY_SELECTOR); +} diff --git a/packages/runtime/renderer/renderer.js b/packages/runtime/renderer/renderer.js index 717575a..b6c8d40 100644 --- a/packages/runtime/renderer/renderer.js +++ b/packages/runtime/renderer/renderer.js @@ -1,8 +1,3 @@ -const templates = new Map(); - -export function clearTemplates() { - templates.clear(); -} export function makeStringRenderer(id, html, isFragment = false) { return () => { @@ -19,54 +14,8 @@ export function makeStringRenderer(id, html, isFragment = false) { }; } -export function makeRenderer(id, html, isFragment = false) { - if(templates.has(id)) return templates.get(id); - - const template = document.createElement('template'); - template.innerHTML = html; - return rendererFactory(id, template.content, isFragment); -} - -export function rendererById(id, isFragment = false) { - if(templates.has(id)) return templates.get(id); - - const templateEl = document.getElementById(id); - if(!templateEl) { - throw new Error(`No template with id "${id}"`); - } - - return rendererFactory(id, templateEl.content, isFragment); -} - -function rendererFactory(id, node, isFragment) { - const render = renderer(node, isFragment); - templates.set(id, render); - return render; -} - -const QUERY_SELECTOR = '[data-bind]'; - -function renderer(fragment, isFragment) { - if(!isFragment) fragment = fragment.firstElementChild; - // TODO: malformed fragments...necessary? - - return function render() { - const clone = fragment.cloneNode(true); - const targets = clone.querySelectorAll(QUERY_SELECTOR); - return [clone, targets]; - }; -} - -export function getBoundElements(dom) { - return dom.querySelectorAll(QUERY_SELECTOR); -} - - - -const map = new Map(); - - -export const injectable = []; +// stack +const injectable = []; export function inject(node, callback) { injectable.push(node); callback(); @@ -77,23 +26,28 @@ export function inject(node, callback) { } } +const map = new Map(); -export function makeTemplate(source, targets, makeBind) { +export function makeTemplate(source, targets, makeBind, getBound) { let bind = null; + let boundEls = null; let node = injectable.at(-1); // peek! + // TODO: test injectable is right template id if(node) bind = map.get(node); - if(!bind) { - const result = node - ? [node, getBoundElements(node)] - : source(); - node = result[0]; - const nodes = targets(node, result[1]); - bind = makeBind(nodes); - map.set(node, bind); + if(bind) return [node, bind]; + + if(node) boundEls = getBound(node); + else { + // (destructured re-assignment) + ([node, boundEls] = source()); } + const nodes = targets(node, boundEls); + bind = makeBind(nodes); + + map.set(node, bind); return [node, bind]; } @@ -113,11 +67,11 @@ export class Controller { } export class Updater extends Controller { - #dom = null; + #node = null; render(props) { - return this.#dom = super.render(props); + return this.#node = super.render(props); } update(props) { - super.update(this.#dom, props); + super.update(this.#node, props); } } \ No newline at end of file From d1e59e82a222f302511b53a865d84f6f2c4c0361 Mon Sep 17 00:00:00 2001 From: Marty Nelson Date: Sat, 16 Mar 2024 07:44:10 -0700 Subject: [PATCH 09/23] deps update --- docs/pnpm-lock.yaml | 22 ++++++------- package.json | 4 +-- pnpm-lock.yaml | 70 ++++++++++++++++++++-------------------- vite-test/package.json | 2 +- vite-test/pnpm-lock.yaml | 60 +++++++++++++++++----------------- 5 files changed, 79 insertions(+), 79 deletions(-) diff --git a/docs/pnpm-lock.yaml b/docs/pnpm-lock.yaml index 9b8e3cc..559ee12 100644 --- a/docs/pnpm-lock.yaml +++ b/docs/pnpm-lock.yaml @@ -530,14 +530,14 @@ packages: dev: true optional: true - /@shikijs/core@1.1.7: - resolution: {integrity: sha512-gTYLUIuD1UbZp/11qozD3fWpUTuMqPSf3svDMMrL0UmlGU7D9dPw/V1FonwAorCUJBltaaESxq90jrSjQyGixg==} + /@shikijs/core@1.2.0: + resolution: {integrity: sha512-OlFvx+nyr5C8zpcMBnSGir0YPD6K11uYhouqhNmm1qLiis4GA7SsGtu07r9gKS9omks8RtQqHrJL4S+lqWK01A==} dev: true - /@shikijs/transformers@1.1.7: - resolution: {integrity: sha512-lXz011ao4+rvweps/9h3CchBfzb1U5OtP5D51Tqc9lQYdLblWMIxQxH6Ybe1GeGINcEVM4goMyPrI0JvlIp4UQ==} + /@shikijs/transformers@1.2.0: + resolution: {integrity: sha512-xKn7DtA65DQV4FOfYsrvqM80xOy2xuXnxWWKsZmHv1VII/IOuDUDsWDu3KnpeLH6wqNJWp1GRoNUsHR1aw/VhQ==} dependencies: - shiki: 1.1.7 + shiki: 1.2.0 dev: true /@types/estree@1.0.5: @@ -907,10 +907,10 @@ packages: resolution: {integrity: sha512-Orrsjf9trHHxFRuo9/rzm0KIWmgzE8RMlZMzuhZOJ01Rnz3D0YBAe+V6473t6/H6c7irs6Lt48brULAiRWb3Vw==} dev: true - /shiki@1.1.7: - resolution: {integrity: sha512-9kUTMjZtcPH3i7vHunA6EraTPpPOITYTdA5uMrvsJRexktqP0s7P3s9HVK80b4pP42FRVe03D7fT3NmJv2yYhw==} + /shiki@1.2.0: + resolution: {integrity: sha512-xLhiTMOIUXCv5DqJ4I70GgQCtdlzsTqFLZWcMHHG3TAieBUbvEGthdrlPDlX4mL/Wszx9C6rEcxU6kMlg4YlxA==} dependencies: - '@shikijs/core': 1.1.7 + '@shikijs/core': 1.2.0 dev: true /source-map-js@1.0.2: @@ -981,8 +981,8 @@ packages: dependencies: '@docsearch/css': 3.6.0 '@docsearch/js': 3.6.0(@algolia/client-search@4.22.1)(search-insights@2.13.0) - '@shikijs/core': 1.1.7 - '@shikijs/transformers': 1.1.7 + '@shikijs/core': 1.2.0 + '@shikijs/transformers': 1.2.0 '@types/markdown-it': 13.0.7 '@vitejs/plugin-vue': 5.0.4(vite@5.1.6)(vue@3.4.21) '@vue/devtools-api': 7.0.17(vue@3.4.21) @@ -991,7 +991,7 @@ packages: focus-trap: 7.5.4 mark.js: 8.11.1 minisearch: 6.3.0 - shiki: 1.1.7 + shiki: 1.2.0 vite: 5.1.6 vue: 3.4.21 transitivePeerDependencies: diff --git a/package.json b/package.json index 7c1af8a..129f407 100644 --- a/package.json +++ b/package.json @@ -22,10 +22,10 @@ "azoth": "workspace:./packages/azoth", "eslint": "^8.57.0", "globals": "^14.0.0", - "happy-dom": "^13.8.2", + "happy-dom": "^13.8.6", "vite": "^5.1.6", "vite-plugin-inspect": "^0.8.3", - "vitest": "^1.3.1" + "vitest": "^1.4.0" }, "packageManager": "^pnpm@8.15.1" } \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f2e6877..5c56a61 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -18,8 +18,8 @@ devDependencies: specifier: ^14.0.0 version: 14.0.0 happy-dom: - specifier: ^13.8.2 - version: 13.8.2 + specifier: ^13.8.6 + version: 13.8.6 vite: specifier: ^5.1.6 version: 5.1.6 @@ -27,8 +27,8 @@ devDependencies: specifier: ^0.8.3 version: 0.8.3(vite@5.1.6) vitest: - specifier: ^1.3.1 - version: 1.3.1(happy-dom@13.8.2) + specifier: ^1.4.0 + version: 1.4.0(happy-dom@13.8.6) packages: @@ -518,38 +518,38 @@ packages: resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} dev: true - /@vitest/expect@1.3.1: - resolution: {integrity: sha512-xofQFwIzfdmLLlHa6ag0dPV8YsnKOCP1KdAeVVh34vSjN2dcUiXYCD9htu/9eM7t8Xln4v03U9HLxLpPlsXdZw==} + /@vitest/expect@1.4.0: + resolution: {integrity: sha512-Jths0sWCJZ8BxjKe+p+eKsoqev1/T8lYcrjavEaz8auEJ4jAVY0GwW3JKmdVU4mmNPLPHixh4GNXP7GFtAiDHA==} dependencies: - '@vitest/spy': 1.3.1 - '@vitest/utils': 1.3.1 + '@vitest/spy': 1.4.0 + '@vitest/utils': 1.4.0 chai: 4.4.1 dev: true - /@vitest/runner@1.3.1: - resolution: {integrity: sha512-5FzF9c3jG/z5bgCnjr8j9LNq/9OxV2uEBAITOXfoe3rdZJTdO7jzThth7FXv/6b+kdY65tpRQB7WaKhNZwX+Kg==} + /@vitest/runner@1.4.0: + resolution: {integrity: sha512-EDYVSmesqlQ4RD2VvWo3hQgTJ7ZrFQ2VSJdfiJiArkCerDAGeyF1i6dHkmySqk573jLp6d/cfqCN+7wUB5tLgg==} dependencies: - '@vitest/utils': 1.3.1 + '@vitest/utils': 1.4.0 p-limit: 5.0.0 pathe: 1.1.2 dev: true - /@vitest/snapshot@1.3.1: - resolution: {integrity: sha512-EF++BZbt6RZmOlE3SuTPu/NfwBF6q4ABS37HHXzs2LUVPBLx2QoY/K0fKpRChSo8eLiuxcbCVfqKgx/dplCDuQ==} + /@vitest/snapshot@1.4.0: + resolution: {integrity: sha512-saAFnt5pPIA5qDGxOHxJ/XxhMFKkUSBJmVt5VgDsAqPTX6JP326r5C/c9UuCMPoXNzuudTPsYDZCoJ5ilpqG2A==} dependencies: magic-string: 0.30.8 pathe: 1.1.2 pretty-format: 29.7.0 dev: true - /@vitest/spy@1.3.1: - resolution: {integrity: sha512-xAcW+S099ylC9VLU7eZfdT9myV67Nor9w9zhf0mGCYJSO+zM2839tOeROTdikOi/8Qeusffvxb/MyBSOja1Uig==} + /@vitest/spy@1.4.0: + resolution: {integrity: sha512-Ywau/Qs1DzM/8Uc+yA77CwSegizMlcgTJuYGAi0jujOteJOUf1ujunHThYo243KG9nAyWT3L9ifPYZ5+As/+6Q==} dependencies: tinyspy: 2.2.1 dev: true - /@vitest/utils@1.3.1: - resolution: {integrity: sha512-d3Waie/299qqRyHTm2DjADeTaNdNSVsnwHPWrs20JMpjh6eiVq7ggggweO8rc4arhf6rRkWuHKwvxGvejUXZZQ==} + /@vitest/utils@1.4.0: + resolution: {integrity: sha512-mx3Yd1/6e2Vt/PUC98DcqTirtfxUyAZ32uK82r8rZzbtBeBo+nqgnjx/LvqQdWsrvNtm14VmurNgcf4nqY5gJg==} dependencies: diff-sequences: 29.6.3 estree-walker: 3.0.3 @@ -1185,8 +1185,8 @@ packages: resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} dev: true - /happy-dom@13.8.2: - resolution: {integrity: sha512-u9KxyeQNIzkJDR2iCitKeS5Uy0YUv5eOntpO8e7ZzbDVv4kP5Y77Zo2LnZitwMrss/1pY2Uc2e5qOVGkiKE5Gg==} + /happy-dom@13.8.6: + resolution: {integrity: sha512-Urcv2jvNel19QirWimOwYTW3slpEYGS8PLtzEwAlpTWpnKycXF8s0I7xUBK9fPvWAIc8uZf/CnV2cIwWE8jptw==} engines: {node: '>=16.0.0'} dependencies: entities: 4.5.0 @@ -1544,7 +1544,7 @@ packages: acorn: 8.11.3 pathe: 1.1.2 pkg-types: 1.0.3 - ufo: 1.4.0 + ufo: 1.5.1 dev: true /mrmime@2.0.0: @@ -1990,8 +1990,8 @@ packages: engines: {node: '>=10'} dev: true - /ufo@1.4.0: - resolution: {integrity: sha512-Hhy+BhRBleFjpJ2vchUNN40qgkh0366FWJGqVLYBHev0vpHTrXSA0ryT+74UiW6KWsldNurQMKGqCm1M2zBciQ==} + /ufo@1.5.1: + resolution: {integrity: sha512-HGyF79+/qZ4soRvM+nHERR2pJ3VXDZ/8sL1uLahdgEDf580NkgiWOxLk33FetExqOWp352JZRsgXbG/4MaGOSg==} dev: true /universalify@2.0.1: @@ -2005,8 +2005,8 @@ packages: punycode: 2.3.1 dev: true - /vite-node@1.3.1: - resolution: {integrity: sha512-azbRrqRxlWTJEVbzInZCTchx0X69M/XPTCz4H+TLvlTcR/xH/3hkRqhOakT41fMJCMzXTu4UvegkZiEoJAWvng==} + /vite-node@1.4.0: + resolution: {integrity: sha512-VZDAseqjrHgNd4Kh8icYHWzTKSCZMhia7GyHfhtzLW33fZlG9SwsB6CEhgyVOWkJfJ2pFLrp/Gj1FSfAiqH9Lw==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true dependencies: @@ -2086,15 +2086,15 @@ packages: fsevents: 2.3.3 dev: true - /vitest@1.3.1(happy-dom@13.8.2): - resolution: {integrity: sha512-/1QJqXs8YbCrfv/GPQ05wAZf2eakUPLPa18vkJAKE7RXOKfVHqMZZ1WlTjiwl6Gcn65M5vpNUB6EFLnEdRdEXQ==} + /vitest@1.4.0(happy-dom@13.8.6): + resolution: {integrity: sha512-gujzn0g7fmwf83/WzrDTnncZt2UiXP41mHuFYFrdwaLRVQ6JYQEiME2IfEjU3vcFL3VKa75XhI3lFgn+hfVsQw==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' '@types/node': ^18.0.0 || >=20.0.0 - '@vitest/browser': 1.3.1 - '@vitest/ui': 1.3.1 + '@vitest/browser': 1.4.0 + '@vitest/ui': 1.4.0 happy-dom: '*' jsdom: '*' peerDependenciesMeta: @@ -2111,16 +2111,16 @@ packages: jsdom: optional: true dependencies: - '@vitest/expect': 1.3.1 - '@vitest/runner': 1.3.1 - '@vitest/snapshot': 1.3.1 - '@vitest/spy': 1.3.1 - '@vitest/utils': 1.3.1 + '@vitest/expect': 1.4.0 + '@vitest/runner': 1.4.0 + '@vitest/snapshot': 1.4.0 + '@vitest/spy': 1.4.0 + '@vitest/utils': 1.4.0 acorn-walk: 8.3.2 chai: 4.4.1 debug: 4.3.4 execa: 8.0.1 - happy-dom: 13.8.2 + happy-dom: 13.8.6 local-pkg: 0.5.0 magic-string: 0.30.8 pathe: 1.1.2 @@ -2130,7 +2130,7 @@ packages: tinybench: 2.6.0 tinypool: 0.8.2 vite: 5.1.6 - vite-node: 1.3.1 + vite-node: 1.4.0 why-is-node-running: 2.2.2 transitivePeerDependencies: - less diff --git a/vite-test/package.json b/vite-test/package.json index 1a959b1..f3c43a4 100644 --- a/vite-test/package.json +++ b/vite-test/package.json @@ -37,7 +37,7 @@ }, "devDependencies": { "vite": "^5.1.6", - "vitest": "^1.3.1" + "vitest": "^1.4.0" }, "dependencies": { "azoth": "workspace:*" diff --git a/vite-test/pnpm-lock.yaml b/vite-test/pnpm-lock.yaml index a18aab6..b80dd4b 100644 --- a/vite-test/pnpm-lock.yaml +++ b/vite-test/pnpm-lock.yaml @@ -14,8 +14,8 @@ devDependencies: specifier: ^5.1.6 version: 5.1.6 vitest: - specifier: ^1.3.1 - version: 1.3.1 + specifier: ^1.4.0 + version: 1.4.0 packages: @@ -349,38 +349,38 @@ packages: resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} dev: true - /@vitest/expect@1.3.1: - resolution: {integrity: sha512-xofQFwIzfdmLLlHa6ag0dPV8YsnKOCP1KdAeVVh34vSjN2dcUiXYCD9htu/9eM7t8Xln4v03U9HLxLpPlsXdZw==} + /@vitest/expect@1.4.0: + resolution: {integrity: sha512-Jths0sWCJZ8BxjKe+p+eKsoqev1/T8lYcrjavEaz8auEJ4jAVY0GwW3JKmdVU4mmNPLPHixh4GNXP7GFtAiDHA==} dependencies: - '@vitest/spy': 1.3.1 - '@vitest/utils': 1.3.1 + '@vitest/spy': 1.4.0 + '@vitest/utils': 1.4.0 chai: 4.4.1 dev: true - /@vitest/runner@1.3.1: - resolution: {integrity: sha512-5FzF9c3jG/z5bgCnjr8j9LNq/9OxV2uEBAITOXfoe3rdZJTdO7jzThth7FXv/6b+kdY65tpRQB7WaKhNZwX+Kg==} + /@vitest/runner@1.4.0: + resolution: {integrity: sha512-EDYVSmesqlQ4RD2VvWo3hQgTJ7ZrFQ2VSJdfiJiArkCerDAGeyF1i6dHkmySqk573jLp6d/cfqCN+7wUB5tLgg==} dependencies: - '@vitest/utils': 1.3.1 + '@vitest/utils': 1.4.0 p-limit: 5.0.0 pathe: 1.1.2 dev: true - /@vitest/snapshot@1.3.1: - resolution: {integrity: sha512-EF++BZbt6RZmOlE3SuTPu/NfwBF6q4ABS37HHXzs2LUVPBLx2QoY/K0fKpRChSo8eLiuxcbCVfqKgx/dplCDuQ==} + /@vitest/snapshot@1.4.0: + resolution: {integrity: sha512-saAFnt5pPIA5qDGxOHxJ/XxhMFKkUSBJmVt5VgDsAqPTX6JP326r5C/c9UuCMPoXNzuudTPsYDZCoJ5ilpqG2A==} dependencies: magic-string: 0.30.8 pathe: 1.1.2 pretty-format: 29.7.0 dev: true - /@vitest/spy@1.3.1: - resolution: {integrity: sha512-xAcW+S099ylC9VLU7eZfdT9myV67Nor9w9zhf0mGCYJSO+zM2839tOeROTdikOi/8Qeusffvxb/MyBSOja1Uig==} + /@vitest/spy@1.4.0: + resolution: {integrity: sha512-Ywau/Qs1DzM/8Uc+yA77CwSegizMlcgTJuYGAi0jujOteJOUf1ujunHThYo243KG9nAyWT3L9ifPYZ5+As/+6Q==} dependencies: tinyspy: 2.2.1 dev: true - /@vitest/utils@1.3.1: - resolution: {integrity: sha512-d3Waie/299qqRyHTm2DjADeTaNdNSVsnwHPWrs20JMpjh6eiVq7ggggweO8rc4arhf6rRkWuHKwvxGvejUXZZQ==} + /@vitest/utils@1.4.0: + resolution: {integrity: sha512-mx3Yd1/6e2Vt/PUC98DcqTirtfxUyAZ32uK82r8rZzbtBeBo+nqgnjx/LvqQdWsrvNtm14VmurNgcf4nqY5gJg==} dependencies: diff-sequences: 29.6.3 estree-walker: 3.0.3 @@ -592,7 +592,7 @@ packages: acorn: 8.11.3 pathe: 1.1.2 pkg-types: 1.0.3 - ufo: 1.4.0 + ufo: 1.5.1 dev: true /ms@2.1.2: @@ -765,12 +765,12 @@ packages: engines: {node: '>=4'} dev: true - /ufo@1.4.0: - resolution: {integrity: sha512-Hhy+BhRBleFjpJ2vchUNN40qgkh0366FWJGqVLYBHev0vpHTrXSA0ryT+74UiW6KWsldNurQMKGqCm1M2zBciQ==} + /ufo@1.5.1: + resolution: {integrity: sha512-HGyF79+/qZ4soRvM+nHERR2pJ3VXDZ/8sL1uLahdgEDf580NkgiWOxLk33FetExqOWp352JZRsgXbG/4MaGOSg==} dev: true - /vite-node@1.3.1: - resolution: {integrity: sha512-azbRrqRxlWTJEVbzInZCTchx0X69M/XPTCz4H+TLvlTcR/xH/3hkRqhOakT41fMJCMzXTu4UvegkZiEoJAWvng==} + /vite-node@1.4.0: + resolution: {integrity: sha512-VZDAseqjrHgNd4Kh8icYHWzTKSCZMhia7GyHfhtzLW33fZlG9SwsB6CEhgyVOWkJfJ2pFLrp/Gj1FSfAiqH9Lw==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true dependencies: @@ -825,15 +825,15 @@ packages: fsevents: 2.3.3 dev: true - /vitest@1.3.1: - resolution: {integrity: sha512-/1QJqXs8YbCrfv/GPQ05wAZf2eakUPLPa18vkJAKE7RXOKfVHqMZZ1WlTjiwl6Gcn65M5vpNUB6EFLnEdRdEXQ==} + /vitest@1.4.0: + resolution: {integrity: sha512-gujzn0g7fmwf83/WzrDTnncZt2UiXP41mHuFYFrdwaLRVQ6JYQEiME2IfEjU3vcFL3VKa75XhI3lFgn+hfVsQw==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' '@types/node': ^18.0.0 || >=20.0.0 - '@vitest/browser': 1.3.1 - '@vitest/ui': 1.3.1 + '@vitest/browser': 1.4.0 + '@vitest/ui': 1.4.0 happy-dom: '*' jsdom: '*' peerDependenciesMeta: @@ -850,11 +850,11 @@ packages: jsdom: optional: true dependencies: - '@vitest/expect': 1.3.1 - '@vitest/runner': 1.3.1 - '@vitest/snapshot': 1.3.1 - '@vitest/spy': 1.3.1 - '@vitest/utils': 1.3.1 + '@vitest/expect': 1.4.0 + '@vitest/runner': 1.4.0 + '@vitest/snapshot': 1.4.0 + '@vitest/spy': 1.4.0 + '@vitest/utils': 1.4.0 acorn-walk: 8.3.2 chai: 4.4.1 debug: 4.3.4 @@ -868,7 +868,7 @@ packages: tinybench: 2.6.0 tinypool: 0.8.2 vite: 5.1.6 - vite-node: 1.3.1 + vite-node: 1.4.0 why-is-node-running: 2.2.2 transitivePeerDependencies: - less From 5b24646d823a19b18fdf2051b59d4cef238bb0c1 Mon Sep 17 00:00:00 2001 From: Marty Nelson Date: Sat, 16 Mar 2024 10:20:19 -0700 Subject: [PATCH 10/23] small refactor --- packages/runtime/renderer/magic.test.js | 31 ++++++++++++++++---- packages/runtime/renderer/renderer.dom.js | 2 +- packages/runtime/renderer/renderer.js | 33 +++++++++++++++++----- packages/runtime/renderer/renderer.test.js | 2 +- 4 files changed, 54 insertions(+), 14 deletions(-) diff --git a/packages/runtime/renderer/magic.test.js b/packages/runtime/renderer/magic.test.js index 1160ba2..99653b2 100644 --- a/packages/runtime/renderer/magic.test.js +++ b/packages/runtime/renderer/magic.test.js @@ -2,13 +2,15 @@ import { describe, test } from 'vitest'; import { compose } from '../compose/compose.js'; import { makeRenderer, - getBoundElements, + getDOMBound, } from './renderer.dom.js'; import { makeTemplate, makeStringRenderer, + getStringBound, Controller, Updater, + clearBind, } from './renderer.js'; // template generated artifacts @@ -41,7 +43,7 @@ function renderDOM(p0) { source, getTargets, makeBind, - getBoundElements, + getDOMBound, ); bind(p0); return root; @@ -50,14 +52,15 @@ function renderString(p0) { const [root, bind] = makeTemplate( stringSource, getStringTargets, - makeStringBind + makeStringBind, + getStringBound, ); bind(p0); return root; } /* const NameTag = Controller.for(({ greeting, name }) => { - const Greeting = Controller.for(greeting => {greeting}); + const Greeting = Updater.for(greeting => {greeting}); return

{name} @@ -68,7 +71,8 @@ const Hello = Controller.for(name =>

{name}

); */ describe('string render', () => { const flatRender = node => node.flat().join(''); - test.only('Controller.for', ({ expect }) => { + + test('Controller.for', ({ expect }) => { const controller = Controller.for(name => renderString(name)); let node1 = controller.render('felix'); @@ -90,6 +94,14 @@ describe('string render', () => { ); }); + test('inject unknown node', ({ expect }) => { + const controller = Controller.for(name => renderString(name)); + let node = controller.render('felix'); + clearBind(node); + controller.update(node, 'garfield'); + expect(flatRender(node)).toMatchInlineSnapshot(`"

garfield

"`); + }); + test('Updater.for', ({ expect }) => { const updater = Updater.for(name => renderString(name)); const node = updater.render('felix'); @@ -104,6 +116,7 @@ describe('string render', () => { }); }); + describe('dom render', () => { test('Controller.for', ({ expect }) => { @@ -120,6 +133,14 @@ describe('dom render', () => { expect(node2.outerHTML).toMatchInlineSnapshot(`"

stimpy

"`); }); + test('inject unknown node creates bind', ({ expect }) => { + const controller = Controller.for(name => renderDOM(name)); + let node = controller.render('felix'); + clearBind(node); + controller.update(node, 'garfield'); + expect(node.outerHTML).toMatchInlineSnapshot(`"

garfield1

"`); + }); + test('Updater.for', ({ expect }) => { const updater = Updater.for(name => renderDOM(name)); const node = updater.render('felix'); diff --git a/packages/runtime/renderer/renderer.dom.js b/packages/runtime/renderer/renderer.dom.js index 3b6fec1..525cec9 100644 --- a/packages/runtime/renderer/renderer.dom.js +++ b/packages/runtime/renderer/renderer.dom.js @@ -42,6 +42,6 @@ function renderer(fragment, isFragment) { }; } -export function getBoundElements(dom) { +export function getDOMBound(dom) { return dom.querySelectorAll(QUERY_SELECTOR); } diff --git a/packages/runtime/renderer/renderer.js b/packages/runtime/renderer/renderer.js index b6c8d40..6db077a 100644 --- a/packages/runtime/renderer/renderer.js +++ b/packages/runtime/renderer/renderer.js @@ -1,19 +1,29 @@ export function makeStringRenderer(id, html, isFragment = false) { + const template = []; + for(let i = 0; i < html.length; i++) { + if(i !== 0) template.push(null); + template.push(html[i]); + } + return () => { - const root = []; + const root = template.slice(); const targets = []; - for(let i = 0; i < html.length; i++) { - root.push(html[i]); - if(i === html.length - 1) break; - const target = []; - targets.push(target); - root.push(target); + for(let i = 1; i < root.length; i += 2) { + targets.push(root[i] = []); } return [root, targets]; }; } +export function getStringBound(root) { + const targets = []; + for(let i = 1; i < root.length; i += 2) { + targets.push(root[i] = []); + } + return targets; +} + // stack const injectable = []; export function inject(node, callback) { @@ -28,6 +38,11 @@ export function inject(node, callback) { const map = new Map(); +// TODO: will evolve once "clean-up" happens +export function clearBind(node) { + if(map.has(node)) map.delete(node); +} + export function makeTemplate(source, targets, makeBind, getBound) { let bind = null; let boundEls = null; @@ -38,6 +53,10 @@ export function makeTemplate(source, targets, makeBind, getBound) { if(node) bind = map.get(node); if(bind) return [node, bind]; + // use case would be list component optimize by + // not keeping bind functions, + // honestly not sure this really needed, the + // overhead is small as it is simple function if(node) boundEls = getBound(node); else { // (destructured re-assignment) diff --git a/packages/runtime/renderer/renderer.test.js b/packages/runtime/renderer/renderer.test.js index 1e8e1aa..e435771 100644 --- a/packages/runtime/renderer/renderer.test.js +++ b/packages/runtime/renderer/renderer.test.js @@ -1,5 +1,5 @@ import { beforeEach, describe, test } from 'vitest'; -import { clearTemplates, makeRenderer } from './renderer.js'; +import { clearTemplates, makeRenderer } from './renderer.dom.js'; describe('isFragment', () => { From 032e3274290592dbb88902354aeb327c8e0dbc65 Mon Sep 17 00:00:00 2001 From: Marty Nelson Date: Sat, 16 Mar 2024 18:41:23 -0700 Subject: [PATCH 11/23] remove .only on test, refactor some code in renderer --- packages/compiler/compiler.test.js | 2 +- packages/runtime/renderer/renderer.js | 15 ++++++++------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/packages/compiler/compiler.test.js b/packages/compiler/compiler.test.js index 00e75f8..3256eec 100644 --- a/packages/compiler/compiler.test.js +++ b/packages/compiler/compiler.test.js @@ -16,7 +16,7 @@ const compile = input => { describe('JSX dom literals', () => { - test.only('Hello Azoth', ({ expect }) => { + test('Hello Azoth', ({ expect }) => { const input = `const t =

Hello {"Azoth"}

;`; diff --git a/packages/runtime/renderer/renderer.js b/packages/runtime/renderer/renderer.js index 6db077a..8bf88da 100644 --- a/packages/runtime/renderer/renderer.js +++ b/packages/runtime/renderer/renderer.js @@ -24,6 +24,14 @@ export function getStringBound(root) { return targets; } + +const map = new Map(); + +// TODO: cleanup actions on nodes +export function clearBind(node) { + if(map.has(node)) map.delete(node); +} + // stack const injectable = []; export function inject(node, callback) { @@ -36,13 +44,6 @@ export function inject(node, callback) { } } -const map = new Map(); - -// TODO: will evolve once "clean-up" happens -export function clearBind(node) { - if(map.has(node)) map.delete(node); -} - export function makeTemplate(source, targets, makeBind, getBound) { let bind = null; let boundEls = null; From 2dd6900532c77c7b7fecafef4f02e19c79a785fc Mon Sep 17 00:00:00 2001 From: Marty Nelson Date: Sun, 17 Mar 2024 07:44:54 -0700 Subject: [PATCH 12/23] remove class constructor then calling render with props --- .../runtime/compose/compose.element.test.js | 15 ++----- packages/runtime/compose/compose.js | 41 +++++++++---------- packages/runtime/compose/compose.test.js | 5 +-- 3 files changed, 24 insertions(+), 37 deletions(-) diff --git a/packages/runtime/compose/compose.element.test.js b/packages/runtime/compose/compose.element.test.js index 4bc4933..da340df 100644 --- a/packages/runtime/compose/compose.element.test.js +++ b/packages/runtime/compose/compose.element.test.js @@ -20,17 +20,13 @@ class ClassComp { } } const ArrowComp = ({ name }) => runCompose(name, elementWithAnchor); -class ClassCompRender { - render({ name }) { - return runCompose(name, elementWithAnchor); - } -} + const RenderObject = { render({ name }) { return runCompose(name, elementWithAnchor); } }; -const CONSTRUCTORS = [Component, ClassComp, RenderObject, ClassCompRender, ArrowComp]; +const CONSTRUCTORS = [Component, ClassComp, RenderObject, ArrowComp]; function ComponentP({ name }) { return Promise.resolve(runCompose(name, elementWithAnchor)); @@ -48,13 +44,8 @@ const RenderObjectP = { return runCompose(name, elementWithAnchor); } }; -class ClassCompRenderP { - async render({ name }) { - return () => runCompose(name, elementWithAnchor); - } -} const ArrowCompP = async ({ name }) => runCompose(name, elementWithAnchor); -const ASYNC_CONSTRUCTORS = [ComponentP, ClassCompP, RenderObjectP, ClassCompRenderP, ArrowCompP]; +const ASYNC_CONSTRUCTORS = [ComponentP, ClassCompP, RenderObjectP, ArrowCompP]; describe('create element', () => { diff --git a/packages/runtime/compose/compose.js b/packages/runtime/compose/compose.js index 062303d..f93453a 100644 --- a/packages/runtime/compose/compose.js +++ b/packages/runtime/compose/compose.js @@ -80,7 +80,7 @@ export function composeElement(anchor, Constructor, props, slottable) { export function createElement(Constructor, props, slottable) { const result = create(Constructor, props, slottable); // result is returned to caller, force to be of type Node - // and convert strings and numbers into text nodes + // by converting strings and numbers into text nodes const type = typeof result; if(type === 'string' || type === 'number') { return document.createTextNode(result); @@ -104,10 +104,7 @@ function create(input, props, slottable, anchor) { return anchor ? void compose(anchor, input) : input; case !!(input.prototype?.constructor): { // eslint-disable-next-line new-cap - const created = new input(props, slottable); - return isRenderObject(created) - ? create(created, props, slottable, anchor) - : create(created, null, null, anchor); + return create(new input(props, slottable), null, null, anchor); } case type === 'function': return create(input(props, slottable), null, null, anchor); @@ -115,27 +112,29 @@ function create(input, props, slottable, anchor) { throwTypeError(input, type); break; } - case !!input[Symbol.asyncIterator]: - if(!anchor) anchor = document.createComment('0'); - composeAsyncIterator(anchor, input, false, props, slottable); - return anchor; case isRenderObject(input): return create(input.render(props, slottable), null, null, anchor); - case input instanceof Promise: { - if(!anchor) anchor = document.createComment('0'); - input.then(value => { - create(value, props, slottable, anchor); - }); - return anchor; - } - case Array.isArray(input): { + default: { + // these inputs require a comment anchor to which they can render if(!anchor) anchor = document.createComment('0'); - compose(anchor, input, false); + + if(input[Symbol.asyncIterator]) { + composeAsyncIterator(anchor, input, false, props, slottable); + } + else if(input instanceof Promise) { + input.then(value => { + create(value, props, slottable, anchor); + }); + } + else if(Array.isArray(input)) { + composeArray(anchor, input, false); + } + else { + throwTypeErrorForObject(input, type); + } + return anchor; } - default: { - throwTypeErrorForObject(input, type); - } } } diff --git a/packages/runtime/compose/compose.test.js b/packages/runtime/compose/compose.test.js index 9b3772c..e44616b 100644 --- a/packages/runtime/compose/compose.test.js +++ b/packages/runtime/compose/compose.test.js @@ -1,9 +1,6 @@ import { describe, test } from 'vitest'; import { IGNORE, compose } from './compose.js'; -import { - elements, elementWithAnchor, elementWithText, - $anchor -} from 'test-utils/elements'; +import { elements, elementWithAnchor, elementWithText, $anchor } from 'test-utils/elements'; export function runCompose(value, create) { const { dom, anchor } = create(); From 0aca3c4b77e579bacb071070c58645a59265bc3a Mon Sep 17 00:00:00 2001 From: Marty Nelson Date: Sun, 17 Mar 2024 09:47:08 -0700 Subject: [PATCH 13/23] refactor into single RenderService with DOMRenderEngine and HTMLRenderEngine --- packages/runtime/renderer/magic.test.js | 185 +++++++++++---------- packages/runtime/renderer/renderer.dom.js | 76 ++++----- packages/runtime/renderer/renderer.html.js | 61 +++++++ packages/runtime/renderer/renderer.js | 64 +++---- 4 files changed, 223 insertions(+), 163 deletions(-) create mode 100644 packages/runtime/renderer/renderer.html.js diff --git a/packages/runtime/renderer/magic.test.js b/packages/runtime/renderer/magic.test.js index 99653b2..3d63a6b 100644 --- a/packages/runtime/renderer/magic.test.js +++ b/packages/runtime/renderer/magic.test.js @@ -1,63 +1,14 @@ -import { describe, test } from 'vitest'; +import { describe, test, beforeEach, beforeAll } from 'vitest'; import { compose } from '../compose/compose.js'; import { - makeRenderer, - getDOMBound, -} from './renderer.dom.js'; -import { + get, makeTemplate, - makeStringRenderer, - getStringBound, Controller, Updater, clearBind, + RenderService, } from './renderer.js'; -// template generated artifacts -const source = makeRenderer('id', `

`); -const stringSource = makeStringRenderer('id', [`

`, `

`]); - -function getTargets(r, boundEls) { - return [r.childNodes[0]]; -} - -const makeBind = targets => { - const t0 = targets[0]; - return p0 => { - compose(t0, p0); - }; -}; - -function getStringTargets(r, boundEls) { - return [boundEls[0]]; -} - -const makeStringBind = targets => { - const t0 = targets[0]; - return p0 => { - t0[0] = p0; - }; -}; -function renderDOM(p0) { - const [root, bind] = makeTemplate( - source, - getTargets, - makeBind, - getDOMBound, - ); - bind(p0); - return root; -} -function renderString(p0) { - const [root, bind] = makeTemplate( - stringSource, - getStringTargets, - makeStringBind, - getStringBound, - ); - bind(p0); - return root; -} /* const NameTag = Controller.for(({ greeting, name }) => { const Greeting = Updater.for(greeting => {greeting}); @@ -69,85 +20,141 @@ const NameTag = Controller.for(({ greeting, name }) => { const Hello = Controller.for(name =>

{name}

); */ -describe('string render', () => { - const flatRender = node => node.flat().join(''); + + +describe('dom render', () => { + let source = null; + beforeAll(() => { + RenderService.clear(); + RenderService.useDOMEngine(); + source = get('id', false, `

`); + }); + + function getTargets(r, boundEls) { + return [r.childNodes[0]]; + } + + const makeBind = targets => { + const t0 = targets[0]; + return p0 => { + compose(t0, p0); + }; + }; + + function renderDOM(p0) { + const [root, bind] = makeTemplate( + source, + getTargets, + makeBind, + ); + bind(p0); + return root; + } test('Controller.for', ({ expect }) => { - const controller = Controller.for(name => renderString(name)); + const controller = Controller.for(name => renderDOM(name)); let node1 = controller.render('felix'); let node2 = controller.render('duchess'); - expect(flatRender(node1)).toMatchInlineSnapshot( - `"

felix

"` - ); - expect(flatRender(node2)).toMatchInlineSnapshot( - `"

duchess

"` - ); + expect(node1.outerHTML).toMatchInlineSnapshot(`"

felix

"`); + expect(node2.outerHTML).toMatchInlineSnapshot(`"

duchess

"`); controller.update(node1, 'garfield'); controller.update(node2, 'stimpy'); - expect(flatRender(node1)).toMatchInlineSnapshot( - `"

garfield

"` - ); - expect(flatRender(node2)).toMatchInlineSnapshot( - `"

stimpy

"` - ); + expect(node1.outerHTML).toMatchInlineSnapshot(`"

garfield

"`); + expect(node2.outerHTML).toMatchInlineSnapshot(`"

stimpy

"`); }); - test('inject unknown node', ({ expect }) => { - const controller = Controller.for(name => renderString(name)); + test('inject unknown node creates bind', ({ expect }) => { + const controller = Controller.for(name => renderDOM(name)); let node = controller.render('felix'); clearBind(node); controller.update(node, 'garfield'); - expect(flatRender(node)).toMatchInlineSnapshot(`"

garfield

"`); + expect(node.outerHTML).toMatchInlineSnapshot(`"

garfield1

"`); }); test('Updater.for', ({ expect }) => { - const updater = Updater.for(name => renderString(name)); + const updater = Updater.for(name => renderDOM(name)); const node = updater.render('felix'); - expect(flatRender(node)).toMatchInlineSnapshot( - `"

felix

"` - ); + expect(node.outerHTML).toMatchInlineSnapshot(`"

felix

"`); updater.update('duchess'); - expect(flatRender(node)).toMatchInlineSnapshot( - `"

duchess

"` - ); + expect(node.outerHTML).toMatchInlineSnapshot(`"

duchess

"`); }); }); -describe('dom render', () => { +describe('html render', () => { + const flatRender = node => node.flat().join(''); + + let source = null; + beforeAll(() => { + RenderService.clear(); + RenderService.useHTMLEngine(); + source = get('id', false, [`

`, `

`]); + }); + + function getTargets(r, boundEls) { + return [boundEls[0]]; + } + const makeBind = targets => { + const t0 = targets[0]; + return p0 => { + t0[0] = p0; + }; + }; + function render(p0) { + const [root, bind] = makeTemplate( + source, + getTargets, + makeBind, + ); + bind(p0); + return root; + } test('Controller.for', ({ expect }) => { - const controller = Controller.for(name => renderDOM(name)); + const controller = Controller.for(name => render(name)); let node1 = controller.render('felix'); let node2 = controller.render('duchess'); - expect(node1.outerHTML).toMatchInlineSnapshot(`"

felix

"`); - expect(node2.outerHTML).toMatchInlineSnapshot(`"

duchess

"`); + expect(flatRender(node1)).toMatchInlineSnapshot( + `"

felix

"` + ); + expect(flatRender(node2)).toMatchInlineSnapshot( + `"

duchess

"` + ); controller.update(node1, 'garfield'); controller.update(node2, 'stimpy'); - expect(node1.outerHTML).toMatchInlineSnapshot(`"

garfield

"`); - expect(node2.outerHTML).toMatchInlineSnapshot(`"

stimpy

"`); + expect(flatRender(node1)).toMatchInlineSnapshot( + `"

garfield

"` + ); + expect(flatRender(node2)).toMatchInlineSnapshot( + `"

stimpy

"` + ); }); - test('inject unknown node creates bind', ({ expect }) => { - const controller = Controller.for(name => renderDOM(name)); + test('inject unknown node', ({ expect }) => { + const controller = Controller.for(name => render(name)); let node = controller.render('felix'); clearBind(node); controller.update(node, 'garfield'); - expect(node.outerHTML).toMatchInlineSnapshot(`"

garfield1

"`); + expect(flatRender(node)).toMatchInlineSnapshot(`"

garfield

"`); }); test('Updater.for', ({ expect }) => { - const updater = Updater.for(name => renderDOM(name)); + const updater = Updater.for(name => render(name)); const node = updater.render('felix'); - expect(node.outerHTML).toMatchInlineSnapshot(`"

felix

"`); + expect(flatRender(node)).toMatchInlineSnapshot( + `"

felix

"` + ); updater.update('duchess'); - expect(node.outerHTML).toMatchInlineSnapshot(`"

duchess

"`); + expect(flatRender(node)).toMatchInlineSnapshot( + `"

duchess

"` + ); }); }); + diff --git a/packages/runtime/renderer/renderer.dom.js b/packages/runtime/renderer/renderer.dom.js index 525cec9..807a0a8 100644 --- a/packages/runtime/renderer/renderer.dom.js +++ b/packages/runtime/renderer/renderer.dom.js @@ -1,47 +1,31 @@ -const templates = new Map(); - -export function clearTemplates() { - templates.clear(); -} - -export function makeRenderer(id, html, isFragment = false) { - if(templates.has(id)) return templates.get(id); - - const template = document.createElement('template'); - template.innerHTML = html; - return rendererFactory(id, template.content, isFragment); -} - -export function rendererById(id, isFragment = false) { - if(templates.has(id)) return templates.get(id); - - const templateEl = document.getElementById(id); - if(!templateEl) { - throw new Error(`No template with id "${id}"`); - } - - return rendererFactory(id, templateEl.content, isFragment); -} - -function rendererFactory(id, node, isFragment) { - const render = renderer(node, isFragment); - templates.set(id, render); - return render; -} - const QUERY_SELECTOR = '[data-bind]'; - -function renderer(fragment, isFragment) { - if(!isFragment) fragment = fragment.firstElementChild; - // TODO: malformed fragments...necessary? - - return function render() { - const clone = fragment.cloneNode(true); - const targets = clone.querySelectorAll(QUERY_SELECTOR); - return [clone, targets]; - }; -} - -export function getDOMBound(dom) { - return dom.querySelectorAll(QUERY_SELECTOR); -} +export const DOMRenderEngine = { + name: 'DOMRenderEngine', + make(html) { + const template = document.createElement('template'); + template.innerHTML = html; + return template.content; + }, + get(id) { + const template = document.getElementById(id); + if(!template) { + throw new Error(`No template with id "${id}"`); + } + return template.content; + }, + renderer(fragment, isFragment) { + if(!isFragment) fragment = fragment.firstElementChild; + // TODO: malformed fragments...necessary? + + return function render() { + const clone = fragment.cloneNode(true); + const targets = clone.querySelectorAll(QUERY_SELECTOR); + return [clone, targets]; + }; + }, + bound(dom) { + /* bound */ + return dom.querySelectorAll(QUERY_SELECTOR); + /* */ + } +}; diff --git a/packages/runtime/renderer/renderer.html.js b/packages/runtime/renderer/renderer.html.js new file mode 100644 index 0000000..f1065e7 --- /dev/null +++ b/packages/runtime/renderer/renderer.html.js @@ -0,0 +1,61 @@ +export const HTMLRenderEngine = { + name: 'HTMLRenderEngine', + make(html) { + return html; + }, + get(id) { + // TODO: what is the prod equiv? if any + throw new Error(`HTMLRenderEngine does not support "get(id)" of "${id}". Use "make(html)" instead`); + }, + // TODO: are fragments a thing with html render? + renderer(html/*, isFragment*/) { + const template = []; + for(let i = 0; i < html.length; i++) { + if(i !== 0) template.push(null); + template.push(html[i]); + } + + return () => { + const root = template.slice(); + const targets = []; + for(let i = 1; i < root.length; i += 2) { + targets.push(root[i] = []); + } + return [root, targets]; + }; + }, + bound(root) { + const targets = []; + for(let i = 1; i < root.length; i += 2) { + targets.push(root[i] = []); + } + return targets; + } +}; + + +export function makeStringRenderer(id, html, isFragment = false) { + const template = []; + for(let i = 0; i < html.length; i++) { + if(i !== 0) template.push(null); + template.push(html[i]); + } + + return () => { + const root = template.slice(); + const targets = []; + for(let i = 1; i < root.length; i += 2) { + targets.push(root[i] = []); + } + return [root, targets]; + }; +} + +export function getStringBound(root) { + const targets = []; + for(let i = 1; i < root.length; i += 2) { + targets.push(root[i] = []); + } + return targets; +} + diff --git a/packages/runtime/renderer/renderer.js b/packages/runtime/renderer/renderer.js index 8bf88da..b017330 100644 --- a/packages/runtime/renderer/renderer.js +++ b/packages/runtime/renderer/renderer.js @@ -1,35 +1,43 @@ +import { DOMRenderEngine } from './renderer.dom.js'; +import { HTMLRenderEngine } from './renderer.html.js'; -export function makeStringRenderer(id, html, isFragment = false) { - const template = []; - for(let i = 0; i < html.length; i++) { - if(i !== 0) template.push(null); - template.push(html[i]); - } +let renderEngine = DOMRenderEngine; +export const RenderService = { + useDOMEngine() { + renderEngine = DOMRenderEngine; + }, + useHTMLEngine() { + renderEngine = HTMLRenderEngine; + }, + clear, + get, + bound, +}; - return () => { - const root = template.slice(); - const targets = []; - for(let i = 1; i < root.length; i += 2) { - targets.push(root[i] = []); - } - return [root, targets]; - }; +const templates = new Map(); +function clear() { + templates.clear(); } -export function getStringBound(root) { - const targets = []; - for(let i = 1; i < root.length; i += 2) { - targets.push(root[i] = []); - } - return targets; +export function get(id, isFragment = false, html = '') { + if(templates.has(id)) return templates.get(id); + + let node = html ? renderEngine.make(html) : renderEngine.get(id); + const render = renderEngine.renderer(node, isFragment); + + templates.set(id, render); + return render; } +export function bound(node) { + return renderEngine.bound(node); +} -const map = new Map(); +const bindings = new Map(); -// TODO: cleanup actions on nodes +// TODO: impl cleanup actions on nodes export function clearBind(node) { - if(map.has(node)) map.delete(node); + if(bindings.has(node)) bindings.delete(node); } // stack @@ -44,21 +52,21 @@ export function inject(node, callback) { } } -export function makeTemplate(source, targets, makeBind, getBound) { +export function makeTemplate(source, targets, makeBind) { let bind = null; let boundEls = null; let node = injectable.at(-1); // peek! - // TODO: test injectable is right template id + // TODO: test injectable is right template id type - if(node) bind = map.get(node); + if(node) bind = bindings.get(node); if(bind) return [node, bind]; // use case would be list component optimize by // not keeping bind functions, // honestly not sure this really needed, the // overhead is small as it is simple function - if(node) boundEls = getBound(node); + if(node) boundEls = renderEngine.bound(node); else { // (destructured re-assignment) ([node, boundEls] = source()); @@ -67,7 +75,7 @@ export function makeTemplate(source, targets, makeBind, getBound) { const nodes = targets(node, boundEls); bind = makeBind(nodes); - map.set(node, bind); + bindings.set(node, bind); return [node, bind]; } From 9df889d6a0157469ec0483174ba15a93831bb99c Mon Sep 17 00:00:00 2001 From: Marty Nelson Date: Sun, 17 Mar 2024 13:31:25 -0700 Subject: [PATCH 14/23] new rendering engine, WIP for integrating with new asset generation --- .../{magic.test.js => controller.test.js} | 42 +++++++------------ packages/runtime/renderer/index.js | 2 +- packages/runtime/renderer/renderer.dom.js | 8 ++-- packages/runtime/renderer/renderer.html.js | 41 ++++-------------- packages/runtime/renderer/renderer.js | 34 ++++++++------- packages/runtime/renderer/renderer.test.js | 4 +- 6 files changed, 48 insertions(+), 83 deletions(-) rename packages/runtime/renderer/{magic.test.js => controller.test.js} (81%) diff --git a/packages/runtime/renderer/magic.test.js b/packages/runtime/renderer/controller.test.js similarity index 81% rename from packages/runtime/renderer/magic.test.js rename to packages/runtime/renderer/controller.test.js index 3d63a6b..8c02237 100644 --- a/packages/runtime/renderer/magic.test.js +++ b/packages/runtime/renderer/controller.test.js @@ -2,32 +2,16 @@ import { describe, test, beforeEach, beforeAll } from 'vitest'; import { compose } from '../compose/compose.js'; import { get, - makeTemplate, + render, Controller, Updater, clearBind, RenderService, } from './renderer.js'; -/* -const NameTag = Controller.for(({ greeting, name }) => { - const Greeting = Updater.for(greeting => {greeting}); - - return

- {name} -

; -}); - -const Hello = Controller.for(name =>

{name}

); -*/ - - describe('dom render', () => { - let source = null; beforeAll(() => { - RenderService.clear(); RenderService.useDOMEngine(); - source = get('id', false, `

`); }); function getTargets(r, boundEls) { @@ -42,10 +26,12 @@ describe('dom render', () => { }; function renderDOM(p0) { - const [root, bind] = makeTemplate( - source, + const [root, bind] = render( + 'id', getTargets, makeBind, + false, + `

`, ); bind(p0); return root; @@ -87,34 +73,34 @@ describe('dom render', () => { describe('html render', () => { const flatRender = node => node.flat().join(''); - let source = null; beforeAll(() => { - RenderService.clear(); RenderService.useHTMLEngine(); - source = get('id', false, [`

`, `

`]); }); function getTargets(r, boundEls) { return [boundEls[0]]; } + const makeBind = targets => { const t0 = targets[0]; return p0 => { t0[0] = p0; }; }; - function render(p0) { - const [root, bind] = makeTemplate( - source, + function renderHTML(p0) { + const [root, bind] = render( + 'id', getTargets, makeBind, + false, + [`

`, `

`], ); bind(p0); return root; } test('Controller.for', ({ expect }) => { - const controller = Controller.for(name => render(name)); + const controller = Controller.for(name => renderHTML(name)); let node1 = controller.render('felix'); let node2 = controller.render('duchess'); @@ -136,7 +122,7 @@ describe('html render', () => { }); test('inject unknown node', ({ expect }) => { - const controller = Controller.for(name => render(name)); + const controller = Controller.for(name => renderHTML(name)); let node = controller.render('felix'); clearBind(node); controller.update(node, 'garfield'); @@ -144,7 +130,7 @@ describe('html render', () => { }); test('Updater.for', ({ expect }) => { - const updater = Updater.for(name => render(name)); + const updater = Updater.for(name => renderHTML(name)); const node = updater.render('felix'); expect(flatRender(node)).toMatchInlineSnapshot( `"

felix

"` diff --git a/packages/runtime/renderer/index.js b/packages/runtime/renderer/index.js index 0b844f3..b579bc3 100644 --- a/packages/runtime/renderer/index.js +++ b/packages/runtime/renderer/index.js @@ -1 +1 @@ -export { makeRenderer, rendererById } from './renderer.dom.js'; \ No newline at end of file +export { makeRenderer, rendererById } from './renderer.js'; \ No newline at end of file diff --git a/packages/runtime/renderer/renderer.dom.js b/packages/runtime/renderer/renderer.dom.js index 807a0a8..726c79d 100644 --- a/packages/runtime/renderer/renderer.dom.js +++ b/packages/runtime/renderer/renderer.dom.js @@ -1,6 +1,6 @@ const QUERY_SELECTOR = '[data-bind]'; -export const DOMRenderEngine = { - name: 'DOMRenderEngine', +export const DOMRenderer = { + name: 'DOMRenderer', make(html) { const template = document.createElement('template'); template.innerHTML = html; @@ -15,7 +15,7 @@ export const DOMRenderEngine = { }, renderer(fragment, isFragment) { if(!isFragment) fragment = fragment.firstElementChild; - // TODO: malformed fragments...necessary? + // TODO: malformed fragment check...necessary? return function render() { const clone = fragment.cloneNode(true); @@ -24,8 +24,6 @@ export const DOMRenderEngine = { }; }, bound(dom) { - /* bound */ return dom.querySelectorAll(QUERY_SELECTOR); - /* */ } }; diff --git a/packages/runtime/renderer/renderer.html.js b/packages/runtime/renderer/renderer.html.js index f1065e7..e77d01d 100644 --- a/packages/runtime/renderer/renderer.html.js +++ b/packages/runtime/renderer/renderer.html.js @@ -1,13 +1,17 @@ -export const HTMLRenderEngine = { - name: 'HTMLRenderEngine', +export const HTMLRenderer = { + name: 'HTMLRenderer', make(html) { return html; }, get(id) { - // TODO: what is the prod equiv? if any - throw new Error(`HTMLRenderEngine does not support "get(id)" of "${id}". Use "make(html)" instead`); + // Q: what is the prod optimized equiv? if any? + // Array/String literal seems as good or better than JSON file? + // TODO: benchmark + throw new Error(`HTMLRenderer does not support "get(id)" of "${id}". Use "make(html)" instead`); }, - // TODO: are fragments a thing with html render? + // pretty sure fragments NOT needed for html render, + // really a DOM optimization to avoid Fragment container + // on single element root renderer(html/*, isFragment*/) { const template = []; for(let i = 0; i < html.length; i++) { @@ -32,30 +36,3 @@ export const HTMLRenderEngine = { return targets; } }; - - -export function makeStringRenderer(id, html, isFragment = false) { - const template = []; - for(let i = 0; i < html.length; i++) { - if(i !== 0) template.push(null); - template.push(html[i]); - } - - return () => { - const root = template.slice(); - const targets = []; - for(let i = 1; i < root.length; i += 2) { - targets.push(root[i] = []); - } - return [root, targets]; - }; -} - -export function getStringBound(root) { - const targets = []; - for(let i = 1; i < root.length; i += 2) { - targets.push(root[i] = []); - } - return targets; -} - diff --git a/packages/runtime/renderer/renderer.js b/packages/runtime/renderer/renderer.js index b017330..b40b330 100644 --- a/packages/runtime/renderer/renderer.js +++ b/packages/runtime/renderer/renderer.js @@ -1,48 +1,51 @@ -import { DOMRenderEngine } from './renderer.dom.js'; -import { HTMLRenderEngine } from './renderer.html.js'; +import { DOMRenderer } from './renderer.dom.js'; +import { HTMLRenderer } from './renderer.html.js'; + +const templates = new Map(); // cache +let renderEngine = DOMRenderer; // DOM or HTML engine + +// TODO: Class !!!!! -let renderEngine = DOMRenderEngine; export const RenderService = { useDOMEngine() { - renderEngine = DOMRenderEngine; + renderEngine = DOMRenderer; + clear(); }, useHTMLEngine() { - renderEngine = HTMLRenderEngine; + renderEngine = HTMLRenderer; + clear(); }, - clear, get, bound, }; -const templates = new Map(); function clear() { templates.clear(); } -export function get(id, isFragment = false, html = '') { +function get(id, isFragment = false, content) { if(templates.has(id)) return templates.get(id); - let node = html ? renderEngine.make(html) : renderEngine.get(id); + const node = content ? renderEngine.make(content) : renderEngine.get(id); const render = renderEngine.renderer(node, isFragment); templates.set(id, render); return render; } -export function bound(node) { +function bound(node) { return renderEngine.bound(node); } -const bindings = new Map(); - -// TODO: impl cleanup actions on nodes +const bindings = new Map(); // cache +// TODO: implement cleanup actions on nodes export function clearBind(node) { if(bindings.has(node)) bindings.delete(node); } // stack const injectable = []; -export function inject(node, callback) { +function inject(node, callback) { injectable.push(node); callback(); const popped = injectable.pop(); @@ -52,7 +55,8 @@ export function inject(node, callback) { } } -export function makeTemplate(source, targets, makeBind) { +export function render(id, targets, makeBind, isFragment, content) { + let source = get(id, isFragment, content); let bind = null; let boundEls = null; let node = injectable.at(-1); // peek! diff --git a/packages/runtime/renderer/renderer.test.js b/packages/runtime/renderer/renderer.test.js index e435771..c6753ac 100644 --- a/packages/runtime/renderer/renderer.test.js +++ b/packages/runtime/renderer/renderer.test.js @@ -1,7 +1,7 @@ import { beforeEach, describe, test } from 'vitest'; -import { clearTemplates, makeRenderer } from './renderer.dom.js'; +import { clearTemplates, makeRenderer, Renderer } from './renderer.js'; -describe('isFragment', () => { +describe('DOM isFragment', () => { beforeEach(clearTemplates); From 1108506905559a588ee038b1ef587fd8b9038c09 Mon Sep 17 00:00:00 2001 From: Marty Nelson Date: Sun, 17 Mar 2024 18:30:48 -0700 Subject: [PATCH 15/23] test passing, still need to connect new render assets --- packages/channels/repeat.test.jsx | 2 +- .../{renderer.dom.js => dom-renderer.js} | 17 +++++++++++++++-- .../{renderer.test.js => dom-renderer.test.js} | 12 +++++------- .../{renderer.html.js => html-renderer.js} | 18 ++++++++++-------- packages/runtime/renderer/renderer.js | 15 ++++++--------- 5 files changed, 37 insertions(+), 27 deletions(-) rename packages/runtime/renderer/{renderer.dom.js => dom-renderer.js} (70%) rename packages/runtime/renderer/{renderer.test.js => dom-renderer.test.js} (54%) rename packages/runtime/renderer/{renderer.html.js => html-renderer.js} (69%) diff --git a/packages/channels/repeat.test.jsx b/packages/channels/repeat.test.jsx index 1e8530c..2ccc2fc 100644 --- a/packages/channels/repeat.test.jsx +++ b/packages/channels/repeat.test.jsx @@ -1,4 +1,4 @@ -import { describe, test, beforeEach } from 'vitest'; +import { test, beforeEach } from 'vitest'; import './with-resolvers-polyfill.js'; import { fixtureSetup } from 'test-utils/fixtures'; import { subject } from './generators.js'; diff --git a/packages/runtime/renderer/renderer.dom.js b/packages/runtime/renderer/dom-renderer.js similarity index 70% rename from packages/runtime/renderer/renderer.dom.js rename to packages/runtime/renderer/dom-renderer.js index 726c79d..59fe488 100644 --- a/packages/runtime/renderer/renderer.dom.js +++ b/packages/runtime/renderer/dom-renderer.js @@ -1,18 +1,31 @@ const QUERY_SELECTOR = '[data-bind]'; export const DOMRenderer = { name: 'DOMRenderer', - make(html) { + + createTemplate(id, content, isFragment) { + const node = DOMRenderer.template(id, content); + const render = DOMRenderer.renderer(node, isFragment); + return render; + }, + + template(id, content) { + if(content) return DOMRenderer.create(content); + DOMRenderer.getById(id); + }, + + create(html) { const template = document.createElement('template'); template.innerHTML = html; return template.content; }, - get(id) { + getById(id) { const template = document.getElementById(id); if(!template) { throw new Error(`No template with id "${id}"`); } return template.content; }, + renderer(fragment, isFragment) { if(!isFragment) fragment = fragment.firstElementChild; // TODO: malformed fragment check...necessary? diff --git a/packages/runtime/renderer/renderer.test.js b/packages/runtime/renderer/dom-renderer.test.js similarity index 54% rename from packages/runtime/renderer/renderer.test.js rename to packages/runtime/renderer/dom-renderer.test.js index c6753ac..5610645 100644 --- a/packages/runtime/renderer/renderer.test.js +++ b/packages/runtime/renderer/dom-renderer.test.js @@ -1,24 +1,22 @@ import { beforeEach, describe, test } from 'vitest'; -import { clearTemplates, makeRenderer, Renderer } from './renderer.js'; +import { DOMRenderer } from './dom-renderer.js'; describe('DOM isFragment', () => { - beforeEach(clearTemplates); - test('element root w/ false and true', async ({ expect }) => { - const [div] = makeRenderer('element-root', `
text
`)(); + const [div] = DOMRenderer.createTemplate('element-root', false, `
text
`)(); expect(div).toBeInstanceOf(HTMLDivElement); - const [fragment] = makeRenderer('fragment-root', `
text
`, true)(); + const [fragment] = DOMRenderer.createTemplate('fragment-root', true, `
text
`)(); expect(fragment).toBeInstanceOf(DocumentFragment); }); test('fragment root w/ false and true', async ({ expect }) => { - const [fragment] = makeRenderer('fragment-root', `


`, true)(); + const [fragment] = DOMRenderer.createTemplate('fragment-root', `


`, true)(); expect(fragment).toBeInstanceOf(DocumentFragment); // TODO: should this be a thrown exception? - const [hr] = makeRenderer('element-root', `


`)(); + const [hr] = DOMRenderer.createTemplate('element-root', `


`)(); expect(hr).toBeInstanceOf(HTMLHRElement); }); }); diff --git a/packages/runtime/renderer/renderer.html.js b/packages/runtime/renderer/html-renderer.js similarity index 69% rename from packages/runtime/renderer/renderer.html.js rename to packages/runtime/renderer/html-renderer.js index e77d01d..cc6b545 100644 --- a/packages/runtime/renderer/renderer.html.js +++ b/packages/runtime/renderer/html-renderer.js @@ -1,14 +1,16 @@ export const HTMLRenderer = { name: 'HTMLRenderer', - make(html) { - return html; - }, - get(id) { - // Q: what is the prod optimized equiv? if any? - // Array/String literal seems as good or better than JSON file? - // TODO: benchmark - throw new Error(`HTMLRenderer does not support "get(id)" of "${id}". Use "make(html)" instead`); + + createTemplate(_id, content) { + if(!content) { + // Q: what is the prod optimized equiv? if any? + // Array/String literal seems as good or better than JSON file? + // TODO: benchmark + throw new TypeError(`HTMLRenderer.createTemplate requires "content" parameter`); + } + return HTMLRenderer.renderer(content); }, + // pretty sure fragments NOT needed for html render, // really a DOM optimization to avoid Fragment container // on single element root diff --git a/packages/runtime/renderer/renderer.js b/packages/runtime/renderer/renderer.js index b40b330..7fdfc63 100644 --- a/packages/runtime/renderer/renderer.js +++ b/packages/runtime/renderer/renderer.js @@ -1,11 +1,9 @@ -import { DOMRenderer } from './renderer.dom.js'; -import { HTMLRenderer } from './renderer.html.js'; +import { DOMRenderer } from './dom-renderer.js'; +import { HTMLRenderer } from './html-renderer.js'; const templates = new Map(); // cache let renderEngine = DOMRenderer; // DOM or HTML engine -// TODO: Class !!!!! - export const RenderService = { useDOMEngine() { renderEngine = DOMRenderer; @@ -26,11 +24,10 @@ function clear() { function get(id, isFragment = false, content) { if(templates.has(id)) return templates.get(id); - const node = content ? renderEngine.make(content) : renderEngine.get(id); - const render = renderEngine.renderer(node, isFragment); + const template = renderEngine.createTemplate(id, content, isFragment); - templates.set(id, render); - return render; + templates.set(id, template); + return template; } function bound(node) { @@ -106,4 +103,4 @@ export class Updater extends Controller { update(props) { super.update(this.#node, props); } -} \ No newline at end of file +} From 30c699eff6b08c53b48e537b7df4fd64fad2df87 Mon Sep 17 00:00:00 2001 From: Marty Nelson Date: Wed, 20 Mar 2024 15:54:16 -0700 Subject: [PATCH 16/23] primary functions generated --- packages/compiler/transform/Analyzer.js | 3 + .../compiler/transform/AssetsGenerator.js | 28 +++ packages/compiler/transform/BindGenerator.js | 16 +- .../compiler/transform/RenderGenerator.js | 192 ++++++++++++++++++ packages/compiler/transform/bind.test.js | 72 ------- packages/compiler/transform/modules.test.js | 171 ++++++++++++++++ packages/runtime/renderer/controller.test.js | 37 ++-- .../runtime/renderer/dom-renderer.test.js | 4 +- packages/runtime/renderer/renderer.js | 55 ++--- packages/vite-plugin/index.js | 2 +- pnpm-workspace.yaml | 1 - 11 files changed, 455 insertions(+), 126 deletions(-) create mode 100644 packages/compiler/transform/AssetsGenerator.js create mode 100644 packages/compiler/transform/RenderGenerator.js delete mode 100644 packages/compiler/transform/bind.test.js create mode 100644 packages/compiler/transform/modules.test.js diff --git a/packages/compiler/transform/Analyzer.js b/packages/compiler/transform/Analyzer.js index 0a40832..7f3fc62 100644 --- a/packages/compiler/transform/Analyzer.js +++ b/packages/compiler/transform/Analyzer.js @@ -36,6 +36,7 @@ export class Analyzer { }); } + // TODO: move generation elsewhere generateTemplate(htmlGenerator) { const template = this.#template; if(!template.isEmpty) template.html = htmlGenerator(template.node); @@ -99,6 +100,8 @@ export class Analyzer { node, expr, index, + // placeholder value until after analysis complete + queryIndex: -2, }; if(element.isComponent) { diff --git a/packages/compiler/transform/AssetsGenerator.js b/packages/compiler/transform/AssetsGenerator.js new file mode 100644 index 0000000..f74a9f5 --- /dev/null +++ b/packages/compiler/transform/AssetsGenerator.js @@ -0,0 +1,28 @@ +export function makeTargets(template) { + const { boundElements, bindings } = template; + const { length: elLength } = boundElements; + + const values = bindings.map(({ element, type, index }) => { + const { isRoot, queryIndex } = element; + const target = isRoot ? 'r' : `${'t'}[${queryIndex}]`; + return type === 'child' + ? `${target}.childNodes[${index}]` + : target; + }); + + return `const targets = (${elLength ? 'r,t' : 'r'}) => [${values.join()}];\n`; +} + +export function makeGetBound({ id, isDomFragment, html }, options) { + const content = options?.noContent ? '' : `, ${html}`; + return `const getBound = renderer('${id}', targets, bind, ${isDomFragment}${content});\n`; +} + +export function makeRender({ bindings: { length } }) { + const params = Array.from({ length }, (_, i) => `p${i}`); + return `function renderDOM(${params}) { + const [root, bind] = getBound(); + bind(${params}); + return root; +}\n`; +} \ No newline at end of file diff --git a/packages/compiler/transform/BindGenerator.js b/packages/compiler/transform/BindGenerator.js index f2975cf..acb0d77 100644 --- a/packages/compiler/transform/BindGenerator.js +++ b/packages/compiler/transform/BindGenerator.js @@ -88,7 +88,7 @@ export class BindGenerator extends Generator { } Targets(template, state) { - const { boundElements, bindings, isBoundRoot } = template; + const { boundElements, bindings } = template; const { length: elLength } = boundElements; state.write(`function targets(`); @@ -103,17 +103,17 @@ export class BindGenerator extends Generator { for(let i = 0; i < bindings.length; i++) { const { element, type, index, node } = bindings[i]; const { isRoot, queryIndex } = element; - const varName = isRoot ? ROOT : `${TARGETS}[${queryIndex}]`; + const target = isRoot ? ROOT : `${TARGETS}[${queryIndex}]`; if(i !== 0) state.write(', '); if(type !== 'child') { - state.write(`${varName}`); + state.write(`${target}`); continue; } const opening = getOpening(element, node); - state.write(`${varName}.childNodes`, opening.name); + state.write(`${target}.childNodes`, opening.name); state.write(`[${index}]`, node); } state.write(`];`); @@ -125,18 +125,18 @@ export class BindGenerator extends Generator { } Render(template, state) { - const { bindings } = template; + const { bindings, isDomFragment } = template; const params = []; for(let i = 0; i < bindings.length; i++) { params.push(`${VALUE}${i}`); } - state.write(`function render(${params.join(', ')}) {`); + state.write(`function renderDOM(${params.join(', ')}) {`); state.indentLevel++; writeNextLine(state); - state.write(`const [root, bind] = makeTemplate(source);`); + state.write(`const [root, bind] = render('id', targets, bind, ${isDomFragment});`); writeNextLine(state); state.write(`bind(${params.join(', ')});`); writeNextLine(state); @@ -252,7 +252,7 @@ export class BindGenerator extends Generator { } BindingProp(node, expr, index, element, state) { - const { queryIndex, openingElement, openingFragment } = element; + const { openingElement, openingFragment } = element; const opening = openingElement ?? openingFragment; state.write(`${TARGET}${index}`, opening.name); // TODO: more property validation diff --git a/packages/compiler/transform/RenderGenerator.js b/packages/compiler/transform/RenderGenerator.js new file mode 100644 index 0000000..3901601 --- /dev/null +++ b/packages/compiler/transform/RenderGenerator.js @@ -0,0 +1,192 @@ +import { Generator, writeNextLine } from './GeneratorBase.js'; +import { isValidESIdentifier } from 'is-valid-es-identifier'; +import { generateWith } from '../compiler.js'; + +const TARGETS = 'ts'; +const TARGET = 't'; +const VALUE = 'v'; + +export class RenderGenerator extends Generator { + static generate(template) { + const generator = new this(template); + return generateWith(generator, template.node); + } + #bindings = null; + + constructor(template) { + super(); + this.#bindings = template.bindings; + } + + JSXFragment(_node, state) { + this.JSXTemplate(state); + } + + JSXElement(_node, state) { + this.JSXTemplate(state); + } + + JSXTemplate(state) { + this.Bindings(state); + } + + JSXExpressionContainer({ expression }, state) { + this[expression.type](expression, state); + } + + JSXIdentifier(identifier, state) { + state.write(identifier.name, identifier); + } + + // Render(template, state) { + // const { bindings, isDomFragment } = template; + + // const params = []; + // for(let i = 0; i < bindings.length; i++) { + // params.push(`${VALUE}${i}`); + // } + + // state.write(`function renderDOM(${params.join(', ')}) {`); + // state.indentLevel++; + // writeNextLine(state); + + // state.write(`const [root, bind] = render('id', targets, bind, ${isDomFragment});`); + // writeNextLine(state); + // state.write(`bind(${params.join(', ')});`); + // writeNextLine(state); + // state.write(`return root;`); + + // state.indentLevel--; + // writeNextLine(state); + // state.write(`}`); + // state.write(state.lineEnd); + // } + + Bindings(state) { + const bindings = this.#bindings; + + state.write(`function bind(${TARGETS}) {`); + state.indentLevel++; + writeNextLine(state); + + const targets = []; + const params = []; + for(let i = 0; i < bindings.length; i++) { + targets.push(`${TARGET}${i} = ${TARGETS}[${i}]`); + params.push(`${VALUE}${i}`); + } + + state.write(`const ${targets.join(', ')};`); + writeNextLine(state); + state.write(`return (${params.join(', ')}) => {`); + state.indentLevel++; + + for(let i = 0; i < bindings.length; i++) { + const { element, type, node, expr } = bindings[i]; + writeNextLine(state); + + if(!this[expr.type]) { + throw new TypeError(`Unexpected Binding expression AST type "${expr.type}"`); + } + + if(node.isComponent) { + this.ComposeElement(node, expr, i, state); + continue; + } + if(type === 'child') { + this.Compose(node, expr, i, state); + continue; + } + if(type === 'prop') { + this.BindingProp(node, expr, i, element, state); + continue; + } + + const message = `Unexpected binding type "${type}", expected "child" or "prop"`; + throw new Error(message); + } + + state.indentLevel--; + writeNextLine(state); + state.write(`};`); + state.indentLevel--; + writeNextLine(state); + state.write(`}`); + + state.write(state.lineEnd); + } + + Compose(node, expr, index, state) { + state.write(`compose(`, node); + state.write(`${TARGET}${index}, `, node); + state.write(`${VALUE}${index}`, expr); + state.write(`);`); + } + + ComposeElement(node, expr, index, state) { + state.write(`composeElement(`, node); + state.write(`${TARGET}${index}, `); + this.CompleteElement(node, expr, state); + state.write(`);`); + } + + CreateElement(node, state) { + state.write(`createElement(`, node); + this.CompleteElement(node, node.componentExpr, state); + state.write(`)`); + } + + CompleteElement({ props, slotFragment }, expr, state) { + this[expr.type](expr, state); + if(props?.length) { + this.ComponentProps(props, state); + } + else if(slotFragment) state.write(`, null`); + + if(slotFragment) { + state.write(', '); + this.JSXTemplate(slotFragment, state); + } + } + + ComponentProps(props, state) { + state.write(`, {`); + for(let i = 0; i < props.length; i++) { + const { node, expr } = props[i]; + // TODO: Dom lookup, JS .prop v['prop'], etc. + // refactor with code below + state.write(` `); + state.write(node.name.name, node.name); + state.write(`: `); + this[expr.type](expr, state); + state.write(`,`); + } + state.write(` }`); + } + + BindingProp(node, expr, index, element, state) { + const { openingElement, openingFragment } = element; + const opening = openingElement ?? openingFragment; + state.write(`${TARGET}${index}`, opening.name); + // TODO: more property validation + const identity = node.name; + const propName = identity.name; + // TODO: refactor with component props + if(isValidESIdentifier(propName)) { + state.write(`.`); + state.write(propName, node.name); + } + else { + state.write(`["`, node.name); + state.write(propName, node.name); + state.write(`"]`); + } + + /* expression */ + state.write(` = `); + // this[expr.type](expr, state); + state.write(`${VALUE}${index}`, expr); + state.write(`;`); + } +} + diff --git a/packages/compiler/transform/bind.test.js b/packages/compiler/transform/bind.test.js deleted file mode 100644 index fb925da..0000000 --- a/packages/compiler/transform/bind.test.js +++ /dev/null @@ -1,72 +0,0 @@ -/* eslint-disable no-undef */ -import { BindGenerator } from './BindGenerator.js'; -import { parse, generate as _generate } from '../compiler.js'; -import { describe, test } from 'vitest'; - -function preParse(input) { - const ast = parse(input); - return _generate(ast); -} -describe('Bind Generator', () => { - - test('Hello bind', ({ expect }) => { - // const input = `const t =

yo

;`; - const input = `name =>

{name}

`; - const initial = preParse(input); - const template = initial.templates[0]; - expect(template.node.type).toBe('JSXElement'); - - const { code } = BindGenerator.generate(template); - - expect(code).toMatchInlineSnapshot(` - "function targets(r) { - return [r.childNodes[0]]; - } - function bind(ts) { - const t0 = ts[0]; - return (v0) => { - compose(t0, v0); - }; - } - function render(v0) { - const [root, bind] = makeTemplate(source); - bind(v0); - return root; - } - " - `); - }); - - test('Bind children and props', ({ expect }) => { - // const input = `const t =

yo

;`; - const input = `const t =

- {"Greeting"} hey {"Azoth"}! -

;`; - - const initial = preParse(input); - const template = initial.templates[0]; - expect(template.node.type).toBe('JSXElement'); - - const { code } = BindGenerator.generate(template); - - expect(code).toMatchInlineSnapshot(` - "function targets(r, ts) { - return [r, r.childNodes[1], ts[0].childNodes[1]]; - } - function bind(ts) { - const t0 = ts[0], t1 = ts[1], t2 = ts[2]; - return (v0, v1, v2) => { - t0.className = v0; - compose(t1, v1); - compose(t2, v2); - }; - } - function render(v0, v1, v2) { - const [root, bind] = makeTemplate(source); - bind(v0, v1, v2); - return root; - } - " - `); - }); -}); \ No newline at end of file diff --git a/packages/compiler/transform/modules.test.js b/packages/compiler/transform/modules.test.js new file mode 100644 index 0000000..ddfdd17 --- /dev/null +++ b/packages/compiler/transform/modules.test.js @@ -0,0 +1,171 @@ +/* eslint-disable no-undef */ +import { makeTargets, makeGetBound, makeRender } from './AssetsGenerator.js'; +import { parse, generate as _generate } from '../compiler.js'; +import { describe, test, beforeEach } from 'vitest'; +import { RenderGenerator } from './RenderGenerator.js'; + +function preParse(input, expect) { + const ast = parse(input); + const initial = _generate(ast); + const template = initial.templates[0]; + expect(template.node.type).toBe('JSXElement'); + return template; +} + + +describe('targets generator', () => { + + beforeEach(context => { + context.compile = code => { + const template = preParse(code, context.expect); + return makeTargets(template); + }; + }); + + test('simple', ({ compile, expect }) => { + const code = compile(`name =>

{name}

`); + expect(code).toMatchInlineSnapshot(` + "const targets = (r) => [r.childNodes[0]]; + " + `); + }); + + test('props and elements', ({ compile, expect }) => { + const code = compile(`const t =

+ {"Greeting"} hey {"Azoth"}! +

;`); + expect(code).toMatchInlineSnapshot( + ` + "const targets = (r,t) => [r,r.childNodes[1],t[0].childNodes[1]]; + " + ` + ); + }); +}); + +describe('getBound generator', () => { + + beforeEach(context => { + context.getTemplate = code => { + return preParse(code, context.expect); + }; + }); + + test('simple', ({ expect }) => { + const template = preParse(`name =>

{name}

`, expect); + const code = makeGetBound(template); + + expect(code).toMatchInlineSnapshot(` + "const getBound = renderer('904ca237ee', targets, bind, false,

); + " + `); + }); + + test('props and elements', ({ expect }) => { + const template = preParse(`const t =

+ {"Greeting"} hey {"Azoth"}! +

;`, expect); + const code = makeGetBound(template); + expect(code).toMatchInlineSnapshot( + ` + "const getBound = renderer('5252cfebed', targets, bind, false,

+ hey ! +

); + " + ` + ); + }); + + test('option noContent: true', ({ getTemplate, expect }) => { + const template = getTemplate(`name =>

{name}

`); + const code = makeGetBound(template, { noContent: true }); + + expect(code).toMatchInlineSnapshot(` + "const getBound = renderer('904ca237ee', targets, bind, false); + " + `); + }); + + + +}); + +describe('bind generator', () => { + + beforeEach(context => { + context.compile = code => { + const template = preParse(code, context.expect); + return RenderGenerator.generate(template).code; + }; + }); + + test('simple', ({ compile, expect }) => { + const code = compile(`name =>

{name}

`); + expect(code).toMatchInlineSnapshot(` + "function bind(ts) { + const t0 = ts[0]; + return (v0) => { + compose(t0, v0); + }; + } + " + `); + }); + + test('props and elements', ({ compile, expect }) => { + const code = compile(`const t =

+ {"Greeting"} hey {"Azoth"}! +

;`); + expect(code).toMatchInlineSnapshot( + ` + "function bind(ts) { + const t0 = ts[0], t1 = ts[1], t2 = ts[2]; + return (v0, v1, v2) => { + t0.className = v0; + compose(t1, v1); + compose(t2, v2); + }; + } + " + ` + ); + }); +}); + +describe('render generator', () => { + + beforeEach(context => { + context.compile = code => { + const template = preParse(code, context.expect); + return makeRender(template); + }; + }); + + test('simple', ({ compile, expect }) => { + const code = compile(`name =>

{name}

`); + expect(code).toMatchInlineSnapshot(` + "function renderDOM(p0) { + const [root, bind] = getBound(); + bind(p0); + return root; + } + " + `); + }); + + test('props and elements', ({ compile, expect }) => { + const code = compile(`const t =

+ {"Greeting"} hey {"Azoth"}! +

;`); + expect(code).toMatchInlineSnapshot( + ` + "function renderDOM(p0,p1,p2) { + const [root, bind] = getBound(); + bind(p0,p1,p2); + return root; + } + " + ` + ); + }); +}); \ No newline at end of file diff --git a/packages/runtime/renderer/controller.test.js b/packages/runtime/renderer/controller.test.js index 8c02237..d73064d 100644 --- a/packages/runtime/renderer/controller.test.js +++ b/packages/runtime/renderer/controller.test.js @@ -1,8 +1,7 @@ import { describe, test, beforeEach, beforeAll } from 'vitest'; import { compose } from '../compose/compose.js'; import { - get, - render, + renderer, Controller, Updater, clearBind, @@ -10,8 +9,17 @@ import { } from './renderer.js'; describe('dom render', () => { + + let getBound = null; beforeAll(() => { RenderService.useDOMEngine(); + getBound = renderer( + 'id', + getTargets, + makeBind, + false, + `

`, + ); }); function getTargets(r, boundEls) { @@ -26,13 +34,7 @@ describe('dom render', () => { }; function renderDOM(p0) { - const [root, bind] = render( - 'id', - getTargets, - makeBind, - false, - `

`, - ); + const [root, bind] = getBound(); bind(p0); return root; } @@ -73,8 +75,16 @@ describe('dom render', () => { describe('html render', () => { const flatRender = node => node.flat().join(''); + let getBound = null; beforeAll(() => { RenderService.useHTMLEngine(); + getBound = renderer( + 'id', + getTargets, + makeBind, + false, + [`

`, `

`], + ); }); function getTargets(r, boundEls) { @@ -87,14 +97,9 @@ describe('html render', () => { t0[0] = p0; }; }; + function renderHTML(p0) { - const [root, bind] = render( - 'id', - getTargets, - makeBind, - false, - [`

`, `

`], - ); + const [root, bind] = getBound(); bind(p0); return root; } diff --git a/packages/runtime/renderer/dom-renderer.test.js b/packages/runtime/renderer/dom-renderer.test.js index 5610645..216d0f2 100644 --- a/packages/runtime/renderer/dom-renderer.test.js +++ b/packages/runtime/renderer/dom-renderer.test.js @@ -4,10 +4,10 @@ import { DOMRenderer } from './dom-renderer.js'; describe('DOM isFragment', () => { test('element root w/ false and true', async ({ expect }) => { - const [div] = DOMRenderer.createTemplate('element-root', false, `
text
`)(); + const [div] = DOMRenderer.createTemplate('element-root', `
text
`, false)(); expect(div).toBeInstanceOf(HTMLDivElement); - const [fragment] = DOMRenderer.createTemplate('fragment-root', true, `
text
`)(); + const [fragment] = DOMRenderer.createTemplate('fragment-root', `
text
`, true)(); expect(fragment).toBeInstanceOf(DocumentFragment); }); diff --git a/packages/runtime/renderer/renderer.js b/packages/runtime/renderer/renderer.js index 7fdfc63..8846f2d 100644 --- a/packages/runtime/renderer/renderer.js +++ b/packages/runtime/renderer/renderer.js @@ -52,32 +52,35 @@ function inject(node, callback) { } } -export function render(id, targets, makeBind, isFragment, content) { - let source = get(id, isFragment, content); - let bind = null; - let boundEls = null; - let node = injectable.at(-1); // peek! - - // TODO: test injectable is right template id type - - if(node) bind = bindings.get(node); - if(bind) return [node, bind]; - - // use case would be list component optimize by - // not keeping bind functions, - // honestly not sure this really needed, the - // overhead is small as it is simple function - if(node) boundEls = renderEngine.bound(node); - else { - // (destructured re-assignment) - ([node, boundEls] = source()); - } - - const nodes = targets(node, boundEls); - bind = makeBind(nodes); - - bindings.set(node, bind); - return [node, bind]; +export function renderer(id, targets, makeBind, isFragment, content) { + const create = get(id, isFragment, content); + + return function getBound() { + let bind = null; + let boundEls = null; + let node = injectable.at(-1); // peek! + + // TODO: test injectable is right template id type + + if(node) bind = bindings.get(node); + if(bind) return [node, bind]; + + // Honestly not sure this really needed, + // use case would be list component optimize by + // not keeping bind functions? + // overhead is small as it is simple function + if(node) boundEls = renderEngine.bound(node); + else { + // (destructuring re-assignment) + ([node, boundEls] = create()); + } + + const nodes = targets(node, boundEls); + bind = makeBind(nodes); + + bindings.set(node, bind); + return [node, bind]; + }; } export class Controller { diff --git a/packages/vite-plugin/index.js b/packages/vite-plugin/index.js index 273ecf8..8ddc96c 100644 --- a/packages/vite-plugin/index.js +++ b/packages/vite-plugin/index.js @@ -17,7 +17,7 @@ export default function azothPlugin(options) { name: 'azoth-jsx', enforce: 'pre', - config(config, { command: cmd }) { + config(_config, { command: cmd }) { command = cmd; }, diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 5b9165b..d125432 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,4 +1,3 @@ -# azoth/pnpm-workspace.yaml packages: - "./packages/*" - "./sandbox" From 2eeedf951cb326f27a2263a9c4ee8502b511dabf Mon Sep 17 00:00:00 2001 From: Marty Nelson Date: Wed, 20 Mar 2024 16:25:12 -0700 Subject: [PATCH 17/23] template assets all generating --- packages/compiler/transform/BindGenerator.js | 279 ------------------ packages/compiler/transform/GeneratorBase.js | 3 +- .../compiler/transform/RenderGenerator.js | 24 -- ...{AssetsGenerator.js => template-assets.js} | 2 +- ...odules.test.js => template-assets.test.js} | 3 +- 5 files changed, 3 insertions(+), 308 deletions(-) delete mode 100644 packages/compiler/transform/BindGenerator.js rename packages/compiler/transform/{AssetsGenerator.js => template-assets.js} (99%) rename packages/compiler/transform/{modules.test.js => template-assets.test.js} (98%) diff --git a/packages/compiler/transform/BindGenerator.js b/packages/compiler/transform/BindGenerator.js deleted file mode 100644 index acb0d77..0000000 --- a/packages/compiler/transform/BindGenerator.js +++ /dev/null @@ -1,279 +0,0 @@ -import { Generator, writeNextLine } from './GeneratorBase.js'; -import { isValidESIdentifier } from 'is-valid-es-identifier'; -import { generateWith } from '../compiler.js'; - -const OPENING_TYPES = { - JSXOpeningElement: true, - JSXOpeningFragment: true, -}; - -const OPENING_PROP_BY_TYPE = { - JSXElement: 'openingElement', - JSXFragment: 'openingFragment', -}; - -function getOpening(element) { - const { type } = element; - if(OPENING_TYPES[type]) return element; - - const prop = OPENING_PROP_BY_TYPE[type]; - if(prop) return element[prop]; - - throw new TypeError(`Unexpected binding element type "${element.type}"`); -} - -const ROOT = 'r'; -const TARGETS = 'ts'; -const TARGET = 't'; -const VALUE = 'v'; - -export class BindGenerator extends Generator { - static generate(template) { - const generator = new BindGenerator(template); - return generateWith(generator, template.node); - } - - constructor(template) { - super(); - this.template = template; - } - - JSXFragment(_node, state) { - this.JSXTemplate(state); - } - - JSXElement(_node, state) { - this.JSXTemplate(state); - } - - JSXTemplate(state) { - const { template } = this; - // Short-circuit templates - const { isStatic, node: root } = template; - const { isComponent } = root; - if(isStatic || isComponent) { - if(isComponent) this.CreateElement(root, state); - else if(isStatic) this.StaticRoot(template, state); - return; - } - - this.Targets(template, state); - this.Bindings(template, state); - this.Render(template, state); - } - - JSXExpressionContainer({ expression }, state) { - this[expression.type](expression, state); - } - - JSXIdentifier(identifier, state) { - state.write(identifier.name, identifier); - } - - StaticRoot(template, state) { - this.TemplateRenderer(template, state); - if(!template.isEmpty) state.write(`[0]`, template.node); // dom root - } - - TemplateRenderer({ id, isEmpty, isDomFragment, node }, state) { - if(isEmpty) { - state.write('null', node); - return; - } - state.write(`t${id}`, node); - state.write(`(`); - // TODO: this should go in template module - if(isDomFragment) state.write('true'); - state.write(`)`); - } - - Targets(template, state) { - const { boundElements, bindings } = template; - const { length: elLength } = boundElements; - - state.write(`function targets(`); - state.write(ROOT); - if(elLength) state.write(`, ${TARGETS}`); - state.write(') {'); - - state.indentLevel++; - writeNextLine(state); - - state.write(`return [`); - for(let i = 0; i < bindings.length; i++) { - const { element, type, index, node } = bindings[i]; - const { isRoot, queryIndex } = element; - const target = isRoot ? ROOT : `${TARGETS}[${queryIndex}]`; - - if(i !== 0) state.write(', '); - - if(type !== 'child') { - state.write(`${target}`); - continue; - } - - const opening = getOpening(element, node); - state.write(`${target}.childNodes`, opening.name); - state.write(`[${index}]`, node); - } - state.write(`];`); - - state.indentLevel--; - writeNextLine(state); - state.write(`}`); - state.write(state.lineEnd); - } - - Render(template, state) { - const { bindings, isDomFragment } = template; - - const params = []; - for(let i = 0; i < bindings.length; i++) { - params.push(`${VALUE}${i}`); - } - - state.write(`function renderDOM(${params.join(', ')}) {`); - state.indentLevel++; - writeNextLine(state); - - state.write(`const [root, bind] = render('id', targets, bind, ${isDomFragment});`); - writeNextLine(state); - state.write(`bind(${params.join(', ')});`); - writeNextLine(state); - state.write(`return root;`); - - state.indentLevel--; - writeNextLine(state); - state.write(`}`); - state.write(state.lineEnd); - } - - Bindings(template, state) { - const { bindings } = template; - - state.write(`function bind(${TARGETS}) {`); - state.indentLevel++; - writeNextLine(state); - - const targets = []; - const params = []; - for(let i = 0; i < bindings.length; i++) { - targets.push(`${TARGET}${i} = ${TARGETS}[${i}]`); - params.push(`${VALUE}${i}`); - } - - state.write(`const ${targets.join(', ')};`); - writeNextLine(state); - state.write(`return (${params.join(', ')}) => {`); - state.indentLevel++; - - for(let i = 0; i < bindings.length; i++) { - const { element, type, node, expr } = bindings[i]; - writeNextLine(state); - - if(!this[expr.type]) { - throw new TypeError(`Unexpected Binding expression AST type "${expr.type}"`); - } - - if(node.isComponent) { - this.ComposeElement(node, expr, i, state); - continue; - } - if(type === 'child') { - this.Compose(node, expr, i, state); - continue; - } - if(type === 'prop') { - this.BindingProp(node, expr, i, element, state); - continue; - } - - const message = `Unexpected binding type "${type}", expected "child" or "prop"`; - throw new Error(message); - } - - state.indentLevel--; - writeNextLine(state); - state.write(`};`); - state.indentLevel--; - writeNextLine(state); - state.write(`}`); - - state.write(state.lineEnd); - } - - Compose(node, expr, index, state) { - state.write(`compose(`, node); - state.write(`${TARGET}${index}, `, node); - state.write(`${VALUE}${index}`, expr); - // this[expr.type](expr, state); - state.write(`);`); - } - - ComposeElement(node, expr, index, state) { - state.write(`composeElement(`, node); - state.write(`${TARGET}${index}, `); - this.CompleteElement(node, expr, state); - state.write(`);`); - } - - CreateElement(node, state) { - state.write(`createElement(`, node); - this.CompleteElement(node, node.componentExpr, state); - state.write(`)`); - } - - CompleteElement({ props, slotFragment }, expr, state) { - this[expr.type](expr, state); - if(props?.length) { - this.ComponentProps(props, state); - } - else if(slotFragment) state.write(`, null`); - - if(slotFragment) { - state.write(', '); - this.JSXTemplate(slotFragment, state); - } - } - - ComponentProps(props, state) { - state.write(`, {`); - for(let i = 0; i < props.length; i++) { - const { node, expr } = props[i]; - // TODO: Dom lookup, JS .prop v['prop'], etc. - // refactor with code below - state.write(` `); - state.write(node.name.name, node.name); - state.write(`: `); - this[expr.type](expr, state); - state.write(`,`); - } - state.write(` }`); - } - - BindingProp(node, expr, index, element, state) { - const { openingElement, openingFragment } = element; - const opening = openingElement ?? openingFragment; - state.write(`${TARGET}${index}`, opening.name); - // TODO: more property validation - const identity = node.name; - const propName = identity.name; - // TODO: refactor with component props - if(isValidESIdentifier(propName)) { - state.write(`.`); - state.write(propName, node.name); - } - else { - state.write(`["`, node.name); - state.write(propName, node.name); - state.write(`"]`); - } - - /* expression */ - state.write(` = `); - // this[expr.type](expr, state); - state.write(`${VALUE}${index}`, expr); - state.write(`;`); - } -} - diff --git a/packages/compiler/transform/GeneratorBase.js b/packages/compiler/transform/GeneratorBase.js index e756c0b..639e4b1 100644 --- a/packages/compiler/transform/GeneratorBase.js +++ b/packages/compiler/transform/GeneratorBase.js @@ -1,6 +1,6 @@ import { GENERATOR } from 'astring'; -// enable extending as es6 class +// enables extending as es6 class export function Generator() { } Generator.prototype = GENERATOR; @@ -9,4 +9,3 @@ export function writeNextLine(state) { state.write(lineEnd); state.write(indent.repeat(state.indentLevel)); } - diff --git a/packages/compiler/transform/RenderGenerator.js b/packages/compiler/transform/RenderGenerator.js index 3901601..7533aee 100644 --- a/packages/compiler/transform/RenderGenerator.js +++ b/packages/compiler/transform/RenderGenerator.js @@ -38,30 +38,6 @@ export class RenderGenerator extends Generator { state.write(identifier.name, identifier); } - // Render(template, state) { - // const { bindings, isDomFragment } = template; - - // const params = []; - // for(let i = 0; i < bindings.length; i++) { - // params.push(`${VALUE}${i}`); - // } - - // state.write(`function renderDOM(${params.join(', ')}) {`); - // state.indentLevel++; - // writeNextLine(state); - - // state.write(`const [root, bind] = render('id', targets, bind, ${isDomFragment});`); - // writeNextLine(state); - // state.write(`bind(${params.join(', ')});`); - // writeNextLine(state); - // state.write(`return root;`); - - // state.indentLevel--; - // writeNextLine(state); - // state.write(`}`); - // state.write(state.lineEnd); - // } - Bindings(state) { const bindings = this.#bindings; diff --git a/packages/compiler/transform/AssetsGenerator.js b/packages/compiler/transform/template-assets.js similarity index 99% rename from packages/compiler/transform/AssetsGenerator.js rename to packages/compiler/transform/template-assets.js index f74a9f5..d4798eb 100644 --- a/packages/compiler/transform/AssetsGenerator.js +++ b/packages/compiler/transform/template-assets.js @@ -25,4 +25,4 @@ export function makeRender({ bindings: { length } }) { bind(${params}); return root; }\n`; -} \ No newline at end of file +} diff --git a/packages/compiler/transform/modules.test.js b/packages/compiler/transform/template-assets.test.js similarity index 98% rename from packages/compiler/transform/modules.test.js rename to packages/compiler/transform/template-assets.test.js index ddfdd17..9977463 100644 --- a/packages/compiler/transform/modules.test.js +++ b/packages/compiler/transform/template-assets.test.js @@ -1,5 +1,5 @@ /* eslint-disable no-undef */ -import { makeTargets, makeGetBound, makeRender } from './AssetsGenerator.js'; +import { makeTargets, makeGetBound, makeRender } from './template-assets.js'; import { parse, generate as _generate } from '../compiler.js'; import { describe, test, beforeEach } from 'vitest'; import { RenderGenerator } from './RenderGenerator.js'; @@ -12,7 +12,6 @@ function preParse(input, expect) { return template; } - describe('targets generator', () => { beforeEach(context => { From c79d191a681e38dac06a1abc3ba2c3a206176dcd Mon Sep 17 00:00:00 2001 From: Marty Nelson Date: Thu, 21 Mar 2024 07:21:06 -0700 Subject: [PATCH 18/23] refactor generators --- packages/compiler/compiler.test.js | 707 ++---------------- packages/compiler/source-maps.test.js | 150 +--- packages/compiler/transform/Analyzer.js | 6 +- .../{RenderGenerator.js => BindGenerator.js} | 57 +- .../compiler/transform/TemplateGenerator.js | 179 +---- ...plate-assets.js => template-generators.js} | 1 + ...ts.test.js => template-generators.test.js} | 6 +- packages/runtime/compose/compose.js | 28 +- packages/runtime/renderer/controller.test.js | 2 +- 9 files changed, 129 insertions(+), 1007 deletions(-) rename packages/compiler/transform/{RenderGenerator.js => BindGenerator.js} (67%) rename packages/compiler/transform/{template-assets.js => template-generators.js} (99%) rename packages/compiler/transform/{template-assets.test.js => template-generators.test.js} (97%) diff --git a/packages/compiler/compiler.test.js b/packages/compiler/compiler.test.js index 3256eec..2ab60de 100644 --- a/packages/compiler/compiler.test.js +++ b/packages/compiler/compiler.test.js @@ -8,8 +8,8 @@ const compile = input => { }); return { code, map, - templates: templates.map(({ id, html, isDomFragment, isEmpty, isStatic, imports }) => { - return { id, html, isDomFragment, isEmpty, isStatic, imports }; + templates: templates.map(({ id, html, isDomFragment, isEmpty }) => { + return { id, html, isDomFragment, isEmpty }; }) }; }; @@ -24,15 +24,9 @@ describe('JSX dom literals', () => { const { code, templates } = compile(input); expect(code).toMatchInlineSnapshot(` - "import { __compose } from 'azoth/runtime'; - import { ta516887159 } from 'virtual:azoth-templates?id=a516887159'; - const t = (() => { - const __root = ta516887159()[0]; - const __child1 = __root.childNodes[1]; - __root.className = ("className"); - __compose(__child1, "Azoth"); - return __root; - })(); + "import { ta516887159 } from 'virtual:azoth-templates?id=a516887159'; + + const t = ta516887159("className","Azoth"); " `); expect(templates).toMatchInlineSnapshot(` @@ -42,12 +36,8 @@ describe('JSX dom literals', () => { Hello

", "id": "a516887159", - "imports": [ - "compose", - ], "isDomFragment": false, "isEmpty": false, - "isStatic": false, }, ] `); @@ -73,34 +63,9 @@ describe('JSX dom literals', () => { const { code, templates } = compile(input); expect(code).toMatchInlineSnapshot(` - "import { __compose } from 'azoth/runtime'; - import { tfdd1a869cf } from 'virtual:azoth-templates?id=fdd1a869cf'; - const t = (() => { - const [__root, __targets] = tfdd1a869cf(); - const __target0 =__targets[0]; - const __target1 =__targets[1]; - const __target2 =__targets[2]; - const __target3 =__targets[3]; - const __target4 =__targets[4]; - const __target5 =__targets[5]; - const __child1 = __target0.childNodes[0]; - const __child2 = __target1.childNodes[0]; - const __child3 = __target2.childNodes[0]; - const __child4 = __target4.childNodes[1]; - const __child5 = __target4.childNodes[3]; - const __child7 = __target3.childNodes[7]; - const __child8 = __root.childNodes[15]; - __target0.className = ("my-class"); - __compose(__child1, "felix"); - __compose(__child2, "this is"); - __compose(__child3, "azoth"); - __compose(__child4, "two"); - __compose(__child5, "and..."); - __target5.className = ("span-class"); - __compose(__child7, "ul-footer"); - __compose(__child8, "footer"); - return __root; - })(); + "import { tfdd1a869cf } from 'virtual:azoth-templates?id=fdd1a869cf'; + + const t = tfdd1a869cf("my-class","felix","this is","azoth","two","and...","span-class","ul-footer","footer"); " `); @@ -123,12 +88,8 @@ describe('JSX dom literals', () => {
", "id": "fdd1a869cf", - "imports": [ - "compose", - ], "isDomFragment": false, "isEmpty": false, - "isStatic": false, }, ] `); @@ -148,14 +109,7 @@ describe('JSX dom literals', () => { expect(code).toMatchInlineSnapshot(` "import { t10073da0ec } from 'virtual:azoth-templates?id=10073da0ec'; - const t = (() => { - const __root = t10073da0ec()[0]; - __root.className = ("className"); - __root.name = ("name"); - __root["class"] = ("class"); - __root["class-name"] = ("class-name"); - return __root; - })(); + const t = t10073da0ec("className","name","class","class-name"); " `); @@ -164,10 +118,8 @@ describe('JSX dom literals', () => { { "html": "", "id": "10073da0ec", - "imports": [], "isDomFragment": false, "isEmpty": false, - "isStatic": false, }, ] `); @@ -181,14 +133,9 @@ describe('nested context', () => { const { code, templates } = compile(input); expect(code).toMatchInlineSnapshot(` - "import { __compose } from 'azoth/runtime'; - import { t8dae88052a, t1a78cbe949 } from 'virtual:azoth-templates?id=8dae88052a&id=1a78cbe949'; - (() => { - const __root = t8dae88052a()[0]; - const __child0 = __root.childNodes[0]; - __compose(__child0, t1a78cbe949()[0]); - return __root; - })(); + "import { t8dae88052a, t1a78cbe949 } from 'virtual:azoth-templates?id=8dae88052a&id=1a78cbe949'; + + t8dae88052a(t1a78cbe949()); " `); @@ -197,20 +144,14 @@ describe('nested context', () => { { "html": "
", "id": "8dae88052a", - "imports": [ - "compose", - ], "isDomFragment": false, "isEmpty": false, - "isStatic": false, }, { "html": "
", "id": "1a78cbe949", - "imports": [], "isDomFragment": false, "isEmpty": false, - "isStatic": true, }, ] `); @@ -227,7 +168,7 @@ describe('template optimizations', () => { expect(code).toMatchInlineSnapshot(` "import { t5bf3d2f523 } from 'virtual:azoth-templates?id=5bf3d2f523'; - const template = t5bf3d2f523()[0]; + const template = t5bf3d2f523(); " `); @@ -236,86 +177,8 @@ describe('template optimizations', () => { { "html": "

Hello

", "id": "5bf3d2f523", - "imports": [], "isDomFragment": false, "isEmpty": false, - "isStatic": true, - }, - ] - `); - }); -}); - -describe('surrounding code integration', () => { - - test('ArrowFunctionExpression: implicit return is block return', ({ expect }) => { - const input = ` - const template = (text) =>

{text}

- `; - - const { code, templates } = compile(input); - - expect(code).toMatchInlineSnapshot(` - "import { __compose } from 'azoth/runtime'; - import { t904ca237ee } from 'virtual:azoth-templates?id=904ca237ee'; - const template = text => { - const __root = t904ca237ee()[0]; - const __child0 = __root.childNodes[0]; - __compose(__child0, text); - return __root; - }; - " - `); - - expect(templates).toMatchInlineSnapshot(` - [ - { - "html": "

", - "id": "904ca237ee", - "imports": [ - "compose", - ], - "isDomFragment": false, - "isEmpty": false, - "isStatic": false, - }, - ] - `); - }); - - test('ReturnStatement: injects statements before, returns root', ({ expect }) => { - const input = ` - function template(text) { - const format = 'text' + '!'; - return

{text}

; - } - `; - - const { code, templates } = compile(input); - - expect(code).toMatchInlineSnapshot(` - "import { __compose } from 'azoth/runtime'; - import { t904ca237ee } from 'virtual:azoth-templates?id=904ca237ee'; - function template(text) { - const format = 'text' + '!'; - const __root = t904ca237ee()[0]; - const __child0 = __root.childNodes[0]; - __compose(__child0, text); - return __root; - } - " - `); - expect(templates).toMatchInlineSnapshot(` - [ - { - "html": "

", - "id": "904ca237ee", - "imports": [ - "compose", - ], - "isDomFragment": false, - "isEmpty": false, - "isStatic": false, }, ] `); @@ -323,38 +186,20 @@ describe('surrounding code integration', () => { }); describe('fragments', () => { - test('<> ... basic', ({ expect }) => { + test('empty', ({ expect }) => { const input = ` const fragment = <>

; - const single = <>
; - const fragInFrag = <><>
; - const fragInFragCompose = <><>{x}; - const empty = <>; const compose = <>{x}; - const text = <>text; + const empty = <>; `; const { code, templates } = compile(input); expect(code).toMatchInlineSnapshot(` - "import { __compose } from 'azoth/runtime'; - import { tc203fe7dcd, t1a78cbe949, tc084de4382, t1cb251ec0d } from 'virtual:azoth-templates?id=c203fe7dcd&id=1a78cbe949&id=c084de4382&id=1cb251ec0d'; - const fragment = tc203fe7dcd(true)[0]; - const single = t1a78cbe949()[0]; - const fragInFrag = t1a78cbe949()[0]; - const fragInFragCompose = (() => { - const __root = tc084de4382(true)[0]; - const __child0 = __root.childNodes[0]; - __compose(__child0, x); - return __root; - })(); + "import { tc203fe7dcd, tc084de4382 } from 'virtual:azoth-templates?id=c203fe7dcd&id=c084de4382'; + + const fragment = tc203fe7dcd(); + const compose = tc084de4382(x); const empty = null; - const compose = (() => { - const __root = tc084de4382(true)[0]; - const __child0 = __root.childNodes[0]; - __compose(__child0, x); - return __root; - })(); - const text = t1cb251ec0d(true)[0]; " `); @@ -363,62 +208,20 @@ describe('fragments', () => { { "html": "

", "id": "c203fe7dcd", - "imports": [], "isDomFragment": true, "isEmpty": false, - "isStatic": true, - }, - { - "html": "
", - "id": "1a78cbe949", - "imports": [], - "isDomFragment": false, - "isEmpty": false, - "isStatic": true, - }, - { - "html": "
", - "id": "1a78cbe949", - "imports": [], - "isDomFragment": false, - "isEmpty": false, - "isStatic": true, }, { "html": "", "id": "c084de4382", - "imports": [ - "compose", - ], "isDomFragment": true, "isEmpty": false, - "isStatic": false, }, { "html": "", "id": "", - "imports": [], "isDomFragment": true, "isEmpty": true, - "isStatic": true, - }, - { - "html": "", - "id": "c084de4382", - "imports": [ - "compose", - ], - "isDomFragment": true, - "isEmpty": false, - "isStatic": false, - }, - { - "html": "text", - "id": "1cb251ec0d", - "imports": [], - "isDomFragment": true, - "isEmpty": false, - "isStatic": true, }, ] `); @@ -455,25 +258,15 @@ describe('fragments', () => { const { code, templates } = compile(input); expect(code).toMatchInlineSnapshot(` - "import { __compose } from 'azoth/runtime'; - import { tc203fe7dcd, t1a78cbe949, tc084de4382, t6c72de769d } from 'virtual:azoth-templates?id=c203fe7dcd&id=1a78cbe949&id=c084de4382&id=6c72de769d'; - const fragment = tc203fe7dcd(true)[0]; - const single = t1a78cbe949()[0]; - const fragInFrag = t1a78cbe949()[0]; - const fragInFragCompose = (() => { - const __root = tc084de4382(true)[0]; - const __child0 = __root.childNodes[0]; - __compose(__child0, x); - return __root; - })(); + "import { tc203fe7dcd, t1a78cbe949, tc084de4382, t6c72de769d } from 'virtual:azoth-templates?id=c203fe7dcd&id=1a78cbe949&id=c084de4382&id=6c72de769d'; + + const fragment = tc203fe7dcd(); + const single = t1a78cbe949(); + const fragInFrag = t1a78cbe949(); + const fragInFragCompose = tc084de4382(x); const empty = null; - const compose = (() => { - const __root = tc084de4382(true)[0]; - const __child0 = __root.childNodes[0]; - __compose(__child0, x); - return __root; - })(); - const text = t6c72de769d(true)[0]; + const compose = tc084de4382(x); + const text = t6c72de769d(); " `); @@ -482,64 +275,46 @@ describe('fragments', () => { { "html": "

", "id": "c203fe7dcd", - "imports": [], "isDomFragment": true, "isEmpty": false, - "isStatic": true, }, { "html": "
", "id": "1a78cbe949", - "imports": [], "isDomFragment": false, "isEmpty": false, - "isStatic": true, }, { "html": "
", "id": "1a78cbe949", - "imports": [], "isDomFragment": false, "isEmpty": false, - "isStatic": true, }, { "html": "", "id": "c084de4382", - "imports": [ - "compose", - ], "isDomFragment": true, "isEmpty": false, - "isStatic": false, }, { "html": "", "id": "", - "imports": [], "isDomFragment": true, "isEmpty": true, - "isStatic": true, }, { "html": "", "id": "c084de4382", - "imports": [ - "compose", - ], "isDomFragment": true, "isEmpty": false, - "isStatic": false, }, { "html": " text ", "id": "6c72de769d", - "imports": [], "isDomFragment": true, "isEmpty": false, - "isStatic": true, }, ] `); @@ -560,22 +335,12 @@ describe('fragments', () => { const { code, templates } = compile(input); expect(code).toMatchInlineSnapshot(` - "import { __compose } from 'azoth/runtime'; - import { t1a78cbe949, tc084de4382 } from 'virtual:azoth-templates?id=1a78cbe949&id=c084de4382'; - const start = t1a78cbe949()[0]; - const end = t1a78cbe949()[0]; - const composeStart = (() => { - const __root = tc084de4382(true)[0]; - const __child0 = __root.childNodes[0]; - __compose(__child0, x); - return __root; - })(); - const composeEnd = (() => { - const __root = tc084de4382(true)[0]; - const __child0 = __root.childNodes[0]; - __compose(__child0, x); - return __root; - })(); + "import { t1a78cbe949, tc084de4382 } from 'virtual:azoth-templates?id=1a78cbe949&id=c084de4382'; + + const start = t1a78cbe949(); + const end = t1a78cbe949(); + const composeStart = tc084de4382(x); + const composeEnd = tc084de4382(x); " `); @@ -584,38 +349,26 @@ describe('fragments', () => { { "html": "
", "id": "1a78cbe949", - "imports": [], "isDomFragment": false, "isEmpty": false, - "isStatic": true, }, { "html": "
", "id": "1a78cbe949", - "imports": [], "isDomFragment": false, "isEmpty": false, - "isStatic": true, }, { "html": "", "id": "c084de4382", - "imports": [ - "compose", - ], "isDomFragment": true, "isEmpty": false, - "isStatic": false, }, { "html": "", "id": "c084de4382", - "imports": [ - "compose", - ], "isDomFragment": true, "isEmpty": false, - "isStatic": false, }, ] `); @@ -633,18 +386,13 @@ describe('fragments', () => { const { code, templates } = compile(input); expect(code).toMatchInlineSnapshot(` - "import { __compose } from 'azoth/runtime'; - import { t653a3aad80, tdcaa233028, t2dc1738d5c, t0cf31b2c28, t5bc2a159b1 } from 'virtual:azoth-templates?id=653a3aad80&id=dcaa233028&id=2dc1738d5c&id=0cf31b2c28&id=5bc2a159b1'; - const fragment = t653a3aad80(true)[0]; - const single = tdcaa233028(true)[0]; - const fragInFrag = t2dc1738d5c(true)[0]; - const spaces = t0cf31b2c28(true)[0]; - const compose = (() => { - const __root = t5bc2a159b1(true)[0]; - const __child0 = __root.childNodes[1]; - __compose(__child0, x); - return __root; - })(); + "import { t653a3aad80, tdcaa233028, t2dc1738d5c, t0cf31b2c28, t5bc2a159b1 } from 'virtual:azoth-templates?id=653a3aad80&id=dcaa233028&id=2dc1738d5c&id=0cf31b2c28&id=5bc2a159b1'; + + const fragment = t653a3aad80(); + const single = tdcaa233028(); + const fragInFrag = t2dc1738d5c(); + const spaces = t0cf31b2c28(); + const compose = t5bc2a159b1(x); " `); @@ -653,44 +401,32 @@ describe('fragments', () => { { "html": "

", "id": "653a3aad80", - "imports": [], "isDomFragment": true, "isEmpty": false, - "isStatic": true, }, { "html": "
", "id": "dcaa233028", - "imports": [], "isDomFragment": true, "isEmpty": false, - "isStatic": true, }, { "html": "
", "id": "2dc1738d5c", - "imports": [], "isDomFragment": true, "isEmpty": false, - "isStatic": true, }, { "html": " ", "id": "0cf31b2c28", - "imports": [], "isDomFragment": true, "isEmpty": false, - "isStatic": true, }, { "html": " ", "id": "5bc2a159b1", - "imports": [ - "compose", - ], "isDomFragment": true, "isEmpty": false, - "isStatic": false, }, ] `); @@ -704,14 +440,9 @@ describe('fragments', () => { const { code, templates } = compile(input); expect(code).toMatchInlineSnapshot(` - "import { __compose } from 'azoth/runtime'; - import { tfaf808e6cc } from 'virtual:azoth-templates?id=faf808e6cc'; - const fragment = (() => { - const __root = tfaf808e6cc(true)[0]; - const __child0 = __root.childNodes[1]; - __compose(__child0, "two"); - return __root; - })(); + "import { tfaf808e6cc } from 'virtual:azoth-templates?id=faf808e6cc'; + + const fragment = tfaf808e6cc("two"); " `); @@ -720,12 +451,8 @@ describe('fragments', () => { { "html": "onethree", "id": "faf808e6cc", - "imports": [ - "compose", - ], "isDomFragment": true, "isEmpty": false, - "isStatic": false, }, ] `); @@ -745,15 +472,10 @@ describe('fragments', () => { const { code, templates } = compile(input); expect(code).toMatchInlineSnapshot(` - "import { __compose } from 'azoth/runtime'; - import { tccaa44c114, t681310be49 } from 'virtual:azoth-templates?id=ccaa44c114&id=681310be49'; - const extraneous = tccaa44c114()[0]; - const childNodeIndex = (() => { - const __root = t681310be49()[0]; - const __child0 = __root.childNodes[3]; - __compose(__child0, "expect index 3"); - return __root; - })(); + "import { tccaa44c114, t681310be49 } from 'virtual:azoth-templates?id=ccaa44c114&id=681310be49'; + + const extraneous = tccaa44c114(); + const childNodeIndex = t681310be49("expect index 3"); " `); @@ -762,10 +484,8 @@ describe('fragments', () => { { "html": "



", "id": "ccaa44c114", - "imports": [], "isDomFragment": false, "isEmpty": false, - "isStatic": true, }, { "html": "
@@ -774,12 +494,8 @@ describe('fragments', () => {

", "id": "681310be49", - "imports": [ - "compose", - ], "isDomFragment": false, "isEmpty": false, - "isStatic": false, }, ] `); @@ -791,19 +507,9 @@ describe('fragments', () => { const { code, templates } = compile(input); expect(code).toMatchInlineSnapshot(` - "import { __compose } from 'azoth/runtime'; - import { tef691fa27a } from 'virtual:azoth-templates?id=ef691fa27a'; - const App = (() => { - const [__root, __targets] = tef691fa27a(true); - const __target0 =__targets[0]; - const __child0 = __root.childNodes[0]; - const __child1 = __target0.childNodes[0]; - const __child2 = __root.childNodes[2]; - __compose(__child0, 'foo'); - __compose(__child1, 'bar'); - __compose(__child2, 'qux'); - return __root; - })(); + "import { tef691fa27a } from 'virtual:azoth-templates?id=ef691fa27a'; + + const App = tef691fa27a('foo','bar','qux'); " `); @@ -812,49 +518,8 @@ describe('fragments', () => { { "html": "
", "id": "ef691fa27a", - "imports": [ - "compose", - ], "isDomFragment": true, "isEmpty": false, - "isStatic": false, - }, - ] - `); - - }); -}); - -describe('template root', () => { - test('single element is root', ({ expect }) => { - const input = ` - const div =
{hello}
; - `; - const { code, templates } = compile(input); - - expect(code).toMatchInlineSnapshot(` - "import { __compose } from 'azoth/runtime'; - import { t8dae88052a } from 'virtual:azoth-templates?id=8dae88052a'; - const div = (() => { - const __root = t8dae88052a()[0]; - const __child0 = __root.childNodes[0]; - __compose(__child0, hello); - return __root; - })(); - " - `); - - expect(templates).toMatchInlineSnapshot(` - [ - { - "html": "
", - "id": "8dae88052a", - "imports": [ - "compose", - ], - "isDomFragment": false, - "isEmpty": false, - "isStatic": false, }, ] `); @@ -873,11 +538,7 @@ describe('element composition', () => { expect(code).toMatchInlineSnapshot(` "import { t1cdf0d646f } from 'virtual:azoth-templates?id=1cdf0d646f'; - document.body.append((() => { - const __root = t1cdf0d646f()[0]; - __root.prop = (prop); - return __root; - })()); + document.body.append(t1cdf0d646f(prop)); " `); @@ -886,10 +547,8 @@ describe('element composition', () => { { "html": "", "id": "1cdf0d646f", - "imports": [], "isDomFragment": false, "isEmpty": false, - "isStatic": false, }, ] `); @@ -915,22 +574,14 @@ describe('element composition', () => { { "html": "", "id": "", - "imports": [ - "createElement", - ], "isDomFragment": false, "isEmpty": true, - "isStatic": true, }, { "html": "", "id": "", - "imports": [ - "createElement", - ], "isDomFragment": false, "isEmpty": true, - "isStatic": true, }, ] `); @@ -946,16 +597,9 @@ describe('element composition', () => { const { code, templates } = compile(input); expect(code).toMatchInlineSnapshot(` - "import { __composeElement, __compose } from 'azoth/runtime'; + "import { __createElement } from 'azoth/runtime'; import { t2288998344 } from 'virtual:azoth-templates?id=2288998344'; - const component = (() => { - const __root = t2288998344()[0]; - const __child0 = __root.childNodes[1]; - const __child1 = __root.childNodes[3]; - __composeElement(__child0, Component, { prop: value, prop2: "literal", }); - __composeElement(__child1, GotNoPropsAsYouCanSee); - return __root; - })(); + const component = t2288998344(__createElement(Component, { prop: value, prop2: "literal", }),__createElement(GotNoPropsAsYouCanSee)); " `); expect(templates).toMatchInlineSnapshot(` @@ -966,13 +610,8 @@ describe('element composition', () => { ", "id": "2288998344", - "imports": [ - "composeElement", - "compose", - ], "isDomFragment": false, "isEmpty": false, - "isStatic": false, }, ] `); @@ -991,18 +630,11 @@ describe('element composition', () => { const { code, templates } = compile(input); expect(code).toMatchInlineSnapshot(` - "import { __createElement, __compose } from 'azoth/runtime'; + "import { __createElement } from 'azoth/runtime'; import { t2288998344 } from 'virtual:azoth-templates?id=2288998344'; const $A = __createElement(A); const $B = __createElement(B); - const dom = (() => { - const __root = t2288998344()[0]; - const __child0 = __root.childNodes[1]; - const __child1 = __root.childNodes[3]; - __compose(__child0, $A); - __compose(__child1, $B); - return __root; - })(); + const dom = t2288998344($A,$B); " `); expect(templates).toMatchInlineSnapshot(` @@ -1010,22 +642,14 @@ describe('element composition', () => { { "html": "", "id": "", - "imports": [ - "createElement", - ], "isDomFragment": false, "isEmpty": true, - "isStatic": true, }, { "html": "", "id": "", - "imports": [ - "createElement", - ], "isDomFragment": false, "isEmpty": true, - "isStatic": true, }, { "html": "
@@ -1033,50 +657,8 @@ describe('element composition', () => {
", "id": "2288998344", - "imports": [ - "compose", - ], - "isDomFragment": false, - "isEmpty": false, - "isStatic": false, - }, - ] - `); - - }); - - test('return keyword in Function with static jsx', ({ expect }) => { - const input = ` - function Surprise() { - return
-

Guess What...

-

surprise!

-
; - } - `; - - const { code, templates } = compile(input); - - expect(code).toMatchInlineSnapshot(` - "import { t92cc583556 } from 'virtual:azoth-templates?id=92cc583556'; - - function Surprise() { - return t92cc583556()[0]; - } - " - `); - expect(templates).toMatchInlineSnapshot(` - [ - { - "html": "
-

Guess What...

-

surprise!

-
", - "id": "92cc583556", - "imports": [], "isDomFragment": false, "isEmpty": false, - "isStatic": true, }, ] `); @@ -1102,43 +684,14 @@ describe('element composition', () => { const { code, templates } = compile(input); expect(code).toMatchInlineSnapshot(` - "import { __createElement, __compose } from 'azoth/runtime'; + "import { __createElement } from 'azoth/runtime'; import { t904ca237ee, t1cb251ec0d, t9b045328fb } from 'virtual:azoth-templates?id=904ca237ee&id=1cb251ec0d&id=9b045328fb'; - const c = __createElement(Component, null, (() => { - const __root = t904ca237ee()[0]; - const __child0 = __root.childNodes[0]; - __compose(__child0, "test"); - return __root; - })()); - const cTrim = __createElement(Component, null, (() => { - const __root = t904ca237ee()[0]; - const __child0 = __root.childNodes[0]; - __compose(__child0, "test"); - return __root; - })()); - const cTrimStart = __createElement(Component, null, (() => { - const __root = t904ca237ee()[0]; - const __child0 = __root.childNodes[0]; - __compose(__child0, "test"); - return __root; - })()); - const cTrimEnd = __createElement(Component, null, (() => { - const __root = t904ca237ee()[0]; - const __child0 = __root.childNodes[0]; - __compose(__child0, "test"); - return __root; - })()); - const cText = __createElement(Component, null, t1cb251ec0d(true)[0]); - const cFrag = __createElement(Component, null, (() => { - const [__root, __targets] = t9b045328fb(true); - const __target0 =__targets[0]; - const __target1 =__targets[1]; - const __child0 = __target0.childNodes[0]; - const __child1 = __target1.childNodes[0]; - __compose(__child0, 1); - __compose(__child1, 2); - return __root; - })()); + const c = __createElement(Component, null, t904ca237ee("test")); + const cTrim = __createElement(Component, null, t904ca237ee("test")); + const cTrimStart = __createElement(Component, null, t904ca237ee("test")); + const cTrimEnd = __createElement(Component, null, t904ca237ee("test")); + const cText = __createElement(Component, null, t1cb251ec0d()); + const cFrag = __createElement(Component, null, t9b045328fb(1,2)); " `); @@ -1147,121 +700,75 @@ describe('element composition', () => { { "html": "", "id": "", - "imports": [ - "createElement", - ], "isDomFragment": false, "isEmpty": true, - "isStatic": true, }, { "html": "

", "id": "904ca237ee", - "imports": [ - "compose", - ], "isDomFragment": false, "isEmpty": false, - "isStatic": false, }, { "html": "", "id": "", - "imports": [ - "createElement", - ], "isDomFragment": false, "isEmpty": true, - "isStatic": true, }, { "html": "

", "id": "904ca237ee", - "imports": [ - "compose", - ], "isDomFragment": false, "isEmpty": false, - "isStatic": false, }, { "html": "", "id": "", - "imports": [ - "createElement", - ], "isDomFragment": false, "isEmpty": true, - "isStatic": true, }, { "html": "

", "id": "904ca237ee", - "imports": [ - "compose", - ], "isDomFragment": false, "isEmpty": false, - "isStatic": false, }, { "html": "", "id": "", - "imports": [ - "createElement", - ], "isDomFragment": false, "isEmpty": true, - "isStatic": true, }, { "html": "

", "id": "904ca237ee", - "imports": [ - "compose", - ], "isDomFragment": false, "isEmpty": false, - "isStatic": false, }, { "html": "", "id": "", - "imports": [ - "createElement", - ], "isDomFragment": false, "isEmpty": true, - "isStatic": true, }, { "html": "text", "id": "1cb251ec0d", - "imports": [], "isDomFragment": true, "isEmpty": false, - "isStatic": true, }, { "html": "", "id": "", - "imports": [ - "createElement", - ], "isDomFragment": false, "isEmpty": true, - "isStatic": true, }, { "html": "

", "id": "9b045328fb", - "imports": [ - "compose", - ], "isDomFragment": true, "isEmpty": false, - "isStatic": false, }, ] `); @@ -1279,22 +786,10 @@ describe('render and composition cases', () => { const { code, templates } = compile(input); expect(code).toMatchInlineSnapshot(` - "import { __compose } from 'azoth/runtime'; - import { t62831a5152, t8dc93cc914 } from 'virtual:azoth-templates?id=62831a5152&id=8dc93cc914'; - const Item = name => { - const __root = t62831a5152()[0]; - const __child0 = __root.childNodes[0]; - __compose(__child0, name); - return __root; - }; - const Template = () => { - const __root = t8dc93cc914()[0]; - const __child0 = __root.childNodes[0]; - const __child1 = __root.childNodes[1]; - __compose(__child0, [2, 4, 7].map(Item)); - __compose(__child1, "text"); - return __root; - }; + "import { t62831a5152, t8dc93cc914 } from 'virtual:azoth-templates?id=62831a5152&id=8dc93cc914'; + + const Item = name => t62831a5152(name); + const Template = () => t8dc93cc914([2, 4, 7].map(Item),"text"); " `); @@ -1303,58 +798,14 @@ describe('render and composition cases', () => { { "html": "
  • ", "id": "62831a5152", - "imports": [ - "compose", - ], "isDomFragment": false, "isEmpty": false, - "isStatic": false, }, { "html": "
    ", "id": "8dc93cc914", - "imports": [ - "compose", - ], - "isDomFragment": false, - "isEmpty": false, - "isStatic": false, - }, - ] - `); - - }); - - test('edge case: previously broken esbuild jsx', ({ expect }) => { - const input = ` - const render = () =>
  • Hello {place}
  • - `; - const { code, templates } = compile(input); - - expect(code).toMatchInlineSnapshot(` - "import { __compose } from 'azoth/runtime'; - import { t2b440f4741 } from 'virtual:azoth-templates?id=2b440f4741'; - const render = () => { - const __root = t2b440f4741()[0]; - const __child1 = __root.childNodes[1]; - __root.className = (category); - __compose(__child1, place); - return __root; - }; - " - `); - - expect(templates).toMatchInlineSnapshot(` - [ - { - "html": "
  • Hello
  • ", - "id": "2b440f4741", - "imports": [ - "compose", - ], "isDomFragment": false, "isEmpty": false, - "isStatic": false, }, ] `); @@ -1371,21 +822,11 @@ describe('render and composition cases', () => { const { code, templates } = compile(input); expect(code).toMatchInlineSnapshot(` - "import { __compose } from 'azoth/runtime'; - import { t62831a5152, t25ec157413 } from 'virtual:azoth-templates?id=62831a5152&id=25ec157413'; - const Emoji = ({name}) => { - const __root = t62831a5152()[0]; - const __child0 = __root.childNodes[0]; - __compose(__child0, name); - return __root; - }; + "import { t62831a5152, t25ec157413 } from 'virtual:azoth-templates?id=62831a5152&id=25ec157413'; + + const Emoji = ({name}) => t62831a5152(name); const promise = fetchEmojis().then(emojis => emojis.map(Emoji)); - const Emojis = (() => { - const __root = t25ec157413()[0]; - const __child0 = __root.childNodes[0]; - __compose(__child0, promise); - return __root; - })(); + const Emojis = t25ec157413(promise); document.body.append(Emojis); " `); @@ -1394,22 +835,14 @@ describe('render and composition cases', () => { { "html": "
  • ", "id": "62831a5152", - "imports": [ - "compose", - ], "isDomFragment": false, "isEmpty": false, - "isStatic": false, }, { "html": "", "id": "25ec157413", - "imports": [ - "compose", - ], "isDomFragment": false, "isEmpty": false, - "isStatic": false, }, ] `); diff --git a/packages/compiler/source-maps.test.js b/packages/compiler/source-maps.test.js index 59dd680..835bbc9 100644 --- a/packages/compiler/source-maps.test.js +++ b/packages/compiler/source-maps.test.js @@ -12,7 +12,7 @@ test('static one line', ({ expect }) => { expect(code).toMatchInlineSnapshot(` "import { tbc5b60ab9f } from 'virtual:azoth-templates?id=bc5b60ab9f'; - const t = tbc5b60ab9f()[0]; + const t = tbc5b60ab9f(); " `); @@ -34,14 +34,6 @@ test('static one line', ({ expect }) => { "originalLine": 1, "source": "script.js", }, - { - "generatedColumn": 23, - "generatedLine": 3, - "name": undefined, - "originalColumn": 10, - "originalLine": 1, - "source": "script.js", - }, ] `); }); @@ -50,82 +42,29 @@ test('{...} one line', ({ expect }) => { const input = `
    Hello {place}
    `; const { _sourceMap, code } = compile(input); expect(code).toMatchInlineSnapshot(` - "import { __compose } from 'azoth/runtime'; - import { ta94b210052 } from 'virtual:azoth-templates?id=a94b210052'; - (() => { - const __root = ta94b210052()[0]; - const __child0 = __root.childNodes[1]; - __compose(__child0, place); - return __root; - })(); + "import { ta94b210052 } from 'virtual:azoth-templates?id=a94b210052'; + + ta94b210052(place); " `); expect(_sourceMap._mappings._array).toMatchInlineSnapshot(` [ { - "generatedColumn": 17, - "generatedLine": 4, - "name": undefined, - "originalColumn": 0, - "originalLine": 1, - "source": "script.js", - }, - { - "generatedColumn": 30, - "generatedLine": 4, + "generatedColumn": 0, + "generatedLine": 3, "name": undefined, "originalColumn": 0, "originalLine": 1, "source": "script.js", }, - { - "generatedColumn": 19, - "generatedLine": 5, - "name": "div", - "originalColumn": 1, - "originalLine": 1, - "source": "script.js", - }, - { - "generatedColumn": 36, - "generatedLine": 5, - "name": undefined, - "originalColumn": 11, - "originalLine": 1, - "source": "script.js", - }, - { - "generatedColumn": 2, - "generatedLine": 6, - "name": undefined, - "originalColumn": 11, - "originalLine": 1, - "source": "script.js", - }, { "generatedColumn": 12, - "generatedLine": 6, - "name": undefined, - "originalColumn": 11, - "originalLine": 1, - "source": "script.js", - }, - { - "generatedColumn": 22, - "generatedLine": 6, + "generatedLine": 3, "name": "place", "originalColumn": 12, "originalLine": 1, "source": "script.js", }, - { - "generatedColumn": 9, - "generatedLine": 7, - "name": undefined, - "originalColumn": 0, - "originalLine": 1, - "source": "script.js", - }, ] `); }); @@ -138,7 +77,7 @@ test('static three line', ({ expect }) => { expect(code).toMatchInlineSnapshot(` "import { te36ec5cf73 } from 'virtual:azoth-templates?id=e36ec5cf73'; - const t = te36ec5cf73()[0]; + const t = te36ec5cf73(); " `); expect(_sourceMap._mappings._array).toMatchInlineSnapshot(` @@ -159,14 +98,6 @@ test('static three line', ({ expect }) => { "originalLine": 1, "source": "script.js", }, - { - "generatedColumn": 23, - "generatedLine": 3, - "name": undefined, - "originalColumn": 10, - "originalLine": 1, - "source": "script.js", - }, ] `); }); @@ -177,14 +108,9 @@ test('{...} three line', ({ expect }) => { `; const { _sourceMap, code } = compile(input); expect(code).toMatchInlineSnapshot(` - "import { __compose } from 'azoth/runtime'; - import { tf2d718c3f5 } from 'virtual:azoth-templates?id=f2d718c3f5'; - const t = (() => { - const __root = tf2d718c3f5()[0]; - const __child0 = __root.childNodes[1]; - __compose(__child0, place); - return __root; - })(); + "import { tf2d718c3f5 } from 'virtual:azoth-templates?id=f2d718c3f5'; + + const t = tf2d718c3f5(place); " `); expect(_sourceMap._mappings._array).toMatchInlineSnapshot(` @@ -198,69 +124,21 @@ test('{...} three line', ({ expect }) => { "source": "script.js", }, { - "generatedColumn": 17, - "generatedLine": 4, - "name": undefined, - "originalColumn": 10, - "originalLine": 1, - "source": "script.js", - }, - { - "generatedColumn": 30, - "generatedLine": 4, + "generatedColumn": 10, + "generatedLine": 3, "name": undefined, "originalColumn": 10, "originalLine": 1, "source": "script.js", }, - { - "generatedColumn": 19, - "generatedLine": 5, - "name": "div", - "originalColumn": 11, - "originalLine": 1, - "source": "script.js", - }, - { - "generatedColumn": 36, - "generatedLine": 5, - "name": undefined, - "originalColumn": 14, - "originalLine": 2, - "source": "script.js", - }, - { - "generatedColumn": 2, - "generatedLine": 6, - "name": undefined, - "originalColumn": 14, - "originalLine": 2, - "source": "script.js", - }, - { - "generatedColumn": 12, - "generatedLine": 6, - "name": undefined, - "originalColumn": 14, - "originalLine": 2, - "source": "script.js", - }, { "generatedColumn": 22, - "generatedLine": 6, + "generatedLine": 3, "name": "place", "originalColumn": 15, "originalLine": 2, "source": "script.js", }, - { - "generatedColumn": 9, - "generatedLine": 7, - "name": undefined, - "originalColumn": 10, - "originalLine": 1, - "source": "script.js", - }, ] `); }); diff --git a/packages/compiler/transform/Analyzer.js b/packages/compiler/transform/Analyzer.js index 7f3fc62..6387eb5 100644 --- a/packages/compiler/transform/Analyzer.js +++ b/packages/compiler/transform/Analyzer.js @@ -117,10 +117,6 @@ export class Analyzer { this.#bindings.push(binding); - if(binding.type === 'child') { - this.#imports.add('compose'); - } - if(element.isRoot) { // root can't be a "target", it gets a -1 queryIndex // to signal bound template root (either el or fragment) @@ -202,7 +198,7 @@ export class Analyzer { else { assessElement(child); if(child.isComponent) { - this.#imports.add('composeElement'); + this.#imports.add('createElement'); this.#bind('child', child, child.componentExpr, i + adj); } this[type](child, i + adj); diff --git a/packages/compiler/transform/RenderGenerator.js b/packages/compiler/transform/BindGenerator.js similarity index 67% rename from packages/compiler/transform/RenderGenerator.js rename to packages/compiler/transform/BindGenerator.js index 7533aee..091a416 100644 --- a/packages/compiler/transform/RenderGenerator.js +++ b/packages/compiler/transform/BindGenerator.js @@ -6,7 +6,7 @@ const TARGETS = 'ts'; const TARGET = 't'; const VALUE = 'v'; -export class RenderGenerator extends Generator { +export class BindGenerator extends Generator { static generate(template) { const generator = new this(template); return generateWith(generator, template.node); @@ -19,25 +19,13 @@ export class RenderGenerator extends Generator { } JSXFragment(_node, state) { - this.JSXTemplate(state); + this.Bindings(state); } JSXElement(_node, state) { - this.JSXTemplate(state); - } - - JSXTemplate(state) { this.Bindings(state); } - JSXExpressionContainer({ expression }, state) { - this[expression.type](expression, state); - } - - JSXIdentifier(identifier, state) { - state.write(identifier.name, identifier); - } - Bindings(state) { const bindings = this.#bindings; @@ -99,47 +87,6 @@ export class RenderGenerator extends Generator { state.write(`);`); } - ComposeElement(node, expr, index, state) { - state.write(`composeElement(`, node); - state.write(`${TARGET}${index}, `); - this.CompleteElement(node, expr, state); - state.write(`);`); - } - - CreateElement(node, state) { - state.write(`createElement(`, node); - this.CompleteElement(node, node.componentExpr, state); - state.write(`)`); - } - - CompleteElement({ props, slotFragment }, expr, state) { - this[expr.type](expr, state); - if(props?.length) { - this.ComponentProps(props, state); - } - else if(slotFragment) state.write(`, null`); - - if(slotFragment) { - state.write(', '); - this.JSXTemplate(slotFragment, state); - } - } - - ComponentProps(props, state) { - state.write(`, {`); - for(let i = 0; i < props.length; i++) { - const { node, expr } = props[i]; - // TODO: Dom lookup, JS .prop v['prop'], etc. - // refactor with code below - state.write(` `); - state.write(node.name.name, node.name); - state.write(`: `); - this[expr.type](expr, state); - state.write(`,`); - } - state.write(` }`); - } - BindingProp(node, expr, index, element, state) { const { openingElement, openingFragment } = element; const opening = openingElement ?? openingFragment; diff --git a/packages/compiler/transform/TemplateGenerator.js b/packages/compiler/transform/TemplateGenerator.js index f233e53..de5b18b 100644 --- a/packages/compiler/transform/TemplateGenerator.js +++ b/packages/compiler/transform/TemplateGenerator.js @@ -80,17 +80,14 @@ export class TemplateGenerator extends Generator { if(template.id && !uniqueIds.has(template.id)) uniqueIds.add(template.id); // Short-circuit templates - const { isStatic, node: root } = template; - const { isComponent, returnStatement } = root; - if(isStatic || isComponent) { - if(returnStatement) state.write(`return `, returnStatement); - if(isComponent) this.CreateElement(root, state); - else if(isStatic) this.StaticRoot(template, state); - if(returnStatement) state.write(`;`); + const { node: root } = template; + const { isComponent } = root; + if(isComponent) { + this.CreateElement(root, state); return; } - this.InjectionWrapper(template, state); + this.DomLiteral(template, state); } // process javascript in {...} exprs, @@ -103,154 +100,33 @@ export class TemplateGenerator extends Generator { state.write(identifier.name, identifier); } - /* Adopt implicit arrow as containing function */ - ArrowFunctionExpression(node, state) { - if(node.body?.type === 'JSXElement') { - node.body = { - type: 'BlockStatement', - body: [{ - type: 'ReturnStatement', - argument: node.body, - }] - }; - } - super.ArrowFunctionExpression(node, state); - } - - /* Inject template statements above and return root dom */ - ReturnStatement(node, state) { - // custom handling for direct return of template jsx - const type = node.argument?.type; - if(type === 'JSXElement' || type === 'JSXFragment') { - node.argument.returnStatement = node; - this.JSXTemplate(node.argument, state); - return; - } - - super.ReturnStatement(node, state); - } - - InjectionWrapper(template, state) { - const returnStatement = template.node.returnStatement; - const useIIFEWrapper = !returnStatement; - - if(useIIFEWrapper) { - state.write(`(() => {`); - state.indentLevel++; - writeNextLine(state); - } - - this.DomLiteral(template, state); - writeNextLine(state); - state.write(`return `, returnStatement); - state.write(`__root;`, template.node); - - if(useIIFEWrapper) { - state.indentLevel--; - writeNextLine(state); - state.write(`})()`); - } - } - - StaticRoot(template, state) { - this.TemplateRenderer(template, state); - if(!template.isEmpty) state.write(`[0]`, template.node); // dom root - } - - TemplateRenderer({ id, isEmpty, isDomFragment, node }, state) { - if(isEmpty) { - state.write('null', node); - return; - } - state.write(`t${id}`, node); - state.write(`(`); - if(isDomFragment) state.write('true'); - state.write(`)`); - } - DomLiteral(template, state) { - const { boundElements, bindings, node } = template; + const { id, boundElements, bindings, node, isEmpty } = template; // template service renderer call const hasTargets = !!boundElements.length; - state.write(hasTargets ? `const [__root, __targets] = ` : `const __root = `); - this.TemplateRenderer(template, state); - state.write(hasTargets ? ';' : '[0];', node); - - // target variables - for(let i = 0; i < boundElements.length; i++) { - const boundElement = boundElements[i]; - const opening = boundElement.openingElement || boundElement.openFragment; - writeNextLine(state); - state.write(`const __target${i} =`); - state.write(`__targets[${i}]`, opening?.name); - state.write(`;`); - } - // sequential tasks before bindings generation below, - // variables prevent downstream binding mutations from - // changing index because childNodes is live list. - for(let i = 0; i < bindings.length; i++) { - const { element, type, index, node } = bindings[i]; - const { queryIndex } = element; - if(type !== 'child') continue; - const varName = queryIndex === -1 ? `__root` : `__target${queryIndex}`; - - let opening = null; - if(IS_OPENING[element.type]) opening = element; - else { - const prop = OPENING_PROP[element.type]; - if(prop) opening = element[prop]; - else { - throw new TypeError(`Unexpected binding node type "${node.type}"`); - } - } - - writeNextLine(state); - state.write(`const __child${i} = `); - state.write(`${varName}.childNodes`, opening.name); - state.write(`[${index}]`, node); - state.write(`;`); + if(isEmpty) { + state.write('null', node); + return; } + state.write(`t${id}(`, node); - // bindings for(let i = 0; i < bindings.length; i++) { - const { element, type, node, expr } = bindings[i]; - writeNextLine(state); + const { node, expr } = bindings[i]; + if(i !== 0) state.write(`,`); if(!this[expr.type]) { throw new TypeError(`Unexpected Binding expression AST type "${expr.type}"`); } if(node.isComponent) { - this.ComposeElement(node, expr, i, state); - continue; - } - if(type === 'child') { - this.Compose(node, expr, i, state); - continue; - } - if(type === 'prop') { - this.BindingProp(node, expr, element, state); + this.CreateElement(node, state); continue; } - - const message = `Unexpected binding type "${type}", expected "child" or "prop"`; - throw new Error(message); + this[expr.type](expr, state); } - } - Compose(node, expr, index, state) { - state.write(`__compose(`, node); - state.write(`__child${index}, `, node); - this[expr.type](expr, state); - state.write(`);`); - } - - ComposeElement(node, expr, index, state) { - state.write(`__composeElement(`, node); - state.write(`__child${index}, `); - this.CompleteElement(node, expr, state); - state.write(`);`); + state.write(`)`); } CreateElement(node, state) { @@ -286,29 +162,4 @@ export class TemplateGenerator extends Generator { } state.write(` }`); } - - BindingProp(node, expr, element, state) { - const { queryIndex, openingElement, openingFragment } = element; - const varName = queryIndex === -1 ? `__root` : `__target${queryIndex}`; - const opening = openingElement ?? openingFragment; - state.write(`${varName}`, opening.name); - // TODO: more property validation - const identity = node.name; - const propName = identity.name; - // TODO: refactor with component props - if(isValidESIdentifier(propName)) { - state.write(`.`); - state.write(propName, node.name); - } - else { - state.write(`["`, node.name); - state.write(propName, node.name); - state.write(`"]`); - } - - /* expression */ - state.write(` = (`); - this[expr.type](expr, state); - state.write(`);`); - } } diff --git a/packages/compiler/transform/template-assets.js b/packages/compiler/transform/template-generators.js similarity index 99% rename from packages/compiler/transform/template-assets.js rename to packages/compiler/transform/template-generators.js index d4798eb..b551f8a 100644 --- a/packages/compiler/transform/template-assets.js +++ b/packages/compiler/transform/template-generators.js @@ -26,3 +26,4 @@ export function makeRender({ bindings: { length } }) { return root; }\n`; } + diff --git a/packages/compiler/transform/template-assets.test.js b/packages/compiler/transform/template-generators.test.js similarity index 97% rename from packages/compiler/transform/template-assets.test.js rename to packages/compiler/transform/template-generators.test.js index 9977463..003ea43 100644 --- a/packages/compiler/transform/template-assets.test.js +++ b/packages/compiler/transform/template-generators.test.js @@ -1,8 +1,8 @@ /* eslint-disable no-undef */ -import { makeTargets, makeGetBound, makeRender } from './template-assets.js'; +import { makeTargets, makeGetBound, makeRender } from './template-generators.js'; import { parse, generate as _generate } from '../compiler.js'; import { describe, test, beforeEach } from 'vitest'; -import { RenderGenerator } from './RenderGenerator.js'; +import { BindGenerator } from './BindGenerator.js'; function preParse(input, expect) { const ast = parse(input); @@ -94,7 +94,7 @@ describe('bind generator', () => { beforeEach(context => { context.compile = code => { const template = preParse(code, context.expect); - return RenderGenerator.generate(template).code; + return BindGenerator.generate(template).code; }; }); diff --git a/packages/runtime/compose/compose.js b/packages/runtime/compose/compose.js index f93453a..b2d0428 100644 --- a/packages/runtime/compose/compose.js +++ b/packages/runtime/compose/compose.js @@ -77,15 +77,30 @@ export function composeElement(anchor, Constructor, props, slottable) { create(Constructor, props, slottable, anchor); } -export function createElement(Constructor, props, slottable) { +export function createElement(Constructor, props, slottable, topLevel = false) { const result = create(Constructor, props, slottable); - // result is returned to caller, force to be of type Node - // by converting strings and numbers into text nodes + if(!topLevel) return result; + + // result is returned to caller, not composed by Azoth, + // force to be of type Node or null: + // strings and numbers into text nodes + // non-values to null const type = typeof result; - if(type === 'string' || type === 'number') { - return document.createTextNode(result); + switch(true) { + case type === 'string': + case type === 'number': + return document.createTextNode(result); + case result === undefined: + case result === null: + case result === true: + case result === false: + case result === IGNORE: + return null; + default: + return result; } - return result; + + } function create(input, props, slottable, anchor) { @@ -101,6 +116,7 @@ function create(input, props, slottable, anchor) { case input === true: case input === false: case input === '': + case input === IGNORE: return anchor ? void compose(anchor, input) : input; case !!(input.prototype?.constructor): { // eslint-disable-next-line new-cap diff --git a/packages/runtime/renderer/controller.test.js b/packages/runtime/renderer/controller.test.js index d73064d..169bb2d 100644 --- a/packages/runtime/renderer/controller.test.js +++ b/packages/runtime/renderer/controller.test.js @@ -1,4 +1,4 @@ -import { describe, test, beforeEach, beforeAll } from 'vitest'; +import { describe, test, beforeAll } from 'vitest'; import { compose } from '../compose/compose.js'; import { renderer, From 521e32f11e9da0d3a5a0d92ab8e80c323ed80a14 Mon Sep 17 00:00:00 2001 From: Marty Nelson Date: Thu, 21 Mar 2024 08:03:25 -0700 Subject: [PATCH 19/23] convert bind generation to single function with rest of template generators --- packages/compiler/transform/BindGenerator.js | 115 ------------------ .../compiler/transform/template-generators.js | 44 +++++++ .../transform/template-generators.test.js | 9 +- 3 files changed, 48 insertions(+), 120 deletions(-) delete mode 100644 packages/compiler/transform/BindGenerator.js diff --git a/packages/compiler/transform/BindGenerator.js b/packages/compiler/transform/BindGenerator.js deleted file mode 100644 index 091a416..0000000 --- a/packages/compiler/transform/BindGenerator.js +++ /dev/null @@ -1,115 +0,0 @@ -import { Generator, writeNextLine } from './GeneratorBase.js'; -import { isValidESIdentifier } from 'is-valid-es-identifier'; -import { generateWith } from '../compiler.js'; - -const TARGETS = 'ts'; -const TARGET = 't'; -const VALUE = 'v'; - -export class BindGenerator extends Generator { - static generate(template) { - const generator = new this(template); - return generateWith(generator, template.node); - } - #bindings = null; - - constructor(template) { - super(); - this.#bindings = template.bindings; - } - - JSXFragment(_node, state) { - this.Bindings(state); - } - - JSXElement(_node, state) { - this.Bindings(state); - } - - Bindings(state) { - const bindings = this.#bindings; - - state.write(`function bind(${TARGETS}) {`); - state.indentLevel++; - writeNextLine(state); - - const targets = []; - const params = []; - for(let i = 0; i < bindings.length; i++) { - targets.push(`${TARGET}${i} = ${TARGETS}[${i}]`); - params.push(`${VALUE}${i}`); - } - - state.write(`const ${targets.join(', ')};`); - writeNextLine(state); - state.write(`return (${params.join(', ')}) => {`); - state.indentLevel++; - - for(let i = 0; i < bindings.length; i++) { - const { element, type, node, expr } = bindings[i]; - writeNextLine(state); - - if(!this[expr.type]) { - throw new TypeError(`Unexpected Binding expression AST type "${expr.type}"`); - } - - if(node.isComponent) { - this.ComposeElement(node, expr, i, state); - continue; - } - if(type === 'child') { - this.Compose(node, expr, i, state); - continue; - } - if(type === 'prop') { - this.BindingProp(node, expr, i, element, state); - continue; - } - - const message = `Unexpected binding type "${type}", expected "child" or "prop"`; - throw new Error(message); - } - - state.indentLevel--; - writeNextLine(state); - state.write(`};`); - state.indentLevel--; - writeNextLine(state); - state.write(`}`); - - state.write(state.lineEnd); - } - - Compose(node, expr, index, state) { - state.write(`compose(`, node); - state.write(`${TARGET}${index}, `, node); - state.write(`${VALUE}${index}`, expr); - state.write(`);`); - } - - BindingProp(node, expr, index, element, state) { - const { openingElement, openingFragment } = element; - const opening = openingElement ?? openingFragment; - state.write(`${TARGET}${index}`, opening.name); - // TODO: more property validation - const identity = node.name; - const propName = identity.name; - // TODO: refactor with component props - if(isValidESIdentifier(propName)) { - state.write(`.`); - state.write(propName, node.name); - } - else { - state.write(`["`, node.name); - state.write(propName, node.name); - state.write(`"]`); - } - - /* expression */ - state.write(` = `); - // this[expr.type](expr, state); - state.write(`${VALUE}${index}`, expr); - state.write(`;`); - } -} - diff --git a/packages/compiler/transform/template-generators.js b/packages/compiler/transform/template-generators.js index b551f8a..c337a74 100644 --- a/packages/compiler/transform/template-generators.js +++ b/packages/compiler/transform/template-generators.js @@ -1,3 +1,5 @@ +import { isValidESIdentifier } from 'is-valid-es-identifier'; + export function makeTargets(template) { const { boundElements, bindings } = template; const { length: elLength } = boundElements; @@ -27,3 +29,45 @@ export function makeRender({ bindings: { length } }) { }\n`; } +const TARGETS = 'ts'; +const TARGET = 't'; +const VALUE = 'v'; + +export function makeBind({ bindings }) { + const targets = [], params = []; + for(let i = 0; i < bindings.length; i++) { + targets.push(`${TARGET}${i} = ${TARGETS}[${i}]`); + params.push(`${VALUE}${i}`); + } + + const bound = bindings.map(({ type, node }, index) => { + if(node.isComponent) { + throw new Error('need compose element'); + // return ComposeElement(node, expr, i, state); + } + if(type === 'child') { + return `compose(${TARGET}${index}, ${VALUE}${index});`; + } + if(type === 'prop') { + // TODO: consider source maps for prop on element + // TODO: refactor with component props names + const identity = node.name; + const propName = identity.name; + const isValidId = isValidESIdentifier(propName); + const refinement = isValidId ? `.${propName}` : `[${propName}]`; + + return `${TARGET}${index}${refinement} = ${VALUE}${index};`; + + } + const message = `Unexpected binding type "${type}", expected "child" or "prop"`; + throw new Error(message); + }); + + return `function bind(${TARGETS}) { + const ${targets.join(', ')}; + return (${params.join(', ')}) => { + ${bound.join('\n ')} + }; +}\n`; + +} diff --git a/packages/compiler/transform/template-generators.test.js b/packages/compiler/transform/template-generators.test.js index 003ea43..e8c70f3 100644 --- a/packages/compiler/transform/template-generators.test.js +++ b/packages/compiler/transform/template-generators.test.js @@ -1,8 +1,7 @@ /* eslint-disable no-undef */ -import { makeTargets, makeGetBound, makeRender } from './template-generators.js'; +import { makeTargets, makeGetBound, makeRender, makeBind } from './template-generators.js'; import { parse, generate as _generate } from '../compiler.js'; import { describe, test, beforeEach } from 'vitest'; -import { BindGenerator } from './BindGenerator.js'; function preParse(input, expect) { const ast = parse(input); @@ -94,7 +93,7 @@ describe('bind generator', () => { beforeEach(context => { context.compile = code => { const template = preParse(code, context.expect); - return BindGenerator.generate(template).code; + return makeBind(template); }; }); @@ -105,7 +104,7 @@ describe('bind generator', () => { const t0 = ts[0]; return (v0) => { compose(t0, v0); - }; + }; } " `); @@ -123,7 +122,7 @@ describe('bind generator', () => { t0.className = v0; compose(t1, v1); compose(t2, v2); - }; + }; } " ` From 0bccbf432d33ed2edcb8a04d81612dc139da14e2 Mon Sep 17 00:00:00 2001 From: Marty Nelson Date: Thu, 21 Mar 2024 09:46:24 -0700 Subject: [PATCH 20/23] test refactor --- .../transform/template-generators.test.js | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/packages/compiler/transform/template-generators.test.js b/packages/compiler/transform/template-generators.test.js index e8c70f3..843b339 100644 --- a/packages/compiler/transform/template-generators.test.js +++ b/packages/compiler/transform/template-generators.test.js @@ -44,14 +44,14 @@ describe('targets generator', () => { describe('getBound generator', () => { beforeEach(context => { - context.getTemplate = code => { - return preParse(code, context.expect); + context.compile = code => { + const template = preParse(code, context.expect); + return makeGetBound(template); }; }); - test('simple', ({ expect }) => { - const template = preParse(`name =>

    {name}

    `, expect); - const code = makeGetBound(template); + test('simple', ({ compile, expect }) => { + const code = compile(`name =>

    {name}

    `, expect); expect(code).toMatchInlineSnapshot(` "const getBound = renderer('904ca237ee', targets, bind, false,

    ); @@ -59,11 +59,12 @@ describe('getBound generator', () => { `); }); - test('props and elements', ({ expect }) => { - const template = preParse(`const t =

    + + test('props and elements', ({ compile, expect }) => { + const code = compile(`const t =

    {"Greeting"} hey {"Azoth"}! -

    ;`, expect); - const code = makeGetBound(template); +

    ;`); + expect(code).toMatchInlineSnapshot( ` "const getBound = renderer('5252cfebed', targets, bind, false,

    @@ -74,8 +75,8 @@ describe('getBound generator', () => { ); }); - test('option noContent: true', ({ getTemplate, expect }) => { - const template = getTemplate(`name =>

    {name}

    `); + test('option noContent: true', ({ expect }) => { + const template = preParse(`name =>

    {name}

    `, expect); const code = makeGetBound(template, { noContent: true }); expect(code).toMatchInlineSnapshot(` From 5cde1105d8af5127e434c1a732c08cee2ef1d652 Mon Sep 17 00:00:00 2001 From: Marty Nelson Date: Thu, 21 Mar 2024 10:46:02 -0700 Subject: [PATCH 21/23] refactor renderDOM to be higherOrder dynamic function --- packages/compiler/compiler.test.js | 20 +++---- packages/compiler/transform/Template.js | 1 + .../compiler/transform/TemplateGenerator.js | 5 +- .../compiler/transform/template-generators.js | 13 +---- .../transform/template-generators.test.js | 52 +++---------------- packages/runtime/renderer/controller.test.js | 20 ++----- packages/runtime/renderer/renderer.js | 12 ++++- 7 files changed, 37 insertions(+), 86 deletions(-) diff --git a/packages/compiler/compiler.test.js b/packages/compiler/compiler.test.js index 2ab60de..f55d05e 100644 --- a/packages/compiler/compiler.test.js +++ b/packages/compiler/compiler.test.js @@ -565,8 +565,8 @@ describe('element composition', () => { expect(code).toMatchInlineSnapshot(` "import { __createElement } from 'azoth/runtime'; - const c = __createElement(Component); - const cProps = __createElement(Component, { prop: value, attr: "static", }); + const c = __createElement(Component, true); + const cProps = __createElement(Component, { prop: value, attr: "static", }, true); " `); expect(templates).toMatchInlineSnapshot(` @@ -632,8 +632,8 @@ describe('element composition', () => { expect(code).toMatchInlineSnapshot(` "import { __createElement } from 'azoth/runtime'; import { t2288998344 } from 'virtual:azoth-templates?id=2288998344'; - const $A = __createElement(A); - const $B = __createElement(B); + const $A = __createElement(A, true); + const $B = __createElement(B, true); const dom = t2288998344($A,$B); " `); @@ -686,12 +686,12 @@ describe('element composition', () => { expect(code).toMatchInlineSnapshot(` "import { __createElement } from 'azoth/runtime'; import { t904ca237ee, t1cb251ec0d, t9b045328fb } from 'virtual:azoth-templates?id=904ca237ee&id=1cb251ec0d&id=9b045328fb'; - const c = __createElement(Component, null, t904ca237ee("test")); - const cTrim = __createElement(Component, null, t904ca237ee("test")); - const cTrimStart = __createElement(Component, null, t904ca237ee("test")); - const cTrimEnd = __createElement(Component, null, t904ca237ee("test")); - const cText = __createElement(Component, null, t1cb251ec0d()); - const cFrag = __createElement(Component, null, t9b045328fb(1,2)); + const c = __createElement(Component, null, t904ca237ee("test"), true); + const cTrim = __createElement(Component, null, t904ca237ee("test"), true); + const cTrimStart = __createElement(Component, null, t904ca237ee("test"), true); + const cTrimEnd = __createElement(Component, null, t904ca237ee("test"), true); + const cText = __createElement(Component, null, t1cb251ec0d(), true); + const cFrag = __createElement(Component, null, t9b045328fb(1,2), true); " `); diff --git a/packages/compiler/transform/Template.js b/packages/compiler/transform/Template.js index 9419d6b..2f008e9 100644 --- a/packages/compiler/transform/Template.js +++ b/packages/compiler/transform/Template.js @@ -28,6 +28,7 @@ export class Template { if(node.isComponent && bindings.length) { throw new Error('Unexpected component binding length'); } + this.isBoundRoot = node.queryIndex === -1; this.isDomFragment = node.isJSXFragment; this.isEmpty = node.isComponent || diff --git a/packages/compiler/transform/TemplateGenerator.js b/packages/compiler/transform/TemplateGenerator.js index de5b18b..9d475f0 100644 --- a/packages/compiler/transform/TemplateGenerator.js +++ b/packages/compiler/transform/TemplateGenerator.js @@ -83,7 +83,7 @@ export class TemplateGenerator extends Generator { const { node: root } = template; const { isComponent } = root; if(isComponent) { - this.CreateElement(root, state); + this.CreateElement(root, state, true); return; } @@ -129,9 +129,10 @@ export class TemplateGenerator extends Generator { state.write(`)`); } - CreateElement(node, state) { + CreateElement(node, state, topLevel = false) { state.write(`__createElement(`, node); this.CompleteElement(node, node.componentExpr, state); + if(topLevel) state.write(`, true`); state.write(`)`); } diff --git a/packages/compiler/transform/template-generators.js b/packages/compiler/transform/template-generators.js index c337a74..23c5d02 100644 --- a/packages/compiler/transform/template-generators.js +++ b/packages/compiler/transform/template-generators.js @@ -15,18 +15,9 @@ export function makeTargets(template) { return `const targets = (${elLength ? 'r,t' : 'r'}) => [${values.join()}];\n`; } -export function makeGetBound({ id, isDomFragment, html }, options) { +export function makeRenderer({ id, isDomFragment, html }, options) { const content = options?.noContent ? '' : `, ${html}`; - return `const getBound = renderer('${id}', targets, bind, ${isDomFragment}${content});\n`; -} - -export function makeRender({ bindings: { length } }) { - const params = Array.from({ length }, (_, i) => `p${i}`); - return `function renderDOM(${params}) { - const [root, bind] = getBound(); - bind(${params}); - return root; -}\n`; + return `const renderDOM = renderer('${id}', targets, bind, ${isDomFragment}${content});\n`; } const TARGETS = 'ts'; diff --git a/packages/compiler/transform/template-generators.test.js b/packages/compiler/transform/template-generators.test.js index 843b339..e54424d 100644 --- a/packages/compiler/transform/template-generators.test.js +++ b/packages/compiler/transform/template-generators.test.js @@ -1,5 +1,5 @@ /* eslint-disable no-undef */ -import { makeTargets, makeGetBound, makeRender, makeBind } from './template-generators.js'; +import { makeTargets, makeRenderer, makeRender, makeBind } from './template-generators.js'; import { parse, generate as _generate } from '../compiler.js'; import { describe, test, beforeEach } from 'vitest'; @@ -41,12 +41,12 @@ describe('targets generator', () => { }); }); -describe('getBound generator', () => { +describe('renderDOM generator', () => { beforeEach(context => { context.compile = code => { const template = preParse(code, context.expect); - return makeGetBound(template); + return makeRenderer(template); }; }); @@ -54,7 +54,7 @@ describe('getBound generator', () => { const code = compile(`name =>

    {name}

    `, expect); expect(code).toMatchInlineSnapshot(` - "const getBound = renderer('904ca237ee', targets, bind, false,

    ); + "const renderDOM = renderer('904ca237ee', targets, bind, false,

    ); " `); }); @@ -67,7 +67,7 @@ describe('getBound generator', () => { expect(code).toMatchInlineSnapshot( ` - "const getBound = renderer('5252cfebed', targets, bind, false,

    + "const renderDOM = renderer('5252cfebed', targets, bind, false,

    hey !

    ); " @@ -77,10 +77,10 @@ describe('getBound generator', () => { test('option noContent: true', ({ expect }) => { const template = preParse(`name =>

    {name}

    `, expect); - const code = makeGetBound(template, { noContent: true }); + const code = makeRenderer(template, { noContent: true }); expect(code).toMatchInlineSnapshot(` - "const getBound = renderer('904ca237ee', targets, bind, false); + "const renderDOM = renderer('904ca237ee', targets, bind, false); " `); }); @@ -130,41 +130,3 @@ describe('bind generator', () => { ); }); }); - -describe('render generator', () => { - - beforeEach(context => { - context.compile = code => { - const template = preParse(code, context.expect); - return makeRender(template); - }; - }); - - test('simple', ({ compile, expect }) => { - const code = compile(`name =>

    {name}

    `); - expect(code).toMatchInlineSnapshot(` - "function renderDOM(p0) { - const [root, bind] = getBound(); - bind(p0); - return root; - } - " - `); - }); - - test('props and elements', ({ compile, expect }) => { - const code = compile(`const t =

    - {"Greeting"} hey {"Azoth"}! -

    ;`); - expect(code).toMatchInlineSnapshot( - ` - "function renderDOM(p0,p1,p2) { - const [root, bind] = getBound(); - bind(p0,p1,p2); - return root; - } - " - ` - ); - }); -}); \ No newline at end of file diff --git a/packages/runtime/renderer/controller.test.js b/packages/runtime/renderer/controller.test.js index 169bb2d..cfc8a12 100644 --- a/packages/runtime/renderer/controller.test.js +++ b/packages/runtime/renderer/controller.test.js @@ -10,10 +10,10 @@ import { describe('dom render', () => { - let getBound = null; + let renderDOM = null; beforeAll(() => { RenderService.useDOMEngine(); - getBound = renderer( + renderDOM = renderer( 'id', getTargets, makeBind, @@ -33,12 +33,6 @@ describe('dom render', () => { }; }; - function renderDOM(p0) { - const [root, bind] = getBound(); - bind(p0); - return root; - } - test('Controller.for', ({ expect }) => { const controller = Controller.for(name => renderDOM(name)); @@ -75,10 +69,10 @@ describe('dom render', () => { describe('html render', () => { const flatRender = node => node.flat().join(''); - let getBound = null; + let renderHTML = null; beforeAll(() => { RenderService.useHTMLEngine(); - getBound = renderer( + renderHTML = renderer( 'id', getTargets, makeBind, @@ -98,12 +92,6 @@ describe('html render', () => { }; }; - function renderHTML(p0) { - const [root, bind] = getBound(); - bind(p0); - return root; - } - test('Controller.for', ({ expect }) => { const controller = Controller.for(name => renderHTML(name)); diff --git a/packages/runtime/renderer/renderer.js b/packages/runtime/renderer/renderer.js index 8846f2d..2fa9bcf 100644 --- a/packages/runtime/renderer/renderer.js +++ b/packages/runtime/renderer/renderer.js @@ -52,10 +52,16 @@ function inject(node, callback) { } } +const templateRenderer = getBound => (...args) => { + const [root, bind] = getBound(); + bind(...args); + return root; +}; + export function renderer(id, targets, makeBind, isFragment, content) { const create = get(id, isFragment, content); - return function getBound() { + function getBound() { let bind = null; let boundEls = null; let node = injectable.at(-1); // peek! @@ -80,7 +86,9 @@ export function renderer(id, targets, makeBind, isFragment, content) { bindings.set(node, bind); return [node, bind]; - }; + } + + return templateRenderer(getBound); } export class Controller { From 6aa6ceaabb0d2dcd21feece5fff6bae1f97a1cb4 Mon Sep 17 00:00:00 2001 From: Marty Nelson Date: Thu, 21 Mar 2024 14:49:32 -0700 Subject: [PATCH 22/23] new compilation integrated! --- .vscode/settings.json | 1 + docs/pnpm-lock.yaml | 212 +++++----- package.json | 4 +- packages/azoth/runtime.js | 4 +- packages/compiler/compiler.js | 5 +- packages/compiler/compiler.test.js | 268 +++++++++---- packages/compiler/source-maps.test.js | 8 +- packages/compiler/transform/Analyzer.js | 10 +- packages/compiler/transform/HtmlGenerator.js | 1 - packages/compiler/transform/Template.js | 48 +-- .../{TemplateGenerator.js => Transpiler.js} | 17 +- .../compiler/transform/template-generators.js | 28 +- .../transform/template-generators.test.js | 110 +++--- packages/runtime/renderer/index.js | 2 +- packages/runtime/renderer/renderer.js | 13 +- packages/vite-plugin/index.js | 104 +++-- packages/vite-plugin/package.json | 1 + packages/vite-plugin/pnpm-lock.yaml | 170 ++++---- pnpm-lock.yaml | 215 +++++----- sandbox/package.json | 2 +- sandbox/pnpm-lock.yaml | 164 ++++---- vite-test/expected-out/index-BDbUw1te.js | 250 ------------ vite-test/expected-out/index-CHvwx500.js | 374 ++++++++++++++++++ vite-test/expected-out/index.html | 11 +- vite-test/out/index-BDbUw1te.js | 250 ------------ vite-test/out/index-CHvwx500.js | 374 ++++++++++++++++++ vite-test/out/index.html | 11 +- vite-test/package.json | 4 +- vite-test/plugin.test.js | 4 +- vite-test/pnpm-lock.yaml | 174 ++++---- vite-test/src/fetchEmojis.js | 18 +- vite-test/src/main.jsx | 12 +- vite-test/vite.config.js | 1 + 33 files changed, 1625 insertions(+), 1245 deletions(-) rename packages/compiler/transform/{TemplateGenerator.js => Transpiler.js} (91%) delete mode 100644 vite-test/expected-out/index-BDbUw1te.js create mode 100644 vite-test/expected-out/index-CHvwx500.js delete mode 100644 vite-test/out/index-BDbUw1te.js create mode 100644 vite-test/out/index-CHvwx500.js diff --git a/.vscode/settings.json b/.vscode/settings.json index 50bde4f..870a1b4 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -57,4 +57,5 @@ "explorer.fileNesting.patterns": { "package.json": "package-lock.json, yarn.lock, pnpm-lock.yaml, bun.lockb, pnpm-workspace.yaml" }, + "liveServer.settings.port": 5501, } \ No newline at end of file diff --git a/docs/pnpm-lock.yaml b/docs/pnpm-lock.yaml index 559ee12..a1ef8e5 100644 --- a/docs/pnpm-lock.yaml +++ b/docs/pnpm-lock.yaml @@ -145,8 +145,8 @@ packages: '@algolia/requester-common': 4.22.1 dev: true - /@babel/helper-string-parser@7.23.4: - resolution: {integrity: sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==} + /@babel/helper-string-parser@7.24.1: + resolution: {integrity: sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ==} engines: {node: '>=6.9.0'} dev: true @@ -155,8 +155,8 @@ packages: engines: {node: '>=6.9.0'} dev: true - /@babel/parser@7.24.0: - resolution: {integrity: sha512-QuP/FxEAzMSjXygs8v4N9dvdXzEHN4W1oF3PxuWAtPo08UdM17u89RDMgjLn/mlc56iM0HlLmVkO/wgR+rDgHg==} + /@babel/parser@7.24.1: + resolution: {integrity: sha512-Zo9c7N3xdOIQrNip7Lc9wvRPzlRtovHVE4lkz8WEDr7uYh/GMQhSiIgFxGIArRHYdJE5kxtZjAf8rT0xhdLCzg==} engines: {node: '>=6.0.0'} hasBin: true dependencies: @@ -167,7 +167,7 @@ packages: resolution: {integrity: sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==} engines: {node: '>=6.9.0'} dependencies: - '@babel/helper-string-parser': 7.23.4 + '@babel/helper-string-parser': 7.24.1 '@babel/helper-validator-identifier': 7.22.20 to-fast-properties: 2.0.0 dev: true @@ -180,7 +180,7 @@ packages: resolution: {integrity: sha512-QujhqINEElrkIfKwyyyTfbsfMAYCkylInLYMRqHy7PHc8xTBQCow73tlo/Kc7oIwBrCLf0P3YhjlOeV4v8hevQ==} dependencies: '@docsearch/react': 3.6.0(@algolia/client-search@4.22.1)(search-insights@2.13.0) - preact: 10.19.6 + preact: 10.20.0 transitivePeerDependencies: - '@algolia/client-search' - '@types/react' @@ -215,8 +215,8 @@ packages: - '@algolia/client-search' dev: true - /@esbuild/aix-ppc64@0.19.12: - resolution: {integrity: sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==} + /@esbuild/aix-ppc64@0.20.2: + resolution: {integrity: sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==} engines: {node: '>=12'} cpu: [ppc64] os: [aix] @@ -224,8 +224,8 @@ packages: dev: true optional: true - /@esbuild/android-arm64@0.19.12: - resolution: {integrity: sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==} + /@esbuild/android-arm64@0.20.2: + resolution: {integrity: sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==} engines: {node: '>=12'} cpu: [arm64] os: [android] @@ -233,8 +233,8 @@ packages: dev: true optional: true - /@esbuild/android-arm@0.19.12: - resolution: {integrity: sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==} + /@esbuild/android-arm@0.20.2: + resolution: {integrity: sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==} engines: {node: '>=12'} cpu: [arm] os: [android] @@ -242,8 +242,8 @@ packages: dev: true optional: true - /@esbuild/android-x64@0.19.12: - resolution: {integrity: sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==} + /@esbuild/android-x64@0.20.2: + resolution: {integrity: sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==} engines: {node: '>=12'} cpu: [x64] os: [android] @@ -251,8 +251,8 @@ packages: dev: true optional: true - /@esbuild/darwin-arm64@0.19.12: - resolution: {integrity: sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==} + /@esbuild/darwin-arm64@0.20.2: + resolution: {integrity: sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==} engines: {node: '>=12'} cpu: [arm64] os: [darwin] @@ -260,8 +260,8 @@ packages: dev: true optional: true - /@esbuild/darwin-x64@0.19.12: - resolution: {integrity: sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==} + /@esbuild/darwin-x64@0.20.2: + resolution: {integrity: sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==} engines: {node: '>=12'} cpu: [x64] os: [darwin] @@ -269,8 +269,8 @@ packages: dev: true optional: true - /@esbuild/freebsd-arm64@0.19.12: - resolution: {integrity: sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==} + /@esbuild/freebsd-arm64@0.20.2: + resolution: {integrity: sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==} engines: {node: '>=12'} cpu: [arm64] os: [freebsd] @@ -278,8 +278,8 @@ packages: dev: true optional: true - /@esbuild/freebsd-x64@0.19.12: - resolution: {integrity: sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==} + /@esbuild/freebsd-x64@0.20.2: + resolution: {integrity: sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==} engines: {node: '>=12'} cpu: [x64] os: [freebsd] @@ -287,8 +287,8 @@ packages: dev: true optional: true - /@esbuild/linux-arm64@0.19.12: - resolution: {integrity: sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==} + /@esbuild/linux-arm64@0.20.2: + resolution: {integrity: sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==} engines: {node: '>=12'} cpu: [arm64] os: [linux] @@ -296,8 +296,8 @@ packages: dev: true optional: true - /@esbuild/linux-arm@0.19.12: - resolution: {integrity: sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==} + /@esbuild/linux-arm@0.20.2: + resolution: {integrity: sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==} engines: {node: '>=12'} cpu: [arm] os: [linux] @@ -305,8 +305,8 @@ packages: dev: true optional: true - /@esbuild/linux-ia32@0.19.12: - resolution: {integrity: sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==} + /@esbuild/linux-ia32@0.20.2: + resolution: {integrity: sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==} engines: {node: '>=12'} cpu: [ia32] os: [linux] @@ -314,8 +314,8 @@ packages: dev: true optional: true - /@esbuild/linux-loong64@0.19.12: - resolution: {integrity: sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==} + /@esbuild/linux-loong64@0.20.2: + resolution: {integrity: sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==} engines: {node: '>=12'} cpu: [loong64] os: [linux] @@ -323,8 +323,8 @@ packages: dev: true optional: true - /@esbuild/linux-mips64el@0.19.12: - resolution: {integrity: sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==} + /@esbuild/linux-mips64el@0.20.2: + resolution: {integrity: sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==} engines: {node: '>=12'} cpu: [mips64el] os: [linux] @@ -332,8 +332,8 @@ packages: dev: true optional: true - /@esbuild/linux-ppc64@0.19.12: - resolution: {integrity: sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==} + /@esbuild/linux-ppc64@0.20.2: + resolution: {integrity: sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==} engines: {node: '>=12'} cpu: [ppc64] os: [linux] @@ -341,8 +341,8 @@ packages: dev: true optional: true - /@esbuild/linux-riscv64@0.19.12: - resolution: {integrity: sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==} + /@esbuild/linux-riscv64@0.20.2: + resolution: {integrity: sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==} engines: {node: '>=12'} cpu: [riscv64] os: [linux] @@ -350,8 +350,8 @@ packages: dev: true optional: true - /@esbuild/linux-s390x@0.19.12: - resolution: {integrity: sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==} + /@esbuild/linux-s390x@0.20.2: + resolution: {integrity: sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==} engines: {node: '>=12'} cpu: [s390x] os: [linux] @@ -359,8 +359,8 @@ packages: dev: true optional: true - /@esbuild/linux-x64@0.19.12: - resolution: {integrity: sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==} + /@esbuild/linux-x64@0.20.2: + resolution: {integrity: sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==} engines: {node: '>=12'} cpu: [x64] os: [linux] @@ -368,8 +368,8 @@ packages: dev: true optional: true - /@esbuild/netbsd-x64@0.19.12: - resolution: {integrity: sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==} + /@esbuild/netbsd-x64@0.20.2: + resolution: {integrity: sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==} engines: {node: '>=12'} cpu: [x64] os: [netbsd] @@ -377,8 +377,8 @@ packages: dev: true optional: true - /@esbuild/openbsd-x64@0.19.12: - resolution: {integrity: sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==} + /@esbuild/openbsd-x64@0.20.2: + resolution: {integrity: sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==} engines: {node: '>=12'} cpu: [x64] os: [openbsd] @@ -386,8 +386,8 @@ packages: dev: true optional: true - /@esbuild/sunos-x64@0.19.12: - resolution: {integrity: sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==} + /@esbuild/sunos-x64@0.20.2: + resolution: {integrity: sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==} engines: {node: '>=12'} cpu: [x64] os: [sunos] @@ -395,8 +395,8 @@ packages: dev: true optional: true - /@esbuild/win32-arm64@0.19.12: - resolution: {integrity: sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==} + /@esbuild/win32-arm64@0.20.2: + resolution: {integrity: sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==} engines: {node: '>=12'} cpu: [arm64] os: [win32] @@ -404,8 +404,8 @@ packages: dev: true optional: true - /@esbuild/win32-ia32@0.19.12: - resolution: {integrity: sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==} + /@esbuild/win32-ia32@0.20.2: + resolution: {integrity: sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==} engines: {node: '>=12'} cpu: [ia32] os: [win32] @@ -413,8 +413,8 @@ packages: dev: true optional: true - /@esbuild/win32-x64@0.19.12: - resolution: {integrity: sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==} + /@esbuild/win32-x64@0.20.2: + resolution: {integrity: sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==} engines: {node: '>=12'} cpu: [x64] os: [win32] @@ -563,25 +563,25 @@ packages: resolution: {integrity: sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==} dev: true - /@vitejs/plugin-vue@5.0.4(vite@5.1.6)(vue@3.4.21): + /@vitejs/plugin-vue@5.0.4(vite@5.2.2)(vue@3.4.21): resolution: {integrity: sha512-WS3hevEszI6CEVEx28F8RjTX97k3KsrcY6kvTg7+Whm5y3oYvcqzVeGCU3hxSAn4uY2CLCkeokkGKpoctccilQ==} engines: {node: ^18.0.0 || >=20.0.0} peerDependencies: vite: ^5.0.0 vue: ^3.2.25 dependencies: - vite: 5.1.6 + vite: 5.2.2 vue: 3.4.21 dev: true /@vue/compiler-core@3.4.21: resolution: {integrity: sha512-MjXawxZf2SbZszLPYxaFCjxfibYrzr3eYbKxwpLR9EQN+oaziSu3qKVbwBERj1IFIB8OLUewxB5m/BFzi613og==} dependencies: - '@babel/parser': 7.24.0 + '@babel/parser': 7.24.1 '@vue/shared': 3.4.21 entities: 4.5.0 estree-walker: 2.0.2 - source-map-js: 1.0.2 + source-map-js: 1.2.0 dev: true /@vue/compiler-dom@3.4.21: @@ -594,15 +594,15 @@ packages: /@vue/compiler-sfc@3.4.21: resolution: {integrity: sha512-me7epoTxYlY+2CUM7hy9PCDdpMPfIwrOvAXud2Upk10g4YLv9UBW7kL798TvMeDhPthkZ0CONNrK2GoeI1ODiQ==} dependencies: - '@babel/parser': 7.24.0 + '@babel/parser': 7.24.1 '@vue/compiler-core': 3.4.21 '@vue/compiler-dom': 3.4.21 '@vue/compiler-ssr': 3.4.21 '@vue/shared': 3.4.21 estree-walker: 2.0.2 magic-string: 0.30.8 - postcss: 8.4.35 - source-map-js: 1.0.2 + postcss: 8.4.38 + source-map-js: 1.2.0 dev: true /@vue/compiler-ssr@3.4.21: @@ -612,20 +612,20 @@ packages: '@vue/shared': 3.4.21 dev: true - /@vue/devtools-api@7.0.17(vue@3.4.21): - resolution: {integrity: sha512-UWU9tqzUBv+ttUxYLaQcL5IxSSdF+i6yheFiEtz7mh88YZUYkxpEmT43iKBs3YsC54ROwPD2iZIndnju6PWfOQ==} + /@vue/devtools-api@7.0.20(vue@3.4.21): + resolution: {integrity: sha512-DGEIdotTQFll4187YGc/0awcag7UGJu9M6rE1Pxcs8AX/sGm0Ikk7UqQELmqYsyPzTT9s6OZzSPuBc4OatOXKA==} dependencies: - '@vue/devtools-kit': 7.0.17(vue@3.4.21) + '@vue/devtools-kit': 7.0.20(vue@3.4.21) transitivePeerDependencies: - vue dev: true - /@vue/devtools-kit@7.0.17(vue@3.4.21): - resolution: {integrity: sha512-znPLSOoTP3RnR9fvkq5M+nnpEA+WocybzOo5ID73vYkE0/n0VcfU8Ld0j4AHQjV/omTdAzh6QLpPlUYdIHXg+w==} + /@vue/devtools-kit@7.0.20(vue@3.4.21): + resolution: {integrity: sha512-FgFuPuqrhQ51rR/sVi52FnGgrxJ3X1bvNra/SkBzPhxJVhfyL5w2YUJZI1FgCvtLAyPSomJNdvlG415ZbJsr6w==} peerDependencies: vue: ^3.0.0 dependencies: - '@vue/devtools-shared': 7.0.17 + '@vue/devtools-shared': 7.0.20 hookable: 5.5.3 mitt: 3.0.1 perfect-debounce: 1.0.0 @@ -633,8 +633,8 @@ packages: vue: 3.4.21 dev: true - /@vue/devtools-shared@7.0.17: - resolution: {integrity: sha512-QNg2TMQBFFffRbTKE9NjytXBywGR77p2UMi/gJ0ow58S+1jkAvL8ikU/JnSs9ePvsVtspHX32m2cdfe4DJ4ygw==} + /@vue/devtools-shared@7.0.20: + resolution: {integrity: sha512-E6CiCaYr6ZWOCYJgWodXcPCXxB12vgbUA1X1sG0F1tK5Bo5I35GJuTR8LBJLFHV0VpwLWvyrIi9drT1ZbuJxlg==} dependencies: rfdc: 1.3.1 dev: true @@ -777,35 +777,35 @@ packages: engines: {node: '>=0.12'} dev: true - /esbuild@0.19.12: - resolution: {integrity: sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==} + /esbuild@0.20.2: + resolution: {integrity: sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==} engines: {node: '>=12'} hasBin: true requiresBuild: true optionalDependencies: - '@esbuild/aix-ppc64': 0.19.12 - '@esbuild/android-arm': 0.19.12 - '@esbuild/android-arm64': 0.19.12 - '@esbuild/android-x64': 0.19.12 - '@esbuild/darwin-arm64': 0.19.12 - '@esbuild/darwin-x64': 0.19.12 - '@esbuild/freebsd-arm64': 0.19.12 - '@esbuild/freebsd-x64': 0.19.12 - '@esbuild/linux-arm': 0.19.12 - '@esbuild/linux-arm64': 0.19.12 - '@esbuild/linux-ia32': 0.19.12 - '@esbuild/linux-loong64': 0.19.12 - '@esbuild/linux-mips64el': 0.19.12 - '@esbuild/linux-ppc64': 0.19.12 - '@esbuild/linux-riscv64': 0.19.12 - '@esbuild/linux-s390x': 0.19.12 - '@esbuild/linux-x64': 0.19.12 - '@esbuild/netbsd-x64': 0.19.12 - '@esbuild/openbsd-x64': 0.19.12 - '@esbuild/sunos-x64': 0.19.12 - '@esbuild/win32-arm64': 0.19.12 - '@esbuild/win32-ia32': 0.19.12 - '@esbuild/win32-x64': 0.19.12 + '@esbuild/aix-ppc64': 0.20.2 + '@esbuild/android-arm': 0.20.2 + '@esbuild/android-arm64': 0.20.2 + '@esbuild/android-x64': 0.20.2 + '@esbuild/darwin-arm64': 0.20.2 + '@esbuild/darwin-x64': 0.20.2 + '@esbuild/freebsd-arm64': 0.20.2 + '@esbuild/freebsd-x64': 0.20.2 + '@esbuild/linux-arm': 0.20.2 + '@esbuild/linux-arm64': 0.20.2 + '@esbuild/linux-ia32': 0.20.2 + '@esbuild/linux-loong64': 0.20.2 + '@esbuild/linux-mips64el': 0.20.2 + '@esbuild/linux-ppc64': 0.20.2 + '@esbuild/linux-riscv64': 0.20.2 + '@esbuild/linux-s390x': 0.20.2 + '@esbuild/linux-x64': 0.20.2 + '@esbuild/netbsd-x64': 0.20.2 + '@esbuild/openbsd-x64': 0.20.2 + '@esbuild/sunos-x64': 0.20.2 + '@esbuild/win32-arm64': 0.20.2 + '@esbuild/win32-ia32': 0.20.2 + '@esbuild/win32-x64': 0.20.2 dev: true /estree-walker@2.0.2: @@ -863,17 +863,17 @@ packages: resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} dev: true - /postcss@8.4.35: - resolution: {integrity: sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==} + /postcss@8.4.38: + resolution: {integrity: sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==} engines: {node: ^10 || ^12 || >=14} dependencies: nanoid: 3.3.7 picocolors: 1.0.0 - source-map-js: 1.0.2 + source-map-js: 1.2.0 dev: true - /preact@10.19.6: - resolution: {integrity: sha512-gympg+T2Z1fG1unB8NH29yHJwnEaCH37Z32diPDku316OTnRPeMbiRV9kTrfZpocXjdfnWuFUl/Mj4BHaf6gnw==} + /preact@10.20.0: + resolution: {integrity: sha512-wU7iZw2BjsaKDal3pDRDy/HpPB6cuFOnVUCcw9aIPKG98+ZrXx3F+szkos8BVME5bquyKDKvRlOJFG8kMkcAbg==} dev: true /rfdc@1.3.1: @@ -913,8 +913,8 @@ packages: '@shikijs/core': 1.2.0 dev: true - /source-map-js@1.0.2: - resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} + /source-map-js@1.2.0: + resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==} engines: {node: '>=0.10.0'} dev: true @@ -932,8 +932,8 @@ packages: engines: {node: '>=4'} dev: true - /vite@5.1.6: - resolution: {integrity: sha512-yYIAZs9nVfRJ/AiOLCA91zzhjsHUgMjB+EigzFb6W2XTLO8JixBCKCjvhKZaye+NKYHCrkv3Oh50dH9EdLU2RA==} + /vite@5.2.2: + resolution: {integrity: sha512-FWZbz0oSdLq5snUI0b6sULbz58iXFXdvkZfZWR/F0ZJuKTSPO7v72QPXt6KqYeMFb0yytNp6kZosxJ96Nr/wDQ==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: @@ -960,8 +960,8 @@ packages: terser: optional: true dependencies: - esbuild: 0.19.12 - postcss: 8.4.35 + esbuild: 0.20.2 + postcss: 8.4.38 rollup: 4.13.0 optionalDependencies: fsevents: 2.3.3 @@ -984,15 +984,15 @@ packages: '@shikijs/core': 1.2.0 '@shikijs/transformers': 1.2.0 '@types/markdown-it': 13.0.7 - '@vitejs/plugin-vue': 5.0.4(vite@5.1.6)(vue@3.4.21) - '@vue/devtools-api': 7.0.17(vue@3.4.21) + '@vitejs/plugin-vue': 5.0.4(vite@5.2.2)(vue@3.4.21) + '@vue/devtools-api': 7.0.20(vue@3.4.21) '@vueuse/core': 10.9.0(vue@3.4.21) '@vueuse/integrations': 10.9.0(focus-trap@7.5.4)(vue@3.4.21) focus-trap: 7.5.4 mark.js: 8.11.1 minisearch: 6.3.0 shiki: 1.2.0 - vite: 5.1.6 + vite: 5.2.2 vue: 3.4.21 transitivePeerDependencies: - '@algolia/client-search' diff --git a/package.json b/package.json index 129f407..a62c355 100644 --- a/package.json +++ b/package.json @@ -22,8 +22,8 @@ "azoth": "workspace:./packages/azoth", "eslint": "^8.57.0", "globals": "^14.0.0", - "happy-dom": "^13.8.6", - "vite": "^5.1.6", + "happy-dom": "^13.10.1", + "vite": "^5.2.2", "vite-plugin-inspect": "^0.8.3", "vitest": "^1.4.0" }, diff --git a/packages/azoth/runtime.js b/packages/azoth/runtime.js index 659c312..9486dbf 100644 --- a/packages/azoth/runtime.js +++ b/packages/azoth/runtime.js @@ -1,10 +1,8 @@ export { compose as __compose, - composeElement as __composeElement, createElement as __createElement, } from '@azothjs/runtime/compose'; export { - makeRenderer as __makeRenderer, - rendererById as __rendererById, + renderer as __renderer, } from '@azothjs/runtime/renderer'; \ No newline at end of file diff --git a/packages/compiler/compiler.js b/packages/compiler/compiler.js index 5ecd364..951894a 100644 --- a/packages/compiler/compiler.js +++ b/packages/compiler/compiler.js @@ -1,9 +1,10 @@ import { Parser } from 'acorn'; import acornJsx from 'acorn-jsx'; import { generate as astring } from 'astring'; -import { TemplateGenerator, templateModule } from './transform/TemplateGenerator.js'; +import { Transpiler, templateModule } from './transform/Transpiler.js'; import { SourceMapGenerator } from 'source-map'; +export * from './transform/template-generators.js'; export { templateModule }; // compile = parse + generate @@ -51,7 +52,7 @@ export function generateWith(generator, ast, config) { } export function generate(ast, config) { - const generator = new TemplateGenerator(); + const generator = new Transpiler(); const generated = generateWith(generator, ast, config); const { templates } = generator; diff --git a/packages/compiler/compiler.test.js b/packages/compiler/compiler.test.js index f55d05e..dfd643b 100644 --- a/packages/compiler/compiler.test.js +++ b/packages/compiler/compiler.test.js @@ -8,8 +8,8 @@ const compile = input => { }); return { code, map, - templates: templates.map(({ id, html, isDomFragment, isEmpty }) => { - return { id, html, isDomFragment, isEmpty }; + templates: templates.map(({ id, childBindKey, propBindKey, html, isDomFragment, isEmpty }) => { + return { id, childBindKey, propBindKey, html, isDomFragment, isEmpty }; }) }; }; @@ -24,20 +24,22 @@ describe('JSX dom literals', () => { const { code, templates } = compile(input); expect(code).toMatchInlineSnapshot(` - "import { ta516887159 } from 'virtual:azoth-templates?id=a516887159'; + "import { tf312946f36 } from 'virtual:azoth-templates?id=f312946f36'; - const t = ta516887159("className","Azoth"); + const t = tf312946f36("className","Azoth"); " `); expect(templates).toMatchInlineSnapshot(` [ { + "childBindKey": undefined, "html": "

    Hello

    ", - "id": "a516887159", + "id": "f312946f36", "isDomFragment": false, "isEmpty": false, + "propBindKey": undefined, }, ] `); @@ -63,15 +65,16 @@ describe('JSX dom literals', () => { const { code, templates } = compile(input); expect(code).toMatchInlineSnapshot(` - "import { tfdd1a869cf } from 'virtual:azoth-templates?id=fdd1a869cf'; + "import { t9fcf48061b } from 'virtual:azoth-templates?id=9fcf48061b'; - const t = tfdd1a869cf("my-class","felix","this is","azoth","two","and...","span-class","ul-footer","footer"); + const t = t9fcf48061b("my-class","felix","this is","azoth","two","and...","span-class","ul-footer","footer"); " `); expect(templates).toMatchInlineSnapshot(` [ { + "childBindKey": undefined, "html": "

    static

    @@ -87,9 +90,10 @@ describe('JSX dom literals', () => {
    ", - "id": "fdd1a869cf", + "id": "9fcf48061b", "isDomFragment": false, "isEmpty": false, + "propBindKey": undefined, }, ] `); @@ -107,19 +111,21 @@ describe('JSX dom literals', () => { const { code, templates } = compile(input); expect(code).toMatchInlineSnapshot(` - "import { t10073da0ec } from 'virtual:azoth-templates?id=10073da0ec'; + "import { tfef7238342 } from 'virtual:azoth-templates?id=fef7238342'; - const t = t10073da0ec("className","name","class","class-name"); + const t = tfef7238342("className","name","class","class-name"); " `); expect(templates).toMatchInlineSnapshot(` [ { + "childBindKey": undefined, "html": "", - "id": "10073da0ec", + "id": "fef7238342", "isDomFragment": false, "isEmpty": false, + "propBindKey": undefined, }, ] `); @@ -133,25 +139,29 @@ describe('nested context', () => { const { code, templates } = compile(input); expect(code).toMatchInlineSnapshot(` - "import { t8dae88052a, t1a78cbe949 } from 'virtual:azoth-templates?id=8dae88052a&id=1a78cbe949'; + "import { t728beb50f0, t1a78cbe949 } from 'virtual:azoth-templates?id=728beb50f0&id=1a78cbe949'; - t8dae88052a(t1a78cbe949()); + t728beb50f0(t1a78cbe949()); " `); expect(templates).toMatchInlineSnapshot(` [ { + "childBindKey": undefined, "html": "
    ", - "id": "8dae88052a", + "id": "728beb50f0", "isDomFragment": false, "isEmpty": false, + "propBindKey": undefined, }, { + "childBindKey": undefined, "html": "
    ", "id": "1a78cbe949", "isDomFragment": false, "isEmpty": false, + "propBindKey": undefined, }, ] `); @@ -175,10 +185,12 @@ describe('template optimizations', () => { expect(templates).toMatchInlineSnapshot(` [ { + "childBindKey": undefined, "html": "

    Hello

    ", "id": "5bf3d2f523", "isDomFragment": false, "isEmpty": false, + "propBindKey": undefined, }, ] `); @@ -195,10 +207,10 @@ describe('fragments', () => { const { code, templates } = compile(input); expect(code).toMatchInlineSnapshot(` - "import { tc203fe7dcd, tc084de4382 } from 'virtual:azoth-templates?id=c203fe7dcd&id=c084de4382'; + "import { tc203fe7dcd, t84c9741b4d, td41d8cd98f } from 'virtual:azoth-templates?id=c203fe7dcd&id=84c9741b4d&id=d41d8cd98f'; const fragment = tc203fe7dcd(); - const compose = tc084de4382(x); + const compose = t84c9741b4d(x); const empty = null; " `); @@ -206,22 +218,28 @@ describe('fragments', () => { expect(templates).toMatchInlineSnapshot(` [ { + "childBindKey": undefined, "html": "

    ", "id": "c203fe7dcd", "isDomFragment": true, "isEmpty": false, + "propBindKey": undefined, }, { + "childBindKey": undefined, "html": "", - "id": "c084de4382", + "id": "84c9741b4d", "isDomFragment": true, "isEmpty": false, + "propBindKey": undefined, }, { + "childBindKey": undefined, "html": "", - "id": "", + "id": "d41d8cd98f", "isDomFragment": true, "isEmpty": true, + "propBindKey": undefined, }, ] `); @@ -258,14 +276,14 @@ describe('fragments', () => { const { code, templates } = compile(input); expect(code).toMatchInlineSnapshot(` - "import { tc203fe7dcd, t1a78cbe949, tc084de4382, t6c72de769d } from 'virtual:azoth-templates?id=c203fe7dcd&id=1a78cbe949&id=c084de4382&id=6c72de769d'; + "import { tc203fe7dcd, t1a78cbe949, t84c9741b4d, td41d8cd98f, t6c72de769d } from 'virtual:azoth-templates?id=c203fe7dcd&id=1a78cbe949&id=84c9741b4d&id=d41d8cd98f&id=6c72de769d'; const fragment = tc203fe7dcd(); const single = t1a78cbe949(); const fragInFrag = t1a78cbe949(); - const fragInFragCompose = tc084de4382(x); + const fragInFragCompose = t84c9741b4d(x); const empty = null; - const compose = tc084de4382(x); + const compose = t84c9741b4d(x); const text = t6c72de769d(); " `); @@ -273,48 +291,62 @@ describe('fragments', () => { expect(templates).toMatchInlineSnapshot(` [ { + "childBindKey": undefined, "html": "

    ", "id": "c203fe7dcd", "isDomFragment": true, "isEmpty": false, + "propBindKey": undefined, }, { + "childBindKey": undefined, "html": "
    ", "id": "1a78cbe949", "isDomFragment": false, "isEmpty": false, + "propBindKey": undefined, }, { + "childBindKey": undefined, "html": "
    ", "id": "1a78cbe949", "isDomFragment": false, "isEmpty": false, + "propBindKey": undefined, }, { + "childBindKey": undefined, "html": "", - "id": "c084de4382", + "id": "84c9741b4d", "isDomFragment": true, "isEmpty": false, + "propBindKey": undefined, }, { + "childBindKey": undefined, "html": "", - "id": "", + "id": "d41d8cd98f", "isDomFragment": true, "isEmpty": true, + "propBindKey": undefined, }, { + "childBindKey": undefined, "html": "", - "id": "c084de4382", + "id": "84c9741b4d", "isDomFragment": true, "isEmpty": false, + "propBindKey": undefined, }, { + "childBindKey": undefined, "html": " text ", "id": "6c72de769d", "isDomFragment": true, "isEmpty": false, + "propBindKey": undefined, }, ] `); @@ -335,40 +367,48 @@ describe('fragments', () => { const { code, templates } = compile(input); expect(code).toMatchInlineSnapshot(` - "import { t1a78cbe949, tc084de4382 } from 'virtual:azoth-templates?id=1a78cbe949&id=c084de4382'; + "import { t1a78cbe949, t84c9741b4d } from 'virtual:azoth-templates?id=1a78cbe949&id=84c9741b4d'; const start = t1a78cbe949(); const end = t1a78cbe949(); - const composeStart = tc084de4382(x); - const composeEnd = tc084de4382(x); + const composeStart = t84c9741b4d(x); + const composeEnd = t84c9741b4d(x); " `); expect(templates).toMatchInlineSnapshot(` [ { + "childBindKey": undefined, "html": "
    ", "id": "1a78cbe949", "isDomFragment": false, "isEmpty": false, + "propBindKey": undefined, }, { + "childBindKey": undefined, "html": "
    ", "id": "1a78cbe949", "isDomFragment": false, "isEmpty": false, + "propBindKey": undefined, }, { + "childBindKey": undefined, "html": "", - "id": "c084de4382", + "id": "84c9741b4d", "isDomFragment": true, "isEmpty": false, + "propBindKey": undefined, }, { + "childBindKey": undefined, "html": "", - "id": "c084de4382", + "id": "84c9741b4d", "isDomFragment": true, "isEmpty": false, + "propBindKey": undefined, }, ] `); @@ -386,47 +426,57 @@ describe('fragments', () => { const { code, templates } = compile(input); expect(code).toMatchInlineSnapshot(` - "import { t653a3aad80, tdcaa233028, t2dc1738d5c, t0cf31b2c28, t5bc2a159b1 } from 'virtual:azoth-templates?id=653a3aad80&id=dcaa233028&id=2dc1738d5c&id=0cf31b2c28&id=5bc2a159b1'; + "import { t653a3aad80, tdcaa233028, t2dc1738d5c, t0cf31b2c28, t3c41ad0e2b } from 'virtual:azoth-templates?id=653a3aad80&id=dcaa233028&id=2dc1738d5c&id=0cf31b2c28&id=3c41ad0e2b'; const fragment = t653a3aad80(); const single = tdcaa233028(); const fragInFrag = t2dc1738d5c(); const spaces = t0cf31b2c28(); - const compose = t5bc2a159b1(x); + const compose = t3c41ad0e2b(x); " `); expect(templates).toMatchInlineSnapshot(` [ { + "childBindKey": undefined, "html": "

    ", "id": "653a3aad80", "isDomFragment": true, "isEmpty": false, + "propBindKey": undefined, }, { + "childBindKey": undefined, "html": "
    ", "id": "dcaa233028", "isDomFragment": true, "isEmpty": false, + "propBindKey": undefined, }, { + "childBindKey": undefined, "html": "
    ", "id": "2dc1738d5c", "isDomFragment": true, "isEmpty": false, + "propBindKey": undefined, }, { + "childBindKey": undefined, "html": " ", "id": "0cf31b2c28", "isDomFragment": true, "isEmpty": false, + "propBindKey": undefined, }, { + "childBindKey": undefined, "html": " ", - "id": "5bc2a159b1", + "id": "3c41ad0e2b", "isDomFragment": true, "isEmpty": false, + "propBindKey": undefined, }, ] `); @@ -440,19 +490,21 @@ describe('fragments', () => { const { code, templates } = compile(input); expect(code).toMatchInlineSnapshot(` - "import { tfaf808e6cc } from 'virtual:azoth-templates?id=faf808e6cc'; + "import { t253e2ffa84 } from 'virtual:azoth-templates?id=253e2ffa84'; - const fragment = tfaf808e6cc("two"); + const fragment = t253e2ffa84("two"); " `); expect(templates).toMatchInlineSnapshot(` [ { + "childBindKey": undefined, "html": "onethree", - "id": "faf808e6cc", + "id": "253e2ffa84", "isDomFragment": true, "isEmpty": false, + "propBindKey": undefined, }, ] `); @@ -472,30 +524,34 @@ describe('fragments', () => { const { code, templates } = compile(input); expect(code).toMatchInlineSnapshot(` - "import { tccaa44c114, t681310be49 } from 'virtual:azoth-templates?id=ccaa44c114&id=681310be49'; + "import { tccaa44c114, taccbe53128 } from 'virtual:azoth-templates?id=ccaa44c114&id=accbe53128'; const extraneous = tccaa44c114(); - const childNodeIndex = t681310be49("expect index 3"); + const childNodeIndex = taccbe53128("expect index 3"); " `); expect(templates).toMatchInlineSnapshot(` [ { + "childBindKey": undefined, "html": "



    ", "id": "ccaa44c114", "isDomFragment": false, "isEmpty": false, + "propBindKey": undefined, }, { + "childBindKey": undefined, "html": "

    ", - "id": "681310be49", + "id": "accbe53128", "isDomFragment": false, "isEmpty": false, + "propBindKey": undefined, }, ] `); @@ -507,19 +563,21 @@ describe('fragments', () => { const { code, templates } = compile(input); expect(code).toMatchInlineSnapshot(` - "import { tef691fa27a } from 'virtual:azoth-templates?id=ef691fa27a'; + "import { t97874fe2c4 } from 'virtual:azoth-templates?id=97874fe2c4'; - const App = tef691fa27a('foo','bar','qux'); + const App = t97874fe2c4('foo','bar','qux'); " `); expect(templates).toMatchInlineSnapshot(` [ { + "childBindKey": undefined, "html": "
    ", - "id": "ef691fa27a", + "id": "97874fe2c4", "isDomFragment": true, "isEmpty": false, + "propBindKey": undefined, }, ] `); @@ -536,19 +594,21 @@ describe('element composition', () => { const { code, templates } = compile(input); expect(code).toMatchInlineSnapshot(` - "import { t1cdf0d646f } from 'virtual:azoth-templates?id=1cdf0d646f'; + "import { tb3611c2834 } from 'virtual:azoth-templates?id=b3611c2834'; - document.body.append(t1cdf0d646f(prop)); + document.body.append(tb3611c2834(prop)); " `); expect(templates).toMatchInlineSnapshot(` [ { + "childBindKey": undefined, "html": "", - "id": "1cdf0d646f", + "id": "b3611c2834", "isDomFragment": false, "isEmpty": false, + "propBindKey": undefined, }, ] `); @@ -564,7 +624,7 @@ describe('element composition', () => { expect(code).toMatchInlineSnapshot(` "import { __createElement } from 'azoth/runtime'; - + import { td41d8cd98f } from 'virtual:azoth-templates?id=d41d8cd98f'; const c = __createElement(Component, true); const cProps = __createElement(Component, { prop: value, attr: "static", }, true); " @@ -572,16 +632,20 @@ describe('element composition', () => { expect(templates).toMatchInlineSnapshot(` [ { + "childBindKey": undefined, "html": "", - "id": "", + "id": "d41d8cd98f", "isDomFragment": false, "isEmpty": true, + "propBindKey": undefined, }, { + "childBindKey": undefined, "html": "", - "id": "", + "id": "d41d8cd98f", "isDomFragment": false, "isEmpty": true, + "propBindKey": undefined, }, ] `); @@ -598,20 +662,22 @@ describe('element composition', () => { expect(code).toMatchInlineSnapshot(` "import { __createElement } from 'azoth/runtime'; - import { t2288998344 } from 'virtual:azoth-templates?id=2288998344'; - const component = t2288998344(__createElement(Component, { prop: value, prop2: "literal", }),__createElement(GotNoPropsAsYouCanSee)); + import { t06bef50336 } from 'virtual:azoth-templates?id=06bef50336'; + const component = t06bef50336(__createElement(Component, { prop: value, prop2: "literal", }),__createElement(GotNoPropsAsYouCanSee)); " `); expect(templates).toMatchInlineSnapshot(` [ { + "childBindKey": undefined, "html": "
    ", - "id": "2288998344", + "id": "06bef50336", "isDomFragment": false, "isEmpty": false, + "propBindKey": undefined, }, ] `); @@ -631,34 +697,40 @@ describe('element composition', () => { expect(code).toMatchInlineSnapshot(` "import { __createElement } from 'azoth/runtime'; - import { t2288998344 } from 'virtual:azoth-templates?id=2288998344'; + import { td41d8cd98f, t06bef50336 } from 'virtual:azoth-templates?id=d41d8cd98f&id=06bef50336'; const $A = __createElement(A, true); const $B = __createElement(B, true); - const dom = t2288998344($A,$B); + const dom = t06bef50336($A,$B); " `); expect(templates).toMatchInlineSnapshot(` [ { + "childBindKey": undefined, "html": "", - "id": "", + "id": "d41d8cd98f", "isDomFragment": false, "isEmpty": true, + "propBindKey": undefined, }, { + "childBindKey": undefined, "html": "", - "id": "", + "id": "d41d8cd98f", "isDomFragment": false, "isEmpty": true, + "propBindKey": undefined, }, { + "childBindKey": undefined, "html": "
    ", - "id": "2288998344", + "id": "06bef50336", "isDomFragment": false, "isEmpty": false, + "propBindKey": undefined, }, ] `); @@ -685,90 +757,114 @@ describe('element composition', () => { expect(code).toMatchInlineSnapshot(` "import { __createElement } from 'azoth/runtime'; - import { t904ca237ee, t1cb251ec0d, t9b045328fb } from 'virtual:azoth-templates?id=904ca237ee&id=1cb251ec0d&id=9b045328fb'; - const c = __createElement(Component, null, t904ca237ee("test"), true); - const cTrim = __createElement(Component, null, t904ca237ee("test"), true); - const cTrimStart = __createElement(Component, null, t904ca237ee("test"), true); - const cTrimEnd = __createElement(Component, null, t904ca237ee("test"), true); + import { td41d8cd98f, tc193fcb516, t1cb251ec0d, t047dd60a46 } from 'virtual:azoth-templates?id=d41d8cd98f&id=c193fcb516&id=1cb251ec0d&id=047dd60a46'; + const c = __createElement(Component, null, tc193fcb516("test"), true); + const cTrim = __createElement(Component, null, tc193fcb516("test"), true); + const cTrimStart = __createElement(Component, null, tc193fcb516("test"), true); + const cTrimEnd = __createElement(Component, null, tc193fcb516("test"), true); const cText = __createElement(Component, null, t1cb251ec0d(), true); - const cFrag = __createElement(Component, null, t9b045328fb(1,2), true); + const cFrag = __createElement(Component, null, t047dd60a46(1,2), true); " `); expect(templates).toMatchInlineSnapshot(` [ { + "childBindKey": undefined, "html": "", - "id": "", + "id": "d41d8cd98f", "isDomFragment": false, "isEmpty": true, + "propBindKey": undefined, }, { + "childBindKey": undefined, "html": "

    ", - "id": "904ca237ee", + "id": "c193fcb516", "isDomFragment": false, "isEmpty": false, + "propBindKey": undefined, }, { + "childBindKey": undefined, "html": "", - "id": "", + "id": "d41d8cd98f", "isDomFragment": false, "isEmpty": true, + "propBindKey": undefined, }, { + "childBindKey": undefined, "html": "

    ", - "id": "904ca237ee", + "id": "c193fcb516", "isDomFragment": false, "isEmpty": false, + "propBindKey": undefined, }, { + "childBindKey": undefined, "html": "", - "id": "", + "id": "d41d8cd98f", "isDomFragment": false, "isEmpty": true, + "propBindKey": undefined, }, { + "childBindKey": undefined, "html": "

    ", - "id": "904ca237ee", + "id": "c193fcb516", "isDomFragment": false, "isEmpty": false, + "propBindKey": undefined, }, { + "childBindKey": undefined, "html": "", - "id": "", + "id": "d41d8cd98f", "isDomFragment": false, "isEmpty": true, + "propBindKey": undefined, }, { + "childBindKey": undefined, "html": "

    ", - "id": "904ca237ee", + "id": "c193fcb516", "isDomFragment": false, "isEmpty": false, + "propBindKey": undefined, }, { + "childBindKey": undefined, "html": "", - "id": "", + "id": "d41d8cd98f", "isDomFragment": false, "isEmpty": true, + "propBindKey": undefined, }, { + "childBindKey": undefined, "html": "text", "id": "1cb251ec0d", "isDomFragment": true, "isEmpty": false, + "propBindKey": undefined, }, { + "childBindKey": undefined, "html": "", - "id": "", + "id": "d41d8cd98f", "isDomFragment": false, "isEmpty": true, + "propBindKey": undefined, }, { + "childBindKey": undefined, "html": "

    ", - "id": "9b045328fb", + "id": "047dd60a46", "isDomFragment": true, "isEmpty": false, + "propBindKey": undefined, }, ] `); @@ -786,26 +882,30 @@ describe('render and composition cases', () => { const { code, templates } = compile(input); expect(code).toMatchInlineSnapshot(` - "import { t62831a5152, t8dc93cc914 } from 'virtual:azoth-templates?id=62831a5152&id=8dc93cc914'; + "import { tcb5355f810, t9d84b2deff } from 'virtual:azoth-templates?id=cb5355f810&id=9d84b2deff'; - const Item = name => t62831a5152(name); - const Template = () => t8dc93cc914([2, 4, 7].map(Item),"text"); + const Item = name => tcb5355f810(name); + const Template = () => t9d84b2deff([2, 4, 7].map(Item),"text"); " `); expect(templates).toMatchInlineSnapshot(` [ { + "childBindKey": undefined, "html": "
  • ", - "id": "62831a5152", + "id": "cb5355f810", "isDomFragment": false, "isEmpty": false, + "propBindKey": undefined, }, { + "childBindKey": undefined, "html": "
    ", - "id": "8dc93cc914", + "id": "9d84b2deff", "isDomFragment": false, "isEmpty": false, + "propBindKey": undefined, }, ] `); @@ -822,27 +922,31 @@ describe('render and composition cases', () => { const { code, templates } = compile(input); expect(code).toMatchInlineSnapshot(` - "import { t62831a5152, t25ec157413 } from 'virtual:azoth-templates?id=62831a5152&id=25ec157413'; + "import { tcb5355f810, t65cf075bba } from 'virtual:azoth-templates?id=cb5355f810&id=65cf075bba'; - const Emoji = ({name}) => t62831a5152(name); + const Emoji = ({name}) => tcb5355f810(name); const promise = fetchEmojis().then(emojis => emojis.map(Emoji)); - const Emojis = t25ec157413(promise); + const Emojis = t65cf075bba(promise); document.body.append(Emojis); " `); expect(templates).toMatchInlineSnapshot(` [ { + "childBindKey": undefined, "html": "
  • ", - "id": "62831a5152", + "id": "cb5355f810", "isDomFragment": false, "isEmpty": false, + "propBindKey": undefined, }, { + "childBindKey": undefined, "html": "
    ", - "id": "25ec157413", + "id": "65cf075bba", "isDomFragment": false, "isEmpty": false, + "propBindKey": undefined, }, ] `); diff --git a/packages/compiler/source-maps.test.js b/packages/compiler/source-maps.test.js index 835bbc9..372961a 100644 --- a/packages/compiler/source-maps.test.js +++ b/packages/compiler/source-maps.test.js @@ -42,9 +42,9 @@ test('{...} one line', ({ expect }) => { const input = `
    Hello {place}
    `; const { _sourceMap, code } = compile(input); expect(code).toMatchInlineSnapshot(` - "import { ta94b210052 } from 'virtual:azoth-templates?id=a94b210052'; + "import { t9be5e1247d } from 'virtual:azoth-templates?id=9be5e1247d'; - ta94b210052(place); + t9be5e1247d(place); " `); expect(_sourceMap._mappings._array).toMatchInlineSnapshot(` @@ -108,9 +108,9 @@ test('{...} three line', ({ expect }) => { `; const { _sourceMap, code } = compile(input); expect(code).toMatchInlineSnapshot(` - "import { tf2d718c3f5 } from 'virtual:azoth-templates?id=f2d718c3f5'; + "import { t1f0853a65c } from 'virtual:azoth-templates?id=1f0853a65c'; - const t = tf2d718c3f5(place); + const t = t1f0853a65c(place); " `); expect(_sourceMap._mappings._array).toMatchInlineSnapshot(` diff --git a/packages/compiler/transform/Analyzer.js b/packages/compiler/transform/Analyzer.js index 6387eb5..6b48bf3 100644 --- a/packages/compiler/transform/Analyzer.js +++ b/packages/compiler/transform/Analyzer.js @@ -17,7 +17,6 @@ export class Analyzer { #documentOrder = 0; #boundElements = new Set(); #bindings = []; - #template = null; #root = null; #imports = new Set(); @@ -29,20 +28,13 @@ export class Analyzer { boundElements[i].queryIndex = i; } - this.#template = new Template(this.#root, { + this.template = new Template(this.#root, { bindings: this.#bindings, boundElements, imports: [...(this.#imports.values())], }); } - // TODO: move generation elsewhere - generateTemplate(htmlGenerator) { - const template = this.#template; - if(!template.isEmpty) template.html = htmlGenerator(template.node); - return template; - } - #analyze(node) { const isJSXFragment = node.isJSXFragment = node.type === 'JSXFragment'; diff --git a/packages/compiler/transform/HtmlGenerator.js b/packages/compiler/transform/HtmlGenerator.js index c927ef2..46b12ec 100644 --- a/packages/compiler/transform/HtmlGenerator.js +++ b/packages/compiler/transform/HtmlGenerator.js @@ -94,7 +94,6 @@ export class HtmlGenerator extends Generator { // ...text... JSXText({ value }, state) { - // const normalized = value.replace(NORMALIZE_PATTERN, ' '); state.write(value); } } diff --git a/packages/compiler/transform/Template.js b/packages/compiler/transform/Template.js index 2f008e9..e5f2b15 100644 --- a/packages/compiler/transform/Template.js +++ b/packages/compiler/transform/Template.js @@ -1,38 +1,42 @@ +import { generate } from 'astring'; import revHash from 'rev-hash'; +import { HtmlGenerator } from './HtmlGenerator.js'; -export class Template { - isDomFragment = false; - isEmpty = false; - isStatic = false; - #html = ''; - #id = ''; - - get id() { - return this.#id; - } - get html() { - return this.#html; - } +const generator = new HtmlGenerator(); +const htmlGenerator = node => generate(node, { generator }); - set html(html) { - this.#html = html; - this.#id = revHash(html); - } +export class Template { constructor(node, { bindings, boundElements, imports }) { - this.node = node; - this.bindings = bindings; - this.boundElements = boundElements; - this.imports = imports; - if(node.isComponent && bindings.length) { throw new Error('Unexpected component binding length'); } + this.node = node; + this.bindings = bindings; + this.boundElements = boundElements; + this.imports = imports; this.isBoundRoot = node.queryIndex === -1; this.isDomFragment = node.isJSXFragment; this.isEmpty = node.isComponent || (node.isJSXFragment && node.children.length === 0); this.isStatic = this.isEmpty || (!boundElements.length) && node.queryIndex !== -1; + + this.html = this.isEmpty ? '' : htmlGenerator(node); + + let tKey = '', bKey = ''; + if(bindings.length) { + tKey = revHash(bindings.map(({ type, index, element: { isRoot, queryIndex } }) => { + return (isRoot ? '' : `${queryIndex}`) + (type === 'child' ? `:${index}` : ''); + }).join()); + bKey = revHash(bindings.map(({ type, node }) => { + return type === 'prop' ? node.name.name : ''; + }).join()); + } + + this.targetKey = tKey; + this.bindKey = bKey; + this.id = revHash(this.html + this.bindKey + this.targetKey); + } } diff --git a/packages/compiler/transform/TemplateGenerator.js b/packages/compiler/transform/Transpiler.js similarity index 91% rename from packages/compiler/transform/TemplateGenerator.js rename to packages/compiler/transform/Transpiler.js index 9d475f0..5195c26 100644 --- a/packages/compiler/transform/TemplateGenerator.js +++ b/packages/compiler/transform/Transpiler.js @@ -1,22 +1,11 @@ import { generate } from 'astring'; import { HtmlGenerator } from './HtmlGenerator.js'; -import { Generator, writeNextLine } from './GeneratorBase.js'; -import { isValidESIdentifier } from 'is-valid-es-identifier'; +import { Generator } from './GeneratorBase.js'; import { Analyzer } from './Analyzer.js'; export const templateModule = `virtual:azoth-templates`; -const OPENING_PROP = { - JSXElement: 'openingElement', - JSXFragment: 'openingFragment', -}; - -const IS_OPENING = { - JSXOpeningElement: true, - JSXOpeningFragment: true, -}; - -export class TemplateGenerator extends Generator { +export class Transpiler extends Generator { templates = []; uniqueIds = new Set(); @@ -73,7 +62,7 @@ export class TemplateGenerator extends Generator { JSXTemplate(node, state) { const analyzer = new Analyzer(node); - const template = analyzer.generateTemplate(this.htmlGenerator); + const template = analyzer.template; const { templates, uniqueIds } = this; templates.push(template); diff --git a/packages/compiler/transform/template-generators.js b/packages/compiler/transform/template-generators.js index 23c5d02..24287a2 100644 --- a/packages/compiler/transform/template-generators.js +++ b/packages/compiler/transform/template-generators.js @@ -12,12 +12,20 @@ export function makeTargets(template) { : target; }); - return `const targets = (${elLength ? 'r,t' : 'r'}) => [${values.join()}];\n`; + return `(${elLength ? 'r,t' : 'r'}) => [${values.join()}]`; } -export function makeRenderer({ id, isDomFragment, html }, options) { - const content = options?.noContent ? '' : `, ${html}`; - return `const renderDOM = renderer('${id}', targets, bind, ${isDomFragment}${content});\n`; +export function makeRenderer({ id, targetKey, bindKey, isDomFragment, html }, options) { + const content = !!options?.includeContent; + const target = targetKey ? `g${targetKey}` : `null`; + const bind = bindKey ? `b${bindKey}` : `null`; + + let renderer = `__renderer(`; + renderer += `"${id}", ${target}, ${bind}, ${isDomFragment}`; + if(content) renderer += `, \`${html}\``; + renderer += `)`; + + return renderer; } const TARGETS = 'ts'; @@ -32,16 +40,12 @@ export function makeBind({ bindings }) { } const bound = bindings.map(({ type, node }, index) => { - if(node.isComponent) { - throw new Error('need compose element'); - // return ComposeElement(node, expr, i, state); - } if(type === 'child') { - return `compose(${TARGET}${index}, ${VALUE}${index});`; + return `__compose(${TARGET}${index}, ${VALUE}${index});`; } if(type === 'prop') { // TODO: consider source maps for prop on element - // TODO: refactor with component props names + // TODO: refactor with component props names when DOMProp/attr lookup exists const identity = node.name; const propName = identity.name; const isValidId = isValidESIdentifier(propName); @@ -54,11 +58,11 @@ export function makeBind({ bindings }) { throw new Error(message); }); - return `function bind(${TARGETS}) { + return `(${TARGETS}) => { const ${targets.join(', ')}; return (${params.join(', ')}) => { ${bound.join('\n ')} }; -}\n`; +}`; } diff --git a/packages/compiler/transform/template-generators.test.js b/packages/compiler/transform/template-generators.test.js index e54424d..b27127a 100644 --- a/packages/compiler/transform/template-generators.test.js +++ b/packages/compiler/transform/template-generators.test.js @@ -1,5 +1,5 @@ /* eslint-disable no-undef */ -import { makeTargets, makeRenderer, makeRender, makeBind } from './template-generators.js'; +import { makeTargets, makeRenderer, makeBind } from './template-generators.js'; import { parse, generate as _generate } from '../compiler.js'; import { describe, test, beforeEach } from 'vitest'; @@ -22,10 +22,7 @@ describe('targets generator', () => { test('simple', ({ compile, expect }) => { const code = compile(`name =>

    {name}

    `); - expect(code).toMatchInlineSnapshot(` - "const targets = (r) => [r.childNodes[0]]; - " - `); + expect(code).toMatchInlineSnapshot(`"(r) => [r.childNodes[0]]"`); }); test('props and elements', ({ compile, expect }) => { @@ -33,100 +30,105 @@ describe('targets generator', () => { {"Greeting"} hey {"Azoth"}!

    ;`); expect(code).toMatchInlineSnapshot( - ` - "const targets = (r,t) => [r,r.childNodes[1],t[0].childNodes[1]]; - " - ` + `"(r,t) => [r,r.childNodes[1],t[0].childNodes[1]]"` ); }); }); -describe('renderDOM generator', () => { +describe('bind generator', () => { beforeEach(context => { context.compile = code => { const template = preParse(code, context.expect); - return makeRenderer(template); + return makeBind(template); }; }); test('simple', ({ compile, expect }) => { - const code = compile(`name =>

    {name}

    `, expect); - + const code = compile(`name =>

    {name}

    `); expect(code).toMatchInlineSnapshot(` - "const renderDOM = renderer('904ca237ee', targets, bind, false,

    ); - " + "(ts) => { + const t0 = ts[0]; + return (v0) => { + compose(t0, v0); + }; + }" `); }); - test('props and elements', ({ compile, expect }) => { const code = compile(`const t =

    {"Greeting"} hey {"Azoth"}!

    ;`); - expect(code).toMatchInlineSnapshot( ` - "const renderDOM = renderer('5252cfebed', targets, bind, false,

    - hey ! -

    ); - " + "(ts) => { + const t0 = ts[0], t1 = ts[1], t2 = ts[2]; + return (v0, v1, v2) => { + t0.className = v0; + compose(t1, v1); + compose(t2, v2); + }; + }" ` ); }); - - test('option noContent: true', ({ expect }) => { - const template = preParse(`name =>

    {name}

    `, expect); - const code = makeRenderer(template, { noContent: true }); - - expect(code).toMatchInlineSnapshot(` - "const renderDOM = renderer('904ca237ee', targets, bind, false); - " - `); - }); - - - }); -describe('bind generator', () => { +describe('renderDOM generator', () => { beforeEach(context => { context.compile = code => { const template = preParse(code, context.expect); - return makeBind(template); + return makeRenderer(template, { includeContent: true }); }; }); test('simple', ({ compile, expect }) => { const code = compile(`name =>

    {name}

    `); - expect(code).toMatchInlineSnapshot(` - "function bind(ts) { - const t0 = ts[0]; - return (v0) => { - compose(t0, v0); - }; - } - " - `); + + expect(code).toMatchInlineSnapshot(`"renderer("c193fcb516", g1a9d5db22c, bd41d8cd98f, false, "

    ")"`); }); + test('static', ({ compile, expect }) => { + const code = compile(`() =>

    static

    `); + + expect(code).toMatchInlineSnapshot(`"renderer("e8a7ca1ef0", null, null, false, "

    static

    ")"`); + }); + + test('props and elements', ({ compile, expect }) => { const code = compile(`const t =

    {"Greeting"} hey {"Azoth"}!

    ;`); + expect(code).toMatchInlineSnapshot( ` - "function bind(ts) { - const t0 = ts[0], t1 = ts[1], t2 = ts[2]; - return (v0, v1, v2) => { - t0.className = v0; - compose(t1, v1); - compose(t2, v2); - }; - } - " + "renderer("b32dab1494", g98cb41d3ff, bb90a39b45c, false, "

    + hey ! +

    ")" ` ); }); + + test('option { noContent: true }', ({ expect }) => { + const template = preParse(`name =>

    {name}

    `, expect); + const code = makeRenderer(template, { noContent: true }); + + expect(code).toMatchInlineSnapshot(`"renderer("c193fcb516", g1a9d5db22c, bd41d8cd98f, false)"`); + }); + + test('option inject { targets: code, bind: code }', ({ expect }) => { + const template = preParse(`name =>

    {name}

    `, expect); + const code = makeRenderer(template, { + targets: `"targets!"`, + bind: `"bind!"`, + }); + + expect(code).toMatchInlineSnapshot(`"renderer("c193fcb516", g1a9d5db22c, bd41d8cd98f, false)"`); + }); + + + }); + diff --git a/packages/runtime/renderer/index.js b/packages/runtime/renderer/index.js index b579bc3..9687fff 100644 --- a/packages/runtime/renderer/index.js +++ b/packages/runtime/renderer/index.js @@ -1 +1 @@ -export { makeRenderer, rendererById } from './renderer.js'; \ No newline at end of file +export { renderer } from './renderer.js'; \ No newline at end of file diff --git a/packages/runtime/renderer/renderer.js b/packages/runtime/renderer/renderer.js index 2fa9bcf..b9739bb 100644 --- a/packages/runtime/renderer/renderer.js +++ b/packages/runtime/renderer/renderer.js @@ -54,7 +54,7 @@ function inject(node, callback) { const templateRenderer = getBound => (...args) => { const [root, bind] = getBound(); - bind(...args); + if(bind) bind(...args); return root; }; @@ -68,8 +68,11 @@ export function renderer(id, targets, makeBind, isFragment, content) { // TODO: test injectable is right template id type - if(node) bind = bindings.get(node); - if(bind) return [node, bind]; + if(node) { + const hasBind = bindings.has(node); + bind = bindings.get(node); + if(hasBind) return [node, bind]; + } // Honestly not sure this really needed, // use case would be list component optimize by @@ -81,8 +84,8 @@ export function renderer(id, targets, makeBind, isFragment, content) { ([node, boundEls] = create()); } - const nodes = targets(node, boundEls); - bind = makeBind(nodes); + const nodes = targets ? targets(node, boundEls) : null; + bind = makeBind ? makeBind(nodes) : null; bindings.set(node, bind); return [node, bind]; diff --git a/packages/vite-plugin/index.js b/packages/vite-plugin/index.js index 8ddc96c..e3f4821 100644 --- a/packages/vite-plugin/index.js +++ b/packages/vite-plugin/index.js @@ -1,9 +1,20 @@ -import { compile, templateModule } from '@azothjs/compiler'; +import revHash from 'rev-hash'; +import { compile, templateModule, makeTargets, makeRenderer, makeBind } from '@azothjs/compiler'; import { createFilter } from '@rollup/pluginutils'; import path from 'node:path'; +export const targetModule = `virtual:azoth-target`; +export const bindModule = `virtual:azoth-bind`; +const resolvedTargetModule = '\0' + targetModule; +const resolvedBindModule = '\0' + bindModule; const resolvedTemplateModule = '\0' + templateModule; +const RESOLVED = { + [templateModule]: resolvedTemplateModule, + [targetModule]: resolvedTargetModule, + [bindModule]: resolvedBindModule, +}; + export default function azothPlugin(options) { options = options ?? {}; @@ -11,6 +22,9 @@ export default function azothPlugin(options) { const filter = createFilter(include, exclude); const programTemplates = new Map(); + const byTarget = new Map(); + const byBind = new Map(); + let command = ''; const transformJSX = { @@ -23,37 +37,26 @@ export default function azothPlugin(options) { resolveId(id) { const [name, ids] = id.split('?', 2); - if(name !== templateModule) return; - return ids ? `${resolvedTemplateModule}?${ids}` : resolvedTemplateModule; + const resolved = RESOLVED[name]; + if(!resolved) return; + return `${resolved}?${ids}`; }, load(id) { - const [name, ids] = id.split('?', 2); - if(name !== resolvedTemplateModule) return; - + const [name, params] = id.split('?', 2); const isBuild = command === 'build'; - const renderer = isBuild ? '__rendererById' : '__makeRenderer'; - const importRenderer = `import { ${renderer} } from 'azoth/runtime';\n`; - - const exports = new URLSearchParams(ids) - .getAll('id') - .map(id => { - const { html, isDomFragment } = programTemplates.get(id); - let exportRender = `\nexport const t${id} = ${renderer}('${id}'`; - - // html gets added to index.html in build, - // but dev mode html is string in virtual module - if(!isBuild) exportRender += `, \`${html}\``; - - // default is false, so only add if true (which is less common) - if(isDomFragment) exportRender += ', true'; - - exportRender += `);\n`; - return exportRender; - }) - .join(''); - - return importRenderer + exports; + const ids = new URLSearchParams(params).getAll('id'); + + switch(name) { + case resolvedTemplateModule: + return loadTemplateModule(ids, isBuild); + case resolvedTargetModule: + return loadTargetModule(ids); + case resolvedBindModule: + return loadBindModule(ids); + default: + return; + } }, async transform(source, id) { @@ -76,6 +79,50 @@ export default function azothPlugin(options) { } }; + function loadTargetModule([id]) { + return `export const g${id} = ${makeTargets(byTarget.get(id))};`; + } + + function loadBindModule([id]) { + return `import { __compose } from 'azoth/runtime';\nexport const b${id} = ${makeBind(byBind.get(id))};`; + } + + function loadTemplateModule(ids, isBuild) { + const moduleImports = [`import { __renderer } from 'azoth/runtime';\n`]; + + const templates = ids.map(id => programTemplates.get(id)); + + const targetGenerators = new Set(); + const bindGenerators = new Set(); + + const templateExports = templates.map(template => { + const { id, targetKey, bindKey } = template; + + // TODO: refactor cleanup on this apparent duplication + + if(targetKey) { + if(!byTarget.has(targetKey)) byTarget.set(targetKey, template); + if(!targetGenerators.has(targetKey)) { + moduleImports.push(`import { g${targetKey} } from '${targetModule}?id=${targetKey}';\n`); + targetGenerators.add(targetKey); + } + } + + if(bindKey) { + if(!byBind.has(bindKey)) byBind.set(bindKey, template); + if(!bindGenerators.has(bindKey)) { + moduleImports.push(`import { b${bindKey} } from '${bindModule}?id=${bindKey}';\n`); + bindGenerators.add(bindKey); + } + } + + return `export const t${id} = ${makeRenderer(template, { includeContent: !isBuild })};\n`; + + }); + + return moduleImports.join('') + templateExports.join(''); + } + const TEMPLATE_COMMENT = ''; const BODY_START = ''; const makeReplacement = (html, prefix) => `${prefix}\n ${html}\n `; @@ -100,3 +147,4 @@ export default function azothPlugin(options) { return [transformJSX, injectHTML]; } + diff --git a/packages/vite-plugin/package.json b/packages/vite-plugin/package.json index 41eba31..532363e 100644 --- a/packages/vite-plugin/package.json +++ b/packages/vite-plugin/package.json @@ -37,6 +37,7 @@ "dependencies": { "@azothjs/compiler": "workspace:*", "@rollup/pluginutils": "^5.1.0", + "rev-hash": "^4.1.0", "source-map": "^0.7.4" } } \ No newline at end of file diff --git a/packages/vite-plugin/pnpm-lock.yaml b/packages/vite-plugin/pnpm-lock.yaml index 3090e96..09d2dde 100644 --- a/packages/vite-plugin/pnpm-lock.yaml +++ b/packages/vite-plugin/pnpm-lock.yaml @@ -11,17 +11,20 @@ dependencies: '@rollup/pluginutils': specifier: ^5.1.0 version: 5.1.0 + rev-hash: + specifier: ^4.1.0 + version: 4.1.0 source-map: specifier: ^0.7.4 version: 0.7.4 vite: specifier: ^5.0.12 - version: 5.1.6 + version: 5.2.2 packages: - /@esbuild/aix-ppc64@0.19.12: - resolution: {integrity: sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==} + /@esbuild/aix-ppc64@0.20.2: + resolution: {integrity: sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==} engines: {node: '>=12'} cpu: [ppc64] os: [aix] @@ -29,8 +32,8 @@ packages: dev: false optional: true - /@esbuild/android-arm64@0.19.12: - resolution: {integrity: sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==} + /@esbuild/android-arm64@0.20.2: + resolution: {integrity: sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==} engines: {node: '>=12'} cpu: [arm64] os: [android] @@ -38,8 +41,8 @@ packages: dev: false optional: true - /@esbuild/android-arm@0.19.12: - resolution: {integrity: sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==} + /@esbuild/android-arm@0.20.2: + resolution: {integrity: sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==} engines: {node: '>=12'} cpu: [arm] os: [android] @@ -47,8 +50,8 @@ packages: dev: false optional: true - /@esbuild/android-x64@0.19.12: - resolution: {integrity: sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==} + /@esbuild/android-x64@0.20.2: + resolution: {integrity: sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==} engines: {node: '>=12'} cpu: [x64] os: [android] @@ -56,8 +59,8 @@ packages: dev: false optional: true - /@esbuild/darwin-arm64@0.19.12: - resolution: {integrity: sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==} + /@esbuild/darwin-arm64@0.20.2: + resolution: {integrity: sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==} engines: {node: '>=12'} cpu: [arm64] os: [darwin] @@ -65,8 +68,8 @@ packages: dev: false optional: true - /@esbuild/darwin-x64@0.19.12: - resolution: {integrity: sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==} + /@esbuild/darwin-x64@0.20.2: + resolution: {integrity: sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==} engines: {node: '>=12'} cpu: [x64] os: [darwin] @@ -74,8 +77,8 @@ packages: dev: false optional: true - /@esbuild/freebsd-arm64@0.19.12: - resolution: {integrity: sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==} + /@esbuild/freebsd-arm64@0.20.2: + resolution: {integrity: sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==} engines: {node: '>=12'} cpu: [arm64] os: [freebsd] @@ -83,8 +86,8 @@ packages: dev: false optional: true - /@esbuild/freebsd-x64@0.19.12: - resolution: {integrity: sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==} + /@esbuild/freebsd-x64@0.20.2: + resolution: {integrity: sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==} engines: {node: '>=12'} cpu: [x64] os: [freebsd] @@ -92,8 +95,8 @@ packages: dev: false optional: true - /@esbuild/linux-arm64@0.19.12: - resolution: {integrity: sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==} + /@esbuild/linux-arm64@0.20.2: + resolution: {integrity: sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==} engines: {node: '>=12'} cpu: [arm64] os: [linux] @@ -101,8 +104,8 @@ packages: dev: false optional: true - /@esbuild/linux-arm@0.19.12: - resolution: {integrity: sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==} + /@esbuild/linux-arm@0.20.2: + resolution: {integrity: sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==} engines: {node: '>=12'} cpu: [arm] os: [linux] @@ -110,8 +113,8 @@ packages: dev: false optional: true - /@esbuild/linux-ia32@0.19.12: - resolution: {integrity: sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==} + /@esbuild/linux-ia32@0.20.2: + resolution: {integrity: sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==} engines: {node: '>=12'} cpu: [ia32] os: [linux] @@ -119,8 +122,8 @@ packages: dev: false optional: true - /@esbuild/linux-loong64@0.19.12: - resolution: {integrity: sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==} + /@esbuild/linux-loong64@0.20.2: + resolution: {integrity: sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==} engines: {node: '>=12'} cpu: [loong64] os: [linux] @@ -128,8 +131,8 @@ packages: dev: false optional: true - /@esbuild/linux-mips64el@0.19.12: - resolution: {integrity: sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==} + /@esbuild/linux-mips64el@0.20.2: + resolution: {integrity: sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==} engines: {node: '>=12'} cpu: [mips64el] os: [linux] @@ -137,8 +140,8 @@ packages: dev: false optional: true - /@esbuild/linux-ppc64@0.19.12: - resolution: {integrity: sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==} + /@esbuild/linux-ppc64@0.20.2: + resolution: {integrity: sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==} engines: {node: '>=12'} cpu: [ppc64] os: [linux] @@ -146,8 +149,8 @@ packages: dev: false optional: true - /@esbuild/linux-riscv64@0.19.12: - resolution: {integrity: sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==} + /@esbuild/linux-riscv64@0.20.2: + resolution: {integrity: sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==} engines: {node: '>=12'} cpu: [riscv64] os: [linux] @@ -155,8 +158,8 @@ packages: dev: false optional: true - /@esbuild/linux-s390x@0.19.12: - resolution: {integrity: sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==} + /@esbuild/linux-s390x@0.20.2: + resolution: {integrity: sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==} engines: {node: '>=12'} cpu: [s390x] os: [linux] @@ -164,8 +167,8 @@ packages: dev: false optional: true - /@esbuild/linux-x64@0.19.12: - resolution: {integrity: sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==} + /@esbuild/linux-x64@0.20.2: + resolution: {integrity: sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==} engines: {node: '>=12'} cpu: [x64] os: [linux] @@ -173,8 +176,8 @@ packages: dev: false optional: true - /@esbuild/netbsd-x64@0.19.12: - resolution: {integrity: sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==} + /@esbuild/netbsd-x64@0.20.2: + resolution: {integrity: sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==} engines: {node: '>=12'} cpu: [x64] os: [netbsd] @@ -182,8 +185,8 @@ packages: dev: false optional: true - /@esbuild/openbsd-x64@0.19.12: - resolution: {integrity: sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==} + /@esbuild/openbsd-x64@0.20.2: + resolution: {integrity: sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==} engines: {node: '>=12'} cpu: [x64] os: [openbsd] @@ -191,8 +194,8 @@ packages: dev: false optional: true - /@esbuild/sunos-x64@0.19.12: - resolution: {integrity: sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==} + /@esbuild/sunos-x64@0.20.2: + resolution: {integrity: sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==} engines: {node: '>=12'} cpu: [x64] os: [sunos] @@ -200,8 +203,8 @@ packages: dev: false optional: true - /@esbuild/win32-arm64@0.19.12: - resolution: {integrity: sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==} + /@esbuild/win32-arm64@0.20.2: + resolution: {integrity: sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==} engines: {node: '>=12'} cpu: [arm64] os: [win32] @@ -209,8 +212,8 @@ packages: dev: false optional: true - /@esbuild/win32-ia32@0.19.12: - resolution: {integrity: sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==} + /@esbuild/win32-ia32@0.20.2: + resolution: {integrity: sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==} engines: {node: '>=12'} cpu: [ia32] os: [win32] @@ -218,8 +221,8 @@ packages: dev: false optional: true - /@esbuild/win32-x64@0.19.12: - resolution: {integrity: sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==} + /@esbuild/win32-x64@0.20.2: + resolution: {integrity: sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==} engines: {node: '>=12'} cpu: [x64] os: [win32] @@ -349,35 +352,35 @@ packages: resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} dev: false - /esbuild@0.19.12: - resolution: {integrity: sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==} + /esbuild@0.20.2: + resolution: {integrity: sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==} engines: {node: '>=12'} hasBin: true requiresBuild: true optionalDependencies: - '@esbuild/aix-ppc64': 0.19.12 - '@esbuild/android-arm': 0.19.12 - '@esbuild/android-arm64': 0.19.12 - '@esbuild/android-x64': 0.19.12 - '@esbuild/darwin-arm64': 0.19.12 - '@esbuild/darwin-x64': 0.19.12 - '@esbuild/freebsd-arm64': 0.19.12 - '@esbuild/freebsd-x64': 0.19.12 - '@esbuild/linux-arm': 0.19.12 - '@esbuild/linux-arm64': 0.19.12 - '@esbuild/linux-ia32': 0.19.12 - '@esbuild/linux-loong64': 0.19.12 - '@esbuild/linux-mips64el': 0.19.12 - '@esbuild/linux-ppc64': 0.19.12 - '@esbuild/linux-riscv64': 0.19.12 - '@esbuild/linux-s390x': 0.19.12 - '@esbuild/linux-x64': 0.19.12 - '@esbuild/netbsd-x64': 0.19.12 - '@esbuild/openbsd-x64': 0.19.12 - '@esbuild/sunos-x64': 0.19.12 - '@esbuild/win32-arm64': 0.19.12 - '@esbuild/win32-ia32': 0.19.12 - '@esbuild/win32-x64': 0.19.12 + '@esbuild/aix-ppc64': 0.20.2 + '@esbuild/android-arm': 0.20.2 + '@esbuild/android-arm64': 0.20.2 + '@esbuild/android-x64': 0.20.2 + '@esbuild/darwin-arm64': 0.20.2 + '@esbuild/darwin-x64': 0.20.2 + '@esbuild/freebsd-arm64': 0.20.2 + '@esbuild/freebsd-x64': 0.20.2 + '@esbuild/linux-arm': 0.20.2 + '@esbuild/linux-arm64': 0.20.2 + '@esbuild/linux-ia32': 0.20.2 + '@esbuild/linux-loong64': 0.20.2 + '@esbuild/linux-mips64el': 0.20.2 + '@esbuild/linux-ppc64': 0.20.2 + '@esbuild/linux-riscv64': 0.20.2 + '@esbuild/linux-s390x': 0.20.2 + '@esbuild/linux-x64': 0.20.2 + '@esbuild/netbsd-x64': 0.20.2 + '@esbuild/openbsd-x64': 0.20.2 + '@esbuild/sunos-x64': 0.20.2 + '@esbuild/win32-arm64': 0.20.2 + '@esbuild/win32-ia32': 0.20.2 + '@esbuild/win32-x64': 0.20.2 dev: false /estree-walker@2.0.2: @@ -407,13 +410,18 @@ packages: engines: {node: '>=8.6'} dev: false - /postcss@8.4.35: - resolution: {integrity: sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==} + /postcss@8.4.38: + resolution: {integrity: sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==} engines: {node: ^10 || ^12 || >=14} dependencies: nanoid: 3.3.7 picocolors: 1.0.0 - source-map-js: 1.0.2 + source-map-js: 1.2.0 + dev: false + + /rev-hash@4.1.0: + resolution: {integrity: sha512-e0EGnaveLY2IYpYwHNdh43WZ2M84KgW3Z/T4F6+Z/BlZI/T1ZbxTWj36xgYgUPOieGXYo2q225jTeUXn+LWYjw==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} dev: false /rollup@4.13.0: @@ -439,8 +447,8 @@ packages: fsevents: 2.3.3 dev: false - /source-map-js@1.0.2: - resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} + /source-map-js@1.2.0: + resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==} engines: {node: '>=0.10.0'} dev: false @@ -449,8 +457,8 @@ packages: engines: {node: '>= 8'} dev: false - /vite@5.1.6: - resolution: {integrity: sha512-yYIAZs9nVfRJ/AiOLCA91zzhjsHUgMjB+EigzFb6W2XTLO8JixBCKCjvhKZaye+NKYHCrkv3Oh50dH9EdLU2RA==} + /vite@5.2.2: + resolution: {integrity: sha512-FWZbz0oSdLq5snUI0b6sULbz58iXFXdvkZfZWR/F0ZJuKTSPO7v72QPXt6KqYeMFb0yytNp6kZosxJ96Nr/wDQ==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: @@ -477,8 +485,8 @@ packages: terser: optional: true dependencies: - esbuild: 0.19.12 - postcss: 8.4.35 + esbuild: 0.20.2 + postcss: 8.4.38 rollup: 4.13.0 optionalDependencies: fsevents: 2.3.3 diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5c56a61..f79d407 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -18,17 +18,17 @@ devDependencies: specifier: ^14.0.0 version: 14.0.0 happy-dom: - specifier: ^13.8.6 - version: 13.8.6 + specifier: ^13.10.1 + version: 13.10.1 vite: - specifier: ^5.1.6 - version: 5.1.6 + specifier: ^5.2.2 + version: 5.2.2 vite-plugin-inspect: specifier: ^0.8.3 - version: 0.8.3(vite@5.1.6) + version: 0.8.3(vite@5.2.2) vitest: specifier: ^1.4.0 - version: 1.4.0(happy-dom@13.8.6) + version: 1.4.0(happy-dom@13.10.1) packages: @@ -41,12 +41,12 @@ packages: resolution: {integrity: sha512-gFPqTG7otEJ8uP6wrhDv6mqwGWYZKNvAcCq6u9hOj0c+IKCEsY4L1oC9trPq2SaWIzAfHvqfBDxF591JkMf+kg==} dev: true - /@babel/code-frame@7.23.5: - resolution: {integrity: sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==} + /@babel/code-frame@7.24.2: + resolution: {integrity: sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==} engines: {node: '>=6.9.0'} dependencies: - '@babel/highlight': 7.23.4 - chalk: 2.4.2 + '@babel/highlight': 7.24.2 + picocolors: 1.0.0 dev: true /@babel/helper-validator-identifier@7.22.20: @@ -54,24 +54,25 @@ packages: engines: {node: '>=6.9.0'} dev: true - /@babel/highlight@7.23.4: - resolution: {integrity: sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==} + /@babel/highlight@7.24.2: + resolution: {integrity: sha512-Yac1ao4flkTxTteCDZLEvdxg2fZfz1v8M4QpaGypq/WPDqg3ijHYbDfs+LG5hvzSoqaSZ9/Z9lKSP3CjZjv+pA==} engines: {node: '>=6.9.0'} dependencies: '@babel/helper-validator-identifier': 7.22.20 chalk: 2.4.2 js-tokens: 4.0.0 + picocolors: 1.0.0 dev: true - /@babel/runtime@7.24.0: - resolution: {integrity: sha512-Chk32uHMg6TnQdvw2e9IlqPpFX/6NLuK0Ys2PqLb7/gL5uFn9mXvK715FGLlOLQrcO4qIkNHkvPGktzzXexsFw==} + /@babel/runtime@7.24.1: + resolution: {integrity: sha512-+BIznRzyqBf+2wCTxcKE3wDjfGeCoVE61KSHGpkzqrLi8qxqFwBeUFyId2cxkTmm55fzDGnm0+yCxaxygrLUnQ==} engines: {node: '>=6.9.0'} dependencies: regenerator-runtime: 0.14.1 dev: true - /@esbuild/aix-ppc64@0.19.12: - resolution: {integrity: sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==} + /@esbuild/aix-ppc64@0.20.2: + resolution: {integrity: sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==} engines: {node: '>=12'} cpu: [ppc64] os: [aix] @@ -79,8 +80,8 @@ packages: dev: true optional: true - /@esbuild/android-arm64@0.19.12: - resolution: {integrity: sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==} + /@esbuild/android-arm64@0.20.2: + resolution: {integrity: sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==} engines: {node: '>=12'} cpu: [arm64] os: [android] @@ -88,8 +89,8 @@ packages: dev: true optional: true - /@esbuild/android-arm@0.19.12: - resolution: {integrity: sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==} + /@esbuild/android-arm@0.20.2: + resolution: {integrity: sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==} engines: {node: '>=12'} cpu: [arm] os: [android] @@ -97,8 +98,8 @@ packages: dev: true optional: true - /@esbuild/android-x64@0.19.12: - resolution: {integrity: sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==} + /@esbuild/android-x64@0.20.2: + resolution: {integrity: sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==} engines: {node: '>=12'} cpu: [x64] os: [android] @@ -106,8 +107,8 @@ packages: dev: true optional: true - /@esbuild/darwin-arm64@0.19.12: - resolution: {integrity: sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==} + /@esbuild/darwin-arm64@0.20.2: + resolution: {integrity: sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==} engines: {node: '>=12'} cpu: [arm64] os: [darwin] @@ -115,8 +116,8 @@ packages: dev: true optional: true - /@esbuild/darwin-x64@0.19.12: - resolution: {integrity: sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==} + /@esbuild/darwin-x64@0.20.2: + resolution: {integrity: sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==} engines: {node: '>=12'} cpu: [x64] os: [darwin] @@ -124,8 +125,8 @@ packages: dev: true optional: true - /@esbuild/freebsd-arm64@0.19.12: - resolution: {integrity: sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==} + /@esbuild/freebsd-arm64@0.20.2: + resolution: {integrity: sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==} engines: {node: '>=12'} cpu: [arm64] os: [freebsd] @@ -133,8 +134,8 @@ packages: dev: true optional: true - /@esbuild/freebsd-x64@0.19.12: - resolution: {integrity: sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==} + /@esbuild/freebsd-x64@0.20.2: + resolution: {integrity: sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==} engines: {node: '>=12'} cpu: [x64] os: [freebsd] @@ -142,8 +143,8 @@ packages: dev: true optional: true - /@esbuild/linux-arm64@0.19.12: - resolution: {integrity: sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==} + /@esbuild/linux-arm64@0.20.2: + resolution: {integrity: sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==} engines: {node: '>=12'} cpu: [arm64] os: [linux] @@ -151,8 +152,8 @@ packages: dev: true optional: true - /@esbuild/linux-arm@0.19.12: - resolution: {integrity: sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==} + /@esbuild/linux-arm@0.20.2: + resolution: {integrity: sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==} engines: {node: '>=12'} cpu: [arm] os: [linux] @@ -160,8 +161,8 @@ packages: dev: true optional: true - /@esbuild/linux-ia32@0.19.12: - resolution: {integrity: sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==} + /@esbuild/linux-ia32@0.20.2: + resolution: {integrity: sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==} engines: {node: '>=12'} cpu: [ia32] os: [linux] @@ -169,8 +170,8 @@ packages: dev: true optional: true - /@esbuild/linux-loong64@0.19.12: - resolution: {integrity: sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==} + /@esbuild/linux-loong64@0.20.2: + resolution: {integrity: sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==} engines: {node: '>=12'} cpu: [loong64] os: [linux] @@ -178,8 +179,8 @@ packages: dev: true optional: true - /@esbuild/linux-mips64el@0.19.12: - resolution: {integrity: sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==} + /@esbuild/linux-mips64el@0.20.2: + resolution: {integrity: sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==} engines: {node: '>=12'} cpu: [mips64el] os: [linux] @@ -187,8 +188,8 @@ packages: dev: true optional: true - /@esbuild/linux-ppc64@0.19.12: - resolution: {integrity: sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==} + /@esbuild/linux-ppc64@0.20.2: + resolution: {integrity: sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==} engines: {node: '>=12'} cpu: [ppc64] os: [linux] @@ -196,8 +197,8 @@ packages: dev: true optional: true - /@esbuild/linux-riscv64@0.19.12: - resolution: {integrity: sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==} + /@esbuild/linux-riscv64@0.20.2: + resolution: {integrity: sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==} engines: {node: '>=12'} cpu: [riscv64] os: [linux] @@ -205,8 +206,8 @@ packages: dev: true optional: true - /@esbuild/linux-s390x@0.19.12: - resolution: {integrity: sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==} + /@esbuild/linux-s390x@0.20.2: + resolution: {integrity: sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==} engines: {node: '>=12'} cpu: [s390x] os: [linux] @@ -214,8 +215,8 @@ packages: dev: true optional: true - /@esbuild/linux-x64@0.19.12: - resolution: {integrity: sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==} + /@esbuild/linux-x64@0.20.2: + resolution: {integrity: sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==} engines: {node: '>=12'} cpu: [x64] os: [linux] @@ -223,8 +224,8 @@ packages: dev: true optional: true - /@esbuild/netbsd-x64@0.19.12: - resolution: {integrity: sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==} + /@esbuild/netbsd-x64@0.20.2: + resolution: {integrity: sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==} engines: {node: '>=12'} cpu: [x64] os: [netbsd] @@ -232,8 +233,8 @@ packages: dev: true optional: true - /@esbuild/openbsd-x64@0.19.12: - resolution: {integrity: sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==} + /@esbuild/openbsd-x64@0.20.2: + resolution: {integrity: sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==} engines: {node: '>=12'} cpu: [x64] os: [openbsd] @@ -241,8 +242,8 @@ packages: dev: true optional: true - /@esbuild/sunos-x64@0.19.12: - resolution: {integrity: sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==} + /@esbuild/sunos-x64@0.20.2: + resolution: {integrity: sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==} engines: {node: '>=12'} cpu: [x64] os: [sunos] @@ -250,8 +251,8 @@ packages: dev: true optional: true - /@esbuild/win32-arm64@0.19.12: - resolution: {integrity: sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==} + /@esbuild/win32-arm64@0.20.2: + resolution: {integrity: sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==} engines: {node: '>=12'} cpu: [arm64] os: [win32] @@ -259,8 +260,8 @@ packages: dev: true optional: true - /@esbuild/win32-ia32@0.19.12: - resolution: {integrity: sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==} + /@esbuild/win32-ia32@0.20.2: + resolution: {integrity: sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==} engines: {node: '>=12'} cpu: [ia32] os: [win32] @@ -268,8 +269,8 @@ packages: dev: true optional: true - /@esbuild/win32-x64@0.19.12: - resolution: {integrity: sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==} + /@esbuild/win32-x64@0.20.2: + resolution: {integrity: sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==} engines: {node: '>=12'} cpu: [x64] os: [win32] @@ -496,8 +497,8 @@ packages: resolution: {integrity: sha512-FlS4ZWlp97iiNWig0Muq8p+3rVDjRiYE+YKGbAqXOu9nwJFFOdL00kFpz42M+4huzYi86vAK1sOOfyOG45muIQ==} engines: {node: '>=14'} dependencies: - '@babel/code-frame': 7.23.5 - '@babel/runtime': 7.24.0 + '@babel/code-frame': 7.24.2 + '@babel/runtime': 7.24.1 '@types/aria-query': 5.0.4 aria-query: 5.1.3 chalk: 4.1.2 @@ -881,35 +882,35 @@ packages: stop-iteration-iterator: 1.0.0 dev: true - /esbuild@0.19.12: - resolution: {integrity: sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==} + /esbuild@0.20.2: + resolution: {integrity: sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==} engines: {node: '>=12'} hasBin: true requiresBuild: true optionalDependencies: - '@esbuild/aix-ppc64': 0.19.12 - '@esbuild/android-arm': 0.19.12 - '@esbuild/android-arm64': 0.19.12 - '@esbuild/android-x64': 0.19.12 - '@esbuild/darwin-arm64': 0.19.12 - '@esbuild/darwin-x64': 0.19.12 - '@esbuild/freebsd-arm64': 0.19.12 - '@esbuild/freebsd-x64': 0.19.12 - '@esbuild/linux-arm': 0.19.12 - '@esbuild/linux-arm64': 0.19.12 - '@esbuild/linux-ia32': 0.19.12 - '@esbuild/linux-loong64': 0.19.12 - '@esbuild/linux-mips64el': 0.19.12 - '@esbuild/linux-ppc64': 0.19.12 - '@esbuild/linux-riscv64': 0.19.12 - '@esbuild/linux-s390x': 0.19.12 - '@esbuild/linux-x64': 0.19.12 - '@esbuild/netbsd-x64': 0.19.12 - '@esbuild/openbsd-x64': 0.19.12 - '@esbuild/sunos-x64': 0.19.12 - '@esbuild/win32-arm64': 0.19.12 - '@esbuild/win32-ia32': 0.19.12 - '@esbuild/win32-x64': 0.19.12 + '@esbuild/aix-ppc64': 0.20.2 + '@esbuild/android-arm': 0.20.2 + '@esbuild/android-arm64': 0.20.2 + '@esbuild/android-x64': 0.20.2 + '@esbuild/darwin-arm64': 0.20.2 + '@esbuild/darwin-x64': 0.20.2 + '@esbuild/freebsd-arm64': 0.20.2 + '@esbuild/freebsd-x64': 0.20.2 + '@esbuild/linux-arm': 0.20.2 + '@esbuild/linux-arm64': 0.20.2 + '@esbuild/linux-ia32': 0.20.2 + '@esbuild/linux-loong64': 0.20.2 + '@esbuild/linux-mips64el': 0.20.2 + '@esbuild/linux-ppc64': 0.20.2 + '@esbuild/linux-riscv64': 0.20.2 + '@esbuild/linux-s390x': 0.20.2 + '@esbuild/linux-x64': 0.20.2 + '@esbuild/netbsd-x64': 0.20.2 + '@esbuild/openbsd-x64': 0.20.2 + '@esbuild/sunos-x64': 0.20.2 + '@esbuild/win32-arm64': 0.20.2 + '@esbuild/win32-ia32': 0.20.2 + '@esbuild/win32-x64': 0.20.2 dev: true /escape-string-regexp@1.0.5: @@ -1185,8 +1186,8 @@ packages: resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} dev: true - /happy-dom@13.8.6: - resolution: {integrity: sha512-Urcv2jvNel19QirWimOwYTW3slpEYGS8PLtzEwAlpTWpnKycXF8s0I7xUBK9fPvWAIc8uZf/CnV2cIwWE8jptw==} + /happy-dom@13.10.1: + resolution: {integrity: sha512-9GZLEFvQL5EgfJX2zcBgu1nsPUn98JF/EiJnSfQbdxI6YEQGqpd09lXXxOmYonRBIEFz9JlGCOiPflDzgS1p8w==} engines: {node: '>=16.0.0'} dependencies: entities: 4.5.0 @@ -1544,7 +1545,7 @@ packages: acorn: 8.11.3 pathe: 1.1.2 pkg-types: 1.0.3 - ufo: 1.5.1 + ufo: 1.5.3 dev: true /mrmime@2.0.0: @@ -1717,13 +1718,13 @@ packages: engines: {node: '>= 0.4'} dev: true - /postcss@8.4.35: - resolution: {integrity: sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==} + /postcss@8.4.38: + resolution: {integrity: sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==} engines: {node: ^10 || ^12 || >=14} dependencies: nanoid: 3.3.7 picocolors: 1.0.0 - source-map-js: 1.0.2 + source-map-js: 1.2.0 dev: true /prelude-ls@1.2.1: @@ -1893,8 +1894,8 @@ packages: totalist: 3.0.1 dev: true - /source-map-js@1.0.2: - resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} + /source-map-js@1.2.0: + resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==} engines: {node: '>=0.10.0'} dev: true @@ -1990,8 +1991,8 @@ packages: engines: {node: '>=10'} dev: true - /ufo@1.5.1: - resolution: {integrity: sha512-HGyF79+/qZ4soRvM+nHERR2pJ3VXDZ/8sL1uLahdgEDf580NkgiWOxLk33FetExqOWp352JZRsgXbG/4MaGOSg==} + /ufo@1.5.3: + resolution: {integrity: sha512-Y7HYmWaFwPUmkoQCUIAYpKqkOf+SbVj/2fJJZ4RJMCfZp0rTGwRbzQD+HghfnhKOjL9E01okqz+ncJskGYfBNw==} dev: true /universalify@2.0.1: @@ -2014,7 +2015,7 @@ packages: debug: 4.3.4 pathe: 1.1.2 picocolors: 1.0.0 - vite: 5.1.6 + vite: 5.2.2 transitivePeerDependencies: - '@types/node' - less @@ -2026,7 +2027,7 @@ packages: - terser dev: true - /vite-plugin-inspect@0.8.3(vite@5.1.6): + /vite-plugin-inspect@0.8.3(vite@5.2.2): resolution: {integrity: sha512-SBVzOIdP/kwe6hjkt7LSW4D0+REqqe58AumcnCfRNw4Kt3mbS9pEBkch+nupu2PBxv2tQi69EQHQ1ZA1vgB/Og==} engines: {node: '>=14'} peerDependencies: @@ -2045,14 +2046,14 @@ packages: perfect-debounce: 1.0.0 picocolors: 1.0.0 sirv: 2.0.4 - vite: 5.1.6 + vite: 5.2.2 transitivePeerDependencies: - rollup - supports-color dev: true - /vite@5.1.6: - resolution: {integrity: sha512-yYIAZs9nVfRJ/AiOLCA91zzhjsHUgMjB+EigzFb6W2XTLO8JixBCKCjvhKZaye+NKYHCrkv3Oh50dH9EdLU2RA==} + /vite@5.2.2: + resolution: {integrity: sha512-FWZbz0oSdLq5snUI0b6sULbz58iXFXdvkZfZWR/F0ZJuKTSPO7v72QPXt6KqYeMFb0yytNp6kZosxJ96Nr/wDQ==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: @@ -2079,14 +2080,14 @@ packages: terser: optional: true dependencies: - esbuild: 0.19.12 - postcss: 8.4.35 + esbuild: 0.20.2 + postcss: 8.4.38 rollup: 4.13.0 optionalDependencies: fsevents: 2.3.3 dev: true - /vitest@1.4.0(happy-dom@13.8.6): + /vitest@1.4.0(happy-dom@13.10.1): resolution: {integrity: sha512-gujzn0g7fmwf83/WzrDTnncZt2UiXP41mHuFYFrdwaLRVQ6JYQEiME2IfEjU3vcFL3VKa75XhI3lFgn+hfVsQw==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true @@ -2120,7 +2121,7 @@ packages: chai: 4.4.1 debug: 4.3.4 execa: 8.0.1 - happy-dom: 13.8.6 + happy-dom: 13.10.1 local-pkg: 0.5.0 magic-string: 0.30.8 pathe: 1.1.2 @@ -2129,7 +2130,7 @@ packages: strip-literal: 2.0.0 tinybench: 2.6.0 tinypool: 0.8.2 - vite: 5.1.6 + vite: 5.2.2 vite-node: 1.4.0 why-is-node-running: 2.2.2 transitivePeerDependencies: diff --git a/sandbox/package.json b/sandbox/package.json index 1bf6e19..23e1f3e 100644 --- a/sandbox/package.json +++ b/sandbox/package.json @@ -16,6 +16,6 @@ "jsonic": "workspace:*" }, "devDependencies": { - "vite": "^5.1.6" + "vite": "^5.2.2" } } \ No newline at end of file diff --git a/sandbox/pnpm-lock.yaml b/sandbox/pnpm-lock.yaml index 5846e85..1d5cb17 100644 --- a/sandbox/pnpm-lock.yaml +++ b/sandbox/pnpm-lock.yaml @@ -14,13 +14,13 @@ dependencies: devDependencies: vite: - specifier: ^5.1.6 - version: 5.1.6 + specifier: ^5.2.2 + version: 5.2.2 packages: - /@esbuild/aix-ppc64@0.19.12: - resolution: {integrity: sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==} + /@esbuild/aix-ppc64@0.20.2: + resolution: {integrity: sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==} engines: {node: '>=12'} cpu: [ppc64] os: [aix] @@ -28,8 +28,8 @@ packages: dev: true optional: true - /@esbuild/android-arm64@0.19.12: - resolution: {integrity: sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==} + /@esbuild/android-arm64@0.20.2: + resolution: {integrity: sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==} engines: {node: '>=12'} cpu: [arm64] os: [android] @@ -37,8 +37,8 @@ packages: dev: true optional: true - /@esbuild/android-arm@0.19.12: - resolution: {integrity: sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==} + /@esbuild/android-arm@0.20.2: + resolution: {integrity: sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==} engines: {node: '>=12'} cpu: [arm] os: [android] @@ -46,8 +46,8 @@ packages: dev: true optional: true - /@esbuild/android-x64@0.19.12: - resolution: {integrity: sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==} + /@esbuild/android-x64@0.20.2: + resolution: {integrity: sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==} engines: {node: '>=12'} cpu: [x64] os: [android] @@ -55,8 +55,8 @@ packages: dev: true optional: true - /@esbuild/darwin-arm64@0.19.12: - resolution: {integrity: sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==} + /@esbuild/darwin-arm64@0.20.2: + resolution: {integrity: sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==} engines: {node: '>=12'} cpu: [arm64] os: [darwin] @@ -64,8 +64,8 @@ packages: dev: true optional: true - /@esbuild/darwin-x64@0.19.12: - resolution: {integrity: sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==} + /@esbuild/darwin-x64@0.20.2: + resolution: {integrity: sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==} engines: {node: '>=12'} cpu: [x64] os: [darwin] @@ -73,8 +73,8 @@ packages: dev: true optional: true - /@esbuild/freebsd-arm64@0.19.12: - resolution: {integrity: sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==} + /@esbuild/freebsd-arm64@0.20.2: + resolution: {integrity: sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==} engines: {node: '>=12'} cpu: [arm64] os: [freebsd] @@ -82,8 +82,8 @@ packages: dev: true optional: true - /@esbuild/freebsd-x64@0.19.12: - resolution: {integrity: sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==} + /@esbuild/freebsd-x64@0.20.2: + resolution: {integrity: sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==} engines: {node: '>=12'} cpu: [x64] os: [freebsd] @@ -91,8 +91,8 @@ packages: dev: true optional: true - /@esbuild/linux-arm64@0.19.12: - resolution: {integrity: sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==} + /@esbuild/linux-arm64@0.20.2: + resolution: {integrity: sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==} engines: {node: '>=12'} cpu: [arm64] os: [linux] @@ -100,8 +100,8 @@ packages: dev: true optional: true - /@esbuild/linux-arm@0.19.12: - resolution: {integrity: sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==} + /@esbuild/linux-arm@0.20.2: + resolution: {integrity: sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==} engines: {node: '>=12'} cpu: [arm] os: [linux] @@ -109,8 +109,8 @@ packages: dev: true optional: true - /@esbuild/linux-ia32@0.19.12: - resolution: {integrity: sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==} + /@esbuild/linux-ia32@0.20.2: + resolution: {integrity: sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==} engines: {node: '>=12'} cpu: [ia32] os: [linux] @@ -118,8 +118,8 @@ packages: dev: true optional: true - /@esbuild/linux-loong64@0.19.12: - resolution: {integrity: sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==} + /@esbuild/linux-loong64@0.20.2: + resolution: {integrity: sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==} engines: {node: '>=12'} cpu: [loong64] os: [linux] @@ -127,8 +127,8 @@ packages: dev: true optional: true - /@esbuild/linux-mips64el@0.19.12: - resolution: {integrity: sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==} + /@esbuild/linux-mips64el@0.20.2: + resolution: {integrity: sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==} engines: {node: '>=12'} cpu: [mips64el] os: [linux] @@ -136,8 +136,8 @@ packages: dev: true optional: true - /@esbuild/linux-ppc64@0.19.12: - resolution: {integrity: sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==} + /@esbuild/linux-ppc64@0.20.2: + resolution: {integrity: sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==} engines: {node: '>=12'} cpu: [ppc64] os: [linux] @@ -145,8 +145,8 @@ packages: dev: true optional: true - /@esbuild/linux-riscv64@0.19.12: - resolution: {integrity: sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==} + /@esbuild/linux-riscv64@0.20.2: + resolution: {integrity: sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==} engines: {node: '>=12'} cpu: [riscv64] os: [linux] @@ -154,8 +154,8 @@ packages: dev: true optional: true - /@esbuild/linux-s390x@0.19.12: - resolution: {integrity: sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==} + /@esbuild/linux-s390x@0.20.2: + resolution: {integrity: sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==} engines: {node: '>=12'} cpu: [s390x] os: [linux] @@ -163,8 +163,8 @@ packages: dev: true optional: true - /@esbuild/linux-x64@0.19.12: - resolution: {integrity: sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==} + /@esbuild/linux-x64@0.20.2: + resolution: {integrity: sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==} engines: {node: '>=12'} cpu: [x64] os: [linux] @@ -172,8 +172,8 @@ packages: dev: true optional: true - /@esbuild/netbsd-x64@0.19.12: - resolution: {integrity: sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==} + /@esbuild/netbsd-x64@0.20.2: + resolution: {integrity: sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==} engines: {node: '>=12'} cpu: [x64] os: [netbsd] @@ -181,8 +181,8 @@ packages: dev: true optional: true - /@esbuild/openbsd-x64@0.19.12: - resolution: {integrity: sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==} + /@esbuild/openbsd-x64@0.20.2: + resolution: {integrity: sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==} engines: {node: '>=12'} cpu: [x64] os: [openbsd] @@ -190,8 +190,8 @@ packages: dev: true optional: true - /@esbuild/sunos-x64@0.19.12: - resolution: {integrity: sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==} + /@esbuild/sunos-x64@0.20.2: + resolution: {integrity: sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==} engines: {node: '>=12'} cpu: [x64] os: [sunos] @@ -199,8 +199,8 @@ packages: dev: true optional: true - /@esbuild/win32-arm64@0.19.12: - resolution: {integrity: sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==} + /@esbuild/win32-arm64@0.20.2: + resolution: {integrity: sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==} engines: {node: '>=12'} cpu: [arm64] os: [win32] @@ -208,8 +208,8 @@ packages: dev: true optional: true - /@esbuild/win32-ia32@0.19.12: - resolution: {integrity: sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==} + /@esbuild/win32-ia32@0.20.2: + resolution: {integrity: sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==} engines: {node: '>=12'} cpu: [ia32] os: [win32] @@ -217,8 +217,8 @@ packages: dev: true optional: true - /@esbuild/win32-x64@0.19.12: - resolution: {integrity: sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==} + /@esbuild/win32-x64@0.20.2: + resolution: {integrity: sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==} engines: {node: '>=12'} cpu: [x64] os: [win32] @@ -334,35 +334,35 @@ packages: resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} dev: true - /esbuild@0.19.12: - resolution: {integrity: sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==} + /esbuild@0.20.2: + resolution: {integrity: sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==} engines: {node: '>=12'} hasBin: true requiresBuild: true optionalDependencies: - '@esbuild/aix-ppc64': 0.19.12 - '@esbuild/android-arm': 0.19.12 - '@esbuild/android-arm64': 0.19.12 - '@esbuild/android-x64': 0.19.12 - '@esbuild/darwin-arm64': 0.19.12 - '@esbuild/darwin-x64': 0.19.12 - '@esbuild/freebsd-arm64': 0.19.12 - '@esbuild/freebsd-x64': 0.19.12 - '@esbuild/linux-arm': 0.19.12 - '@esbuild/linux-arm64': 0.19.12 - '@esbuild/linux-ia32': 0.19.12 - '@esbuild/linux-loong64': 0.19.12 - '@esbuild/linux-mips64el': 0.19.12 - '@esbuild/linux-ppc64': 0.19.12 - '@esbuild/linux-riscv64': 0.19.12 - '@esbuild/linux-s390x': 0.19.12 - '@esbuild/linux-x64': 0.19.12 - '@esbuild/netbsd-x64': 0.19.12 - '@esbuild/openbsd-x64': 0.19.12 - '@esbuild/sunos-x64': 0.19.12 - '@esbuild/win32-arm64': 0.19.12 - '@esbuild/win32-ia32': 0.19.12 - '@esbuild/win32-x64': 0.19.12 + '@esbuild/aix-ppc64': 0.20.2 + '@esbuild/android-arm': 0.20.2 + '@esbuild/android-arm64': 0.20.2 + '@esbuild/android-x64': 0.20.2 + '@esbuild/darwin-arm64': 0.20.2 + '@esbuild/darwin-x64': 0.20.2 + '@esbuild/freebsd-arm64': 0.20.2 + '@esbuild/freebsd-x64': 0.20.2 + '@esbuild/linux-arm': 0.20.2 + '@esbuild/linux-arm64': 0.20.2 + '@esbuild/linux-ia32': 0.20.2 + '@esbuild/linux-loong64': 0.20.2 + '@esbuild/linux-mips64el': 0.20.2 + '@esbuild/linux-ppc64': 0.20.2 + '@esbuild/linux-riscv64': 0.20.2 + '@esbuild/linux-s390x': 0.20.2 + '@esbuild/linux-x64': 0.20.2 + '@esbuild/netbsd-x64': 0.20.2 + '@esbuild/openbsd-x64': 0.20.2 + '@esbuild/sunos-x64': 0.20.2 + '@esbuild/win32-arm64': 0.20.2 + '@esbuild/win32-ia32': 0.20.2 + '@esbuild/win32-x64': 0.20.2 dev: true /fsevents@2.3.3: @@ -383,13 +383,13 @@ packages: resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} dev: true - /postcss@8.4.35: - resolution: {integrity: sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==} + /postcss@8.4.38: + resolution: {integrity: sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==} engines: {node: ^10 || ^12 || >=14} dependencies: nanoid: 3.3.7 picocolors: 1.0.0 - source-map-js: 1.0.2 + source-map-js: 1.2.0 dev: true /rollup@4.13.0: @@ -415,13 +415,13 @@ packages: fsevents: 2.3.3 dev: true - /source-map-js@1.0.2: - resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} + /source-map-js@1.2.0: + resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==} engines: {node: '>=0.10.0'} dev: true - /vite@5.1.6: - resolution: {integrity: sha512-yYIAZs9nVfRJ/AiOLCA91zzhjsHUgMjB+EigzFb6W2XTLO8JixBCKCjvhKZaye+NKYHCrkv3Oh50dH9EdLU2RA==} + /vite@5.2.2: + resolution: {integrity: sha512-FWZbz0oSdLq5snUI0b6sULbz58iXFXdvkZfZWR/F0ZJuKTSPO7v72QPXt6KqYeMFb0yytNp6kZosxJ96Nr/wDQ==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: @@ -448,8 +448,8 @@ packages: terser: optional: true dependencies: - esbuild: 0.19.12 - postcss: 8.4.35 + esbuild: 0.20.2 + postcss: 8.4.38 rollup: 4.13.0 optionalDependencies: fsevents: 2.3.3 diff --git a/vite-test/expected-out/index-BDbUw1te.js b/vite-test/expected-out/index-BDbUw1te.js deleted file mode 100644 index b61aa69..0000000 --- a/vite-test/expected-out/index-BDbUw1te.js +++ /dev/null @@ -1,250 +0,0 @@ -/* compose, composeElement, create, createElement */ -const IGNORE = Symbol.for('azoth.compose.IGNORE'); - -function compose(anchor, input, keepLast, props, slottable) { - if(keepLast !== true) keepLast = false; - const type = typeof input; - - switch(true) { - case input === IGNORE: - break; - case input === undefined: - case input === null: - case input === true: - case input === false: - case input === '': - if(!keepLast) clear(anchor); - break; - case type === 'number': - case type === 'bigint': - input = `${input}`; - // eslint-disable-next-line no-fallthrough - case type === 'string': - replace(anchor, input, keepLast); - break; - case input instanceof Node: - if(props) Object.assign(input, props); - if(slottable) input.slottable = slottable; - replace(anchor, input, keepLast); - break; - case type === 'function': { - // will throw if function is class, - // unlike create or compose element - let out = slottable - ? input(props, slottable) - : props ? input(props) : input(); - compose(anchor, out, keepLast); - break; - } - case type !== 'object': { - // ES2023: Symbol should be only type - throwTypeError(input, type); - break; - } - case input instanceof Promise: - input.then(value => compose(anchor, value, keepLast, props, slottable)); - break; - case Array.isArray(input): - composeArray(anchor, input, keepLast); - break; - // w/o the !! this causes intermittent failures :p maybe vitest/node thing? - case !!input[Symbol.asyncIterator]: - composeAsyncIterator(anchor, input, keepLast, props, slottable); - break; - case input instanceof ReadableStream: - // no props and slottable propagation on streams - composeStream(anchor, input, true); - break; - case isRenderObject(input): { - let out = slottable - ? input.render(props, slottable) - : props ? input.render(props) : input.render(); - compose(anchor, out, keepLast); - break; - } - // TODO: - case !!input.subscribe: - case !!input.on: - default: { - throwTypeErrorForObject(input); - } - } -} - -const isRenderObject = obj => obj && typeof obj === 'object' && obj.render && typeof obj.render === 'function'; - - -/* replace and clear */ - -function replace(anchor, input, keepLast) { - if(!keepLast) clear(anchor); - anchor.before(input); - anchor.data = ++anchor.data; -} - -function clear(anchor) { - let node = anchor; - let count = +anchor.data; - - while(count--) { - const { previousSibling } = node; - if(!previousSibling) break; - - if(previousSibling.nodeType === Node.COMMENT_NODE) { - // TODO: how to guard for azoth comments only? - clear(previousSibling); - } - - clear(previousSibling); - previousSibling.remove(); - } - - anchor.data = 0; -} - - -/* complex types */ - -function composeArray(anchor, array, keepLast) { - if(!keepLast) clear(anchor); - // TODO: optimize arrays here if Node[] - for(let i = 0; i < array.length; i++) { - compose(anchor, array[i], true); - } -} - -async function composeStream(anchor, stream, keepLast) { - stream.pipeTo(new WritableStream({ - write(chunk) { - compose(anchor, chunk, keepLast); - } - })); -} - -async function composeAsyncIterator(anchor, iterator, keepLast, props, slottable) { - // TODO: use iterator and intercept system messages - for await(const value of iterator) { - compose(anchor, value, keepLast, props, slottable); - } -} - -/* thrown errors */ - -function throwTypeError(input, type, footer = '') { - // Passing Symbol to `{...}` throws! - if(type === 'symbol') input = 'Symbol'; - throw new TypeError(`\ -Invalid compose {...} input type "${type}", value ${input}.\ -${footer}` - ); -} - -function throwTypeErrorForObject(obj) { - let message = ''; - try { - const json = JSON.stringify(obj, null, 2); - message = `\n\nReceived as:\n\n${json}\n\n`; - } - catch(ex) { - /* no-op */ - } - throwTypeError(obj, 'object', message); -} - -const templates = new Map(); - -function rendererById(id, isFragment = false) { - if(templates.has(id)) return templates.get(id); - - const templateEl = document.getElementById(id); - if(!templateEl) { - throw new Error(`No template with id "${id}"`); - } - - return rendererFactory(id, templateEl.content, isFragment); -} - -function rendererFactory(id, node, isFragment) { - const render = renderer(node, isFragment); - templates.set(id, render); - return render; -} - -function renderer(fragment, isFragment) { - if(!isFragment) fragment = fragment.firstElementChild; - // TODO: malformed fragments...necessary? - - return function render() { - const clone = fragment.cloneNode(true); - const targets = clone.querySelectorAll('[data-bind]'); - return [clone, targets]; - }; -} - -const t14720b3874 = rendererById('14720b3874', true); - -const ta51edaabfe = rendererById('a51edaabfe'); - -const t880311674b = rendererById('880311674b'); - -const tdfc9870d38 = rendererById('dfc9870d38'); - -const EMOJIS = 'EMOJIS'; -async function fetchEmojis() { - const json = localStorage.getItem(EMOJIS); - if(json) { - try { - return JSON.parse(json); - } - catch(ex) { - // failed parse - localStorage.removeItem(EMOJIS); - } - } - // await sleep(3000); - const res = await fetch('https://emojihub.yurace.pro/api/all'); - const emojis = await res.json(); - - localStorage.setItem(EMOJIS, JSON.stringify(emojis, true, 4)); - - return emojis; -} - -const List = fetchEmojis().then(emojis => EmojiList({ - emojis -})); -const App = (() => { - const [__root, __targets] = t14720b3874(true); - const __target0 =__targets[0]; - const __child0 = __target0.childNodes[3]; - compose(__child0, List); - return __root; -})(); -document.body.append(App); -function EmojiList({emojis}) { - const __root = ta51edaabfe()[0]; - const __child0 = __root.childNodes[1]; - compose(__child0, emojis.map(Emoji)); - return __root; -} -function Emoji({name, unicode, htmlCode}) { - const __root = t880311674b()[0]; - const __child0 = __root.childNodes[1]; - const __child1 = __root.childNodes[3]; - const __child2 = __root.childNodes[5]; - compose(__child0, InnerHtml({ - html: htmlCode.join('') - })); - compose(__child1, name); - compose(__child2, unicode); - return __root; -} -function InnerHtml({html, className = ''}) { - const rawEmoji = (() => { - const __root = tdfc9870d38()[0]; - __root.className = (className ?? ''); - return __root; - })(); - rawEmoji.firstChild.innerHTML = html; - return rawEmoji; -} diff --git a/vite-test/expected-out/index-CHvwx500.js b/vite-test/expected-out/index-CHvwx500.js new file mode 100644 index 0000000..bb22775 --- /dev/null +++ b/vite-test/expected-out/index-CHvwx500.js @@ -0,0 +1,374 @@ +/* compose, composeElement, create, createElement */ +const IGNORE = Symbol.for('azoth.compose.IGNORE'); + +function compose(anchor, input, keepLast, props, slottable) { + if(keepLast !== true) keepLast = false; + const type = typeof input; + + switch(true) { + case input === IGNORE: + break; + case input === undefined: + case input === null: + case input === true: + case input === false: + case input === '': + if(!keepLast) clear(anchor); + break; + case type === 'number': + case type === 'bigint': + input = `${input}`; + // eslint-disable-next-line no-fallthrough + case type === 'string': + replace(anchor, input, keepLast); + break; + case input instanceof Node: + if(props) Object.assign(input, props); + if(slottable) input.slottable = slottable; + replace(anchor, input, keepLast); + break; + case type === 'function': { + // will throw if function is class, + // unlike create or compose element + let out = slottable + ? input(props, slottable) + : props ? input(props) : input(); + compose(anchor, out, keepLast); + break; + } + case type !== 'object': { + // ES2023: Symbol should be only type + throwTypeError(input, type); + break; + } + case input instanceof Promise: + input.then(value => compose(anchor, value, keepLast, props, slottable)); + break; + case Array.isArray(input): + composeArray(anchor, input, keepLast); + break; + // w/o the !! this causes intermittent failures :p maybe vitest/node thing? + case !!input[Symbol.asyncIterator]: + composeAsyncIterator(anchor, input, keepLast, props, slottable); + break; + case input instanceof ReadableStream: + // no props and slottable propagation on streams + composeStream(anchor, input, true); + break; + case isRenderObject(input): { + let out = slottable + ? input.render(props, slottable) + : props ? input.render(props) : input.render(); + compose(anchor, out, keepLast); + break; + } + // TODO: + case !!input.subscribe: + case !!input.on: + default: { + throwTypeErrorForObject(input); + } + } +} + +const isRenderObject = obj => obj && typeof obj === 'object' && obj.render && typeof obj.render === 'function'; + +function createElement(Constructor, props, slottable, topLevel = false) { + const result = create(Constructor, props, slottable); + if(!topLevel) return result; + + // result is returned to caller, not composed by Azoth, + // force to be of type Node or null: + // strings and numbers into text nodes + // non-values to null + const type = typeof result; + switch(true) { + case type === 'string': + case type === 'number': + return document.createTextNode(result); + case result === undefined: + case result === null: + case result === true: + case result === false: + case result === IGNORE: + return null; + default: + return result; + } + + +} + +function create(input, props, slottable, anchor) { + const type = typeof input; + switch(true) { + case input instanceof Node: + if(props) Object.assign(input, props); + // eslint-disable-next-line no-fallthrough + case type === 'string': + case type === 'number': + case input === undefined: + case input === null: + case input === true: + case input === false: + case input === '': + case input === IGNORE: + return anchor ? void compose(anchor, input) : input; + case !!(input.prototype?.constructor): { + // eslint-disable-next-line new-cap + return create(new input(props, slottable), null, null, anchor); + } + case type === 'function': + return create(input(props, slottable), null, null, anchor); + case type !== 'object': { + throwTypeError(input, type); + break; + } + case isRenderObject(input): + return create(input.render(props, slottable), null, null, anchor); + default: { + // these inputs require a comment anchor to which they can render + if(!anchor) anchor = document.createComment('0'); + + if(input[Symbol.asyncIterator]) { + composeAsyncIterator(anchor, input, false, props, slottable); + } + else if(input instanceof Promise) { + input.then(value => { + create(value, props, slottable, anchor); + }); + } + else if(Array.isArray(input)) { + composeArray(anchor, input, false); + } + else { + throwTypeErrorForObject(input); + } + + return anchor; + } + } +} + + +/* replace and clear */ + +function replace(anchor, input, keepLast) { + if(!keepLast) clear(anchor); + anchor.before(input); + anchor.data = ++anchor.data; +} + +function clear(anchor) { + let node = anchor; + let count = +anchor.data; + + while(count--) { + const { previousSibling } = node; + if(!previousSibling) break; + + if(previousSibling.nodeType === Node.COMMENT_NODE) { + // TODO: how to guard for azoth comments only? + clear(previousSibling); + } + + clear(previousSibling); + previousSibling.remove(); + } + + anchor.data = 0; +} + + +/* complex types */ + +function composeArray(anchor, array, keepLast) { + if(!keepLast) clear(anchor); + // TODO: optimize arrays here if Node[] + for(let i = 0; i < array.length; i++) { + compose(anchor, array[i], true); + } +} + +async function composeStream(anchor, stream, keepLast) { + stream.pipeTo(new WritableStream({ + write(chunk) { + compose(anchor, chunk, keepLast); + } + })); +} + +async function composeAsyncIterator(anchor, iterator, keepLast, props, slottable) { + // TODO: use iterator and intercept system messages + for await(const value of iterator) { + compose(anchor, value, keepLast, props, slottable); + } +} + +/* thrown errors */ + +function throwTypeError(input, type, footer = '') { + // Passing Symbol to `{...}` throws! + if(type === 'symbol') input = 'Symbol'; + throw new TypeError(`\ +Invalid compose {...} input type "${type}", value ${input}.\ +${footer}` + ); +} + +function throwTypeErrorForObject(obj) { + let message = ''; + try { + const json = JSON.stringify(obj, null, 2); + message = `\n\nReceived as:\n\n${json}\n\n`; + } + catch(ex) { + /* no-op */ + } + throwTypeError(obj, 'object', message); +} + +const QUERY_SELECTOR = '[data-bind]'; +const DOMRenderer = { + name: 'DOMRenderer', + + createTemplate(id, content, isFragment) { + const node = DOMRenderer.template(id, content); + const render = DOMRenderer.renderer(node, isFragment); + return render; + }, + + template(id, content) { + if(content) return DOMRenderer.create(content); + DOMRenderer.getById(id); + }, + + create(html) { + const template = document.createElement('template'); + template.innerHTML = html; + return template.content; + }, + getById(id) { + const template = document.getElementById(id); + if(!template) { + throw new Error(`No template with id "${id}"`); + } + return template.content; + }, + + renderer(fragment, isFragment) { + if(!isFragment) fragment = fragment.firstElementChild; + // TODO: malformed fragment check...necessary? + + return function render() { + const clone = fragment.cloneNode(true); + const targets = clone.querySelectorAll(QUERY_SELECTOR); + return [clone, targets]; + }; + }, + bound(dom) { + return dom.querySelectorAll(QUERY_SELECTOR); + } +}; + +const templates = new Map(); // cache +let renderEngine = DOMRenderer; // DOM or HTML engine + +function get(id, isFragment = false, content) { + if(templates.has(id)) return templates.get(id); + + const template = renderEngine.createTemplate(id, content, isFragment); + + templates.set(id, template); + return template; +} + +const bindings = new Map(); // cache + +// stack +const injectable = []; + +const templateRenderer = getBound => (...args) => { + const [root, bind] = getBound(); + if(bind) bind(...args); + return root; +}; + +function renderer(id, targets, makeBind, isFragment, content) { + const create = get(id, isFragment, content); + + function getBound() { + let bind = null; + let boundEls = null; + let node = injectable.at(-1); // peek! + + // TODO: test injectable is right template id type + + if(node) { + const hasBind = bindings.has(node); + bind = bindings.get(node); + if(hasBind) return [node, bind]; + } + + // Honestly not sure this really needed, + // use case would be list component optimize by + // not keeping bind functions? + // overhead is small as it is simple function + if(node) boundEls = renderEngine.bound(node); + else { + // (destructuring re-assignment) + ([node, boundEls] = create()); + } + + const nodes = targets ? targets(node, boundEls) : null; + bind = makeBind ? makeBind(nodes) : null; + + bindings.set(node, bind); + return [node, bind]; + } + + return templateRenderer(getBound); +} + +const gac282a7be0 = (r,t) => [t[0].childNodes[3]]; + +const bd41d8cd98f = (ts) => { + const t0 = ts[0]; + return (v0) => { + compose(t0, v0); + }; +}; + +const g3558193cd9 = (r) => [r.childNodes[1]]; + +const g2cc7b6176d = (r,t) => [t[0],r.childNodes[3],r.childNodes[5]]; + +const bb3ae510d64 = (ts) => { + const t0 = ts[0], t1 = ts[1], t2 = ts[2]; + return (v0, v1, v2) => { + t0.innerHTML = v0; + compose(t1, v1); + compose(t2, v2); + }; +}; + +const tf30ef00ee2 = renderer("f30ef00ee2", gac282a7be0, bd41d8cd98f, true); +const te23131e855 = renderer("e23131e855", g3558193cd9, bd41d8cd98f, false); +const t0f61ee8206 = renderer("0f61ee8206", g2cc7b6176d, bb3ae510d64, false); + +async function fetchEmojis() { + const res = await fetch('https://emojihub.yurace.pro/api/all'); + return await res.json(); +} + +const List = fetchEmojis().then(emojis => EmojiList({ + emojis +})); +const App = tf30ef00ee2(createElement(List)); +document.body.append(App); +function EmojiList({emojis}) { + return te23131e855(emojis.map(Emoji)); +} +function Emoji({name, unicode, htmlCode}) { + return t0f61ee8206(htmlCode.join(''),name,unicode); +} diff --git a/vite-test/expected-out/index.html b/vite-test/expected-out/index.html index b9605c8..e3ea516 100644 --- a/vite-test/expected-out/index.html +++ b/vite-test/expected-out/index.html @@ -6,13 +6,13 @@ vite-plugin-azoth - + -