More code size reductions

This commit is contained in:
Patrick Steele-Idem 2016-12-30 21:36:15 -07:00
parent 74d802b074
commit 20cf6d364e
118 changed files with 1486 additions and 1387 deletions

View File

@ -0,0 +1,35 @@
import commonjsPlugin from 'rollup-plugin-commonjs';
import browserifyPlugin from 'rollup-plugin-browserify-transform';
import nodeResolvePlugin from 'rollup-plugin-node-resolve';
import markoify from 'markoify';
import envify from 'envify';
import minpropsify from 'minprops/browserify';
import path from 'path';
process.env.NODE_ENV = 'production';
// NODE_ENV=production browserify -t envify -t markoify --extension='.marko' --global-transform minprops/browserify -o build/bundles/marko.js marko/client.js
export default {
entry: path.join(__dirname, 'client.js'),
format: 'iife',
moduleName: 'app',
plugins: [
browserifyPlugin(markoify),
browserifyPlugin(envify),
browserifyPlugin(minpropsify),
nodeResolvePlugin({
jsnext: true, // Default: false
main: true, // Default: true
browser: true, // Default: false
preferBuiltins: false,
extensions: [ '.js', '.marko' ]
}),
commonjsPlugin({
include: [ 'node_modules/**', '**/*.marko', '**/*.js'],
extensions: [ '.js', '.marko' ]
})
],
dest: path.join(__dirname, '../build/bundles/marko.js')
};

View File

@ -37,6 +37,11 @@ var minifiers = {
};
const out = gcc.compile(options);
// if (out.errors && out.errors.length) {
// console.error(out.errors);
// throw new Error(`Minification failed for ${file}`);
// }
return out.compiledCode;
},
uglify: function minifyUglifyJS(src, file) {

View File

@ -8,9 +8,9 @@
"build": "npm run bundle --silent && npm run minify --silent",
"build-marko": "npm run bundle-marko --silent && node minify.js marko",
"bundle": "mkdir -p build/bundles && npm run bundle-marko && npm run bundle-react && npm run bundle-vue && npm run bundle-preact",
"bundle-marko": "NODE_ENV=production browserify -t envify -t markoify --extension='.marko' --global-transform minprops/browserify -o build/bundles/marko.js marko/client.js",
"bundle-react": "NODE_ENV=production browserify -t envify -t babelify --extension='.jsx' --global-transform minprops/browserify -o build/bundles/react.js react/client.jsx",
"bundle-preact": "NODE_ENV=production browserify -t envify -t babelify --extension='.jsx' --global-transform minprops/browserify -o build/bundles/preact.js preact/client.jsx",
"bundle-marko": "NODE_ENV=production rollup -c marko/rollup.config.js",
"bundle-react": "NODE_ENV=production rollup -c react/rollup.config.js",
"bundle-preact": "NODE_ENV=production rollup -c preact/rollup.config.js",
"bundle-vue": "NODE_ENV=production browserify -t envify -t vueify --extension='.vue' --global-transform minprops/browserify -o build/bundles/vue.js vue/client.js",
"minify": "node minify.js",
"http-server": "http-server"
@ -18,6 +18,7 @@
"author": "Patrick Steele-Idem <pnidem@gmail.com>",
"license": "MIT",
"dependencies": {
"babel-plugin-transform-es2015-block-scoping": "^6.21.0",
"babel-plugin-transform-react-constant-elements": "^6.9.1",
"babel-plugin-transform-react-jsx": "^6.8.0",
"babel-preset-es2015": "^6.18.0",
@ -35,6 +36,12 @@
"preact": "^7.1.0",
"react": "^15.4.1",
"react-dom": "^15.4.1",
"rollup": "^0.39.2",
"rollup-plugin-babel": "^2.7.1",
"rollup-plugin-browserify-transform": "^0.1.0",
"rollup-plugin-commonjs": "^7.0.0",
"rollup-plugin-marko": "0.0.2",
"rollup-plugin-node-resolve": "^2.0.0",
"uglify-js": "^2.7.5",
"vue": "^2.1.6",
"vueify": "^9.4.0"

View File

@ -1,9 +1,10 @@
{
"presets": [
"es2015-loose",
["es2015", { "loose": true, "modules": false }],
"stage-0"
],
"plugins": [
[ "transform-react-jsx", { "pragma": "h" } ]
[ "transform-react-jsx", { "pragma": "h" } ],
["transform-es2015-block-scoping"]
]
}

View File

@ -1,5 +1,6 @@
import { h, render } from 'preact';
var preact = require('preact');
var h = preact.h;
var render = preact.render;
var App = require('./components/App');
render(

View File

@ -1,5 +1,7 @@
'use strict';
import { h, Component } from 'preact';
var preact = require('preact');
var h = preact.h;
var Component = preact.Component;
function renderColor(color) {
var style = {

View File

@ -0,0 +1,35 @@
import commonjsPlugin from 'rollup-plugin-commonjs';
import browserifyPlugin from 'rollup-plugin-browserify-transform';
import nodeResolvePlugin from 'rollup-plugin-node-resolve';
import babelPlugin from 'rollup-plugin-babel';
import envify from 'envify';
import path from 'path';
process.env.NODE_ENV = 'production';
// NODE_ENV=production browserify -t envify -t markoify --extension='.marko' --global-transform minprops/browserify -o build/bundles/marko.js marko/client.js
export default {
entry: path.join(__dirname, 'client.jsx'),
format: 'iife',
moduleName: 'app',
plugins: [
babelPlugin({
// include: ['node_modules/**', '**/*.js', '**/*.jsx']
}),
browserifyPlugin(envify),
nodeResolvePlugin({
jsnext: false, // Default: false
main: true, // Default: true
browser: true, // Default: false
preferBuiltins: false,
extensions: [ '.js', '.jsx' ]
}),
commonjsPlugin({
include: [ 'node_modules/**', '**/*.js', '**/*.jsx'],
extensions: [ '.js', '.jsx' ]
})
],
dest: path.join(__dirname, '../build/bundles/preact.js')
};

View File

@ -1,6 +1,6 @@
{
"presets": [
"es2015-loose",
["es2015", { "loose": true, "modules": false }],
"stage-0",
"react"
],

View File

@ -0,0 +1,35 @@
import commonjsPlugin from 'rollup-plugin-commonjs';
import browserifyPlugin from 'rollup-plugin-browserify-transform';
import nodeResolvePlugin from 'rollup-plugin-node-resolve';
import babelPlugin from 'rollup-plugin-babel';
import envify from 'envify';
import path from 'path';
process.env.NODE_ENV = 'production';
// NODE_ENV=production browserify -t envify -t markoify --extension='.marko' --global-transform minprops/browserify -o build/bundles/marko.js marko/client.js
export default {
entry: path.join(__dirname, 'client.jsx'),
format: 'iife',
moduleName: 'app',
plugins: [
babelPlugin({
exclude: 'node_modules/**'
}),
browserifyPlugin(envify),
nodeResolvePlugin({
jsnext: true, // Default: false
main: true, // Default: true
browser: true, // Default: false
preferBuiltins: false,
extensions: [ '.js', '.jsx' ]
}),
commonjsPlugin({
include: [ 'node_modules/**', '**/*.js', '**/*.jsx'],
extensions: [ '.js', '.jsx' ]
})
],
dest: path.join(__dirname, '../build/bundles/react.js')
};

View File

@ -0,0 +1,33 @@
import commonjsPlugin from 'rollup-plugin-commonjs';
import browserifyPlugin from 'rollup-plugin-browserify-transform';
import nodeResolvePlugin from 'rollup-plugin-node-resolve';
import vueify from 'vueify';
import envify from 'envify';
import minpropsify from 'minprops/browserify';
import path from 'path';
process.env.NODE_ENV = 'production';
export default {
entry: path.join(__dirname, 'client.js'),
format: 'iife',
moduleName: 'app',
plugins: [
browserifyPlugin(vueify),
browserifyPlugin(envify),
browserifyPlugin(minpropsify),
nodeResolvePlugin({
jsnext: true, // Default: false
main: true, // Default: true
browser: true, // Default: false
preferBuiltins: false,
extensions: [ '.js', '.vue' ]
}),
commonjsPlugin({
include: [ 'node_modules/**', '**/*.vue', '**/*.js'],
extensions: [ '.js', '.vue' ]
})
],
dest: path.join(__dirname, '../build/bundles/vue.js')
};

View File

@ -58,22 +58,29 @@ const helpers = {
'classList': 'cl',
'const': 'const',
'createElement': 'e',
'createInlineTemplate': 'inline',
'createInlineTemplate': {
vdom: { module: 'marko/runtime/vdom/helper-createInlineTemplate'},
html: { module: 'marko/runtime/html/helper-createInlineTemplate'}
},
'escapeXml': 'x',
'escapeXmlAttr': 'xa',
'escapeScript': 'xs',
'forEach': 'f',
'forEachProp': 'fp',
'forEachWithStatusVar': 'fv',
'forRange': 'fr',
'forEachProp': { module: 'marko/runtime/helper-forEachProperty' },
'forEachPropStatusVar': { module: 'marko/runtime/helper-forEachPropStatusVar' },
'forEachWithStatusVar': { module: 'marko/runtime/helper-forEachWithStatusVar' },
'forRange': { module: 'marko/runtime/helper-forRange' },
'include': 'i',
'loadNestedTag': 'n',
'loadNestedTag': { module: 'marko/runtime/helper-loadNestedTag' },
'loadTag': 't',
'loadTemplate': 'l',
'mergeNestedTagsHelper': 'mn',
'merge': 'm',
'loadTemplate': { module: 'marko/runtime/helper-loadTemplate' },
'mergeNestedTagsHelper': { module: 'marko/runtime/helper-mergeNestedTags' },
'merge': { module: 'marko/runtime/helper-merge' },
'str': 's',
'styleAttr': 'sa',
'styleAttr': {
vdom: { module: 'marko/runtime/vdom/helper-styleAttr'},
html: 'sa'
},
'createText': 't'
};
@ -672,15 +679,32 @@ class CompileContext extends EventEmitter {
helper(name) {
var helperIdentifier = this._helpers[name];
if (!helperIdentifier) {
var methodName = helpers[name];
if (!methodName) {
var helperInfo = helpers[name];
if (helperInfo && typeof helperInfo === 'object') {
if (!helperInfo.module) {
helperInfo = helperInfo[this.outputType];
}
}
if (!helperInfo) {
throw new Error('Invalid helper: ' + name);
}
var methodIdentifier = this.builder.identifier(methodName);
helperIdentifier = this.addStaticVar(
'marko_' + name,
this.builder.memberExpression(this.helpersIdentifier, methodIdentifier));
if (typeof helperInfo === 'string') {
let methodName = helperInfo;
var methodIdentifier = this.builder.identifier(methodName);
helperIdentifier = this.addStaticVar(
'marko_' + name,
this.builder.memberExpression(this.helpersIdentifier, methodIdentifier));
} else if (helperInfo && helperInfo.module) {
helperIdentifier = this.addStaticVar(
'marko_' + name,
this.builder.require(this.builder.literal(helperInfo.module)));
} else {
throw new Error('Invalid helper: ' + name);
}
this._helpers[name] = helperIdentifier;
}

View File

@ -33,7 +33,7 @@ class ForEachProp extends Node {
var builder = codegen.builder;
if (statusVarName) {
let helperVar = builder.require(builder.literal('marko/runtime/forEachPropStatusVar'));
let helperVar = context.helper('forEachPropStatusVar');
let forEachVarName = codegen.addStaticVar('forEacPropStatusVar', helperVar);
let body = this.body;

View File

@ -62,6 +62,11 @@ class TemplateRoot extends Node {
renderStatements)
]);
} else {
var isBrowser = context.options.browser;
var createArgs = isBrowser ?
[] :
[ builder.identifier('__filename') ];
let templateDeclaration = builder.variableDeclarator('marko_template',
builder.assignment(
builder.moduleExports(),
@ -72,9 +77,7 @@ class TemplateRoot extends Node {
),
builder.identifier('t')
),
[
builder.identifier('__filename')
]
createArgs
)
)
);

View File

@ -64,7 +64,7 @@
"lasso-package-root": "^1.0.0",
"listener-tracker": "^2.0.0",
"minimatch": "^3.0.2",
"morphdom": "^2.2.0",
"morphdom": "^2.3.0",
"object-assign": "^4.1.0",
"property-handlers": "^1.0.0",
"raptor-async": "^1.1.2",

View File

@ -1,6 +1,4 @@
var events = require('./events');
var domInsert = require('./dom-insert');
var EMPTY_ARRAY = [];
function checkAddedToDOM(result, method) {
if (!result.$__widgets) {
@ -57,27 +55,24 @@ var proto = RenderResult.prototype = {
afterInsert: function(doc) {
var out = this.$__out;
var widgetsContext = out.global.widgets;
this.$__widgets = (widgetsContext && widgetsContext.$__widgets) || EMPTY_ARRAY;
events.emit('mountNode', {
result: this,
out: this.$__out,
document: doc
}); // NOTE: This will trigger widgets to be initialized if there were any
if (widgetsContext) {
this.$__widgets = widgetsContext.$__widgets;
widgetsContext.$__initWidgets(doc);
}
return this;
},
getNode: function(doc) {
return this.$__out.getNode(doc);
return this.$__out.$__getNode(doc);
},
getOutput: function() {
return this.$__out.getOutput();
return this.$__out.$__getOutput();
},
toString: function() {
return this.$__out.toString();
},
toJSON: function() {
return this.getOutput();
return this.$__out.$__getOutput();
},
document: typeof document !== 'undefined' && document
};

13
runtime/createOut.js Normal file
View File

@ -0,0 +1,13 @@
var actualCreateOut;
function setCreateOut(createOutFunc) {
actualCreateOut = createOutFunc;
}
function createOut(globalData) {
return actualCreateOut(globalData);
}
createOut.$__setCreateOut = setCreateOut;
module.exports = createOut;

View File

@ -1,70 +1,77 @@
var path = require('path');
var resolveFrom = require('resolve-from');
var Template = require('./html').Template;
var Template = require('./html/Template');
function getDeps(template) {
if(!template.meta && template.template) {
template = template.template;
}
if (!template.meta && template.template) {
template = template.template;
}
if(!(template instanceof Template)) {
return [];
}
if (!(template instanceof Template)) {
return [];
}
if(false && template.deps) {
return template.deps;
}
if (false && template.deps) {
return template.deps;
}
if(!template.meta) {
console.error('Metadata not set for template at ', template.path);
return [];
}
if (!template.meta) {
console.error('Metadata not set for template at ', template.path);
return [];
}
var meta = template.meta;
var root = path.dirname(template.path);
var deps = [];
var meta = template.meta;
var root = path.dirname(template.path);
var deps = [];
if(meta.tags) {
meta.tags.forEach(tagPath => {
var tag = resolveFrom(root, tagPath);
var tagDeps = getDeps(require(tag));
deps.push.apply(deps, tagDeps);
});
}
if (meta.tags) {
meta.tags.forEach(tagPath => {
var tag = resolveFrom(root, tagPath);
var tagDeps = getDeps(require(tag));
deps.push.apply(deps, tagDeps);
});
}
if(meta.deps) {
deps.push.apply(deps, meta.deps.map(d => resolveDep(d, root)));
}
if (meta.deps) {
deps.push.apply(deps, meta.deps.map(d => resolveDep(d, root)));
}
deps = dedupeDeps(deps);
deps = dedupeDeps(deps);
template.deps = deps;
template.deps = deps;
return deps;
return deps;
}
function resolveDep(dep, root) {
if(typeof dep === 'string') {
dep = parseDependencyString(dep);
}
if(dep.path) {
return Object.assign({}, dep, { path:resolveFrom(root, dep.path) });
} else if(dep.virtualPath) {
return Object.assign({}, dep, { virtualPath:path.resolve(root, dep.virtualPath) });
} else {
return dep;
}
if (typeof dep === 'string') {
dep = parseDependencyString(dep);
}
if (dep.path) {
return Object.assign({}, dep, {
path: resolveFrom(root, dep.path)
});
} else if (dep.virtualPath) {
return Object.assign({}, dep, {
virtualPath: path.resolve(root, dep.virtualPath)
});
} else {
return dep;
}
}
function parseDependencyString(string) {
var match = /^(?:([\w-]+)(?:\:\s*|\s+))?(.*?(?:\.(\w+))?)$/.exec(string);
return { type:match[1]||match[3], path:match[2] };
var match = /^(?:([\w-]+)(?:\:\s*|\s+))?(.*?(?:\.(\w+))?)$/.exec(string);
return {
type: match[1] || match[3],
path: match[2]
};
}
function dedupeDeps(deps) {
return deps;
return deps;
}
require('./html').Template.prototype.getDependencies = function() {
Template.prototype.getDependencies = function() {
return getDeps(this);
};

View File

@ -1 +0,0 @@
exports.$__document = typeof document != 'undefined' && document;

View File

@ -1,5 +1,7 @@
var events = require('./events');
var extend = require('raptor-util/extend');
var widgetsUtil = require('../widgets/util');
var destroyWidgetForEl = widgetsUtil.$__destroyWidgetForEl;
var destroyElRecursive = widgetsUtil.$__destroyElRecursive;
function resolveEl(el) {
if (typeof el === 'string') {
@ -13,33 +15,34 @@ function resolveEl(el) {
}
function beforeRemove(referenceEl) {
events.emit('dom/beforeRemove', {
el: referenceEl
});
destroyElRecursive(referenceEl);
destroyWidgetForEl(referenceEl);
}
module.exports = function(target, getEl, afterInsert) {
extend(target, {
appendTo: function(referenceEl) {
referenceEl = resolveEl(referenceEl);
var el = getEl(this, referenceEl);
resolveEl(referenceEl).appendChild(el);
referenceEl.appendChild(el);
return afterInsert(this, referenceEl);
},
prependTo: function(referenceEl) {
referenceEl = resolveEl(referenceEl);
var el = getEl(this, referenceEl);
referenceEl.insertBefore(el, referenceEl.firstChild || null);
return afterInsert(this, referenceEl);
},
replace: function(referenceEl) {
var el = getEl(this, referenceEl);
referenceEl = resolveEl(referenceEl);
var el = getEl(this, referenceEl);
beforeRemove(referenceEl);
referenceEl.parentNode.replaceChild(el, referenceEl);
return afterInsert(this, referenceEl);
},
replaceChildrenOf: function(referenceEl) {
var el = getEl(this, referenceEl);
referenceEl = resolveEl(referenceEl);
var el = getEl(this, referenceEl);
var curChild = referenceEl.firstChild;
while(curChild) {
@ -53,14 +56,14 @@ module.exports = function(target, getEl, afterInsert) {
return afterInsert(this, referenceEl);
},
insertBefore: function(referenceEl) {
var el = getEl(this, referenceEl);
referenceEl = resolveEl(referenceEl);
var el = getEl(this, referenceEl);
referenceEl.parentNode.insertBefore(el, referenceEl);
return afterInsert(this, referenceEl);
},
insertAfter: function(referenceEl) {
var el = getEl(this, referenceEl);
referenceEl = resolveEl(referenceEl);
var el = getEl(this, referenceEl);
el = el;
var nextSibling = referenceEl.nextSibling;
var parentNode = referenceEl.parentNode;

30
runtime/env-init.js Normal file
View File

@ -0,0 +1,30 @@
require('./stream');
require('./dependencies');
if (process.env.hasOwnProperty('MARKO_HOT_RELOAD')) {
require('../hot-reload').enable();
}
// If process was launched with browser refresh then automatically
// enable browser-refresh
require('../browser-refresh').enable();
function fixFlush() {
try {
var OutgoingMessage = require('http').OutgoingMessage;
if (OutgoingMessage.prototype.flush && OutgoingMessage.prototype.flush.toString().indexOf('deprecated') !== -1) {
// Yes, we are monkey-patching http. This method should never have been added and it was introduced on
// the iojs fork. It was quickly deprecated and I'm 99% sure no one is actually using it.
// See:
// - https://github.com/marko-js/async-writer/issues/3
// - https://github.com/nodejs/node/issues/2920
//
// This method causes problems since marko looks for the flush method and calls it found.
// The `res.flush()` method is introduced by the [compression](https://www.npmjs.com/package/compression)
// middleware, but, otherwise, it should typically not exist.
delete require('http').OutgoingMessage.prototype.flush;
}
} catch(e) {}
}
fixFlush();

View File

@ -7,7 +7,7 @@ function LoopStatus(getLength, isLast, isFirst, getIndex) {
this.getIndex = getIndex;
}
module.exports = function forEachPropStatusVar(object, callback) {
module.exports = function forEachPropStatusVarHelper(object, callback) {
var keys = Object.keys(object);
var i = 0;

View File

@ -0,0 +1,27 @@
var isArray = Array.isArray;
/**
* Internal helper method for looping over the properties of any object
* @private
*/
module.exports = function forEachPropertyHelper(o, func) {
if (!o) {
return;
}
if (isArray(o)) {
for (var i=0; i<o.length; i++) {
func(i, o[i]);
}
} else if (typeof Map && o instanceof Map) {
o.forEach(function(v, k) {
func(k, v);
});
} else {
for (var k in o) {
if (o.hasOwnProperty(k)) {
func(k, o[k]);
}
}
}
};

View File

@ -0,0 +1,40 @@
function LoopStatus(len) {
this.i = 0;
this.len = len;
}
LoopStatus.prototype = {
getLength: function() {
return this.len;
},
isLast: function() {
return this.i === this.len - 1;
},
isFirst: function() {
return this.i === 0;
},
getIndex: function() {
return this.i;
}
};
/**
* Internal helper method to handle loops with a status variable
* @private
*/
module.exports = function forEachStatusVariableHelper(array, callback) {
if (!array) {
return;
}
if (!array.forEach) {
array = [array];
}
var len = array.length;
var loopStatus = new LoopStatus(len);
for (; loopStatus.i < len; loopStatus.i++) {
var o = array[loopStatus.i];
callback(o, loopStatus);
}
};

View File

@ -0,0 +1,18 @@
module.exports = function forRangeHelper(from, to, step, callback) {
if (step == null) {
step = from <= to ? 1 : -1;
}
var i;
if (step > 0) {
for (i=from; i<=to; i += step) {
callback(i);
}
} else {
for (i=from; i>=to; i += step) {
callback(i);
}
}
};

View File

@ -0,0 +1,15 @@
module.exports = function loadNestedTagHelper(targetProperty, isRepeated) {
return function(input, parent) {
// If we are nested tag then we do not have a renderer
if (isRepeated) {
var existingArray = parent[targetProperty];
if (existingArray) {
existingArray.push(input);
} else {
parent[targetProperty] = [input];
}
} else {
parent[targetProperty] = input;
}
};
};

View File

@ -0,0 +1,4 @@
/**
* Loads a template
*/
module.exports = require('./loader');

16
runtime/helper-merge.js Normal file
View File

@ -0,0 +1,16 @@
/**
* Merges object properties
* @param {[type]} object [description]
* @param {[type]} source [description]
* @return {[type]} [description]
*/
function merge(into, source) {
for (var k in source) {
if (source.hasOwnProperty(k) && !into.hasOwnProperty(k)) {
into[k] = source[k];
}
}
return into;
}
module.exports = merge;

View File

@ -0,0 +1,15 @@
/**
* Merges nested tags by rendering the body
* @param {[type]} object [description]
* @param {[type]} source [description]
* @return {[type]} [description]
*/
function mergeNestedTags(input) {
if (input.renderBody) {
input.renderBody(null, input);
}
input.renderBody = null;
return input;
}
module.exports = mergeNestedTags;

View File

@ -1,6 +1,10 @@
'use strict';
var isArray = Array.isArray;
function isFunction(arg) {
return typeof arg === 'function';
}
function classListHelper(arg, classNames) {
var len;
@ -40,8 +44,8 @@ function createDeferredRenderer(handler) {
// This is the initial function that will do the rendering. We replace
// the renderer with the actual renderer func on the first render
deferredRenderer.renderer = function(input, out) {
var rendererFunc = handler.renderer || handler.render;
if (typeof rendererFunc !== 'function') {
var rendererFunc = handler.renderer || handler._ || handler.render;
if (!isFunction(rendererFunc)) {
throw Error('Invalid renderer');
}
// Use the actual renderer from now on
@ -59,14 +63,10 @@ function resolveRenderer(handler) {
return renderer;
}
if (typeof handler === 'function') {
if (isFunction(handler)) {
return handler;
}
if (typeof (renderer = handler.render) === 'function') {
return renderer.bind(handler);
}
// If the user code has a circular function then the renderer function
// may not be available on the module. Since we can't get a reference
// to the actual renderer(input, out) function right now we lazily
@ -74,26 +74,6 @@ function resolveRenderer(handler) {
return createDeferredRenderer(handler);
}
function LoopStatus(len) {
this.i = 0;
this.len = len;
}
LoopStatus.prototype = {
getLength: function() {
return this.len;
},
isLast: function() {
return this.i === this.len - 1;
},
isFirst: function() {
return this.i === 0;
},
getIndex: function() {
return this.i;
}
};
/**
* Internal helper method to prevent null/undefined from being written out
* when writing text that resolves to null/undefined
@ -103,27 +83,6 @@ exports.s = function strHelper(str) {
return (str == null) ? '' : str.toString();
};
/**
* Internal helper method to handle loops with a status variable
* @private
*/
exports.fv = function forEachStatusVariableHelper(array, callback) {
if (!array) {
return;
}
if (!array.forEach) {
array = [array];
}
var len = array.length;
var loopStatus = new LoopStatus(len);
for (; loopStatus.i < len; loopStatus.i++) {
var o = array[loopStatus.i];
callback(o, loopStatus);
}
};
/**
* Internal helper method to handle loops without a status variable
* @private
@ -133,57 +92,12 @@ exports.f = function forEachHelper(array, callback) {
for (var i=0; i<array.length; i++) {
callback(array[i]);
}
} else if (typeof array === 'function') {
} else if (isFunction(array)) {
// Also allow the first argument to be a custom iterator function
array(callback);
}
};
exports.fr = function forRangeHelper(from, to, step, callback) {
if (step == null) {
step = from <= to ? 1 : -1;
}
var i;
if (step > 0) {
for (i=from; i<=to; i += step) {
callback(i);
}
} else {
for (i=from; i>=to; i += step) {
callback(i);
}
}
};
/**
* Internal helper method for looping over the properties of any object
* @private
*/
exports.fp = function forEachPropertyHelper(o, func) {
if (!o) {
return;
}
if (Array.isArray(o)) {
for (var i=0; i<o.length; i++) {
func(i, o[i]);
}
} else if (typeof Map && o instanceof Map) {
o.forEach(function(v, k) {
func(k, v);
});
} else {
for (var k in o) {
if (o.hasOwnProperty(k)) {
func(k, o[k]);
}
}
}
};
/**
* Helper to load a custom tag
*/
@ -195,51 +109,6 @@ exports.t = function loadTagHelper(renderer, targetProperty, isRepeated) {
return renderer;
};
exports.n = function loadNestedTagHelper(targetProperty, isRepeated) {
return function(input, parent) {
// If we are nested tag then we do not have a renderer
if (isRepeated) {
var existingArray = parent[targetProperty];
if (existingArray) {
existingArray.push(input);
} else {
parent[targetProperty] = [input];
}
} else {
parent[targetProperty] = input;
}
};
};
/**
* Merges object properties
* @param {[type]} object [description]
* @param {[type]} source [description]
* @return {[type]} [description]
*/
exports.m = function mergeHelper(into, source) {
for (var k in source) {
if (source.hasOwnProperty(k) && !into.hasOwnProperty(k)) {
into[k] = source[k];
}
}
return into;
};
/**
* Merges nested tags by rendering the body
* @param {[type]} object [description]
* @param {[type]} source [description]
* @return {[type]} [description]
*/
exports.mn = function mergeNestedTagsHelper(input) {
if (input.renderBody) {
input.renderBody(null, input);
}
input.renderBody = null;
return input;
};
/**
* classList(a, b, c, ...)
* Joines a list of class names with spaces. Empty class names are omitted.
@ -249,9 +118,4 @@ exports.mn = function mergeNestedTagsHelper(input) {
*/
exports.cl = function classListHelper() {
return classList(arguments);
};
/**
* Loads a template (__helpers.l --> marko_loadTemplate(path))
*/
exports.l = require('./loader');
};

View File

@ -2,9 +2,10 @@
var EventEmitter = require('events-light');
var StringWriter = require('./StringWriter');
var BufferedWriter = require('./BufferedWriter');
var documentProvider = require('../document-provider');
var defaultDocument = typeof document != 'undefined' && document;
var RenderResult = require('../RenderResult');
var helpers;
var attrsHelper = require('./helper-attrs');
var escapeXml = require('./escape').escapeXml;
var voidWriter = { write:function(){} };
@ -37,7 +38,7 @@ function AsyncStream(global, writer, state, shouldBuffer) {
writer = new BufferedWriter(writer);
}
} else {
writer = originalStream = new StringWriter(events);
writer = originalStream = new StringWriter();
}
state = new State(this, originalStream, writer, events);
@ -69,7 +70,8 @@ AsyncStream.enableAsyncStackTrace = function() {
var proto = AsyncStream.prototype = {
constructor: AsyncStream,
isOut: true,
$__document: defaultDocument,
$__isOut: true,
sync: function() {
this._sync = true;
@ -86,15 +88,22 @@ var proto = AsyncStream.prototype = {
return this;
},
getOutput: function() {
$__getOutput: function() {
return this._state.writer.toString();
},
/**
* Legacy...
*/
getOutput: function() {
return this.$__getOutput();
},
toString: function() {
return this._state.writer.toString();
},
getResult: function() {
$__getResult: function() {
this._result = this._result || new RenderResult(this);
return this._result;
},
@ -228,10 +237,11 @@ var proto = AsyncStream.prototype = {
if (remaining === 0) {
state.finished = true;
if (state.writer.end) {
state.writer.end();
} else {
state.events.emit('finish', this);
state.events.emit('finish', this.$__getResult());
}
}
}
@ -300,7 +310,7 @@ var proto = AsyncStream.prototype = {
var state = this._state;
if (event === 'finish' && state.finished) {
callback(this);
callback(this.$__getResult());
return this;
}
@ -312,7 +322,7 @@ var proto = AsyncStream.prototype = {
var state = this._state;
if (event === 'finish' && state.finished) {
callback(this);
callback(this.$__getResult());
return this;
}
@ -420,7 +430,7 @@ var proto = AsyncStream.prototype = {
element: function(tagName, elementAttrs, openTagOnly) {
var str = '<' + tagName +
helpers.as(elementAttrs) +
attrsHelper(elementAttrs) +
'>';
if (openTagOnly !== true) {
@ -433,7 +443,7 @@ var proto = AsyncStream.prototype = {
beginElement: function(name, elementAttrs) {
var str = '<' + name +
helpers.as(elementAttrs) +
attrsHelper(elementAttrs) +
'>';
this.write(str);
@ -451,17 +461,17 @@ var proto = AsyncStream.prototype = {
},
text: function(str) {
this.write(helpers.x(str));
this.write(escapeXml(str));
},
getNode: function(doc) {
$__getNode: function(doc) {
var node = this._node;
var curEl;
var newBodyEl;
var html = this.getOutput();
var html = this.$__getOutput();
if (!doc) {
doc = documentProvider.$__document;
doc = this.$__document;
}
if (!node) {
@ -491,8 +501,8 @@ var proto = AsyncStream.prototype = {
var out = this;
var promise = new Promise(function(resolve, reject) {
out.on('error', reject);
out.on('finish', function() {
resolve(out.getResult());
out.on('finish', function(result) {
resolve(result);
});
});
@ -507,6 +517,4 @@ var proto = AsyncStream.prototype = {
// alias:
proto.w = proto.write;
module.exports = AsyncStream;
helpers = require('./helpers');
module.exports = AsyncStream;

View File

@ -1,19 +1,10 @@
'use strict';
function StringWriter(events) {
function StringWriter() {
this.str = '';
this.events = events;
this.finished = false;
}
StringWriter.prototype = {
end: function() {
this.finished = true;
if (this.events) {
this.events.emit('finish');
}
},
write: function(str) {
this.str += str;
return this;

25
runtime/html/Template.js Normal file
View File

@ -0,0 +1,25 @@
'use strict';
var AsyncStream = require('./AsyncStream');
var makeRenderable = require('../renderable');
function Template(path, renderFunc, options) {
this.path = path;
this._ = renderFunc;
this.$__shouldBuffer = !options || options.shouldBuffer !== false;
this.meta = undefined;
}
function createOut(globalData, parent, state, buffer) {
return new AsyncStream(globalData, parent, state, buffer);
}
Template.prototype = {
createOut: createOut,
stream: function() {
throw new Error('You must require("marko/stream")');
}
};
makeRenderable(Template.prototype);
module.exports = Template;

52
runtime/html/escape.js Normal file
View File

@ -0,0 +1,52 @@
var elTest = /[&<]/;
var elTestReplace = /[&<]/g;
var attrTest = /[&<\"\n]/;
var attrReplace = /[&<\"\n]/g;
var replacements = {
'<': '&lt;',
'&': '&amp;',
'"': '&quot;',
'\'': '&#39;',
'\n': '&#10;' //Preserve new lines so that they don't get normalized as space
};
function replaceChar(match) {
return replacements[match];
}
function escapeString(str, regexpTest, regexpReplace) {
return regexpTest.test(str) ? str.replace(regexpReplace, replaceChar) : str;
}
function escapeXmlHelper(value, regexpTest, regexpReplace) {
// check for most common case first
if (typeof value === 'string') {
return escapeString(value, regexpTest, regexpReplace);
} else if (value == null) {
return '';
} else if (typeof value === 'object') {
var safeHTML = value.safeHTML;
if (safeHTML != null) {
return value.safeHTML;
} else {
return '';
}
} else if (value === true || value === false || typeof value === 'number') {
return value.toString();
}
return escapeString(value.toString(), regexpTest, regexpReplace);
}
function escapeXml(value) {
return escapeXmlHelper(value, elTest, elTestReplace);
}
function escapeXmlAttr(value) {
return escapeXmlHelper(value, attrTest, attrReplace);
}
exports.escapeString = escapeString;
exports.escapeXml = escapeXml;
exports.escapeXmlAttr = escapeXmlAttr;

View File

@ -0,0 +1,29 @@
var warp10 = require('warp10');
var escape = require('./escape');
var escapeString = escape.escapeString;
var escapeXmlAttr = escape.escapeXmlAttr;
var stringifiedAttrTest = /[&\'\n]/;
var stringifiedAttrReplace = /[&\'\n]/g;
function attr(name, value, shouldEscape) {
if (typeof value === 'string') {
return ' ' + name + '="' + (shouldEscape !== false ? escapeXmlAttr(value) : value) + '"';
} else if (value === true) {
return ' ' + name;
} else if (value == null || value === false) {
return '';
} else if (typeof value === 'object') {
if (name.substring(0, 6) === 'data-_') {
value = warp10.stringify(value);
} else {
value = JSON.stringify(value);
}
return ' ' + name + "='" + escapeString(value, stringifiedAttrTest, stringifiedAttrReplace) + "'";
} else {
return ' ' + name + '=' + value; // number (doesn't need quotes)
}
}
module.exports = attr;

View File

@ -0,0 +1,16 @@
var attrHelper = require('./helper-attr');
function attrs(arg) {
if (typeof arg === 'object') {
var out = '';
for (var attrName in arg) {
out += attrHelper(attrName, arg[attrName]);
}
return out;
} else if (typeof arg === 'string') {
return arg;
}
return '';
}
module.exports = attrs;

View File

@ -0,0 +1,5 @@
var Template = require('./Template');
module.exports = function(path, renderFunc) {
return new Template(path, renderFunc);
};

View File

@ -1,82 +1,22 @@
'use strict';
var warp10 = require('warp10');
var extend = require('raptor-util/extend');
var STYLE_ATTR = 'style';
var CLASS_ATTR = 'class';
var escapeEndingScriptTagRegExp = /<\//g;
var elTest = /[&<]/;
var elTestReplace = /[&<]/g;
var attrTest = /[&<\"\n]/;
var attrReplace = /[&<\"\n]/g;
var stringifiedAttrTest = /[&\'\n]/;
var stringifiedAttrReplace = /[&\'\n]/g;
var escape = require('./escape');
var escapeXml = escape.escapeXml;
var escapeXmlAttr = escape.escapeXmlAttr;
var attrHelper = require('./helper-attr');
var attrsHelper = require('./helper-attrs');
var classList;
var replacements = {
'<': '&lt;',
'&': '&amp;',
'"': '&quot;',
'\'': '&#39;',
'\n': '&#10;' //Preserve new lines so that they don't get normalized as space
};
function replaceChar(match) {
return replacements[match];
}
function escapeStr(str, regexpTest, regexpReplace) {
return regexpTest.test(str) ? str.replace(regexpReplace, replaceChar) : str;
}
function escapeXmlHelper(value, regexpTest, regexpReplace) {
// check for most common case first
if (typeof value === 'string') {
return escapeStr(value, regexpTest, regexpReplace);
} else if (value == null) {
return '';
} else if (typeof value === 'object') {
var safeHTML = value.safeHTML;
if (safeHTML != null) {
return value.safeHTML;
} else {
return '';
}
} else if (value === true || value === false || typeof value === 'number') {
return value.toString();
}
return escapeStr(value.toString(), regexpTest, regexpReplace);
}
function escapeXml(value) {
return escapeXmlHelper(value, elTest, elTestReplace);
}
function escapeXmlAttr(value) {
return escapeXmlHelper(value, attrTest, attrReplace);
}
function attr(name, value, shouldEscape) {
if (typeof value === 'string') {
return ' ' + name + '="' + (shouldEscape !== false ? escapeStr(value, attrTest, attrReplace) : value) + '"';
} else if (value === true) {
return ' ' + name;
} else if (value == null || value === false) {
return '';
} else if (typeof value === 'object') {
if (name.substring(0, 6) === 'data-_') {
value = warp10.stringify(value);
} else {
value = JSON.stringify(value);
}
return ' ' + name + "='" + escapeStr(value, stringifiedAttrTest, stringifiedAttrReplace) + "'";
} else {
return ' ' + name + '=' + value; // number (doesn't need quotes)
}
}
/**
@ -113,24 +53,13 @@ exports.xs = function escapeScriptHelper(val) {
* Internal method to render a single HTML attribute
* @private
*/
exports.a = attr;
exports.a = attrHelper;
/**
* Internal method to render multiple HTML attributes based on the properties of an object
* @private
*/
exports.as = function(arg) {
if (typeof arg === 'object') {
var out = '';
for (var attrName in arg) {
out += attr(attrName, arg[attrName]);
}
return out;
} else if (typeof arg === 'string') {
return arg;
}
return '';
};
exports.as = attrsHelper;
/**
* Internal helper method to handle the "style" attribute. The value can either
@ -145,7 +74,7 @@ exports.sa = function(style) {
}
if (typeof style === 'string') {
return attr(STYLE_ATTR, style, false);
return attrHelper(STYLE_ATTR, style, false);
} else if (typeof style === 'object') {
var parts = [];
for (var name in style) {
@ -156,7 +85,7 @@ exports.sa = function(style) {
}
}
}
return parts ? attr(STYLE_ATTR, parts.join(';'), false) : '';
return parts ? attrHelper(STYLE_ATTR, parts.join(';'), false) : '';
} else {
return '';
}
@ -176,9 +105,9 @@ exports.ca = function(classNames) {
}
if (typeof classNames === 'string') {
return attr(CLASS_ATTR, classNames, false);
return attrHelper(CLASS_ATTR, classNames, false);
} else {
return attr(CLASS_ATTR, classList(classNames), false);
return attrHelper(CLASS_ATTR, classList(classNames), false);
}
};
@ -186,6 +115,4 @@ exports.ca = function(classNames) {
var commonHelpers = require('../helpers');
classList = commonHelpers.cl;
extend(exports, commonHelpers);
exports.inline = require('./')._inline;
extend(exports, commonHelpers);

View File

@ -1,6 +1,8 @@
'use strict';
require('../env-init');
var AsyncStream = require('./AsyncStream');
var makeRenderable = require('../renderable');
var Template = require('./Template');
/**
* Method is for internal usage only. This method
@ -12,38 +14,17 @@ exports.t = function createTemplate(path) {
return new Template(path);
};
function Template(path, func, options) {
this.path = path;
this._ = func;
this._shouldBuffer = !options || options.shouldBuffer !== false;
this.meta = undefined;
}
function createOut(globalData, parent, state, buffer) {
return new AsyncStream(globalData, parent, state, buffer);
}
Template.prototype = {
createOut: createOut,
stream: function() {
throw new Error('You must require("marko/stream")');
}
};
makeRenderable(Template.prototype);
exports.createWriter = function(writer) {
return new AsyncStream(null, writer);
};
exports._inline = function(filename, renderFunc) {
return new Template(filename, renderFunc);
};
exports.Template = Template;
exports.createOut = createOut;
exports.$__createOut = createOut;
exports.AsyncStream = AsyncStream;
exports.enableAsyncStackTrace = AsyncStream.enableAsyncStackTrace;
exports.helpers = require('./helpers');
require('../').$__setRuntime(exports);
require('../createOut').$__setCreateOut(createOut);

View File

@ -1,25 +1,6 @@
'use strict';
var documentProvider = require('./document-provider');
require('./env-init'); // no-op in the browser, but enables extra features on the server
var runtime;
function setRuntime(_runtime) {
runtime = _runtime;
}
exports.$__setRuntime = setRuntime;
function createOut(globalData) {
return runtime.createOut(globalData);
}
/**
* Used to associate a DOM Document with marko. This is needed
* to parse HTML fragments to insert into the VDOM tree.
*/
exports.setDocument = function(newDoc) {
documentProvider.$__document = newDoc;
};
exports.createOut = createOut;
exports.createOut = require('./createOut');
exports.load = require('./loader');
exports.events = require('./events');

View File

@ -1,4 +1,4 @@
'use strict';
module.exports = function load(templatePath) {
throw Error('Template not found: ' + templatePath);
throw Error('Not found: ' + templatePath);
};

View File

@ -27,34 +27,6 @@ var markoCompiler = require('../../compiler');
var cwd = process.cwd();
var fsOptions = {encoding: 'utf8'};
if (process.env.hasOwnProperty('MARKO_HOT_RELOAD')) {
require('../../hot-reload').enable();
}
// If process was launched with browser refresh then automatically
// enable browser-refresh
require('../../browser-refresh').enable();
function fixFlush() {
try {
var OutgoingMessage = require('http').OutgoingMessage;
if (OutgoingMessage.prototype.flush && OutgoingMessage.prototype.flush.toString().indexOf('deprecated') !== -1) {
// Yes, we are monkey-patching http. This method should never have been added and it was introduced on
// the iojs fork. It was quickly deprecated and I'm 99% sure no one is actually using it.
// See:
// - https://github.com/marko-js/async-writer/issues/3
// - https://github.com/nodejs/node/issues/2920
//
// This method causes problems since marko looks for the flush method and calls it found.
// The `res.flush()` method is introduced by the [compression](https://www.npmjs.com/package/compression)
// middleware, but, otherwise, it should typically not exist.
delete require('http').OutgoingMessage.prototype.flush;
}
} catch(e) {}
}
fixFlush();
function loadSource(templatePath, compiledSrc) {
var templateModulePath = templatePath + '.js';
@ -183,7 +155,4 @@ function doLoad(templatePath, templateSrc, options) {
}
return template;
}
require('../stream');
require('../dependencies');
}

View File

@ -1,5 +1,6 @@
{
"browser": {
"./loader/index.js": "./loader/index-browser.js"
"./loader/index.js": "./loader/index-browser.js",
"./env-init.js": false
}
}

View File

@ -1,9 +1,9 @@
var marko = require('./index');
var defaultCreateOut = require('./createOut');
var extend = require('raptor-util/extend');
module.exports = function(target, renderer) {
var renderFunc = renderer && (renderer.renderer || renderer.render || renderer);
var createOut = target.createOut || renderer.createOut || marko.createOut;
var createOut = target.createOut || renderer.createOut || defaultCreateOut;
return extend(target, {
createOut: createOut,
@ -35,7 +35,7 @@ module.exports = function(target, renderer) {
out.sync();
render(localData, out);
return out.getResult();
return out.$__getResult();
},
/**
@ -61,7 +61,7 @@ module.exports = function(target, renderer) {
var finalData;
var globalData;
var render = renderFunc || this._;
var shouldBuffer = this._shouldBuffer;
var shouldBuffer = this.$__shouldBuffer;
var shouldEnd = true;
if (data) {
@ -73,7 +73,7 @@ module.exports = function(target, renderer) {
finalData = {};
}
if (out && out.isOut){
if (out && out.$__isOut) {
finalOut = out;
shouldEnd = false;
extend(out.global, globalData);
@ -92,12 +92,14 @@ module.exports = function(target, renderer) {
if (callback) {
finalOut
.on('finish', function() {
callback(null, finalOut.getResult());
callback(null, finalOut.$__getResult());
})
.once('error', callback);
}
finalOut.global.template = finalOut.global.template || this;
globalData = finalOut.global;
globalData.template = globalData.template || this;
render(finalData, finalOut);

View File

@ -9,6 +9,7 @@ line to your app:
*/
var stream = require('stream');
var Template = require('./html/Template');
var AsyncStream = require('./html/AsyncStream');
function Readable(template, data, options) {
@ -47,6 +48,6 @@ Readable.prototype = {
require('raptor-util/inherit')(Readable, stream.Readable);
require('./html').Template.prototype.stream = function(data) {
Template.prototype.stream = function(data) {
return new Readable(this, data, this._options);
};

View File

@ -1,15 +1,19 @@
var EventEmitter = require('events-light');
var HTMLElement = require('./HTMLElement');
var DocumentFragment = require('./DocumentFragment');
var Comment = require('./Comment');
var Text = require('./Text');
var virtualizeHTML = require('./virtualizeHTML');
var documentProvider = require('../document-provider');
var vdom = require('./vdom');
var HTMLElement = vdom.$__HTMLElement;
var DocumentFragment = vdom.$__DocumentFragment;
var Comment = vdom.$__Comment;
var Text = vdom.$__Text;
var virtualizeHTML = vdom.$__virtualizeHTML;
var RenderResult = require('../RenderResult');
var defaultDocument = vdom.$__defaultDocument;
var FLAG_FINISHED = 1;
var FLAG_LAST_FIRED = 2;
var EVENT_UPDATE = 'update';
var EVENT_FINISH = 'finish';
function State(tree) {
this.$__remaining = 1;
this.$__events = new EventEmitter();
@ -39,7 +43,8 @@ function AsyncVDOMBuilder(globalData, parentNode, state) {
}
var proto = AsyncVDOMBuilder.prototype = {
isOut: true,
$__isOut: true,
$__document: defaultDocument,
element: function(name, attrs, childCount) {
var element = new HTMLElement(name, attrs, childCount);
@ -87,7 +92,7 @@ var proto = AsyncVDOMBuilder.prototype = {
var parent = this.$__parent;
if (parent) {
var lastChild = parent.lastChild;
if (lastChild && lastChild.nodeType === 3) {
if (lastChild && lastChild.$__Text) {
lastChild.nodeValue += text;
} else {
parent.$__appendChild(new Text(text));
@ -102,7 +107,7 @@ var proto = AsyncVDOMBuilder.prototype = {
html: function(html) {
if (html != null) {
var vdomNode = virtualizeHTML(html, documentProvider.$__document);
var vdomNode = virtualizeHTML(html, this.$__document);
this.node(vdomNode);
}
@ -141,7 +146,7 @@ var proto = AsyncVDOMBuilder.prototype = {
if (!remaining) {
state.$__flags |= FLAG_FINISHED;
state.$__events.emit('finish', this);
state.$__events.emit(EVENT_FINISH, this.$__getResult());
}
return this;
@ -176,23 +181,26 @@ var proto = AsyncVDOMBuilder.prototype = {
},
flush: function() {
var state = this.$__state;
state.$__events.emit('update', this);
var events = this.$__state.$__events;
if (events.listenerCount(EVENT_UPDATE)) {
events.emit(EVENT_UPDATE, new RenderResult(this));
}
},
getOutput: function() {
$__getOutput: function() {
return this.$__state.$__tree;
},
getResult: function() {
$__getResult: function() {
return this.$__result || (this.$__result = new RenderResult(this));
},
on: function(event, callback) {
var state = this.$__state;
if (event === 'finish' && (state.$__flags & FLAG_FINISHED)) {
callback(this);
if (event === EVENT_FINISH && (state.$__flags & FLAG_FINISHED)) {
callback(this.$__getResult());
return this;
}
@ -203,8 +211,8 @@ var proto = AsyncVDOMBuilder.prototype = {
once: function(event, callback) {
var state = this.$__state;
if (event === 'finish' && (state.$__flags & FLAG_FINISHED)) {
callback(this);
if (event === EVENT_FINISH && (state.$__flags & FLAG_FINISHED)) {
callback(this.$__getResult());
return this;
}
@ -267,45 +275,31 @@ var proto = AsyncVDOMBuilder.prototype = {
return this;
},
getNode: function(doc) {
$__getNode: function(doc) {
var node = this.$__node;
if (!node) {
var vdomTree = this.getOutput();
var vdomTree = this.$__getOutput();
if (!doc) {
doc = documentProvider.$__document;
doc = this.$__document;
}
node = vdomTree.actualize(doc);
if (node.nodeType === 11 /* DocumentFragment */) {
var firstChild = node.firstChild;
if (firstChild) {
var nextSibling = firstChild.nextSibling;
if (!nextSibling) {
// If the DocumentFragment only has one child
// then just return that first child as the node
node = firstChild;
}
}
}
this.$__node = node;
node = this.$__node = vdomTree.actualize(doc);
}
return node;
},
toString: function() {
return this.getNode().outerHTML;
return this.$__getNode().outerHTML;
},
then: function(fn, fnErr) {
var out = this;
var promise = new Promise(function(resolve, reject) {
out.on('error', reject);
out.on('finish', function() {
resolve(out.getResult());
});
out.on('error', reject)
.on(EVENT_FINISH, function(result) {
resolve(result);
});
});
return Promise.resolve(promise).then(fn, fnErr);

View File

@ -2,15 +2,15 @@ var Node = require('./Node');
var inherit = require('raptor-util/inherit');
function Comment(value) {
Node.call(this, -1 /* no children */);
this.$__Node(-1 /* no children */);
this.nodeValue = value;
}
Comment.prototype = {
nodeType: 8,
actualize: function(document) {
return document.createComment(this.nodeValue);
actualize: function(doc) {
return doc.createComment(this.nodeValue);
},
$__cloneNode: function() {

View File

@ -9,26 +9,28 @@ function DocumentFragmentClone(other) {
}
function DocumentFragment(documentFragment) {
Node.call(this, null /* childCount */);
this.$__Node(null /* childCount */);
this.namespaceURI = undefined;
}
DocumentFragment.prototype = {
nodeType: 11,
$__DocumentFragment: true,
$__nsAware: true,
$__cloneNode: function() {
return new DocumentFragmentClone(this);
},
actualize: function(document) {
var docFragment = document.createDocumentFragment();
actualize: function(doc) {
var docFragment = doc.createDocumentFragment();
var curChild = this.firstChild;
while(curChild) {
docFragment.appendChild(curChild.actualize(document));
docFragment.appendChild(curChild.actualize(doc));
curChild = curChild.nextSibling;
}

View File

@ -1,12 +1,10 @@
var Node = require('./Node');
var inherit = require('raptor-util/inherit');
var extend = require('raptor-util/extend');
var Text = require('./Text');
var Comment = require('./Comment');
var Node = require('./Node');
var documentProvider = require('../document-provider');
var virtualizeHTML;
var defineProperty = Object.defineProperty;
var NS_XLINK = 'http://www.w3.org/1999/xlink';
var ATTR_XLINK_HREF = 'xlink:href';
var ATTR_HREF = 'href';
var EMPTY_OBJECT = Object.freeze({});
var ATTR_MARKO_CONST = 'data-marko-const';
@ -59,7 +57,7 @@ function HTMLElement(tagName, attrs, childCount, constId) {
break;
}
Node.call(this, childCount);
this.$__Node(childCount);
if (constId) {
if (!attrs) {
@ -68,7 +66,7 @@ function HTMLElement(tagName, attrs, childCount, constId) {
attrs[ATTR_MARKO_CONST] = constId;
}
this.attributes = attrs || EMPTY_OBJECT;
this.$__attributes = attrs || EMPTY_OBJECT;
this.$__isTextArea = isTextArea;
this.namespaceURI = namespaceURI;
this.nodeName = tagName;
@ -77,116 +75,12 @@ function HTMLElement(tagName, attrs, childCount, constId) {
}
HTMLElement.prototype = {
$__HTMLElement: true,
nodeType: 1,
$__nsAware: true,
assignAttributes: function(targetNode) {
var attrs = this.attributes;
var attrName;
var i;
// We use expando properties to associate the previous HTML
// attributes provided as part of the VDOM node with the
// real HTMLElement DOM node. When diffing attributes,
// we only use our internal representation of the attributes.
// When diffing for the first time it's possible that the
// real HTMLElement node will not have the expando property
// so we build the attribute map from the expando property
var oldAttrs = targetNode._vattrs;
if (oldAttrs) {
if (oldAttrs === attrs) {
// For constant attributes the same object will be provided
// every render and we can use that to our advantage to
// not waste time diffing a constant, immutable attribute
// map.
return;
} else {
oldAttrs = removePreservedAttributes(extend({}, oldAttrs));
}
} else {
// We need to build the attribute map from the real attributes
oldAttrs = {};
var oldAttributesList = targetNode.attributes;
for (i = oldAttributesList.length - 1; i >= 0; --i) {
var attr = oldAttributesList[i];
if (attr.specified !== false) {
attrName = attr.name;
var attrNamespaceURI = attr.namespaceURI;
if (attrNamespaceURI === NS_XLINK) {
oldAttrs['xlink:href'] = attr.value;
} else {
oldAttrs[attrName] = attr.value;
}
}
}
// We don't want preserved attributes to show up in either the old
// or new attribute map.
removePreservedAttributes(oldAttrs);
}
// In some cases we only want to set an attribute value for the first
// render or we don't want certain attributes to be touched. To support
// that use case we delete out all of the preserved attributes
// so it's as if they never existed.
var preservedAttrs = attrs['data-preserve-attrs'];
if (preservedAttrs) {
attrs = removePreservedAttributes(extend({}, attrs));
}
// Loop over all of the attributes in the attribute map and compare
// them to the value in the old map. However, if the value is
// null/undefined/false then we want to remove the attribute
for (attrName in attrs) {
var attrValue = attrs[attrName];
if (attrName === 'xlink:href') {
if (attrValue == null || attrValue === false) {
targetNode.removeAttributeNS(NS_XLINK, ATTR_HREF);
} else if (oldAttrs[attrName] !== attrValue) {
targetNode.setAttributeNS(NS_XLINK, ATTR_HREF, attrValue);
}
} else {
if (attrValue == null || attrValue === false) {
targetNode.removeAttribute(attrName);
} else if (oldAttrs[attrName] !== attrValue) {
if (specialAttrRegexp.test(attrName)) {
// Special attributes aren't copied to the real DOM. They are only
// kept in the virtual attributes map
continue;
}
var type = typeof attrValue;
if (type !== 'string') {
attrValue = convertAttrValue(type, attrValue);
}
targetNode.setAttribute(attrName, attrValue);
}
}
}
// If there are any old attributes that are not in the new set of attributes
// then we need to remove those attributes from the target node
for (attrName in oldAttrs) {
if (attrs.hasOwnProperty(attrName) === false) {
if (attrName === 'xlink:href') {
targetNode.removeAttributeNS(NS_XLINK, ATTR_HREF);
} else {
targetNode.removeAttribute(attrName);
}
}
}
targetNode._vattrs = attrs;
},
$__cloneNode: function() {
return new HTMLElementClone(this);
},
@ -208,37 +102,7 @@ HTMLElement.prototype = {
}
},
/**
* Shorthand method for creating and appending a Text node with a given value
* @param {String} value The text value for the new Text node
*/
t: function(value) {
var type = typeof value;
if (type !== 'string') {
if (value == null) {
value = '';
} else if (type === 'object') {
var safeHTML = value.safeHTML;
var vdomNode = virtualizeHTML(safeHTML || '', documentProvider.$__document);
this.$__appendChild(vdomNode);
return this.$__finishChild();
} else {
value = value.toString();
}
}
this.$__appendChild(new Text(value));
return this.$__finishChild();
},
/**
* Shorthand method for creating and appending a Comment node with a given value
* @param {String} value The value for the new Comment node
*/
c: function(value) {
this.$__appendChild(new Comment(value));
return this.$__finishChild();
},
/**
* Shorthand method for creating and appending a static node. The provided node is automatically cloned
@ -251,18 +115,18 @@ HTMLElement.prototype = {
return this.$__finishChild();
},
actualize: function(document) {
actualize: function(doc) {
var el;
var namespaceURI = this.namespaceURI;
var tagName = this.nodeName;
if (namespaceURI) {
el = document.createElementNS(namespaceURI, tagName);
el = doc.createElementNS(namespaceURI, tagName);
} else {
el = document.createElement(tagName);
el = doc.createElement(tagName);
}
var attributes = this.attributes;
var attributes = this.$__attributes;
for (var attrName in attributes) {
var attrValue = attributes[attrName];
@ -279,7 +143,7 @@ HTMLElement.prototype = {
attrValue = convertAttrValue(type, attrValue);
}
if (attrName === 'xlink:href') {
if (attrName === ATTR_XLINK_HREF) {
el.setAttributeNS(NS_XLINK, ATTR_HREF, attrValue);
} else {
el.setAttribute(attrName, attrValue);
@ -288,12 +152,12 @@ HTMLElement.prototype = {
}
if (this.$__isTextArea) {
el.value = this.value;
el.value = this.$__value;
} else {
var curChild = this.firstChild;
while(curChild) {
el.appendChild(curChild.actualize(document));
el.appendChild(curChild.actualize(doc));
curChild = curChild.nextSibling;
}
}
@ -307,25 +171,23 @@ HTMLElement.prototype = {
// We don't care about the namespaces since the there
// is no chance that attributes with the same name will have
// different namespaces
return this.attributes[name] !== undefined;
return this.$__attributes[name] !== undefined;
},
getAttribute: function(name) {
return this.attributes[name];
return this.$__attributes[name];
},
isSameNode: function(otherNode) {
if (otherNode.nodeType !== 1) {
return false;
if (otherNode.nodeType == 1) {
var constId = this.$__constId;
if (constId) {
var otherSameId = otherNode.$__Node ? otherNode.$__constId : otherNode.getAttribute(ATTR_MARKO_CONST);
return constId === otherSameId;
}
}
var constId = this.$__constId;
if (constId) {
var otherSameId = otherNode.actualize ? otherNode.$__constId : otherNode.getAttribute(ATTR_MARKO_CONST);
return constId === otherSameId;
} else {
return false;
}
return false;
}
};
@ -333,39 +195,130 @@ inherit(HTMLElement, Node);
var proto = HTMLElementClone.prototype = HTMLElement.prototype;
Object.defineProperty(proto, 'checked', {
['checked', 'selected', 'disabled'].forEach(function(name) {
defineProperty(proto, name, {
get: function () {
return this.$__attributes[name] !== undefined;
}
});
});
defineProperty(proto, 'id', {
get: function () {
return this.attributes.checked !== undefined;
return this.$__attributes.id;
}
});
Object.defineProperty(proto, 'selected', {
defineProperty(proto, 'value', {
get: function () {
return this.attributes.selected !== undefined;
return this.$__value || this.$__attributes.value;
}
});
Object.defineProperty(proto, 'id', {
get: function () {
return this.attributes.id;
HTMLElement.$__morphAttrs = function(fromEl, toEl) {
var attrs = toEl.$__attributes;
var attrName;
var i;
// We use expando properties to associate the previous HTML
// attributes provided as part of the VDOM node with the
// real HTMLElement DOM node. When diffing attributes,
// we only use our internal representation of the attributes.
// When diffing for the first time it's possible that the
// real HTMLElement node will not have the expando property
// so we build the attribute map from the expando property
var oldAttrs = fromEl._vattrs;
if (oldAttrs) {
if (oldAttrs === attrs) {
// For constant attributes the same object will be provided
// every render and we can use that to our advantage to
// not waste time diffing a constant, immutable attribute
// map.
return;
} else {
oldAttrs = removePreservedAttributes(extend({}, oldAttrs));
}
} else {
// We need to build the attribute map from the real attributes
oldAttrs = {};
var oldAttributesList = fromEl.attributes;
for (i = oldAttributesList.length - 1; i >= 0; --i) {
var attr = oldAttributesList[i];
if (attr.specified !== false) {
attrName = attr.name;
var attrNamespaceURI = attr.namespaceURI;
if (attrNamespaceURI === NS_XLINK) {
oldAttrs[ATTR_XLINK_HREF] = attr.value;
} else {
oldAttrs[attrName] = attr.value;
}
}
}
// We don't want preserved attributes to show up in either the old
// or new attribute map.
removePreservedAttributes(oldAttrs);
}
});
Object.defineProperty(proto, 'value', {
get: function () {
return this.$__value || this.attributes.value;
},
set: function (value) {
this.$__value = value;
// In some cases we only want to set an attribute value for the first
// render or we don't want certain attributes to be touched. To support
// that use case we delete out all of the preserved attributes
// so it's as if they never existed.
var preservedAttrs = attrs['data-preserve-attrs'];
if (preservedAttrs) {
attrs = removePreservedAttributes(extend({}, attrs));
}
});
Object.defineProperty(proto, 'disabled', {
get: function () {
return this.attributes.disabled !== undefined;
// Loop over all of the attributes in the attribute map and compare
// them to the value in the old map. However, if the value is
// null/undefined/false then we want to remove the attribute
for (attrName in attrs) {
var attrValue = attrs[attrName];
if (attrName === ATTR_XLINK_HREF) {
if (attrValue == null || attrValue === false) {
fromEl.removeAttributeNS(NS_XLINK, ATTR_HREF);
} else if (oldAttrs[attrName] !== attrValue) {
fromEl.setAttributeNS(NS_XLINK, ATTR_HREF, attrValue);
}
} else {
if (attrValue == null || attrValue === false) {
fromEl.removeAttribute(attrName);
} else if (oldAttrs[attrName] !== attrValue) {
if (specialAttrRegexp.test(attrName)) {
// Special attributes aren't copied to the real DOM. They are only
// kept in the virtual attributes map
continue;
}
var type = typeof attrValue;
if (type !== 'string') {
attrValue = convertAttrValue(type, attrValue);
}
fromEl.setAttribute(attrName, attrValue);
}
}
}
});
module.exports = HTMLElement;
// If there are any old attributes that are not in the new set of attributes
// then we need to remove those attributes from the target node
for (attrName in oldAttrs) {
if (attrs.hasOwnProperty(attrName) === false) {
if (attrName === ATTR_XLINK_HREF) {
fromEl.removeAttributeNS(NS_XLINK, ATTR_HREF);
} else {
fromEl.removeAttribute(attrName);
}
}
}
virtualizeHTML = require('./virtualizeHTML');
fromEl._vattrs = attrs;
};
module.exports = HTMLElement;

View File

@ -1,7 +1,5 @@
/* jshint newcap:false */
var DocumentFragment;
function assignNamespace(node, namespaceURI) {
node.namespaceURI = namespaceURI;
@ -14,26 +12,27 @@ function assignNamespace(node, namespaceURI) {
}
}
function Node(finalChildCount) {
this.$__finalChildCount = finalChildCount;
this.$__childCount = 0;
this.$__firstChild = undefined;
this.$__lastChild = undefined;
this.$__parentNode = undefined;
this.$__nextSibling = undefined;
}
function Node() {}
Node.prototype = {
removeChildren: function() {
this.$__firstChild = undefined;
$__Node: function(finalChildCount) {
this.$__finalChildCount = finalChildCount;
this.$__childCount = 0;
this.$__firstChild = undefined;
this.$__lastChild = undefined;
this.$__parentNode = undefined;
this.$__nextSibling = undefined;
},
// removeChildren: function() {
// this.$__firstChild = undefined;
// this.$__childCount = 0;
// this.$__lastChild = undefined;
// },
get firstChild() {
var firstChild = this.$__firstChild;
if (firstChild && firstChild.nodeType === 11 /* DocumentFragment */) {
if (firstChild && firstChild.$__DocumentFragment) {
var nestedFirstChild = firstChild.firstChild;
// The first child is a DocumentFragment node.
// If the DocumentFragment node has a first child then we will return that.
@ -45,27 +44,17 @@ Node.prototype = {
return firstChild;
},
get lastChild() {
var lastChild = this.$__lastChild;
if (lastChild && lastChild.nodeType === 11 /* DocumentFragment */) {
return lastChild.lastChild;
}
return lastChild;
},
get nextSibling() {
var nextSibling = this.$__nextSibling;
if (nextSibling) {
if (nextSibling.nodeType === 11 /* DocumentFragment */) {
if (nextSibling.$__DocumentFragment) {
var firstChild = nextSibling.firstChild;
return firstChild || nextSibling.nextSibling;
}
} else {
var parentNode = this.$__parentNode;
if (parentNode && parentNode.nodeType === 11) {
if (parentNode && parentNode.$__DocumentFragment) {
return parentNode.nextSibling;
}
}
@ -73,15 +62,11 @@ Node.prototype = {
return nextSibling;
},
$__appendDocumentFragment: function() {
return this.$__appendChild(new DocumentFragment());
},
$__appendChild: function(child) {
if (this.$__isTextArea) {
if (child.nodeType === 3) {
var currentValue = this.value;
this.value = currentValue ? currentValue + child.nodeValue : child.nodeValue;
if (child.$__Text) {
var childValue = child.nodeValue;
this.$__value = (this.$__value || '') + childValue;
} else {
throw TypeError();
}
@ -135,6 +120,4 @@ Node.prototype = {
// }
};
module.exports = Node;
DocumentFragment = require('./DocumentFragment');
module.exports = Node;

View File

@ -2,15 +2,17 @@ var Node = require('./Node');
var inherit = require('raptor-util/inherit');
function Text(value) {
Node.call(this, -1 /* no children */);
this.$__Node(-1 /* no children */);
this.nodeValue = value;
}
Text.prototype = {
$__Text: true,
nodeType: 3,
actualize: function(document) {
return document.createTextNode(this.nodeValue);
actualize: function(doc) {
return doc.createTextNode(this.nodeValue);
},
$__cloneNode: function() {

View File

@ -0,0 +1,5 @@
var Template = require('./').Template;
module.exports = function(path, renderFunc) {
return new Template(path, renderFunc);
};

View File

@ -0,0 +1,27 @@
/**
* Helper for generating the string for a style attribute
* @param {[type]} style [description]
* @return {[type]} [description]
*/
module.exports = function(style) {
if (!style) {
return null;
}
if (typeof style === 'string') {
return style;
} else if (typeof style === 'object') {
var parts = [];
for (var name in style) {
if (style.hasOwnProperty(name)) {
var value = style[name];
if (value) {
parts.push(name + ':' + value);
}
}
}
return parts ? parts.join(';') : null;
} else {
return null;
}
};

View File

@ -1,7 +1,9 @@
'use strict';
var HTMLElement = require('./HTMLElement');
var Text = require('./Text');
var vdom = require('./vdom');
var HTMLElement = vdom.$__HTMLElement;
var Text = vdom.$__Text;
var commonHelpers = require('../helpers');
var extend = require('raptor-util/extend');
@ -22,34 +24,6 @@ exports.const = function(id) {
};
};
/**
* Helper for generating the string for a style attribute
* @param {[type]} style [description]
* @return {[type]} [description]
*/
exports.sa = function(style) {
if (!style) {
return null;
}
if (typeof style === 'string') {
return style;
} else if (typeof style === 'object') {
var parts = [];
for (var name in style) {
if (style.hasOwnProperty(name)) {
var value = style[name];
if (value) {
parts.push(name + ':' + value);
}
}
}
return parts ? parts.join(';') : null;
} else {
return null;
}
};
/**
* Internal helper method to handle the "class" attribute. The value can either
* be a string, an array or an object. For example:
@ -70,6 +44,4 @@ exports.ca = function(classNames) {
}
};
exports.inline = require('./')._inline;
extend(exports, commonHelpers);

View File

@ -24,18 +24,13 @@ function createOut(globalData, parent, state) {
return new AsyncVDOMBuilder(globalData, parent, state);
}
Template.prototype = {
var Template_prototype = Template.prototype = {
createOut: createOut
};
makeRenderable(Template.prototype);
exports._inline = function(filename, renderFunc) {
return new Template(filename, renderFunc);
};
makeRenderable(Template_prototype);
exports.Template = Template;
exports.createOut = createOut;
exports.helpers = require('./helpers');
exports.$__createOut = createOut;
require('../').$__setRuntime(exports);
require('../createOut').$__setCreateOut(createOut);

139
runtime/vdom/vdom.js Normal file
View File

@ -0,0 +1,139 @@
var Node = require('./Node');
var Comment = require('./Comment');
var DocumentFragment = require('./DocumentFragment');
var HTMLElement = require('./HTMLElement');
var Text = require('./Text');
var defaultDocument = typeof document != 'undefined' && document;
var specialHtmlRegexp = /[&<]/;
var range;
function virtualizeChildNodes(node, vdomParent) {
var curChild = node.firstChild;
while(curChild) {
vdomParent.$__appendChild(virtualize(curChild));
curChild = curChild.nextSibling;
}
}
function virtualize(node) {
switch(node.nodeType) {
case 1:
var attributes = node.attributes;
var attrCount = attributes.length;
var attrs;
if (attrCount) {
attrs = {};
for (var i=0; i<attrCount; i++) {
var attr = attributes[i];
var attrName;
if (attr.namespaceURI === 'http://www.w3.org/1999/xlink' && attr.localName === 'href') {
attrName = 'xlink:href';
} else {
attrName = attr.name;
}
attrs[attrName] = attr.value;
}
}
var vdomEL = new HTMLElement(node.nodeName, attrs);
if (vdomEL.$__isTextArea) {
vdomEL.$__value = node.value;
} else {
virtualizeChildNodes(node, vdomEL);
}
return vdomEL;
case 3:
return new Text(node.nodeValue);
case 8:
return new Comment(node.nodeValue);
case 11:
var vdomDocFragment = new DocumentFragment();
virtualizeChildNodes(node, vdomDocFragment);
return vdomDocFragment;
}
}
function virtualizeHTML(html, doc) {
if (!specialHtmlRegexp.test(html)) {
return new Text(html);
}
if (!range && doc.createRange) {
range = doc.createRange();
range.selectNode(doc.body);
}
var vdomFragment;
var fragment;
if (range && range.createContextualFragment) {
fragment = range.createContextualFragment(html);
vdomFragment = virtualize(fragment);
} else {
var container = doc.createElement('body');
container.innerHTML = html;
vdomFragment = new DocumentFragment();
var curChild = container.firstChild;
while(curChild) {
vdomFragment.$__appendChild(virtualize(curChild));
curChild = curChild.nextSibling;
}
}
return vdomFragment;
}
var Node_prototype = Node.prototype;
/**
* Shorthand method for creating and appending a Text node with a given value
* @param {String} value The text value for the new Text node
*/
Node_prototype.t = function(value) {
var type = typeof value;
if (type !== 'string') {
if (value == null) {
value = '';
} else if (type === 'object') {
var safeHTML = value.safeHTML;
var vdomNode = virtualizeHTML(safeHTML || '', document);
this.$__appendChild(vdomNode);
return this.$__finishChild();
} else {
value = value.toString();
}
}
this.$__appendChild(new Text(value));
return this.$__finishChild();
};
/**
* Shorthand method for creating and appending a Comment node with a given value
* @param {String} value The value for the new Comment node
*/
Node_prototype.c = function(value) {
this.$__appendChild(new Comment(value));
return this.$__finishChild();
};
Node_prototype.$__appendDocumentFragment = function() {
return this.$__appendChild(new DocumentFragment());
};
exports.$__Comment = Comment;
exports.$__DocumentFragment = DocumentFragment;
exports.$__HTMLElement = HTMLElement;
exports.$__Text = Text;
exports.$__virtualize = virtualize;
exports.$__virtualizeHTML = virtualizeHTML;
exports.$__defaultDocument = defaultDocument;

View File

@ -1,57 +0,0 @@
var HTMLElement = require('./HTMLElement');
var DocumentFragment = require('./DocumentFragment');
var Comment = require('./Comment');
var Text = require('./Text');
function virtualizeChildNodes(node, vdomParent) {
var curChild = node.firstChild;
while(curChild) {
vdomParent.$__appendChild(virtualize(curChild));
curChild = curChild.nextSibling;
}
}
function virtualize(node) {
if (node.nodeType === 1) { // HtmlElement node
var attributes = node.attributes;
var attrCount = attributes.length;
var attrs;
if (attrCount) {
attrs = {};
for (var i=0; i<attrCount; i++) {
var attr = attributes[i];
var attrName;
if (attr.namespaceURI === 'http://www.w3.org/1999/xlink' && attr.localName === 'href') {
attrName = 'xlink:href';
} else {
attrName = attr.name;
}
attrs[attrName] = attr.value;
}
}
var vdomEL = new HTMLElement(node.nodeName, attrs);
if (vdomEL.$__isTextArea) {
vdomEL.value = node.value;
} else {
virtualizeChildNodes(node, vdomEL);
}
return vdomEL;
} else if (node.nodeType === 3) { // Text node
return new Text(node.nodeValue);
} else if (node.nodeType === 8) { // Comment node
return new Comment(node.nodeValue);
} else if (node.nodeType === 11) { // DocumentFragment node
var vdomDocFragment = new DocumentFragment();
virtualizeChildNodes(node, vdomDocFragment);
}
}
module.exports = virtualize;

View File

@ -1,37 +0,0 @@
var Text = require('./Text');
var DocumentFragment = require('./DocumentFragment');
var virtualize = require('./virtualize');
var specialHtmlRegexp = /[&<]/;
var range;
module.exports = function virtualizeHTML(html, doc) {
if (!specialHtmlRegexp.test(html)) {
return new Text(html);
}
if (!range && doc.createRange) {
range = doc.createRange();
range.selectNode(doc.body);
}
var vdomFragment;
var fragment;
if (range && range.createContextualFragment) {
fragment = range.createContextualFragment(html);
vdomFragment = virtualize(fragment);
} else {
var container = doc.createElement('body');
container.innerHTML = html;
vdomFragment = new DocumentFragment();
var curChild = container.firstChild;
while(curChild) {
vdomFragment.$__appendChild(virtualize(curChild));
curChild = curChild.nextSibling;
}
}
return vdomFragment;
};

View File

@ -212,8 +212,8 @@ module.exports = function awaitTag(input, out) {
};
asyncOut
.on('finish', function() {
asyncValue.resolve(asyncOut.getOutput());
.on('finish', function(result) {
asyncValue.resolve(result.getOutput());
})
.on('error', function(err) {
asyncValue.reject(err);

View File

@ -19,15 +19,15 @@ module.exports = {
if (input.renderBody) {
input.renderBody(nestedOut);
}
nestedOut.end();
}
nestedOut
.on('error', callback)
.on('finish', function() {
callback(null, nestedOut.getOutput());
.on('finish', function(result) {
callback(null, result.getOutput());
});
nestedOut.end();
}
}, function(err, result) {
if (err) {

View File

@ -50,8 +50,8 @@ describe('AsyncStream', function() {
out.write('3');
out.write('4');
out.end();
out.on('finish', function() {
var output = out.getOutput();
out.on('finish', function(result) {
var output = result.getOutput();
expect(output).to.equal('1234');
done();
});
@ -64,7 +64,7 @@ describe('AsyncStream', function() {
out.write('2');
return out.end().then((result) => {
const output = out.getOutput();
const output = result.getOutput();
expect(output).to.equal('12');
expect(result.toString()).to.equal('12');
});
@ -89,8 +89,8 @@ describe('AsyncStream', function() {
}, 10);
out.end();
out.on('finish', function() {
var output = out.getOutput();
out.on('finish', function(result) {
var output = result.getOutput();
expect(output).to.equal('1234');
done();
});
@ -108,8 +108,8 @@ describe('AsyncStream', function() {
out.write('3');
out.end();
out.on('finish', function() {
var output = out.getOutput();
out.on('finish', function(result) {
var output = result.getOutput();
expect(output).to.equal('123');
done();
});
@ -126,8 +126,8 @@ describe('AsyncStream', function() {
out.write('3');
out.end();
out.on('finish', function() {
var output = out.getOutput();
out.on('finish', function(result) {
var output = result.getOutput();
expect(output).to.equal('123');
done();
});
@ -151,9 +151,9 @@ describe('AsyncStream', function() {
out.write('3');
out.end();
out.on('finish', function() {
out.on('finish', function(result) {
expect(errors.length).to.equal(1);
expect(out.getOutput()).to.equal('13');
expect(result.getOutput()).to.equal('13');
done();
});
});
@ -199,8 +199,8 @@ describe('AsyncStream', function() {
}, 10);
out.end();
out.on('finish', function() {
var output = out.getOutput();
out.on('finish', function(result) {
var output = result.getOutput();
expect(output).to.equal('12a2b2c34a4b4c');
done();
});
@ -209,8 +209,8 @@ describe('AsyncStream', function() {
it('should handle odd execution ordering', function(done) {
var outA = createAsyncStream({ name:'outA' });
outA.on('finish', function() {
var output = outA.getOutput();
outA.on('finish', function(result) {
var output = result.getOutput();
expect(output).to.equal('1234567');
done();
});
@ -255,8 +255,8 @@ describe('AsyncStream', function() {
}, 10);
out.write('3');
out.end();
out.on('finish', function() {
var output = out.getOutput();
out.on('finish', function(result) {
var output = result.getOutput();
expect(errors.length).to.equal(1);
expect(output).to.equal('13');
done();
@ -292,7 +292,7 @@ describe('AsyncStream', function() {
out.catch((err) => {
expect(err).to.be.an('error');
expect(out.getOutput()).to.equal('1');
expect(out.$__getOutput()).to.equal('1');
done();
}).then((data) => {
throw new Error('Should not get here!');
@ -309,8 +309,8 @@ describe('AsyncStream', function() {
.on('error', function(e) {
errors.push(e);
})
.on('finish', function() {
var output = out.getOutput();
.on('finish', function(result) {
var output = result.getOutput();
expect(errors.length).to.equal(1);
expect(output).to.equal('13');
done();
@ -435,8 +435,8 @@ describe('AsyncStream', function() {
out.write('2');
out.end();
out.on('finish', function() {
var output = out.getOutput();
out.on('finish', function(result) {
var output = result.getOutput();
expect(output).to.equal('1Hello World2');
done();
});
@ -492,8 +492,8 @@ describe('AsyncStream', function() {
out.write('3');
out.end();
out.on('finish', function() {
var output = out.getOutput();
out.on('finish', function(result) {
var output = result.getOutput();
expect(output).to.equal('123');
done();
});
@ -519,9 +519,9 @@ describe('AsyncStream', function() {
out.write('3');
out.end();
out.on('finish', function() {
out.on('finish', function(result) {
expect(lastFiredCount).to.equal(1);
var output = out.getOutput();
var output = result.getOutput();
expect(output).to.equal('123');
done();
});
@ -590,8 +590,8 @@ describe('AsyncStream', function() {
out.write('5');
out.end();
out.on('finish', function() {
var output = out.getOutput();
out.on('finish', function(result) {
var output = result.getOutput();
expect(output).to.equal('12345');
expect(onLastCount).to.equal(2);
expect(lastOutput).to.deep.equal(['a', 'b']);
@ -795,8 +795,8 @@ describe('AsyncStream', function() {
var out = new AsyncStream();
out.name = 'outer';
out.on('finish', function() {
var output = out.getOutput();
out.on('finish', function(result) {
var output = result.getOutput();
expect(output).to.equal('123');
done();
});
@ -820,8 +820,8 @@ describe('AsyncStream', function() {
var out = createAsyncStream({global: { foo: 'bar' }});
out.name = 'outer';
out.on('finish', function() {
var output = out.getOutput();
out.on('finish', function(result) {
var output = result.getOutput();
expect(output).to.equal('123');
done();
});

View File

@ -17,7 +17,7 @@ describe('AsyncVDOMBuilder', function() {
it('sync', function() {
var out = new AsyncVDOMBuilder();
out.element('div', {}, 0);
var tree = out.getOutput();
var tree = out.$__getOutput();
expect(getChildNodes(tree).length).to.equal(1);
});

View File

@ -12,7 +12,7 @@ var autotest = require('./autotest');
var marko = require('../');
var markoCompiler = require('../compiler');
describe('api' , function() {
describe('api (compiler)' , function() {
var autoTestDir = nodePath.join(__dirname, 'autotests/api-compiler');
autotest.scanDir(autoTestDir, function run(dir, helpers, done) {

View File

@ -1,4 +1,4 @@
var marko_template = module.exports = require("marko/vdom").t(__filename);
var marko_template = module.exports = require("marko/vdom").t();
function render(data, out) {
out.t("Hello ");

View File

@ -1,4 +1,4 @@
var marko_template = module.exports = require("marko/vdom").t(__filename);
var marko_template = module.exports = require("marko/vdom").t();
function render(data, out) {
out.t("Hello ");

View File

@ -1,4 +1,4 @@
var marko_template = module.exports = require("marko/vdom").t(__filename);
var marko_template = module.exports = require("marko/vdom").t();
function render(data, out) {
out.t("Hello ");

View File

@ -1,4 +1,4 @@
var marko_template = module.exports = require("marko/vdom").t(__filename);
var marko_template = module.exports = require("marko/vdom").t();
function render(data, out) {
out.t("Hello ");

View File

@ -5,13 +5,13 @@ exports.check = function(marko, markoCompiler, expect, done) {
template.renderToString({
name: 'John'
},
function(err, result, out) {
function(err, html, out) {
if (err) {
return done(err);
}
expect(result.toString()).to.equal(out.getOutput());
expect(result.toString()).to.equal('Hello John!');
expect(html).to.equal(out.getOutput());
expect(html).to.equal('Hello John!');
done();
});
};

View File

@ -5,8 +5,8 @@ exports.check = function(marko, markoCompiler, expect, done) {
var out = runtimeHtml.createWriter();
out
.on('finish', function() {
expect(out.getOutput()).to.equal('Hello John!');
.on('finish', function(result) {
expect(result.getOutput()).to.equal('Hello John!');
done();
})
.on('error', function(e) {

View File

@ -1,6 +1,5 @@
var marko_template = module.exports = require("marko/html").t(__filename),
marko_helpers = require("marko/runtime/html/helpers"),
marko_forEachProp = marko_helpers.fp;
marko_forEachProp = require("marko/runtime/helper-forEachProperty");
function render(data, out) {
marko_forEachProp(myObject, function(k, v) {

View File

@ -1,4 +1,4 @@
var marko_template = module.exports = require("marko/vdom").t(__filename),
var marko_template = module.exports = require("marko/vdom").t(),
include_target_template = require("./include-target.marko"),
marko_helpers = require("marko/runtime/vdom/helpers"),
marko_loadTag = marko_helpers.t,

View File

@ -1,4 +1,4 @@
var marko_template = module.exports = require("marko/vdom").t(__filename),
var marko_template = module.exports = require("marko/vdom").t(),
marko_helpers = require("marko/runtime/vdom/helpers"),
marko_forEach = marko_helpers.f,
marko_createElement = marko_helpers.e,

View File

@ -2,7 +2,7 @@ var marko_template = module.exports = require("marko/html").t(__filename),
marko_helpers = require("marko/runtime/html/helpers"),
marko_loadTag = marko_helpers.t,
custom_tag_data_tag = marko_loadTag(require("./custom-tag-data-tag")),
marko_merge = marko_helpers.m;
marko_merge = require("marko/runtime/helper-merge");
function render(data, out) {
custom_tag_data_tag({

View File

@ -1,7 +1,7 @@
var marko_template = module.exports = require("marko/html").t(__filename),
marko_helpers = require("marko/runtime/html/helpers"),
marko_loadTemplate = marko_helpers.l,
marko_loadTemplate = require("marko/runtime/helper-loadTemplate"),
hello_template = marko_loadTemplate(require.resolve("./hello.marko")),
marko_helpers = require("marko/runtime/html/helpers"),
marko_loadTag = marko_helpers.t,
hello_tag = marko_loadTag(hello_template);

View File

@ -1,7 +1,7 @@
var marko_template = module.exports = require("marko/html").t(__filename),
marko_helpers = require("marko/runtime/html/helpers"),
marko_loadTemplate = marko_helpers.l,
marko_loadTemplate = require("marko/runtime/helper-loadTemplate"),
target_template = marko_loadTemplate(require.resolve("./target.marko")),
marko_helpers = require("marko/runtime/html/helpers"),
marko_loadTag = marko_helpers.t,
include_tag = marko_loadTag(require("marko/taglibs/core/include-tag"));

View File

@ -1,7 +1,7 @@
var marko_template = module.exports = require("marko/html").t(__filename),
marko_helpers = require("marko/runtime/html/helpers"),
marko_loadTemplate = marko_helpers.l,
marko_loadTemplate = require("marko/runtime/helper-loadTemplate"),
test_message_template = marko_loadTemplate(require.resolve("./components/test-message/template.marko")),
marko_helpers = require("marko/runtime/html/helpers"),
marko_loadTag = marko_helpers.t,
test_message_tag = marko_loadTag(test_message_template);

View File

@ -0,0 +1 @@
<div>red - true - false - 0 - 3</div><div>green - false - false - 1 - 3</div><div>blue - false - true - 2 - 3</div>

View File

@ -0,0 +1,5 @@
<for(item in ['red', 'green', 'blue'] | status-var=loop)>
<div>
${item} - ${loop.isFirst()} - ${loop.isLast()} - ${loop.getIndex()} - ${loop.getLength()}
</div>
</for>

View File

@ -0,0 +1 @@
exports.templateData = {};

View File

@ -1,24 +1 @@
exports.templateData = {
"accounts": [
{
"balance": 0,
"balanceFormatted": "$0.00",
"status": "open"
},
{
"balance": 10,
"balanceFormatted": "$10.00",
"status": "closed"
},
{
"balance": -100,
"balanceFormatted": "$-100.00",
"status": "suspended"
},
{
"balance": 999,
"balanceFormatted": "$999.00",
"status": "open"
}
]
};
exports.templateData = {};

View File

@ -1,4 +1,4 @@
var marko_template = module.exports = require("marko/vdom").t(__filename),
var marko_template = module.exports = require("marko/vdom").t(),
marko_helpers = require("marko/runtime/vdom/helpers"),
marko_classList = marko_helpers.cl,
marko_classAttr = marko_helpers.ca;

View File

@ -1,4 +1,4 @@
var marko_template = module.exports = require("marko/vdom").t(__filename);
var marko_template = module.exports = require("marko/vdom").t();
function render(data, out) {
out.e("div", {

View File

@ -1,4 +1,4 @@
var marko_template = module.exports = require("marko/vdom").t(__filename);
var marko_template = module.exports = require("marko/vdom").t();
function render(data, out) {
var attrs = {

View File

@ -1,4 +1,4 @@
var marko_template = module.exports = require("marko/vdom").t(__filename),
var marko_template = module.exports = require("marko/vdom").t(),
marko_helpers = require("marko/runtime/vdom/helpers"),
marko_loadTag = marko_helpers.t,
test_hello_tag = marko_loadTag(require("./tags/test-hello/renderer")),

View File

@ -1,4 +1,4 @@
var marko_template = module.exports = require("marko/vdom").t(__filename),
var marko_template = module.exports = require("marko/vdom").t(),
marko_attrs0 = {
"class": "foo"
};

View File

@ -1,4 +1,4 @@
var marko_template = module.exports = require("marko/vdom").t(__filename);
var marko_template = module.exports = require("marko/vdom").t();
function render(data, out) {
out.t("Hello ");

View File

@ -1,4 +1,4 @@
var marko_template = module.exports = require("marko/vdom").t(__filename),
var marko_template = module.exports = require("marko/vdom").t(),
marko_helpers = require("marko/runtime/vdom/helpers"),
marko_forEach = marko_helpers.f,
marko_createElement = marko_helpers.e,

View File

@ -1,4 +1,4 @@
var marko_template = module.exports = require("marko/vdom").t(__filename),
var marko_template = module.exports = require("marko/vdom").t(),
marko_helpers = require("marko/runtime/vdom/helpers"),
marko_createElement = marko_helpers.e,
marko_const = marko_helpers.const,

View File

@ -1,4 +1,4 @@
var marko_template = module.exports = require("marko/vdom").t(__filename),
var marko_template = module.exports = require("marko/vdom").t(),
marko_helpers = require("marko/runtime/vdom/helpers"),
marko_createElement = marko_helpers.e,
marko_const = marko_helpers.const,

View File

@ -1,4 +1,4 @@
var marko_template = module.exports = require("marko/vdom").t(__filename),
var marko_template = module.exports = require("marko/vdom").t(),
marko_helpers = require("marko/runtime/vdom/helpers"),
marko_loadTag = marko_helpers.t,
test_hello_tag = marko_loadTag(require("./tags/test-hello/renderer"));

View File

@ -1,6 +1,9 @@
module.exports = function(helpers) {
var targetEl = helpers.document.createElement('div');
var virtualEl = helpers.vdom.createElement('div', { class: 'foo', 'xlink:href': 'bar.com' });
virtualEl.assignAttributes(targetEl);
return targetEl;
var morphAttrs = helpers.vdom.HTMLElement.$__morphAttrs;
var fromEl = helpers.document.createElement('div');
var toEl = helpers.vdom.createElement('div', { class: 'foo', 'xlink:href': 'bar.com' });
morphAttrs(fromEl, toEl);
return fromEl;
};

View File

@ -1,3 +0,0 @@
<div>
<h1>
"New child"

View File

@ -1,20 +0,0 @@
var expect = require('chai').expect;
module.exports = function(helpers) {
var div = helpers.vdom.createElement('div', null, 1 /* childCount */)
.e('span', { class: 'bar' }, 0);
expect(div.firstChild.nodeName).to.equal('span');
div.removeChildren();
expect(div.firstChild).to.equal(undefined);
var newChild = helpers.vdom.createElement('h1', null, 1)
.t('New child');
div.$__appendChild(newChild);
expect(div.firstChild).to.equal(newChild);
expect(div.firstChild.nextSibling).to.equal(undefined);
return div;
};

View File

@ -4,9 +4,9 @@ var marko_template = module.exports = require("marko/html").t(__filename),
marko_widgetType = marko_registerWidget("/marko-test$1.0.0/autotests/widgets-compilation/ref/index.marko", function() {
return module.exports;
}),
marko_helpers = require("marko/runtime/html/helpers"),
marko_loadTemplate = marko_helpers.l,
marko_loadTemplate = require("marko/runtime/helper-loadTemplate"),
app_foo_template = marko_loadTemplate(require.resolve("./components/app-foo")),
marko_helpers = require("marko/runtime/html/helpers"),
marko_loadTag = marko_helpers.t,
app_foo_tag = marko_loadTag(app_foo_template),
marko_attr = marko_helpers.a;

View File

@ -1,15 +1,16 @@
'use strict';
const jsdom = require("jsdom").jsdom;
const defaultDocument = jsdom('<html><body></body></html>');
const path = require('path');
const fs = require('fs');
const marko = require('marko');
const fsExtra = require('fs-extra');
const domToHTML = require('./domToHTML');
const domToString = require('./domToString');
const jsdom = require("jsdom").jsdom;
const expect = require('chai').expect;
const defaultDocument = jsdom('<html><body></body></html>');
require('../../').setDocument(defaultDocument); // We need this to parse HTML fragments on the server
function createAsyncVerifier(main, helpers, out) {
@ -141,8 +142,8 @@ module.exports = function runRenderTest(dir, helpers, done, options) {
}
out.on('error', done);
out.on('finish', function() {
var renderOutput = out.getOutput();
out.on('finish', function(result) {
var renderOutput = result.getOutput();
if (isVDOM) {
let vdomTree = renderOutput;
@ -158,6 +159,9 @@ module.exports = function runRenderTest(dir, helpers, done, options) {
}
require('marko/compiler').configure({ output: 'html' });
require('marko/runtime/vdom/AsyncVDOMBuilder').prototype.$__document = defaultDocument;
global.document = defaultDocument;
let htmlTemplatePath = path.join(dir, 'template.marko');
let htmlTemplate = marko.load(htmlTemplatePath);
let htmlMainPath = path.join(dir, 'test.js');

View File

@ -28,7 +28,7 @@ function toHTML(node) {
html += indent + '<' + tagName;
var attributes = el.attributes;
var attributes = el.attributes || el.$__attributes;
var attributesArray = [];
var attrName;

View File

@ -23,7 +23,8 @@ var vdomHelpers = {
},
createDocumentFragment: function() {
return new DocumentFragment();
}
},
HTMLElement: HTMLElement
};
describe('marko-vdom', () => {

View File

@ -1,5 +1,5 @@
var path = require('path');
var virtualize = require('../runtime/vdom/virtualize');
var virtualize = require('../runtime/vdom/vdom').$__virtualize;
var fs = require('fs');
var toHTML = require('./util/toHTML');
var jsdom = require("jsdom").jsdom;

View File

@ -5,6 +5,7 @@ describe('marko-widgets (server)', function() {
require('./util/autotest').runTests(
require('./autotests/widgets-server/autotests.tests'),
function run(testFunc, done) {
require('marko/compiler').configure({ output: 'html' });
var helpers = {};
if (testFunc.length === 1) {

View File

@ -62,11 +62,10 @@ State.prototype = {
}
},
$__set: function(name, value, shouldEnsure, forceDirty, noQueue) {
var self = this;
var rawState = self.$__raw;
var rawState = this.$__raw;
if (shouldEnsure) {
ensure(self, name);
ensure(this, name);
}
if (value === null) {
@ -106,7 +105,7 @@ State.prototype = {
if (clean && noQueue !== true) {
// If we were clean before then we are now dirty so queue
// up the widget for update
updateManager.$__queueWidgetUpdate(self.$__widget);
updateManager.$__queueWidgetUpdate(this.$__widget);
}
},
toJSON: function() {

View File

@ -3,15 +3,22 @@
var domInsert = require('../runtime/dom-insert');
var marko = require('../');
var markoWidgets = require('./');
var getRootEls = require('./getRootEls');
var widgetsUtil = require('./util');
var getWidgetForEl = widgetsUtil.$__getWidgetForEl;
var widgetLookup = widgetsUtil.$__widgetLookup;
var emitLifecycleEvent = widgetsUtil.$__emitLifecycleEvent;
var destroyWidgetForEl = widgetsUtil.$__destroyWidgetForEl;
var destroyElRecursive = widgetsUtil.$__destroyElRecursive;
var getElementById = widgetsUtil.$__getElementById;
var EventEmitter = require('events-light');
var RenderResult = require('../runtime/RenderResult');
var SubscriptionTracker = require('listener-tracker');
var inherit = require('raptor-util/inherit');
var updateManager = require('./update-manager');
var morphdom = require('morphdom');
var widgetLookup = require('./lookup').$__widgets;
var morphAttrs = require('../runtime/vdom/HTMLElement').$__morphAttrs;
var morphdomFactory = require('morphdom/factory');
var morphdom = morphdomFactory(morphAttrs);
var slice = Array.prototype.slice;
@ -24,115 +31,14 @@ var NON_WIDGET_SUBSCRIBE_TO_OPTIONS = {
var emit = EventEmitter.prototype.emit;
var lifecycleEventMethods = {};
['beforeDestroy',
'destroy',
'beforeUpdate',
'update',
'mount',
'render',
'beforeInit',
'afterInit'].forEach(function(eventName) {
lifecycleEventMethods[eventName] = 'on' + eventName.charAt(0).toUpperCase() + eventName.substring(1);
});
function removeListener(eventListenerHandle) {
eventListenerHandle();
}
/**
* This method handles invoking a widget's event handler method
* (if present) while also emitting the event through
* the standard EventEmitter.prototype.emit method.
*
* Special events and their corresponding handler methods
* include the following:
*
* beforeDestroy --> onBeforeDestroy
* destroy --> onDestroy
* beforeUpdate --> onBeforeUpdate
* update --> onUpdate
* render --> onRender
*/
function emitLifecycleEvent(widget, eventType, eventArg) {
var listenerMethod = widget[lifecycleEventMethods[eventType]];
if (listenerMethod) {
listenerMethod.call(widget, eventArg);
}
widget.emit(eventType, eventArg);
}
function removeDOMEventListeners(widget) {
var eventListenerHandles = widget.$__domEventListenerHandles;
if (eventListenerHandles) {
eventListenerHandles.forEach(removeListener);
widget.$__domEventListenerHandles = null;
}
}
function destroyWidgetForEl(el) {
var widgetToDestroy = el._w;
if (widgetToDestroy) {
destroyWidgetHelper(widgetToDestroy);
el._w = null;
while ((widgetToDestroy = widgetToDestroy.$__rootFor)) {
widgetToDestroy.$__rootFor = null;
destroyWidgetHelper(widgetToDestroy);
}
}
}
function destroyElRecursive(el) {
var curChild = el.firstChild;
while(curChild) {
if (curChild.nodeType === 1) {
destroyElRecursive(curChild);
destroyWidgetForEl(curChild);
}
curChild = curChild.nextSibling;
}
}
function destroyWidgetHelper(widget) {
if (widget.$__destroyed) {
return;
}
emitLifecycleEvent(widget, 'beforeDestroy');
widget.$__destroyed = true;
widget.els = null;
widget.el = null;
// Unsubscribe from all DOM events
removeDOMEventListeners(widget);
if (widget.$__subscriptions) {
widget.$__subscriptions.removeAllListeners();
widget.$__subscriptions = null;
}
delete widgetLookup[widget.id];
emitLifecycleEvent(widget, 'destroy');
}
function resetWidget(widget) {
widget.$__newProps = null;
widget.$__state.$__reset();
function removeListener(removeEventListenerHandle) {
removeEventListenerHandle();
}
function hasCompatibleWidget(widgetsContext, existingWidget) {
var id = existingWidget.id;
var newWidgetDef = widgetsContext.$__widgetsById[id];
if (!newWidgetDef) {
return false;
}
return existingWidget.$__type === newWidgetDef.$__type;
return newWidgetDef && existingWidget.$__type == newWidgetDef.$__type;
}
function handleCustomEventWithMethodListener(widget, targetMethodName, args, extraArgs) {
@ -174,8 +80,7 @@ function getElIdHelper(widget, widgetElId, index) {
*/
function processUpdateHandlers(widget, stateChanges, oldState) {
var handlerMethod;
var handlers = [];
var handlers;
for (var propName in stateChanges) {
if (stateChanges.hasOwnProperty(propName)) {
@ -183,11 +88,11 @@ function processUpdateHandlers(widget, stateChanges, oldState) {
handlerMethod = widget[handlerMethodName];
if (handlerMethod) {
handlers.push([propName, handlerMethod]);
(handlers || (handlers=[])).push([propName, handlerMethod]);
} else {
// This state change does not have a state handler so return false
// to force a rerender
return false;
return;
}
}
}
@ -195,30 +100,27 @@ function processUpdateHandlers(widget, stateChanges, oldState) {
// If we got here then all of the changed state properties have
// an update handler or there are no state properties that actually
// changed.
if (handlers) {
// Otherwise, there are handlers for all of the changed properties
// so apply the updates using those handlers
if (!handlers.length) {
return true;
emitLifecycleEvent(widget, 'beforeUpdate');
for (var i=0, len=handlers.length; i<len; i++) {
var handler = handlers[i];
var propertyName = handler[0];
handlerMethod = handler[1];
var newValue = stateChanges[propertyName];
var oldValue = oldState[propertyName];
handlerMethod.call(widget, newValue, oldValue);
}
emitLifecycleEvent(widget, 'update');
widget.$__reset();
}
// Otherwise, there are handlers for all of the changed properties
// so apply the updates using those handlers
emitLifecycleEvent(widget, 'beforeUpdate');
for (var i=0, len=handlers.length; i<len; i++) {
var handler = handlers[i];
var propertyName = handler[0];
handlerMethod = handler[1];
var newValue = stateChanges[propertyName];
var oldValue = oldState[propertyName];
handlerMethod.call(widget, newValue, oldValue);
}
emitLifecycleEvent(widget, 'update');
resetWidget(widget);
return true;
}
@ -229,20 +131,23 @@ var widgetProto;
*
* NOTE: Any methods that are prefixed with an underscore should be considered private!
*/
function Widget(id, document) {
function Widget(id, doc) {
EventEmitter.call(this);
this.id = id;
this.el = null;
this.$__bodyEl = null;
this.$__state = null;
this.$__roots = null;
this.$__subscriptions = null;
this.$__domEventListenerHandles = null;
this.$__destroyed = false;
this.$__customEvents = null;
this.$__scope = null;
this.$__updateQueued = false;
this.$__document = document;
this.el =
this.$__state =
this.$__roots =
this.$__subscriptions =
this.$__domEventListenerHandles =
this.$__customEvents =
this.$__scope =
null;
this.$__destroyed =
this.$__updateQueued =
false;
this.$__document = doc;
}
Widget.prototype = widgetProto = {
@ -253,16 +158,13 @@ Widget.prototype = widgetProto = {
throw TypeError();
}
var tracker = this.$__subscriptions;
if (!tracker) {
this.$__subscriptions = tracker = new SubscriptionTracker();
}
var subscriptions = this.$__subscriptions || (subscriptions = new SubscriptionTracker());
var subscribeToOptions = target.$__isWidget ?
WIDGET_SUBSCRIBE_TO_OPTIONS :
NON_WIDGET_SUBSCRIBE_TO_OPTIONS;
return tracker.subscribeTo(target, subscribeToOptions);
return subscriptions.subscribeTo(target, subscribeToOptions);
},
emit: function(eventType) {
@ -286,19 +188,16 @@ Widget.prototype = widgetProto = {
var doc = this.$__document;
if (widgetElId != null) {
return doc.getElementById(getElIdHelper(this, widgetElId, index));
return getElementById(doc, getElIdHelper(this, widgetElId, index));
} else {
return this.el || doc.getElementById(getElIdHelper(this));
return this.el || getElementById(doc, getElIdHelper(this));
}
},
getEls: function(id) {
var els = [];
var i=0;
while(true) {
var el = this.getEl(id, i);
if (!el) {
break;
}
var i = 0;
var el;
while((el = this.getEl(id, i))) {
els.push(el);
i++;
}
@ -309,18 +208,15 @@ Widget.prototype = widgetProto = {
},
getWidgets: function(id) {
var widgets = [];
var i=0;
while(true) {
var widget = widgetLookup[getElIdHelper(this, id, i)];
if (!widget) {
break;
}
var i = 0;
var widget;
while((widget = widgetLookup[getElIdHelper(this, id, i)])) {
widgets.push(widget);
i++;
}
return widgets;
},
destroy: function () {
destroy: function() {
if (this.$__destroyed) {
return;
}
@ -346,25 +242,52 @@ Widget.prototype = widgetProto = {
}
}
destroyWidgetHelper(this);
this.$__destroyShallow();
},
isDestroyed: function () {
$__destroyShallow: function() {
if (this.$__destroyed) {
return;
}
emitLifecycleEvent(this, 'beforeDestroy');
this.$__destroyed = true;
this.els = null;
this.el = null;
// Unsubscribe from all DOM events
this.$__removeDOMEventListeners();
var subscriptions = this.$__subscriptions;
if (subscriptions) {
subscriptions.removeAllListeners();
this.$__subscriptions = null;
}
delete widgetLookup[this.id];
emitLifecycleEvent(this, 'destroy');
},
isDestroyed: function() {
return this.$__destroyed;
},
get state() {
return this.$__state;
},
set state(value) {
if(!this.$__state && value) {
var state = this.$__state;
if(!state && value) {
this.$__state = new this.$__State(this, value);
} else {
this.$__state.$__replace(value);
state.$__replace(value);
}
},
setState: function(name, value) {
var state = this.$__state;
if (typeof name === 'object') {
if (typeof name == 'object') {
// Merge in the new state with the old state
var newState = name;
for (var k in newState) {
@ -372,16 +295,15 @@ Widget.prototype = widgetProto = {
state.$__set(k, newState[k], true /* ensure:true */);
}
}
return;
} else {
state.$__set(name, value, true /* ensure:true */);
}
state.$__set(name, value, true /* ensure:true */);
},
setStateDirty: function(name, value) {
var state = this.$__state;
if (arguments.length === 1) {
if (arguments.length == 1) {
value = state[name];
}
@ -401,25 +323,26 @@ Widget.prototype = widgetProto = {
* @param {Object} props The widget's new props
*/
setProps: function(newProps) {
if (this.getInitialState) {
if (this.getInitialProps) {
newProps = this.getInitialProps(newProps) || {};
var onInput = this.onInput;
var getInitialState;
if (onInput) {
onInput.call(this, newProps || {});
} else if ((getInitialState = this.getInitialState)) {
var getInitialProps = this.getInitialProps;
if (getInitialProps) {
newProps = getInitialProps.call(this, newProps) || {};
}
var newState = getInitialState.call(this, newProps);
this.$__state.$__replace(newState);
} else {
if (!this.$__newProps) {
updateManager.$__queueWidgetUpdate(this);
}
var newState = this.getInitialState(newProps);
this.replaceState(newState);
return;
}
if (this.onInput) {
this.onInput(newProps || {});
return;
this.$__newProps = newProps;
}
if (!this.$__newProps) {
updateManager.$__queueWidgetUpdate(this);
}
this.$__newProps = newProps;
},
update: function() {
@ -432,18 +355,16 @@ Widget.prototype = widgetProto = {
var state = this.$__state;
if (this.shouldUpdate(newProps, state) === false) {
resetWidget(this);
this.$__reset();
return;
}
if (newProps) {
resetWidget(this);
this.$__reset();
this.rerender(newProps);
return;
}
if (!state.$__dirty) {
// Don't even bother trying to update this widget since it is
// not marked as dirty.
@ -458,7 +379,7 @@ Widget.prototype = widgetProto = {
}
// Reset all internal properties for tracking state changes, etc.
resetWidget(this);
this.$__reset();
},
$__replaceState: function(newState) {
@ -478,19 +399,16 @@ Widget.prototype = widgetProto = {
return this.$__state.$__dirty;
},
$__reset: function(shouldRemoveDOMEventListeners) {
resetWidget(this);
if (shouldRemoveDOMEventListeners) {
removeDOMEventListeners(this);
}
$__reset: function() {
this.$__newProps = null;
this.$__state.$__reset();
},
shouldUpdate: function(newState, newProps) {
return true;
},
doUpdate: function (stateChanges, oldState) {
doUpdate: function() {
this.rerender();
},
@ -500,33 +418,32 @@ Widget.prototype = widgetProto = {
rerender: function(props) {
var self = this;
if (!self.renderer) {
throw Error('No renderer');
}
var renderer = self.renderer;
if (!renderer) {
throw TypeError();
}
var state = self.$__state;
var globalData = {};
globalData.$w = [self, !props && state && state.$__raw];
var fromEls = getRootEls(self, {});
var fromEls = self.$__getRootEls({});
var doc = self.$__document;
updateManager.$__batchUpdate(function() {
var createOut = renderer.createOut || marko.createOut;
var out = createOut(globalData);
out.$__document = self.$__document;
renderer(props, out);
var result = new RenderResult(out);
var targetNode = out.getOutput();
var targetNode = out.$__getOutput();
var widgetsContext = out.global.widgets;
function onNodeDiscarded(node) {
if (node.nodeType === 1) {
if (node.nodeType == 1) {
destroyWidgetForEl(node);
}
}
@ -535,20 +452,6 @@ Widget.prototype = widgetProto = {
var id = fromEl.id;
var existingWidget;
var preservedAttrs = !out.isVDOM && toEl.getAttribute('data-preserve-attrs');
if (preservedAttrs) {
preservedAttrs = preservedAttrs.split(/\s*[,]\s*/);
for (var i=0; i<preservedAttrs.length; i++) {
var preservedAttrName = preservedAttrs[i];
var preservedAttrValue = fromEl.getAttribute(preservedAttrName);
if (preservedAttrValue == null) {
toEl.removeAttribute(preservedAttrName);
} else {
toEl.setAttribute(preservedAttrName, preservedAttrValue);
}
}
}
if (widgetsContext && id) {
var preserved = widgetsContext.$__preserved[id];
@ -558,12 +461,12 @@ Widget.prototype = widgetProto = {
// the morphing will take place when the reused widget updates.
return MORPHDOM_SKIP;
} else {
existingWidget = markoWidgets.getWidgetForEl(fromEl);
existingWidget = getWidgetForEl(fromEl);
if (existingWidget && !hasCompatibleWidget(widgetsContext, existingWidget)) {
// We found a widget in an old DOM node that does not have
// a compatible widget that was rendered so we need to
// destroy the old widget
destroyWidgetHelper(existingWidget);
existingWidget.$__destroyShallow();
}
}
}
@ -612,9 +515,38 @@ Widget.prototype = widgetProto = {
// widget was queued for update and the re-rendered
// before the update occurred then nothing will happen
// at the time of the update.
resetWidget(self);
self.$__reset();
}
});
},
$__getRootEls: function(rootEls) {
var i, len;
var widgetEls = this.els;
for (i=0, len=widgetEls.length; i<len; i++) {
var widgetEl = widgetEls[i];
rootEls[widgetEl.id] = widgetEl;
}
var rootWidgets = this.$__rootWidgets;
if (rootWidgets) {
for (i=0, len=rootWidgets.length; i<len; i++) {
var rootWidget = rootWidgets[i];
rootWidget.$__getRootEls(rootEls);
}
}
return rootEls;
},
$__removeDOMEventListeners: function() {
var eventListenerHandles = this.$__domEventListenerHandles;
if (eventListenerHandles) {
eventListenerHandles.forEach(removeListener);
this.$__domEventListenerHandles = null;
}
}
};
@ -639,7 +571,7 @@ domInsert(
}
return fragment;
} else {
return this.els[0];
return els[0];
}
},
function afterInsert(widget) {

Some files were not shown because too many files have changed in this diff Show More