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); 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; return out.compiledCode;
}, },
uglify: function minifyUglifyJS(src, file) { uglify: function minifyUglifyJS(src, file) {

View File

@ -8,9 +8,9 @@
"build": "npm run bundle --silent && npm run minify --silent", "build": "npm run bundle --silent && npm run minify --silent",
"build-marko": "npm run bundle-marko --silent && node minify.js marko", "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": "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-marko": "NODE_ENV=production rollup -c marko/rollup.config.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-react": "NODE_ENV=production rollup -c react/rollup.config.js",
"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-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", "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", "minify": "node minify.js",
"http-server": "http-server" "http-server": "http-server"
@ -18,6 +18,7 @@
"author": "Patrick Steele-Idem <pnidem@gmail.com>", "author": "Patrick Steele-Idem <pnidem@gmail.com>",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"babel-plugin-transform-es2015-block-scoping": "^6.21.0",
"babel-plugin-transform-react-constant-elements": "^6.9.1", "babel-plugin-transform-react-constant-elements": "^6.9.1",
"babel-plugin-transform-react-jsx": "^6.8.0", "babel-plugin-transform-react-jsx": "^6.8.0",
"babel-preset-es2015": "^6.18.0", "babel-preset-es2015": "^6.18.0",
@ -35,6 +36,12 @@
"preact": "^7.1.0", "preact": "^7.1.0",
"react": "^15.4.1", "react": "^15.4.1",
"react-dom": "^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", "uglify-js": "^2.7.5",
"vue": "^2.1.6", "vue": "^2.1.6",
"vueify": "^9.4.0" "vueify": "^9.4.0"

View File

@ -1,9 +1,10 @@
{ {
"presets": [ "presets": [
"es2015-loose", ["es2015", { "loose": true, "modules": false }],
"stage-0" "stage-0"
], ],
"plugins": [ "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'); var App = require('./components/App');
render( render(

View File

@ -1,5 +1,7 @@
'use strict'; 'use strict';
import { h, Component } from 'preact'; var preact = require('preact');
var h = preact.h;
var Component = preact.Component;
function renderColor(color) { function renderColor(color) {
var style = { 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": [ "presets": [
"es2015-loose", ["es2015", { "loose": true, "modules": false }],
"stage-0", "stage-0",
"react" "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', 'classList': 'cl',
'const': 'const', 'const': 'const',
'createElement': 'e', 'createElement': 'e',
'createInlineTemplate': 'inline', 'createInlineTemplate': {
vdom: { module: 'marko/runtime/vdom/helper-createInlineTemplate'},
html: { module: 'marko/runtime/html/helper-createInlineTemplate'}
},
'escapeXml': 'x', 'escapeXml': 'x',
'escapeXmlAttr': 'xa', 'escapeXmlAttr': 'xa',
'escapeScript': 'xs', 'escapeScript': 'xs',
'forEach': 'f', 'forEach': 'f',
'forEachProp': 'fp', 'forEachProp': { module: 'marko/runtime/helper-forEachProperty' },
'forEachWithStatusVar': 'fv', 'forEachPropStatusVar': { module: 'marko/runtime/helper-forEachPropStatusVar' },
'forRange': 'fr', 'forEachWithStatusVar': { module: 'marko/runtime/helper-forEachWithStatusVar' },
'forRange': { module: 'marko/runtime/helper-forRange' },
'include': 'i', 'include': 'i',
'loadNestedTag': 'n', 'loadNestedTag': { module: 'marko/runtime/helper-loadNestedTag' },
'loadTag': 't', 'loadTag': 't',
'loadTemplate': 'l', 'loadTemplate': { module: 'marko/runtime/helper-loadTemplate' },
'mergeNestedTagsHelper': 'mn', 'mergeNestedTagsHelper': { module: 'marko/runtime/helper-mergeNestedTags' },
'merge': 'm', 'merge': { module: 'marko/runtime/helper-merge' },
'str': 's', 'str': 's',
'styleAttr': 'sa', 'styleAttr': {
vdom: { module: 'marko/runtime/vdom/helper-styleAttr'},
html: 'sa'
},
'createText': 't' 'createText': 't'
}; };
@ -672,15 +679,32 @@ class CompileContext extends EventEmitter {
helper(name) { helper(name) {
var helperIdentifier = this._helpers[name]; var helperIdentifier = this._helpers[name];
if (!helperIdentifier) { if (!helperIdentifier) {
var methodName = helpers[name]; var helperInfo = helpers[name];
if (!methodName) {
if (helperInfo && typeof helperInfo === 'object') {
if (!helperInfo.module) {
helperInfo = helperInfo[this.outputType];
}
}
if (!helperInfo) {
throw new Error('Invalid helper: ' + name); throw new Error('Invalid helper: ' + name);
} }
if (typeof helperInfo === 'string') {
let methodName = helperInfo;
var methodIdentifier = this.builder.identifier(methodName); var methodIdentifier = this.builder.identifier(methodName);
helperIdentifier = this.addStaticVar( helperIdentifier = this.addStaticVar(
'marko_' + name, 'marko_' + name,
this.builder.memberExpression(this.helpersIdentifier, methodIdentifier)); 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; this._helpers[name] = helperIdentifier;
} }

View File

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

View File

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

View File

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

View File

@ -1,6 +1,4 @@
var events = require('./events');
var domInsert = require('./dom-insert'); var domInsert = require('./dom-insert');
var EMPTY_ARRAY = [];
function checkAddedToDOM(result, method) { function checkAddedToDOM(result, method) {
if (!result.$__widgets) { if (!result.$__widgets) {
@ -57,27 +55,24 @@ var proto = RenderResult.prototype = {
afterInsert: function(doc) { afterInsert: function(doc) {
var out = this.$__out; var out = this.$__out;
var widgetsContext = out.global.widgets; var widgetsContext = out.global.widgets;
this.$__widgets = (widgetsContext && widgetsContext.$__widgets) || EMPTY_ARRAY; if (widgetsContext) {
this.$__widgets = widgetsContext.$__widgets;
events.emit('mountNode', { widgetsContext.$__initWidgets(doc);
result: this, }
out: this.$__out,
document: doc
}); // NOTE: This will trigger widgets to be initialized if there were any
return this; return this;
}, },
getNode: function(doc) { getNode: function(doc) {
return this.$__out.getNode(doc); return this.$__out.$__getNode(doc);
}, },
getOutput: function() { getOutput: function() {
return this.$__out.getOutput(); return this.$__out.$__getOutput();
}, },
toString: function() { toString: function() {
return this.$__out.toString(); return this.$__out.toString();
}, },
toJSON: function() { toJSON: function() {
return this.getOutput(); return this.$__out.$__getOutput();
}, },
document: typeof document !== 'undefined' && document 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,6 +1,6 @@
var path = require('path'); var path = require('path');
var resolveFrom = require('resolve-from'); var resolveFrom = require('resolve-from');
var Template = require('./html').Template; var Template = require('./html/Template');
function getDeps(template) { function getDeps(template) {
if (!template.meta && template.template) { if (!template.meta && template.template) {
@ -48,9 +48,13 @@ function resolveDep(dep, root) {
dep = parseDependencyString(dep); dep = parseDependencyString(dep);
} }
if (dep.path) { if (dep.path) {
return Object.assign({}, dep, { path:resolveFrom(root, dep.path) }); return Object.assign({}, dep, {
path: resolveFrom(root, dep.path)
});
} else if (dep.virtualPath) { } else if (dep.virtualPath) {
return Object.assign({}, dep, { virtualPath:path.resolve(root, dep.virtualPath) }); return Object.assign({}, dep, {
virtualPath: path.resolve(root, dep.virtualPath)
});
} else { } else {
return dep; return dep;
} }
@ -58,13 +62,16 @@ function resolveDep(dep, root) {
function parseDependencyString(string) { function parseDependencyString(string) {
var match = /^(?:([\w-]+)(?:\:\s*|\s+))?(.*?(?:\.(\w+))?)$/.exec(string); var match = /^(?:([\w-]+)(?:\:\s*|\s+))?(.*?(?:\.(\w+))?)$/.exec(string);
return { type:match[1]||match[3], path:match[2] }; return {
type: match[1] || match[3],
path: match[2]
};
} }
function dedupeDeps(deps) { function dedupeDeps(deps) {
return deps; return deps;
} }
require('./html').Template.prototype.getDependencies = function() { Template.prototype.getDependencies = function() {
return getDeps(this); 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 extend = require('raptor-util/extend');
var widgetsUtil = require('../widgets/util');
var destroyWidgetForEl = widgetsUtil.$__destroyWidgetForEl;
var destroyElRecursive = widgetsUtil.$__destroyElRecursive;
function resolveEl(el) { function resolveEl(el) {
if (typeof el === 'string') { if (typeof el === 'string') {
@ -13,33 +15,34 @@ function resolveEl(el) {
} }
function beforeRemove(referenceEl) { function beforeRemove(referenceEl) {
events.emit('dom/beforeRemove', { destroyElRecursive(referenceEl);
el: referenceEl destroyWidgetForEl(referenceEl);
});
} }
module.exports = function(target, getEl, afterInsert) { module.exports = function(target, getEl, afterInsert) {
extend(target, { extend(target, {
appendTo: function(referenceEl) { appendTo: function(referenceEl) {
referenceEl = resolveEl(referenceEl);
var el = getEl(this, referenceEl); var el = getEl(this, referenceEl);
resolveEl(referenceEl).appendChild(el); referenceEl.appendChild(el);
return afterInsert(this, referenceEl); return afterInsert(this, referenceEl);
}, },
prependTo: function(referenceEl) { prependTo: function(referenceEl) {
referenceEl = resolveEl(referenceEl);
var el = getEl(this, referenceEl); var el = getEl(this, referenceEl);
referenceEl.insertBefore(el, referenceEl.firstChild || null); referenceEl.insertBefore(el, referenceEl.firstChild || null);
return afterInsert(this, referenceEl); return afterInsert(this, referenceEl);
}, },
replace: function(referenceEl) { replace: function(referenceEl) {
var el = getEl(this, referenceEl);
referenceEl = resolveEl(referenceEl); referenceEl = resolveEl(referenceEl);
var el = getEl(this, referenceEl);
beforeRemove(referenceEl); beforeRemove(referenceEl);
referenceEl.parentNode.replaceChild(el, referenceEl); referenceEl.parentNode.replaceChild(el, referenceEl);
return afterInsert(this, referenceEl); return afterInsert(this, referenceEl);
}, },
replaceChildrenOf: function(referenceEl) { replaceChildrenOf: function(referenceEl) {
var el = getEl(this, referenceEl);
referenceEl = resolveEl(referenceEl); referenceEl = resolveEl(referenceEl);
var el = getEl(this, referenceEl);
var curChild = referenceEl.firstChild; var curChild = referenceEl.firstChild;
while(curChild) { while(curChild) {
@ -53,14 +56,14 @@ module.exports = function(target, getEl, afterInsert) {
return afterInsert(this, referenceEl); return afterInsert(this, referenceEl);
}, },
insertBefore: function(referenceEl) { insertBefore: function(referenceEl) {
var el = getEl(this, referenceEl);
referenceEl = resolveEl(referenceEl); referenceEl = resolveEl(referenceEl);
var el = getEl(this, referenceEl);
referenceEl.parentNode.insertBefore(el, referenceEl); referenceEl.parentNode.insertBefore(el, referenceEl);
return afterInsert(this, referenceEl); return afterInsert(this, referenceEl);
}, },
insertAfter: function(referenceEl) { insertAfter: function(referenceEl) {
var el = getEl(this, referenceEl);
referenceEl = resolveEl(referenceEl); referenceEl = resolveEl(referenceEl);
var el = getEl(this, referenceEl);
el = el; el = el;
var nextSibling = referenceEl.nextSibling; var nextSibling = referenceEl.nextSibling;
var parentNode = referenceEl.parentNode; 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; this.getIndex = getIndex;
} }
module.exports = function forEachPropStatusVar(object, callback) { module.exports = function forEachPropStatusVarHelper(object, callback) {
var keys = Object.keys(object); var keys = Object.keys(object);
var i = 0; 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'; 'use strict';
var isArray = Array.isArray; var isArray = Array.isArray;
function isFunction(arg) {
return typeof arg === 'function';
}
function classListHelper(arg, classNames) { function classListHelper(arg, classNames) {
var len; var len;
@ -40,8 +44,8 @@ function createDeferredRenderer(handler) {
// This is the initial function that will do the rendering. We replace // This is the initial function that will do the rendering. We replace
// the renderer with the actual renderer func on the first render // the renderer with the actual renderer func on the first render
deferredRenderer.renderer = function(input, out) { deferredRenderer.renderer = function(input, out) {
var rendererFunc = handler.renderer || handler.render; var rendererFunc = handler.renderer || handler._ || handler.render;
if (typeof rendererFunc !== 'function') { if (!isFunction(rendererFunc)) {
throw Error('Invalid renderer'); throw Error('Invalid renderer');
} }
// Use the actual renderer from now on // Use the actual renderer from now on
@ -59,14 +63,10 @@ function resolveRenderer(handler) {
return renderer; return renderer;
} }
if (typeof handler === 'function') { if (isFunction(handler)) {
return 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 // 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 // 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 // to the actual renderer(input, out) function right now we lazily
@ -74,26 +74,6 @@ function resolveRenderer(handler) {
return createDeferredRenderer(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 * Internal helper method to prevent null/undefined from being written out
* when writing text that resolves to null/undefined * when writing text that resolves to null/undefined
@ -103,27 +83,6 @@ exports.s = function strHelper(str) {
return (str == null) ? '' : str.toString(); 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 * Internal helper method to handle loops without a status variable
* @private * @private
@ -133,57 +92,12 @@ exports.f = function forEachHelper(array, callback) {
for (var i=0; i<array.length; i++) { for (var i=0; i<array.length; i++) {
callback(array[i]); callback(array[i]);
} }
} else if (typeof array === 'function') { } else if (isFunction(array)) {
// Also allow the first argument to be a custom iterator function // Also allow the first argument to be a custom iterator function
array(callback); 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 * Helper to load a custom tag
*/ */
@ -195,51 +109,6 @@ exports.t = function loadTagHelper(renderer, targetProperty, isRepeated) {
return renderer; 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, ...) * classList(a, b, c, ...)
* Joines a list of class names with spaces. Empty class names are omitted. * Joines a list of class names with spaces. Empty class names are omitted.
@ -250,8 +119,3 @@ exports.mn = function mergeNestedTagsHelper(input) {
exports.cl = function classListHelper() { exports.cl = function classListHelper() {
return classList(arguments); 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 EventEmitter = require('events-light');
var StringWriter = require('./StringWriter'); var StringWriter = require('./StringWriter');
var BufferedWriter = require('./BufferedWriter'); var BufferedWriter = require('./BufferedWriter');
var documentProvider = require('../document-provider'); var defaultDocument = typeof document != 'undefined' && document;
var RenderResult = require('../RenderResult'); var RenderResult = require('../RenderResult');
var helpers; var attrsHelper = require('./helper-attrs');
var escapeXml = require('./escape').escapeXml;
var voidWriter = { write:function(){} }; var voidWriter = { write:function(){} };
@ -37,7 +38,7 @@ function AsyncStream(global, writer, state, shouldBuffer) {
writer = new BufferedWriter(writer); writer = new BufferedWriter(writer);
} }
} else { } else {
writer = originalStream = new StringWriter(events); writer = originalStream = new StringWriter();
} }
state = new State(this, originalStream, writer, events); state = new State(this, originalStream, writer, events);
@ -69,7 +70,8 @@ AsyncStream.enableAsyncStackTrace = function() {
var proto = AsyncStream.prototype = { var proto = AsyncStream.prototype = {
constructor: AsyncStream, constructor: AsyncStream,
isOut: true, $__document: defaultDocument,
$__isOut: true,
sync: function() { sync: function() {
this._sync = true; this._sync = true;
@ -86,15 +88,22 @@ var proto = AsyncStream.prototype = {
return this; return this;
}, },
getOutput: function() { $__getOutput: function() {
return this._state.writer.toString(); return this._state.writer.toString();
}, },
/**
* Legacy...
*/
getOutput: function() {
return this.$__getOutput();
},
toString: function() { toString: function() {
return this._state.writer.toString(); return this._state.writer.toString();
}, },
getResult: function() { $__getResult: function() {
this._result = this._result || new RenderResult(this); this._result = this._result || new RenderResult(this);
return this._result; return this._result;
}, },
@ -228,10 +237,11 @@ var proto = AsyncStream.prototype = {
if (remaining === 0) { if (remaining === 0) {
state.finished = true; state.finished = true;
if (state.writer.end) { if (state.writer.end) {
state.writer.end(); state.writer.end();
} else { } else {
state.events.emit('finish', this); state.events.emit('finish', this.$__getResult());
} }
} }
} }
@ -300,7 +310,7 @@ var proto = AsyncStream.prototype = {
var state = this._state; var state = this._state;
if (event === 'finish' && state.finished) { if (event === 'finish' && state.finished) {
callback(this); callback(this.$__getResult());
return this; return this;
} }
@ -312,7 +322,7 @@ var proto = AsyncStream.prototype = {
var state = this._state; var state = this._state;
if (event === 'finish' && state.finished) { if (event === 'finish' && state.finished) {
callback(this); callback(this.$__getResult());
return this; return this;
} }
@ -420,7 +430,7 @@ var proto = AsyncStream.prototype = {
element: function(tagName, elementAttrs, openTagOnly) { element: function(tagName, elementAttrs, openTagOnly) {
var str = '<' + tagName + var str = '<' + tagName +
helpers.as(elementAttrs) + attrsHelper(elementAttrs) +
'>'; '>';
if (openTagOnly !== true) { if (openTagOnly !== true) {
@ -433,7 +443,7 @@ var proto = AsyncStream.prototype = {
beginElement: function(name, elementAttrs) { beginElement: function(name, elementAttrs) {
var str = '<' + name + var str = '<' + name +
helpers.as(elementAttrs) + attrsHelper(elementAttrs) +
'>'; '>';
this.write(str); this.write(str);
@ -451,17 +461,17 @@ var proto = AsyncStream.prototype = {
}, },
text: function(str) { text: function(str) {
this.write(helpers.x(str)); this.write(escapeXml(str));
}, },
getNode: function(doc) { $__getNode: function(doc) {
var node = this._node; var node = this._node;
var curEl; var curEl;
var newBodyEl; var newBodyEl;
var html = this.getOutput(); var html = this.$__getOutput();
if (!doc) { if (!doc) {
doc = documentProvider.$__document; doc = this.$__document;
} }
if (!node) { if (!node) {
@ -491,8 +501,8 @@ var proto = AsyncStream.prototype = {
var out = this; var out = this;
var promise = new Promise(function(resolve, reject) { var promise = new Promise(function(resolve, reject) {
out.on('error', reject); out.on('error', reject);
out.on('finish', function() { out.on('finish', function(result) {
resolve(out.getResult()); resolve(result);
}); });
}); });
@ -508,5 +518,3 @@ var proto = AsyncStream.prototype = {
proto.w = proto.write; proto.w = proto.write;
module.exports = AsyncStream; module.exports = AsyncStream;
helpers = require('./helpers');

View File

@ -1,19 +1,10 @@
'use strict'; 'use strict';
function StringWriter(events) { function StringWriter() {
this.str = ''; this.str = '';
this.events = events;
this.finished = false;
} }
StringWriter.prototype = { StringWriter.prototype = {
end: function() {
this.finished = true;
if (this.events) {
this.events.emit('finish');
}
},
write: function(str) { write: function(str) {
this.str += str; this.str += str;
return this; 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'; 'use strict';
var warp10 = require('warp10');
var extend = require('raptor-util/extend'); var extend = require('raptor-util/extend');
var STYLE_ATTR = 'style'; var STYLE_ATTR = 'style';
var CLASS_ATTR = 'class'; var CLASS_ATTR = 'class';
var escapeEndingScriptTagRegExp = /<\//g; var escapeEndingScriptTagRegExp = /<\//g;
var elTest = /[&<]/; var escape = require('./escape');
var elTestReplace = /[&<]/g; var escapeXml = escape.escapeXml;
var attrTest = /[&<\"\n]/; var escapeXmlAttr = escape.escapeXmlAttr;
var attrReplace = /[&<\"\n]/g; var attrHelper = require('./helper-attr');
var stringifiedAttrTest = /[&\'\n]/; var attrsHelper = require('./helper-attrs');
var stringifiedAttrReplace = /[&\'\n]/g;
var classList; 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 * Internal method to render a single HTML attribute
* @private * @private
*/ */
exports.a = attr; exports.a = attrHelper;
/** /**
* Internal method to render multiple HTML attributes based on the properties of an object * Internal method to render multiple HTML attributes based on the properties of an object
* @private * @private
*/ */
exports.as = function(arg) { exports.as = attrsHelper;
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 '';
};
/** /**
* Internal helper method to handle the "style" attribute. The value can either * Internal helper method to handle the "style" attribute. The value can either
@ -145,7 +74,7 @@ exports.sa = function(style) {
} }
if (typeof style === 'string') { if (typeof style === 'string') {
return attr(STYLE_ATTR, style, false); return attrHelper(STYLE_ATTR, style, false);
} else if (typeof style === 'object') { } else if (typeof style === 'object') {
var parts = []; var parts = [];
for (var name in style) { 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 { } else {
return ''; return '';
} }
@ -176,9 +105,9 @@ exports.ca = function(classNames) {
} }
if (typeof classNames === 'string') { if (typeof classNames === 'string') {
return attr(CLASS_ATTR, classNames, false); return attrHelper(CLASS_ATTR, classNames, false);
} else { } else {
return attr(CLASS_ATTR, classList(classNames), false); return attrHelper(CLASS_ATTR, classList(classNames), false);
} }
}; };
@ -187,5 +116,3 @@ exports.ca = function(classNames) {
var commonHelpers = require('../helpers'); var commonHelpers = require('../helpers');
classList = commonHelpers.cl; classList = commonHelpers.cl;
extend(exports, commonHelpers); extend(exports, commonHelpers);
exports.inline = require('./')._inline;

View File

@ -1,6 +1,8 @@
'use strict'; 'use strict';
require('../env-init');
var AsyncStream = require('./AsyncStream'); var AsyncStream = require('./AsyncStream');
var makeRenderable = require('../renderable'); var Template = require('./Template');
/** /**
* Method is for internal usage only. This method * Method is for internal usage only. This method
@ -12,38 +14,17 @@ exports.t = function createTemplate(path) {
return new Template(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) { function createOut(globalData, parent, state, buffer) {
return new AsyncStream(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) { exports.createWriter = function(writer) {
return new AsyncStream(null, writer); return new AsyncStream(null, writer);
}; };
exports._inline = function(filename, renderFunc) {
return new Template(filename, renderFunc);
};
exports.Template = Template; exports.Template = Template;
exports.createOut = createOut; exports.$__createOut = createOut;
exports.AsyncStream = AsyncStream; exports.AsyncStream = AsyncStream;
exports.enableAsyncStackTrace = AsyncStream.enableAsyncStackTrace; exports.enableAsyncStackTrace = AsyncStream.enableAsyncStackTrace;
exports.helpers = require('./helpers');
require('../').$__setRuntime(exports); require('../createOut').$__setCreateOut(createOut);

View File

@ -1,25 +1,6 @@
'use strict'; 'use strict';
var documentProvider = require('./document-provider'); require('./env-init'); // no-op in the browser, but enables extra features on the server
var runtime; exports.createOut = require('./createOut');
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.load = require('./loader'); exports.load = require('./loader');
exports.events = require('./events'); exports.events = require('./events');

View File

@ -1,4 +1,4 @@
'use strict'; 'use strict';
module.exports = function load(templatePath) { 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 cwd = process.cwd();
var fsOptions = {encoding: 'utf8'}; 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) { function loadSource(templatePath, compiledSrc) {
var templateModulePath = templatePath + '.js'; var templateModulePath = templatePath + '.js';
@ -184,6 +156,3 @@ function doLoad(templatePath, templateSrc, options) {
return template; return template;
} }
require('../stream');
require('../dependencies');

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,12 +1,10 @@
var Node = require('./Node');
var inherit = require('raptor-util/inherit'); var inherit = require('raptor-util/inherit');
var extend = require('raptor-util/extend'); var extend = require('raptor-util/extend');
var Text = require('./Text'); var defineProperty = Object.defineProperty;
var Comment = require('./Comment');
var Node = require('./Node');
var documentProvider = require('../document-provider');
var virtualizeHTML;
var NS_XLINK = 'http://www.w3.org/1999/xlink'; var NS_XLINK = 'http://www.w3.org/1999/xlink';
var ATTR_XLINK_HREF = 'xlink:href';
var ATTR_HREF = 'href'; var ATTR_HREF = 'href';
var EMPTY_OBJECT = Object.freeze({}); var EMPTY_OBJECT = Object.freeze({});
var ATTR_MARKO_CONST = 'data-marko-const'; var ATTR_MARKO_CONST = 'data-marko-const';
@ -59,7 +57,7 @@ function HTMLElement(tagName, attrs, childCount, constId) {
break; break;
} }
Node.call(this, childCount); this.$__Node(childCount);
if (constId) { if (constId) {
if (!attrs) { if (!attrs) {
@ -68,7 +66,7 @@ function HTMLElement(tagName, attrs, childCount, constId) {
attrs[ATTR_MARKO_CONST] = constId; attrs[ATTR_MARKO_CONST] = constId;
} }
this.attributes = attrs || EMPTY_OBJECT; this.$__attributes = attrs || EMPTY_OBJECT;
this.$__isTextArea = isTextArea; this.$__isTextArea = isTextArea;
this.namespaceURI = namespaceURI; this.namespaceURI = namespaceURI;
this.nodeName = tagName; this.nodeName = tagName;
@ -77,12 +75,148 @@ function HTMLElement(tagName, attrs, childCount, constId) {
} }
HTMLElement.prototype = { HTMLElement.prototype = {
$__HTMLElement: true,
nodeType: 1, nodeType: 1,
$__nsAware: true, $__nsAware: true,
assignAttributes: function(targetNode) { $__cloneNode: function() {
var attrs = this.attributes; return new HTMLElementClone(this);
},
/**
* Shorthand method for creating and appending an HTML element
*
* @param {String} tagName The tag name (e.g. "div")
* @param {int|null} attrCount The number of attributes (or `null` if not known)
* @param {int|null} childCount The number of child nodes (or `null` if not known)
*/
e: function(tagName, attrs, childCount, constId) {
var child = this.$__appendChild(new HTMLElement(tagName, attrs, childCount, constId));
if (childCount === 0) {
return this.$__finishChild();
} else {
return child;
}
},
/**
* Shorthand method for creating and appending a static node. The provided node is automatically cloned
* using a shallow clone since it will be mutated as a result of setting `nextSibling` and `parentNode`.
*
* @param {String} value The value for the new Comment node
*/
n: function(node) {
this.$__appendChild(node.$__cloneNode());
return this.$__finishChild();
},
actualize: function(doc) {
var el;
var namespaceURI = this.namespaceURI;
var tagName = this.nodeName;
if (namespaceURI) {
el = doc.createElementNS(namespaceURI, tagName);
} else {
el = doc.createElement(tagName);
}
var attributes = this.$__attributes;
for (var attrName in attributes) {
var attrValue = attributes[attrName];
if (specialAttrRegexp.test(attrName)) {
continue;
}
if (attrValue !== false && attrValue != null) {
var type = typeof attrValue;
if (type !== 'string') {
// Special attributes aren't copied to the real DOM. They are only
// kept in the virtual attributes map
attrValue = convertAttrValue(type, attrValue);
}
if (attrName === ATTR_XLINK_HREF) {
el.setAttributeNS(NS_XLINK, ATTR_HREF, attrValue);
} else {
el.setAttribute(attrName, attrValue);
}
}
}
if (this.$__isTextArea) {
el.value = this.$__value;
} else {
var curChild = this.firstChild;
while(curChild) {
el.appendChild(curChild.actualize(doc));
curChild = curChild.nextSibling;
}
}
el._vattrs = attributes;
return el;
},
hasAttributeNS: function(namespaceURI, name) {
// 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;
},
getAttribute: function(name) {
return this.$__attributes[name];
},
isSameNode: function(otherNode) {
if (otherNode.nodeType == 1) {
var constId = this.$__constId;
if (constId) {
var otherSameId = otherNode.$__Node ? otherNode.$__constId : otherNode.getAttribute(ATTR_MARKO_CONST);
return constId === otherSameId;
}
}
return false;
}
};
inherit(HTMLElement, Node);
var proto = HTMLElementClone.prototype = HTMLElement.prototype;
['checked', 'selected', 'disabled'].forEach(function(name) {
defineProperty(proto, name, {
get: function () {
return this.$__attributes[name] !== undefined;
}
});
});
defineProperty(proto, 'id', {
get: function () {
return this.$__attributes.id;
}
});
defineProperty(proto, 'value', {
get: function () {
return this.$__value || this.$__attributes.value;
}
});
HTMLElement.$__morphAttrs = function(fromEl, toEl) {
var attrs = toEl.$__attributes;
var attrName; var attrName;
var i; var i;
@ -94,7 +228,7 @@ HTMLElement.prototype = {
// real HTMLElement node will not have the expando property // real HTMLElement node will not have the expando property
// so we build the attribute map from the expando property // so we build the attribute map from the expando property
var oldAttrs = targetNode._vattrs; var oldAttrs = fromEl._vattrs;
if (oldAttrs) { if (oldAttrs) {
if (oldAttrs === attrs) { if (oldAttrs === attrs) {
// For constant attributes the same object will be provided // For constant attributes the same object will be provided
@ -109,7 +243,7 @@ HTMLElement.prototype = {
// We need to build the attribute map from the real attributes // We need to build the attribute map from the real attributes
oldAttrs = {}; oldAttrs = {};
var oldAttributesList = targetNode.attributes; var oldAttributesList = fromEl.attributes;
for (i = oldAttributesList.length - 1; i >= 0; --i) { for (i = oldAttributesList.length - 1; i >= 0; --i) {
var attr = oldAttributesList[i]; var attr = oldAttributesList[i];
@ -117,7 +251,7 @@ HTMLElement.prototype = {
attrName = attr.name; attrName = attr.name;
var attrNamespaceURI = attr.namespaceURI; var attrNamespaceURI = attr.namespaceURI;
if (attrNamespaceURI === NS_XLINK) { if (attrNamespaceURI === NS_XLINK) {
oldAttrs['xlink:href'] = attr.value; oldAttrs[ATTR_XLINK_HREF] = attr.value;
} else { } else {
oldAttrs[attrName] = attr.value; oldAttrs[attrName] = attr.value;
} }
@ -144,15 +278,15 @@ HTMLElement.prototype = {
for (attrName in attrs) { for (attrName in attrs) {
var attrValue = attrs[attrName]; var attrValue = attrs[attrName];
if (attrName === 'xlink:href') { if (attrName === ATTR_XLINK_HREF) {
if (attrValue == null || attrValue === false) { if (attrValue == null || attrValue === false) {
targetNode.removeAttributeNS(NS_XLINK, ATTR_HREF); fromEl.removeAttributeNS(NS_XLINK, ATTR_HREF);
} else if (oldAttrs[attrName] !== attrValue) { } else if (oldAttrs[attrName] !== attrValue) {
targetNode.setAttributeNS(NS_XLINK, ATTR_HREF, attrValue); fromEl.setAttributeNS(NS_XLINK, ATTR_HREF, attrValue);
} }
} else { } else {
if (attrValue == null || attrValue === false) { if (attrValue == null || attrValue === false) {
targetNode.removeAttribute(attrName); fromEl.removeAttribute(attrName);
} else if (oldAttrs[attrName] !== attrValue) { } else if (oldAttrs[attrName] !== attrValue) {
if (specialAttrRegexp.test(attrName)) { if (specialAttrRegexp.test(attrName)) {
@ -167,7 +301,7 @@ HTMLElement.prototype = {
attrValue = convertAttrValue(type, attrValue); attrValue = convertAttrValue(type, attrValue);
} }
targetNode.setAttribute(attrName, attrValue); fromEl.setAttribute(attrName, attrValue);
} }
} }
} }
@ -176,196 +310,15 @@ HTMLElement.prototype = {
// then we need to remove those attributes from the target node // then we need to remove those attributes from the target node
for (attrName in oldAttrs) { for (attrName in oldAttrs) {
if (attrs.hasOwnProperty(attrName) === false) { if (attrs.hasOwnProperty(attrName) === false) {
if (attrName === 'xlink:href') { if (attrName === ATTR_XLINK_HREF) {
targetNode.removeAttributeNS(NS_XLINK, ATTR_HREF); fromEl.removeAttributeNS(NS_XLINK, ATTR_HREF);
} else { } else {
targetNode.removeAttribute(attrName); fromEl.removeAttribute(attrName);
} }
} }
} }
targetNode._vattrs = attrs; fromEl._vattrs = attrs;
},
$__cloneNode: function() {
return new HTMLElementClone(this);
},
/**
* Shorthand method for creating and appending an HTML element
*
* @param {String} tagName The tag name (e.g. "div")
* @param {int|null} attrCount The number of attributes (or `null` if not known)
* @param {int|null} childCount The number of child nodes (or `null` if not known)
*/
e: function(tagName, attrs, childCount, constId) {
var child = this.$__appendChild(new HTMLElement(tagName, attrs, childCount, constId));
if (childCount === 0) {
return this.$__finishChild();
} else {
return child;
}
},
/**
* 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
* using a shallow clone since it will be mutated as a result of setting `nextSibling` and `parentNode`.
*
* @param {String} value The value for the new Comment node
*/
n: function(node) {
this.$__appendChild(node.$__cloneNode());
return this.$__finishChild();
},
actualize: function(document) {
var el;
var namespaceURI = this.namespaceURI;
var tagName = this.nodeName;
if (namespaceURI) {
el = document.createElementNS(namespaceURI, tagName);
} else {
el = document.createElement(tagName);
}
var attributes = this.attributes;
for (var attrName in attributes) {
var attrValue = attributes[attrName];
if (specialAttrRegexp.test(attrName)) {
continue;
}
if (attrValue !== false && attrValue != null) {
var type = typeof attrValue;
if (type !== 'string') {
// Special attributes aren't copied to the real DOM. They are only
// kept in the virtual attributes map
attrValue = convertAttrValue(type, attrValue);
}
if (attrName === 'xlink:href') {
el.setAttributeNS(NS_XLINK, ATTR_HREF, attrValue);
} else {
el.setAttribute(attrName, attrValue);
}
}
}
if (this.$__isTextArea) {
el.value = this.value;
} else {
var curChild = this.firstChild;
while(curChild) {
el.appendChild(curChild.actualize(document));
curChild = curChild.nextSibling;
}
}
el._vattrs = attributes;
return el;
},
hasAttributeNS: function(namespaceURI, name) {
// 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;
},
getAttribute: function(name) {
return this.attributes[name];
},
isSameNode: function(otherNode) {
if (otherNode.nodeType !== 1) {
return false;
}
var constId = this.$__constId;
if (constId) {
var otherSameId = otherNode.actualize ? otherNode.$__constId : otherNode.getAttribute(ATTR_MARKO_CONST);
return constId === otherSameId;
} else {
return false;
}
}
}; };
inherit(HTMLElement, Node);
var proto = HTMLElementClone.prototype = HTMLElement.prototype;
Object.defineProperty(proto, 'checked', {
get: function () {
return this.attributes.checked !== undefined;
}
});
Object.defineProperty(proto, 'selected', {
get: function () {
return this.attributes.selected !== undefined;
}
});
Object.defineProperty(proto, 'id', {
get: function () {
return this.attributes.id;
}
});
Object.defineProperty(proto, 'value', {
get: function () {
return this.$__value || this.attributes.value;
},
set: function (value) {
this.$__value = value;
}
});
Object.defineProperty(proto, 'disabled', {
get: function () {
return this.attributes.disabled !== undefined;
}
});
module.exports = HTMLElement; module.exports = HTMLElement;
virtualizeHTML = require('./virtualizeHTML');

View File

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

View File

@ -2,15 +2,17 @@ var Node = require('./Node');
var inherit = require('raptor-util/inherit'); var inherit = require('raptor-util/inherit');
function Text(value) { function Text(value) {
Node.call(this, -1 /* no children */); this.$__Node(-1 /* no children */);
this.nodeValue = value; this.nodeValue = value;
} }
Text.prototype = { Text.prototype = {
$__Text: true,
nodeType: 3, nodeType: 3,
actualize: function(document) { actualize: function(doc) {
return document.createTextNode(this.nodeValue); return doc.createTextNode(this.nodeValue);
}, },
$__cloneNode: function() { $__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'; 'use strict';
var HTMLElement = require('./HTMLElement'); var vdom = require('./vdom');
var Text = require('./Text'); var HTMLElement = vdom.$__HTMLElement;
var Text = vdom.$__Text;
var commonHelpers = require('../helpers'); var commonHelpers = require('../helpers');
var extend = require('raptor-util/extend'); 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 * Internal helper method to handle the "class" attribute. The value can either
* be a string, an array or an object. For example: * 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); extend(exports, commonHelpers);

View File

@ -24,18 +24,13 @@ function createOut(globalData, parent, state) {
return new AsyncVDOMBuilder(globalData, parent, state); return new AsyncVDOMBuilder(globalData, parent, state);
} }
Template.prototype = { var Template_prototype = Template.prototype = {
createOut: createOut createOut: createOut
}; };
makeRenderable(Template.prototype); makeRenderable(Template_prototype);
exports._inline = function(filename, renderFunc) {
return new Template(filename, renderFunc);
};
exports.Template = Template; exports.Template = Template;
exports.createOut = createOut; exports.$__createOut = createOut;
exports.helpers = require('./helpers');
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 asyncOut
.on('finish', function() { .on('finish', function(result) {
asyncValue.resolve(asyncOut.getOutput()); asyncValue.resolve(result.getOutput());
}) })
.on('error', function(err) { .on('error', function(err) {
asyncValue.reject(err); asyncValue.reject(err);

View File

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

View File

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

View File

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

View File

@ -12,7 +12,7 @@ var autotest = require('./autotest');
var marko = require('../'); var marko = require('../');
var markoCompiler = require('../compiler'); var markoCompiler = require('../compiler');
describe('api' , function() { describe('api (compiler)' , function() {
var autoTestDir = nodePath.join(__dirname, 'autotests/api-compiler'); var autoTestDir = nodePath.join(__dirname, 'autotests/api-compiler');
autotest.scanDir(autoTestDir, function run(dir, helpers, done) { 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) { function render(data, out) {
out.t("Hello "); 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) { function render(data, out) {
out.t("Hello "); 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) { function render(data, out) {
out.t("Hello "); 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) { function render(data, out) {
out.t("Hello "); out.t("Hello ");

View File

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

View File

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

View File

@ -1,6 +1,5 @@
var marko_template = module.exports = require("marko/html").t(__filename), var marko_template = module.exports = require("marko/html").t(__filename),
marko_helpers = require("marko/runtime/html/helpers"), marko_forEachProp = require("marko/runtime/helper-forEachProperty");
marko_forEachProp = marko_helpers.fp;
function render(data, out) { function render(data, out) {
marko_forEachProp(myObject, function(k, v) { 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"), include_target_template = require("./include-target.marko"),
marko_helpers = require("marko/runtime/vdom/helpers"), marko_helpers = require("marko/runtime/vdom/helpers"),
marko_loadTag = marko_helpers.t, 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_helpers = require("marko/runtime/vdom/helpers"),
marko_forEach = marko_helpers.f, marko_forEach = marko_helpers.f,
marko_createElement = marko_helpers.e, 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_helpers = require("marko/runtime/html/helpers"),
marko_loadTag = marko_helpers.t, marko_loadTag = marko_helpers.t,
custom_tag_data_tag = marko_loadTag(require("./custom-tag-data-tag")), 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) { function render(data, out) {
custom_tag_data_tag({ custom_tag_data_tag({

View File

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

View File

@ -1,7 +1,7 @@
var marko_template = module.exports = require("marko/html").t(__filename), var marko_template = module.exports = require("marko/html").t(__filename),
marko_helpers = require("marko/runtime/html/helpers"), marko_loadTemplate = require("marko/runtime/helper-loadTemplate"),
marko_loadTemplate = marko_helpers.l,
target_template = marko_loadTemplate(require.resolve("./target.marko")), target_template = marko_loadTemplate(require.resolve("./target.marko")),
marko_helpers = require("marko/runtime/html/helpers"),
marko_loadTag = marko_helpers.t, marko_loadTag = marko_helpers.t,
include_tag = marko_loadTag(require("marko/taglibs/core/include-tag")); 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), var marko_template = module.exports = require("marko/html").t(__filename),
marko_helpers = require("marko/runtime/html/helpers"), marko_loadTemplate = require("marko/runtime/helper-loadTemplate"),
marko_loadTemplate = marko_helpers.l,
test_message_template = marko_loadTemplate(require.resolve("./components/test-message/template.marko")), test_message_template = marko_loadTemplate(require.resolve("./components/test-message/template.marko")),
marko_helpers = require("marko/runtime/html/helpers"),
marko_loadTag = marko_helpers.t, marko_loadTag = marko_helpers.t,
test_message_tag = marko_loadTag(test_message_template); 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 = { 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"
}
]
};

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_helpers = require("marko/runtime/vdom/helpers"),
marko_classList = marko_helpers.cl, marko_classList = marko_helpers.cl,
marko_classAttr = marko_helpers.ca; 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) { function render(data, out) {
out.e("div", { 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) { function render(data, out) {
var attrs = { 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_helpers = require("marko/runtime/vdom/helpers"),
marko_loadTag = marko_helpers.t, marko_loadTag = marko_helpers.t,
test_hello_tag = marko_loadTag(require("./tags/test-hello/renderer")), 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 = { marko_attrs0 = {
"class": "foo" "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) { function render(data, out) {
out.t("Hello "); 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_helpers = require("marko/runtime/vdom/helpers"),
marko_forEach = marko_helpers.f, marko_forEach = marko_helpers.f,
marko_createElement = marko_helpers.e, 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_helpers = require("marko/runtime/vdom/helpers"),
marko_createElement = marko_helpers.e, marko_createElement = marko_helpers.e,
marko_const = marko_helpers.const, 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_helpers = require("marko/runtime/vdom/helpers"),
marko_createElement = marko_helpers.e, marko_createElement = marko_helpers.e,
marko_const = marko_helpers.const, 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_helpers = require("marko/runtime/vdom/helpers"),
marko_loadTag = marko_helpers.t, marko_loadTag = marko_helpers.t,
test_hello_tag = marko_loadTag(require("./tags/test-hello/renderer")); test_hello_tag = marko_loadTag(require("./tags/test-hello/renderer"));

View File

@ -1,6 +1,9 @@
module.exports = function(helpers) { module.exports = function(helpers) {
var targetEl = helpers.document.createElement('div'); var morphAttrs = helpers.vdom.HTMLElement.$__morphAttrs;
var virtualEl = helpers.vdom.createElement('div', { class: 'foo', 'xlink:href': 'bar.com' });
virtualEl.assignAttributes(targetEl); var fromEl = helpers.document.createElement('div');
return targetEl; 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() { marko_widgetType = marko_registerWidget("/marko-test$1.0.0/autotests/widgets-compilation/ref/index.marko", function() {
return module.exports; return module.exports;
}), }),
marko_helpers = require("marko/runtime/html/helpers"), marko_loadTemplate = require("marko/runtime/helper-loadTemplate"),
marko_loadTemplate = marko_helpers.l,
app_foo_template = marko_loadTemplate(require.resolve("./components/app-foo")), app_foo_template = marko_loadTemplate(require.resolve("./components/app-foo")),
marko_helpers = require("marko/runtime/html/helpers"),
marko_loadTag = marko_helpers.t, marko_loadTag = marko_helpers.t,
app_foo_tag = marko_loadTag(app_foo_template), app_foo_tag = marko_loadTag(app_foo_template),
marko_attr = marko_helpers.a; marko_attr = marko_helpers.a;

View File

@ -1,15 +1,16 @@
'use strict'; 'use strict';
const jsdom = require("jsdom").jsdom;
const defaultDocument = jsdom('<html><body></body></html>');
const path = require('path'); const path = require('path');
const fs = require('fs'); const fs = require('fs');
const marko = require('marko'); const marko = require('marko');
const fsExtra = require('fs-extra'); const fsExtra = require('fs-extra');
const domToHTML = require('./domToHTML'); const domToHTML = require('./domToHTML');
const domToString = require('./domToString'); const domToString = require('./domToString');
const jsdom = require("jsdom").jsdom;
const expect = require('chai').expect; 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) { function createAsyncVerifier(main, helpers, out) {
@ -141,8 +142,8 @@ module.exports = function runRenderTest(dir, helpers, done, options) {
} }
out.on('error', done); out.on('error', done);
out.on('finish', function() { out.on('finish', function(result) {
var renderOutput = out.getOutput(); var renderOutput = result.getOutput();
if (isVDOM) { if (isVDOM) {
let vdomTree = renderOutput; let vdomTree = renderOutput;
@ -158,6 +159,9 @@ module.exports = function runRenderTest(dir, helpers, done, options) {
} }
require('marko/compiler').configure({ output: 'html' }); 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 htmlTemplatePath = path.join(dir, 'template.marko');
let htmlTemplate = marko.load(htmlTemplatePath); let htmlTemplate = marko.load(htmlTemplatePath);
let htmlMainPath = path.join(dir, 'test.js'); let htmlMainPath = path.join(dir, 'test.js');

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -3,15 +3,22 @@
var domInsert = require('../runtime/dom-insert'); var domInsert = require('../runtime/dom-insert');
var marko = require('../'); var marko = require('../');
var markoWidgets = require('./'); var widgetsUtil = require('./util');
var getRootEls = require('./getRootEls'); 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 EventEmitter = require('events-light');
var RenderResult = require('../runtime/RenderResult'); var RenderResult = require('../runtime/RenderResult');
var SubscriptionTracker = require('listener-tracker'); var SubscriptionTracker = require('listener-tracker');
var inherit = require('raptor-util/inherit'); var inherit = require('raptor-util/inherit');
var updateManager = require('./update-manager'); var updateManager = require('./update-manager');
var morphdom = require('morphdom'); var morphAttrs = require('../runtime/vdom/HTMLElement').$__morphAttrs;
var widgetLookup = require('./lookup').$__widgets; var morphdomFactory = require('morphdom/factory');
var morphdom = morphdomFactory(morphAttrs);
var slice = Array.prototype.slice; var slice = Array.prototype.slice;
@ -24,115 +31,14 @@ var NON_WIDGET_SUBSCRIBE_TO_OPTIONS = {
var emit = EventEmitter.prototype.emit; var emit = EventEmitter.prototype.emit;
var lifecycleEventMethods = {}; function removeListener(removeEventListenerHandle) {
removeEventListenerHandle();
['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 hasCompatibleWidget(widgetsContext, existingWidget) { function hasCompatibleWidget(widgetsContext, existingWidget) {
var id = existingWidget.id; var id = existingWidget.id;
var newWidgetDef = widgetsContext.$__widgetsById[id]; var newWidgetDef = widgetsContext.$__widgetsById[id];
if (!newWidgetDef) { return newWidgetDef && existingWidget.$__type == newWidgetDef.$__type;
return false;
}
return existingWidget.$__type === newWidgetDef.$__type;
} }
function handleCustomEventWithMethodListener(widget, targetMethodName, args, extraArgs) { function handleCustomEventWithMethodListener(widget, targetMethodName, args, extraArgs) {
@ -174,8 +80,7 @@ function getElIdHelper(widget, widgetElId, index) {
*/ */
function processUpdateHandlers(widget, stateChanges, oldState) { function processUpdateHandlers(widget, stateChanges, oldState) {
var handlerMethod; var handlerMethod;
var handlers = []; var handlers;
for (var propName in stateChanges) { for (var propName in stateChanges) {
if (stateChanges.hasOwnProperty(propName)) { if (stateChanges.hasOwnProperty(propName)) {
@ -183,11 +88,11 @@ function processUpdateHandlers(widget, stateChanges, oldState) {
handlerMethod = widget[handlerMethodName]; handlerMethod = widget[handlerMethodName];
if (handlerMethod) { if (handlerMethod) {
handlers.push([propName, handlerMethod]); (handlers || (handlers=[])).push([propName, handlerMethod]);
} else { } else {
// This state change does not have a state handler so return false // This state change does not have a state handler so return false
// to force a rerender // to force a rerender
return false; return;
} }
} }
} }
@ -195,11 +100,7 @@ function processUpdateHandlers(widget, stateChanges, oldState) {
// If we got here then all of the changed state properties have // If we got here then all of the changed state properties have
// an update handler or there are no state properties that actually // an update handler or there are no state properties that actually
// changed. // changed.
if (handlers) {
if (!handlers.length) {
return true;
}
// Otherwise, there are handlers for all of the changed properties // Otherwise, there are handlers for all of the changed properties
// so apply the updates using those handlers // so apply the updates using those handlers
@ -217,7 +118,8 @@ function processUpdateHandlers(widget, stateChanges, oldState) {
emitLifecycleEvent(widget, 'update'); emitLifecycleEvent(widget, 'update');
resetWidget(widget); widget.$__reset();
}
return true; return true;
} }
@ -229,20 +131,23 @@ var widgetProto;
* *
* NOTE: Any methods that are prefixed with an underscore should be considered private! * NOTE: Any methods that are prefixed with an underscore should be considered private!
*/ */
function Widget(id, document) { function Widget(id, doc) {
EventEmitter.call(this); EventEmitter.call(this);
this.id = id; this.id = id;
this.el = null; this.el =
this.$__bodyEl = null; this.$__state =
this.$__state = null; this.$__roots =
this.$__roots = null; this.$__subscriptions =
this.$__subscriptions = null; this.$__domEventListenerHandles =
this.$__domEventListenerHandles = null; this.$__customEvents =
this.$__destroyed = false; this.$__scope =
this.$__customEvents = null; null;
this.$__scope = null;
this.$__updateQueued = false; this.$__destroyed =
this.$__document = document; this.$__updateQueued =
false;
this.$__document = doc;
} }
Widget.prototype = widgetProto = { Widget.prototype = widgetProto = {
@ -253,16 +158,13 @@ Widget.prototype = widgetProto = {
throw TypeError(); throw TypeError();
} }
var tracker = this.$__subscriptions; var subscriptions = this.$__subscriptions || (subscriptions = new SubscriptionTracker());
if (!tracker) {
this.$__subscriptions = tracker = new SubscriptionTracker();
}
var subscribeToOptions = target.$__isWidget ? var subscribeToOptions = target.$__isWidget ?
WIDGET_SUBSCRIBE_TO_OPTIONS : WIDGET_SUBSCRIBE_TO_OPTIONS :
NON_WIDGET_SUBSCRIBE_TO_OPTIONS; NON_WIDGET_SUBSCRIBE_TO_OPTIONS;
return tracker.subscribeTo(target, subscribeToOptions); return subscriptions.subscribeTo(target, subscribeToOptions);
}, },
emit: function(eventType) { emit: function(eventType) {
@ -286,19 +188,16 @@ Widget.prototype = widgetProto = {
var doc = this.$__document; var doc = this.$__document;
if (widgetElId != null) { if (widgetElId != null) {
return doc.getElementById(getElIdHelper(this, widgetElId, index)); return getElementById(doc, getElIdHelper(this, widgetElId, index));
} else { } else {
return this.el || doc.getElementById(getElIdHelper(this)); return this.el || getElementById(doc, getElIdHelper(this));
} }
}, },
getEls: function(id) { getEls: function(id) {
var els = []; var els = [];
var i = 0; var i = 0;
while(true) { var el;
var el = this.getEl(id, i); while((el = this.getEl(id, i))) {
if (!el) {
break;
}
els.push(el); els.push(el);
i++; i++;
} }
@ -310,11 +209,8 @@ Widget.prototype = widgetProto = {
getWidgets: function(id) { getWidgets: function(id) {
var widgets = []; var widgets = [];
var i = 0; var i = 0;
while(true) { var widget;
var widget = widgetLookup[getElIdHelper(this, id, i)]; while((widget = widgetLookup[getElIdHelper(this, id, i)])) {
if (!widget) {
break;
}
widgets.push(widget); widgets.push(widget);
i++; i++;
} }
@ -346,8 +242,34 @@ Widget.prototype = widgetProto = {
} }
} }
destroyWidgetHelper(this); this.$__destroyShallow();
}, },
$__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() { isDestroyed: function() {
return this.$__destroyed; return this.$__destroyed;
}, },
@ -355,16 +277,17 @@ Widget.prototype = widgetProto = {
return this.$__state; return this.$__state;
}, },
set state(value) { set state(value) {
if(!this.$__state && value) { var state = this.$__state;
if(!state && value) {
this.$__state = new this.$__State(this, value); this.$__state = new this.$__State(this, value);
} else { } else {
this.$__state.$__replace(value); state.$__replace(value);
} }
}, },
setState: function(name, value) { setState: function(name, value) {
var state = this.$__state; var state = this.$__state;
if (typeof name === 'object') { if (typeof name == 'object') {
// Merge in the new state with the old state // Merge in the new state with the old state
var newState = name; var newState = name;
for (var k in newState) { for (var k in newState) {
@ -372,16 +295,15 @@ Widget.prototype = widgetProto = {
state.$__set(k, newState[k], true /* ensure:true */); 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) { setStateDirty: function(name, value) {
var state = this.$__state; var state = this.$__state;
if (arguments.length === 1) { if (arguments.length == 1) {
value = state[name]; value = state[name];
} }
@ -401,25 +323,26 @@ Widget.prototype = widgetProto = {
* @param {Object} props The widget's new props * @param {Object} props The widget's new props
*/ */
setProps: function(newProps) { setProps: function(newProps) {
if (this.getInitialState) { var onInput = this.onInput;
if (this.getInitialProps) { var getInitialState;
newProps = this.getInitialProps(newProps) || {};
}
var newState = this.getInitialState(newProps);
this.replaceState(newState);
return;
}
if (this.onInput) { if (onInput) {
this.onInput(newProps || {}); onInput.call(this, newProps || {});
return; } 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) { if (!this.$__newProps) {
updateManager.$__queueWidgetUpdate(this); updateManager.$__queueWidgetUpdate(this);
} }
this.$__newProps = newProps; this.$__newProps = newProps;
}
}, },
update: function() { update: function() {
@ -432,18 +355,16 @@ Widget.prototype = widgetProto = {
var state = this.$__state; var state = this.$__state;
if (this.shouldUpdate(newProps, state) === false) { if (this.shouldUpdate(newProps, state) === false) {
resetWidget(this); this.$__reset();
return; return;
} }
if (newProps) { if (newProps) {
resetWidget(this); this.$__reset();
this.rerender(newProps); this.rerender(newProps);
return; return;
} }
if (!state.$__dirty) { if (!state.$__dirty) {
// Don't even bother trying to update this widget since it is // Don't even bother trying to update this widget since it is
// not marked as dirty. // not marked as dirty.
@ -458,7 +379,7 @@ Widget.prototype = widgetProto = {
} }
// Reset all internal properties for tracking state changes, etc. // Reset all internal properties for tracking state changes, etc.
resetWidget(this); this.$__reset();
}, },
$__replaceState: function(newState) { $__replaceState: function(newState) {
@ -478,19 +399,16 @@ Widget.prototype = widgetProto = {
return this.$__state.$__dirty; return this.$__state.$__dirty;
}, },
$__reset: function(shouldRemoveDOMEventListeners) { $__reset: function() {
resetWidget(this); this.$__newProps = null;
this.$__state.$__reset();
if (shouldRemoveDOMEventListeners) {
removeDOMEventListeners(this);
}
}, },
shouldUpdate: function(newState, newProps) { shouldUpdate: function(newState, newProps) {
return true; return true;
}, },
doUpdate: function (stateChanges, oldState) { doUpdate: function() {
this.rerender(); this.rerender();
}, },
@ -500,33 +418,32 @@ Widget.prototype = widgetProto = {
rerender: function(props) { rerender: function(props) {
var self = this; var self = this;
if (!self.renderer) {
throw Error('No renderer');
}
var renderer = self.renderer; var renderer = self.renderer;
if (!renderer) {
throw TypeError();
}
var state = self.$__state; var state = self.$__state;
var globalData = {}; var globalData = {};
globalData.$w = [self, !props && state && state.$__raw]; globalData.$w = [self, !props && state && state.$__raw];
var fromEls = getRootEls(self, {}); var fromEls = self.$__getRootEls({});
var doc = self.$__document; var doc = self.$__document;
updateManager.$__batchUpdate(function() { updateManager.$__batchUpdate(function() {
var createOut = renderer.createOut || marko.createOut; var createOut = renderer.createOut || marko.createOut;
var out = createOut(globalData); var out = createOut(globalData);
out.$__document = self.$__document;
renderer(props, out); renderer(props, out);
var result = new RenderResult(out); var result = new RenderResult(out);
var targetNode = out.$__getOutput();
var targetNode = out.getOutput();
var widgetsContext = out.global.widgets; var widgetsContext = out.global.widgets;
function onNodeDiscarded(node) { function onNodeDiscarded(node) {
if (node.nodeType === 1) { if (node.nodeType == 1) {
destroyWidgetForEl(node); destroyWidgetForEl(node);
} }
} }
@ -535,20 +452,6 @@ Widget.prototype = widgetProto = {
var id = fromEl.id; var id = fromEl.id;
var existingWidget; 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) { if (widgetsContext && id) {
var preserved = widgetsContext.$__preserved[id]; var preserved = widgetsContext.$__preserved[id];
@ -558,12 +461,12 @@ Widget.prototype = widgetProto = {
// the morphing will take place when the reused widget updates. // the morphing will take place when the reused widget updates.
return MORPHDOM_SKIP; return MORPHDOM_SKIP;
} else { } else {
existingWidget = markoWidgets.getWidgetForEl(fromEl); existingWidget = getWidgetForEl(fromEl);
if (existingWidget && !hasCompatibleWidget(widgetsContext, existingWidget)) { if (existingWidget && !hasCompatibleWidget(widgetsContext, existingWidget)) {
// We found a widget in an old DOM node that does not have // We found a widget in an old DOM node that does not have
// a compatible widget that was rendered so we need to // a compatible widget that was rendered so we need to
// destroy the old widget // destroy the old widget
destroyWidgetHelper(existingWidget); existingWidget.$__destroyShallow();
} }
} }
} }
@ -612,9 +515,38 @@ Widget.prototype = widgetProto = {
// widget was queued for update and the re-rendered // widget was queued for update and the re-rendered
// before the update occurred then nothing will happen // before the update occurred then nothing will happen
// at the time of the update. // 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; return fragment;
} else { } else {
return this.els[0]; return els[0];
} }
}, },
function afterInsert(widget) { function afterInsert(widget) {

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