Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Compiler #1

Merged
merged 3 commits into from Apr 15, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
17 changes: 15 additions & 2 deletions index.html
@@ -1,5 +1,6 @@
<script type="module">
import { ReactiveTemplate } from './ReactiveTemplate.js';
import { ReactiveTemplate } from './src/ReactiveTemplate.js';
import { TemplateCompiler } from './src/TemplateCompiler.js';

class Index extends ReactiveTemplate {
constructor() {
Expand All @@ -16,7 +17,8 @@
}
}

window.addEventListener('load', new Index());
//window.addEventListener('load', new Index());
window.addEventListener('load', () => new TemplateCompiler().compile(document.body).mount(document.body));
</script>
<body>
<!-- comment -->
Expand Down Expand Up @@ -63,4 +65,15 @@ <h1>{{ input }}</h1>
<a @click="array.push(Date.now())">Add</a>

{{array}}

<!-- A -->
<ul>
<v:for key="item" :source="[1, 2, 3]">
<li>{{ item }}</li>
</v:for>
</ul>
<!-- B -->
<ul>
<li v-for="item of [1, 2, 3]">{{ item }}</li>
</ul>
</body>
8 changes: 8 additions & 0 deletions package.json
@@ -0,0 +1,8 @@
{
"name": "ndat-template",
"version": "0.0.1-SNAPSHOT",
"description": "Template library",
"repository": "git@github.com:nielsvanvelzen/ndat-template.git",
"author": "Niels van Velzen <git@ndat.nl>",
"private": false
}
File renamed without changes.
File renamed without changes.
160 changes: 160 additions & 0 deletions src/TemplateCompiler.js
@@ -0,0 +1,160 @@
export const NODE_TYPE = {
ELEMENT_NODE: 1,
// ATTRIBUTE_NODE: 2, // deprecated
TEXT_NODE: 3,
// CDATA_SECTION_NODE: 4, // deprecated
// ENTITY_REFERENCE_NODE: 5, // deprecated
// ENTITY_NODE: 6, // deprecated
PROCESSING_INSTRUCTION_NODE: 7,
COMMENT_NODE: 8,
DOCUMENT_NODE: 9,
DOCUMENT_TYPE_NODE: 10,
DOCUMENT_FRAGMENT_NODE: 11,
// NOTATION_NODE: 12, // deprecated

get(value) {
return Object.entries(this) // Get all entries
.filter(entry => entry[1] === value) // Find the correct value
.map(entry => entry[0]) // Get the name of the entry
.shift() || null; // return entry or null if not found
}
};

export class Template {
constructor(templateData) {
this._templateData = templateData;
}

mount(container) {
container.appendChild(this._render(this._templateData));
}

_render(tpl, container = null) {
console.log('render', tpl);

if (tpl.type === 'text') {
let text = new Text(tpl.content);
if (container) container.appendChild(text);

return text;
} else if (tpl.type === 'element') {
let element = document.createElement(tpl.name);
if (container) container.appendChild(element);

if (tpl.children) for (let child of tpl.children)
this._render(child, element);

return element;
} else if (tpl.type === 'for') {
if (tpl.children) for (let child of tpl.children)
this._render(child, container);
}
}
}

export class TemplateCompiler {
compile(source) {
if (source instanceof HTMLElement)
source = source.outerHTML;

let container = document.createElement('div');
container.insertAdjacentHTML('beforeend', source);

return new Template(this._compileNode(container));
}

_compileNode(node) {
let nodeTypeCompilers = {
[NODE_TYPE.ELEMENT_NODE]: this._compileElement,
[NODE_TYPE.TEXT_NODE]: this._compileText
};

if (node.nodeType in nodeTypeCompilers)
return nodeTypeCompilers[node.nodeType].call(this, node);

return null;
}

_compileAttributes(element) {
let template = {
attributes: {},
properties: {},
events: {},
};

for (let attr of element.attributes) {
let { name, value } = attr;

if (name.startsWith('@')) // Attributes starting with @ get converted to events
template.events[name.substr(1)] = value;
else if (name.startsWith(':')) // Attributes starting with : get converted to properties
template.properties[name.substr(1)] = value;
else
template.attributes[name] = value;
}

return template;
}

_compileElement(element) {
let template = {};
template.type = 'element';
template.name = element.nodeName;
template = { ...template, ...this._compileAttributes(element) };
template.children = this._compileChildren(element);

if ('v-for' in template.attributes) {
let expression = template.attributes['v-for'];
delete template.attributes['v-for'];

return {
type: 'for',
expression,
children: [template]
};
}

return template;
}

_compileText(text) {
if (text.isElementContentWhitespace || text.wholeText.trim().length === 0) // Check if text element is empty
return null;

return {
type: 'text',
content: text.wholeText
};
}

_compileChildren(node) {
let children = [];

for (let child of node.childNodes) {
let childTemplate = this._compileNode(child);

if (childTemplate !== null)
children.push(childTemplate);
}

return children;
}
}

// Examples
export const exampleForTemplate = {
type: 'element',
name: 'ul',
children: [{
type: 'for',
expression: 'item of [1, 2, 3]',
children: [{
type: 'element',
name: 'li',
children: [{
type: 'text',
content: '{{ item }}'
}]
}]
}]
};