Skip to content

Commit

Permalink
breaking: New Method API (fixes #141)
Browse files Browse the repository at this point in the history
- methods are stored in "instance.methods" and are callable (the context is changed)
- methods are constants and cannot change
  - "m-on" will optimize all method calls unless they pass a dynamic property as a parameter
- methods and data can have the same name, but it is considered a bad practice
  • Loading branch information
kbrsh committed Sep 10, 2017
1 parent cf276f7 commit a83fd4e
Show file tree
Hide file tree
Showing 17 changed files with 261 additions and 238 deletions.
310 changes: 157 additions & 153 deletions dist/moon.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/moon.min.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/compiler/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const whitespaceRE = /[\s\n]/g;
const tagOrCommentStartRE = /<\/?(?:[A-Za-z]+\w*)|<!--/;

// Dynamic expressions
const expressionRE = /"[^"]*"|'[^']*'|\d+[a-zA-Z$_]\w*|\.[a-zA-Z$_]\w*|[a-zA-Z$_]\w*:|([a-zA-Z$_]\w*)/g;
const expressionRE = /"[^"]*"|'[^']*'|\d+[a-zA-Z$_]\w*|\.[a-zA-Z$_]\w*|[a-zA-Z$_]\w*:|([a-zA-Z$_]\w*)(?:\s*\()?/g;

// HTML Escapes
const escapeRE = /(?:(?:&(?:lt|gt|quot|amp);)|"|\\|\n)/g;
Expand Down
25 changes: 18 additions & 7 deletions src/compiler/generator.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const generateProps = function(node, parent, specialDirectivesAfter, state) {
let propKey;
let specialDirective;

let propsCode = "{attrs: {";
let propsCode = "{\"attrs\": {";

let beforeGenerate;
for(propKey in props) {
Expand Down Expand Up @@ -82,7 +82,7 @@ const generateProps = function(node, parent, specialDirectivesAfter, state) {
}

if(hasDirectives === true) {
propsCode += ", directives: {";
propsCode += ", \"directives\": {";

for(let i = 0; i < directiveProps.length; i++) {
let directiveProp = directiveProps[i];
Expand All @@ -97,7 +97,7 @@ const generateProps = function(node, parent, specialDirectivesAfter, state) {

let domProps = node.props.dom;
if(domProps !== undefined) {
propsCode += ", dom: {";
propsCode += ", \"dom\": {";

for(let domProp in domProps) {
propsCode += `"${domProp}": ${domProps[domProp]}, `;
Expand Down Expand Up @@ -232,17 +232,28 @@ const generate = function(tree) {
dynamic: false,
static: false,
exclude: globals,
dependencies: []
dependencies: {
props: [],
methods: []
}
};

const treeCode = generateNode(tree, undefined, 0, state);

const dependencies = state.dependencies;
const props = dependencies.props;
const methods = dependencies.methods;
let dependenciesCode = '';
let i = 0;

for(; i < props.length; i++) {
const propName = props[i];
dependenciesCode += `var ${propName} = instance.get("${propName}");`;
}

for(let i = 0; i < dependencies.length; i++) {
const dependency = dependencies[i];
dependenciesCode += `var ${dependency} = instance.get("${dependency}");`;
for(i = 0; i < methods.length; i++) {
const methodName = methods[i];
dependenciesCode += `var ${methodName} = instance.methods["${methodName}"];`;
}

const code = `var instance = this;${dependenciesCode}return ${treeCode};`;
Expand Down
4 changes: 2 additions & 2 deletions src/compiler/lexer.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ const lex = function(template) {
} else {
// Tag
let tagToken = {
type: "tag",
type: "Tag",
value: ''
}

Expand Down Expand Up @@ -123,7 +123,7 @@ const lex = function(template) {
}
if(text.replace(whitespaceRE, '').length !== 0) {
tokens.push({
type: "text",
type: "Text",
value: text.replace(escapeRE, function(match) {
return escapeMap[match];
})
Expand Down
4 changes: 2 additions & 2 deletions src/compiler/parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ const parse = function(tokens) {

for(let i = 0; i < tokens.length; i++) {
const token = tokens[i];
if(token.type === "text") {
if(token.type === "Text") {
elements[lastIndex].children.push(token.value);
} else if(token.type === "tag") {
} else if(token.type === "Tag") {
if(token.closeStart === true) {
if("__ENV__" !== "production" && token.value !== elements[lastIndex].type) {
error(`The element "${elements[lastIndex].type}" was left unclosed`);
Expand Down
43 changes: 25 additions & 18 deletions src/compiler/template.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
const compileTemplateExpression = function(expression, exclude, dependencies) {
let props = dependencies.props;
let methods = dependencies.methods;
let dynamic = false;
let references;

while((references = expressionRE.exec(expression)) !== null) {
let reference = references[1];
if(reference !== undefined && dependencies.indexOf(reference) === -1) {
if(exclude.indexOf(reference) === -1) {
dependencies.push(reference);
let info;

while((info = expressionRE.exec(expression)) !== null) {
let match = info[0];
let name = info[1];
if(name !== undefined && exclude.indexOf(name) === -1) {
if(match[match.length - 1] === "(") {
if(methods.indexOf(name) === -1) {
methods.push(name);
}
} else if(props.indexOf(name) === -1) {
props.push(name);
dynamic = true;
}
dynamic = true;
}
}

Expand Down Expand Up @@ -37,18 +44,18 @@ const compileTemplate = function(template, exclude, dependencies) {
// Exit opening delimiter
current += textMatch[0].length;

// Get name, and exit closing delimiter
const nameTail = template.substring(current);
const nameMatch = nameTail.match(closeRE);
// Get expression, and exit closing delimiter
const expressionTail = template.substring(current);
const expressionMatch = expressionTail.match(closeRE);

if("__ENV__" !== "production" && nameMatch === null) {
error(`Expected closing delimiter after "${nameTail}"`);
if("__ENV__" !== "production" && expressionMatch === null) {
error(`Expected closing delimiter after "${expressionTail}"`);
} else {
const nameIndex = nameMatch.index;
const name = nameTail.substring(0, nameIndex);
compileTemplateExpression(name, exclude, dependencies);
output += `" + (${name}) + "`;
current += name.length + nameMatch[0].length;
const expressionIndex = expressionMatch.index;
const expression = expressionTail.substring(0, expressionIndex);
compileTemplateExpression(expression, exclude, dependencies);
output += `" + (${expression}) + "`;
current += expression.length + expressionMatch[0].length;
}
}

Expand Down
11 changes: 4 additions & 7 deletions src/directives/default.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
/* ======= Default Directives ======= */

const hashRE = /\.|\[/;

const eventModifiersCode = {
Expand Down Expand Up @@ -127,7 +125,9 @@ specialDirectives["m-on"] = {
}

// Compile method code
compileTemplateExpression(methodCode, state.exclude, state.dependencies)
if(compileTemplateExpression(methodCode, state.exclude, state.dependencies) === true) {
node.meta.dynamic = 1;
}

// Generate any modifiers
let modifiersCode = '';
Expand All @@ -141,9 +141,6 @@ specialDirectives["m-on"] = {
}
}

// Mark as dynamic
node.meta.dynamic = 1;

// Generate event listener code and install handler
const code = `function(event) {${modifiersCode}${methodCode};}`;
addEventListenerCodeToNode(eventType, code, node);
Expand Down Expand Up @@ -190,7 +187,7 @@ specialDirectives["m-literal"] = {
const propName = modifiers.shift();
const propValue = prop.value;

if(compileTemplateExpression(propValue, state.exclude, state.dependencies)) {
if(compileTemplateExpression(propValue, state.exclude, state.dependencies) === true) {
node.meta.dynamic = 1;
}

Expand Down
2 changes: 0 additions & 2 deletions src/global/api.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
/* ======= Global API ======= */

/**
* Configuration of Moon
*/
Expand Down
39 changes: 22 additions & 17 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,16 @@ let directives = {};
let specialDirectives = {};
let components = {};

/* ======= Utilities ======= */
//=require util/util.js
//=require util/dom.js
//=require util/vdom.js

/* ======= Observer ======= */
//=require observer/methods.js
//=require observer/computed.js
//=require observer/observer.js

//=require util/util.js
//=require util/dom.js
//=require util/vdom.js

/* ======= Compiler ======= */
//=require compiler/constants.js
//=require compiler/template.js
Expand All @@ -31,13 +32,13 @@ function Moon(options) {
}
this.options = options;

// Readable name/id
// Name/ID
defineProperty(this, "name", options.name, "Root");

// DOM Node to Mount
// Root DOM Node
this.root = undefined;

// Custom Data
// Data
const data = options.data;
if(data === undefined) {
this.data = {};
Expand All @@ -47,18 +48,19 @@ function Moon(options) {
this.data = data;
}

// Render function
defineProperty(this, "compiledRender", options.render, noop);

// Hooks
defineProperty(this, "hooks", options.hooks, {});

// Custom Methods
// Methods
const methods = options.methods;
this.methods = {};
if(methods !== undefined) {
initMethods(this, methods);
}

// Compiled render function
defineProperty(this, "compiledRender", options.render, noop);

// Hooks
defineProperty(this, "hooks", options.hooks, {});

// Events
this.events = {};

Expand All @@ -68,21 +70,24 @@ function Moon(options) {
// Observer
this.observer = new Observer();

// State of Queue
// Queued state
this.queued = true;

// Setup Computed Properties
// Initialize computed properties
const computed = options.computed;
if(computed !== undefined) {
initComputed(this, computed);
}

/* ======= Initialize 🎉 ======= */
// Initialize
this.init();
}

/* ======= Instance Methods ======= */
//=require instance/methods.js

/* ======= Global API ======= */
//=require global/api.js

/* ======= Default Directives ======= */
//=require directives/default.js
2 changes: 0 additions & 2 deletions src/instance/methods.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
/* ======= Instance Methods ======= */

/**
* Gets Value in Data
* @param {String} key
Expand Down
5 changes: 0 additions & 5 deletions src/observer/computed.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,3 @@
/**
* Makes Computed Properties for an Instance
* @param {Object} instance
* @param {Object} computed
*/
const initComputed = function(instance, computed) {
const setComputedProperty = function(prop) {
const observer = instance.observer;
Expand Down
13 changes: 3 additions & 10 deletions src/observer/methods.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,9 @@
/**
* Initializes Methods
* @param {Object} instance
* @param {Array} methods
*/
const initMethods = function(instance, methods) {
let data = instance.data;
let instanceMethods = instance.methods;

const initMethod = function(methodName, method) {
if("__ENV__" !== "production" && data.hasOwnProperty(methodName) === true) {
error(`Method "${methodName}" has the same key as a data property and will overwrite it`);
}
data[methodName] = function() {
// Change context of method
instanceMethods[methodName] = function() {
return method.apply(instance, arguments);
}
}
Expand Down
8 changes: 5 additions & 3 deletions src/observer/observer.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,24 @@
function Observer() {
// Computed property cache
this.cache = {};

// Property currently being observed
this.target = undefined;

// Computed property cache
this.cache = {};

// Dependency Map
this.map = {};
}

Observer.prototype.notify = function(key) {
// Notify all dependent keys
let map = this.map[key];
if(map !== undefined) {
for(let i = 0; i < map.length; i++) {
this.notify(map[i]);
}
}

// Clear cache for key
let cache = this.cache;
if(cache[key] !== undefined) {
cache[key] = undefined;
Expand Down
2 changes: 0 additions & 2 deletions src/util/util.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
/* ======= Global Utilities ======= */

/**
* Logs a Message
* @param {String} msg
Expand Down
Loading

0 comments on commit a83fd4e

Please sign in to comment.