diff --git a/src/bind-attributes.js b/src/bind-attributes.js new file mode 100644 index 0000000..38516da --- /dev/null +++ b/src/bind-attributes.js @@ -0,0 +1,46 @@ +import HTMLHandler from 'https://dev.jspm.io/parse5'; +import {nanoid} from 'https://deno.land/x/nanoid@v3.0.0/mod.ts'; + +export function parseAttributeBindings({HTML = '', CSS = '', JS = ''}) { + const parsedHTML = HTMLHandler.parseFragment(HTML); + + function _parse(node) { + if (node.nodeName.startsWith('#') && node.nodeName !== '#document-fragment') { + return node; + } + + node.childNodes = node.childNodes?.map(_parse); + + if (!node.attrs) { + return node; + } + + node.attrs = node.attrs.map(attr => { + const {name, value} = attr; + + if (name.startsWith('{') && name.endsWith('}')) { + const cleanName = name.slice(1, -1); + const id = nanoid(5); + const eventSlug = `handle-${cleanName}-${id}`; + + JS += '\n\n' + ` +function __bind__attr__${cleanName}__${id}() { + document.getElementById('${eventSlug}').setAttribute('${cleanName}', ${value || cleanName}); +} + `.trim(); + + return ({name: 'id', value: eventSlug}); + } + + return attr; + }); + + return node; + } + + const out = _parse(parsedHTML); + + HTML = HTMLHandler.serialize(out); + + return {HTML, CSS, JS}; +} diff --git a/src/events.js b/src/events.js index 12dca09..0ecc974 100644 --- a/src/events.js +++ b/src/events.js @@ -20,7 +20,7 @@ export function parseEvents({HTML, CSS, JS}) { if (name.startsWith('on')) { console.log('Got event!!'); - const eventSlug = `handle-${name.slice(2)}-${attr.value.replace(/[[\](){}'"]*/gi, '')}-${nanoid(5)}`; + const eventSlug = `handle-${name.slice(2)}-${attr.value.replace(/[[\](){}'"\s;+]*/gi, '')}-${nanoid(5)}`; JS += '\n\n'; JS += ` diff --git a/src/index.js b/src/index.js index ec7984f..b3424b8 100644 --- a/src/index.js +++ b/src/index.js @@ -2,6 +2,7 @@ import {split} from './split.js'; import {resolve} from './resolve.js'; import {makeReactive} from './react.js'; import {parseEvents} from './events.js'; +import {parseAttributeBindings} from './bind-attributes.js'; /* * Parse a component @@ -15,7 +16,7 @@ import {parseEvents} from './events.js'; * @return {string} code.JS - The parsed JS code */ export function parse(code = '', options = {wrapInHTML: true, HTMLTemplate: null}) { - let {HTML, CSS, JS} = makeReactive(parseEvents(resolve(split(code, true)))); + let {HTML, CSS, JS} = makeReactive(parseAttributeBindings(parseEvents(resolve(split(code, true))))); if (options.wrapInHTML) { if (options.HTMLTemplate) { @@ -41,3 +42,15 @@ ${HTML.split('\n').map(line => '\t' + line).join('\n')} return {HTML, CSS, JS}; } +let out = parse(` + + +`); + +console.log(out.HTML); +console.log(); +console.log(); +console.log(out.JS); diff --git a/src/react.js b/src/react.js index ce05ab0..470de7b 100644 --- a/src/react.js +++ b/src/react.js @@ -6,19 +6,19 @@ const {builders: b} = types; let deps = {}; let depsInverted = false; let HTML = ''; +let ast = {}; const finalReactiveCalls = []; export function makeReactive({HTML: HTMLIn, CSS, JS}) { HTML = HTMLIn; - const ast = parse(JS); + ast = parse(JS); visit(ast, { visitUpdateExpression: visitUpdate, visitVariableDeclaration: visitVariable }); - // It works. Don't touch visit(ast, { visitVariableDeclaration: secondVisitVariable }); @@ -46,6 +46,7 @@ function visitVariable(path) { visit(declaration.init, { visitIdentifier(path) { + // O(h no) deps[declaration.id.name].push(path.node.name); this.traverse(path); } @@ -84,7 +85,12 @@ function secondVisitVariable(path) { ) : b.emptyStatement(), hasBinding(HTML, declaration.id.name) ? - parse(`document.getElementById('__bind__${declaration.id.name}').innerText = ${declaration.id.name}`).program.body[0] : b.emptyStatement(), // I'm lazy + buildBinding(declaration.id.name) : b.emptyStatement(), + + hasAttributeBinding(declaration.id.name) ? + b.expressionStatement( + b.callExpression(b.identifier(hasAttributeBinding(declaration.id.name)), []) + ) : b.emptyStatement(), ...(deps[declaration.id.name] || []).map( dep => b.expressionStatement( @@ -161,10 +167,46 @@ function bindConsts(path) { const reactiveDeclarators = []; for (const declaration of path.node.declarations) { - HTML = directlyBind(HTML, declaration.id.name, eval(print(declaration.init).code)); + HTML = directlyBind(HTML, declaration.id.name, + eval(print(declaration.init).code)); // TODO no eval } path.replace(path.node, ...reactiveDeclarators); return false; } + +function buildBinding(name) { + return b.expressionStatement( + b.assignmentExpression( + '=', + b.memberExpression( + b.callExpression( + b.memberExpression( + b.identifier('document'), + b.identifier('getElementById') + ), + [b.literal('__bind__' + name)] + ), + b.identifier('innerText') + ), + b.identifier(name) + ) + ); +} + +function hasAttributeBinding(name) { + let value = ''; + + visit(ast, { + visitFunctionDeclaration(path) { + if (path.node.id.name.startsWith(`__bind__attr__${name}__`)) { + value = path.node.id.name; + } + + return false; // There will never be a nested fn we need + } + }); + + return value; +}