diff --git a/benchmark/size/minify.js b/benchmark/size/minify.js
index 3539b57c8..c1fead3dc 100644
--- a/benchmark/size/minify.js
+++ b/benchmark/size/minify.js
@@ -38,10 +38,10 @@ var minifiers = {
const out = gcc.compile(options);
- // if (out.errors && out.errors.length) {
- // console.error(out.errors);
- // throw new Error(`Minification failed for ${file}`);
- // }
+ if (out.errors && out.errors.length) {
+ console.error(out.errors);
+ throw new Error(`Minification failed for ${file}`);
+ }
return out.compiledCode;
},
uglify: function minifyUglifyJS(src, file) {
@@ -139,4 +139,4 @@ promiseChain.then(() => {
}
console.log('Minification complete.');
-});
\ No newline at end of file
+});
diff --git a/benchmark/size/package.json b/benchmark/size/package.json
index d9cbe6336..36dfff9bb 100644
--- a/benchmark/size/package.json
+++ b/benchmark/size/package.json
@@ -11,7 +11,7 @@
"build-react": "npm run bundle-react --silent && node minify.js react",
"build-inferno": "npm run bundle-inferno --silent && node minify.js inferno",
"bundle": "mkdir -p build/bundles && npm run bundle-marko && npm run bundle-react && npm run bundle-vue && npm run bundle-preact && npm run bundle-inferno",
- "bundle-marko": "node ../../scripts/build && NODE_ENV=production rollup -c marko/rollup.config.js",
+ "bundle-marko": "node ../../scripts/build src && NODE_ENV=production rollup -c marko/rollup.config.js",
"bundle-react": "NODE_ENV=production rollup -c react/rollup.config.js",
"bundle-preact": "NODE_ENV=production rollup -c preact/rollup.config.js",
"bundle-vue": "NODE_ENV=production rollup -c vue/rollup.config.js",
diff --git a/package.json b/package.json
index a72c1d3af..471a4a612 100644
--- a/package.json
+++ b/package.json
@@ -21,6 +21,7 @@
},
"scripts": {
"build": "node scripts/build.js",
+ "build-src": "node scripts/build.js src",
"prepublish": "npm run build",
"test": "npm run jshint -s && npm run mocha -s && npm run test-components -s && npm run test-components-deprecated -s",
"test-ci": "npm run test-generate-coverage && npm run build && NODE_ENV=production npm run test",
diff --git a/scripts/build.js b/scripts/build.js
index a16a6ce53..f04c6621e 100644
--- a/scripts/build.js
+++ b/scripts/build.js
@@ -16,34 +16,44 @@ const babelOptions = {
]
};
-buildDir('src', 'dist', {
- babelExclude: [
- '/taglibs/async/client-reorder-runtime.min.js'
- ],
- babelOptions
-});
-var buildConstants = {
- isDebug: false
-};
+var target = process.argv[2];
+
+var shouldBuildSrc = true;
+var shouldBuildTest = true;
+
+if (target === 'src') {
+ shouldBuildTest = false;
+}
+
+if (shouldBuildSrc) {
+ buildDir('src', 'dist', {
+ babelExclude: [
+ '/taglibs/async/client-reorder-runtime.min.js'
+ ],
+ babelOptions
+ });
+}
fs.writeFileSync(
path.join(__dirname, '../dist/build.json'),
JSON.stringify({ isDebug: false }, null, 4),
{ encoding: 'utf8' });
-buildDir('test', 'test-dist', {
- babelExclude: [
- '*expected*.*',
- 'input.js*'
- ],
- exclude: [
- '/generated',
- '*.marko.js',
- '*.skip',
- '*.generated.*',
- '*actual*.*',
- 'actualized-expected.html*'
- ],
- babelOptions
-});
+if (shouldBuildTest) {
+ buildDir('test', 'test-dist', {
+ babelExclude: [
+ '*expected*.*',
+ 'input.js*'
+ ],
+ exclude: [
+ '/generated',
+ '*.marko.js',
+ '*.skip',
+ '*.generated.*',
+ '*actual*.*',
+ 'actualized-expected.html*'
+ ],
+ babelOptions
+ });
+}
diff --git a/src/compiler/Builder.js b/src/compiler/Builder.js
index 47b264909..949ebb3fc 100644
--- a/src/compiler/Builder.js
+++ b/src/compiler/Builder.js
@@ -130,15 +130,22 @@ class Builder {
return new MemberExpression({object, property, computed});
}
+ /**
+ * Generates code that joins all of the arguments using `+` (BinaryExpression)
+ *
+ * @param {Array} args If the args object is not an array then `arguments` is used
+ * @return {Node} The resulting Node
+ */
concat(args) {
var prev;
let operator = '+';
+ args = Array.isArray(args) ? args : Array.prototype.slice.call(arguments, 0);
- for (var i=1; i {
+ this.body.appendChild(node);
+ });
+ }
+
insertBefore(newNode, referenceNode) {
ok(this.body, 'Node does not support child nodes: ' + this);
this.body.insertBefore(newNode, referenceNode);
diff --git a/src/compiler/util/vdom/VDOMOptimizer.js b/src/compiler/util/vdom/VDOMOptimizer.js
index 47fb8fed0..e07da4e3d 100644
--- a/src/compiler/util/vdom/VDOMOptimizer.js
+++ b/src/compiler/util/vdom/VDOMOptimizer.js
@@ -53,19 +53,23 @@ class OptimizerContext {
}
class NodeVDOM extends Node {
- constructor(variableIdentifier) {
+ constructor(variableIdentifier, isComponent) {
super('NodeVDOM');
this.variableIdentifier = variableIdentifier;
+ this.isComponent = isComponent;
}
writeCode(writer) {
var builder = writer.builder;
+ var funcCallArgs = [ this.variableIdentifier ];
+
+ if (this.isComponent) {
+ funcCallArgs.push(builder.identifier('component'));
+ }
let funcCall = builder.functionCall(
builder.identifier('n'),
- [
- this.variableIdentifier
- ]);
+ funcCallArgs);
if (this.isChild) {
writer.write('.');
@@ -89,16 +93,16 @@ function generateNodesForArray(nodes, context, options) {
function generateStaticNode(node) {
if (node.type === 'HtmlElementVDOM') {
node.createElementId = context.helper('createElement');
+
+ node.setConstId(builder.functionCall(optimizerContext.nextConstIdFunc, []));
}/* else {
node.createTextId = context.importModule('marko_createText', 'marko/vdom/createText');
}*/
- node.nextConstId = builder.functionCall(optimizerContext.nextConstIdFunc, []);
-
node.isStaticRoot = true;
let staticNodeId = context.addStaticVar('marko_node' + (optimizerContext.nextNodeId++), node);
- return new NodeVDOM(staticNodeId);
+ return new NodeVDOM(staticNodeId, context.isComponent);
}
function handleStaticAttributes(node) {
@@ -131,6 +135,7 @@ function generateNodesForArray(nodes, context, options) {
finalNodes.push(node);
}
+ node.finalizeProperties(context);
} else {
finalNodes.push(node);
}
@@ -157,4 +162,4 @@ class VDOMOptimizer {
}
}
-module.exports = VDOMOptimizer;
\ No newline at end of file
+module.exports = VDOMOptimizer;
diff --git a/src/components/Component.js b/src/components/Component.js
index 257704198..533511b3e 100644
--- a/src/components/Component.js
+++ b/src/components/Component.js
@@ -7,53 +7,27 @@ var getComponentsContext = require('./ComponentsContext').___getComponentsContex
var componentsUtil = require('./util');
var componentLookup = componentsUtil.___componentLookup;
var emitLifecycleEvent = componentsUtil.___emitLifecycleEvent;
-var destroyComponentForEl = componentsUtil.___destroyComponentForEl;
-var destroyElRecursive = componentsUtil.___destroyElRecursive;
-var getElementById = componentsUtil.___getElementById;
+var destroyNodeRecursive = componentsUtil.___destroyNodeRecursive;
var EventEmitter = require('events-light');
var RenderResult = require('../runtime/RenderResult');
var SubscriptionTracker = require('listener-tracker');
var inherit = require('raptor-util/inherit');
var updateManager = require('./update-manager');
var morphdom = require('../morphdom');
-var eventDelegation = require('./event-delegation');
var slice = Array.prototype.slice;
-var MORPHDOM_SKIP = true;
-
var COMPONENT_SUBSCRIBE_TO_OPTIONS;
var NON_COMPONENT_SUBSCRIBE_TO_OPTIONS = {
addDestroyListener: false
};
-function outNoop() { /* jshint -W040 */ return this; }
-
var emit = EventEmitter.prototype.emit;
function removeListener(removeEventListenerHandle) {
removeEventListenerHandle();
}
-function checkCompatibleComponent(globalComponentsContext, el) {
- var component = el._w;
- while(component) {
- var id = component.id;
- var newComponentDef = globalComponentsContext.___componentsById[id];
- if (newComponentDef && component.___type == newComponentDef.___component.___type) {
- break;
- }
-
- var rootFor = component.___rootFor;
- if (rootFor) {
- component = rootFor;
- } else {
- component.___destroyShallow();
- break;
- }
- }
-}
-
function handleCustomEventWithMethodListener(component, targetMethodName, args, extraArgs) {
// Remove the "eventType" argument
args.push(component);
@@ -72,16 +46,12 @@ function handleCustomEventWithMethodListener(component, targetMethodName, args,
targetMethod.apply(targetComponent, args);
}
-function getElIdHelper(component, componentElId, index) {
- var id = component.id;
+function resolveKeyHelper(key, index) {
+ return index ? key + '_' + index : key;
+}
- var elId = componentElId != null ? id + '-' + componentElId : id;
-
- if (index != null) {
- elId += '[' + index + ']';
- }
-
- return elId;
+function resolveComponentIdHelper(component, key, index) {
+ return component.id + '-' + resolveKeyHelper(key, index);
}
/**
@@ -158,46 +128,10 @@ function checkInputChanged(existingComponent, oldInput, newInput) {
return false;
}
-function onNodeDiscarded(node) {
- if (node.nodeType === 1) {
- destroyComponentForEl(node);
- }
-}
-
-function onBeforeNodeDiscarded(node) {
- return eventDelegation.___handleNodeDetach(node);
-}
-
-function onBeforeElUpdated(fromEl, key, globalComponentsContext) {
- if (key) {
- var preserved = globalComponentsContext.___preserved[key];
-
- if (preserved === true) {
- // Don't morph elements that are associated with components that are being
- // reused or elements that are being preserved. For components being reused,
- // the morphing will take place when the reused component updates.
- return MORPHDOM_SKIP;
- } else {
- // We may need to destroy a Component associated with the current element
- // if a new UI component was rendered to the same element and the types
- // do not match
- checkCompatibleComponent(globalComponentsContext, fromEl);
- }
- }
-}
-
-function onBeforeElChildrenUpdated(el, key, globalComponentsContext) {
- if (key) {
- var preserved = globalComponentsContext.___preservedBodies[key];
- if (preserved === true) {
- // Don't morph the children since they are preserved
- return MORPHDOM_SKIP;
- }
- }
-}
-
-function onNodeAdded(node, globalComponentsContext) {
- eventDelegation.___handleNodeAttach(node, globalComponentsContext.___out);
+function getNodes(component) {
+ var nodes = [];
+ component.___forEachNode(nodes.push.bind(nodes));
+ return nodes;
}
var componentProto;
@@ -210,9 +144,9 @@ var componentProto;
function Component(id) {
EventEmitter.call(this);
this.id = id;
- this.el = null;
this.___state = null;
- this.___roots = null;
+ this.___startNode = null;
+ this.___endNode = null;
this.___subscriptions = null;
this.___domEventListenerHandles = null;
this.___bubblingDomEvents = null; // Used to keep track of bubbling DOM events for components rendered on the server
@@ -229,6 +163,8 @@ function Component(id) {
this.___settingInput = false;
this.___document = undefined;
+
+ this.___keyedElements = {};
}
Component.prototype = componentProto = {
@@ -264,63 +200,57 @@ Component.prototype = componentProto = {
return emit.apply(this, arguments);
}
},
- getElId: function (componentElId, index) {
- return getElIdHelper(this, componentElId, index);
+ getElId: function (key, index) {
+ return resolveComponentIdHelper(this, key, index);
},
- getEl: function (componentElId, index) {
- var doc = this.___document;
-
- if (componentElId != null) {
- return getElementById(doc, getElIdHelper(this, componentElId, index));
+ getEl: function (key, index) {
+ if (key) {
+ return this.___keyedElements[resolveKeyHelper(key, index)];
} else {
- return this.el || getElementById(doc, getElIdHelper(this));
+ return this.___startNode;
}
},
- getEls: function(id) {
+ getEls: function(key) {
+ key = key + '[]';
+
var els = [];
var i = 0;
var el;
- while((el = this.getEl(id, i))) {
+ while((el = this.getEl(key, i))) {
els.push(el);
i++;
}
return els;
},
- getComponent: function(id, index) {
- return componentLookup[getElIdHelper(this, id, index)];
+ getComponent: function(key, index) {
+ return componentLookup[resolveComponentIdHelper(this, key, index)];
},
- getComponents: function(id) {
+ getComponents: function(key) {
+ key = key + '[]';
+
var components = [];
var i = 0;
var component;
- while((component = componentLookup[getElIdHelper(this, id, i)])) {
+ while((component = componentLookup[resolveComponentIdHelper(this, key, i)])) {
components.push(component);
i++;
}
return components;
},
- destroy: function() {
+ destroy: function(onBeforeNodeDiscarded) {
if (this.___destroyed) {
return;
}
- var els = this.els;
+ var nodes = getNodes(this);
this.___destroyShallow();
- var rootComponents = this.___rootComponents;
- if (rootComponents) {
- rootComponents.forEach(function(rootComponent) {
- rootComponent.___destroy();
- });
- }
+ nodes.forEach(function(node) {
+ destroyNodeRecursive(node);
- els.forEach(function(el) {
- destroyElRecursive(el);
-
- var parentNode = el.parentNode;
- if (parentNode) {
- parentNode.removeChild(el);
+ if (!onBeforeNodeDiscarded || onBeforeNodeDiscarded(node) != false) {
+ node.parentNode.removeChild(node);
}
});
},
@@ -333,7 +263,9 @@ Component.prototype = componentProto = {
emitLifecycleEvent(this, 'destroy');
this.___destroyed = true;
- this.el = null;
+ this.___startNode.___markoComponent = undefined;
+
+ this.___startNode = this.___endNode = null;
// Unsubscribe from all DOM events
this.___removeDOMEventListeners();
@@ -452,6 +384,7 @@ Component.prototype = componentProto = {
___queueUpdate: function() {
if (!this.___updateQueued) {
+ this.___updateQueued = true;
updateManager.___queueComponentUpdate(this);
}
},
@@ -512,7 +445,10 @@ Component.prototype = componentProto = {
if (!renderer) {
throw TypeError();
}
- var fromEls = self.___getRootEls({});
+
+ var startNode = this.___startNode;
+ var endNodeNextSibling = this.___endNode.nextSibling;
+
var doc = self.___document;
var input = this.___renderInput || this.___input;
var globalData = this.___global;
@@ -523,18 +459,6 @@ Component.prototype = componentProto = {
out.sync();
out.___document = self.___document;
- if (isRerenderInBrowser === true) {
- out.e =
- out.be =
- out.ee =
- out.t =
- out.h =
- out.w =
- out.write =
- out.html =
- outNoop;
- }
-
var componentsContext = getComponentsContext(out);
var globalComponentsContext = componentsContext.___globalContext;
globalComponentsContext.___rerenderComponent = self;
@@ -544,68 +468,40 @@ Component.prototype = componentProto = {
var result = new RenderResult(out);
- if (isRerenderInBrowser !== true) {
- var targetNode = out.___getOutput();
+ var targetNode = out.___getOutput();
- var fromEl;
-
- var targetEl = targetNode.___firstChild;
- while (targetEl) {
- var nodeName = targetEl.___nodeName;
-
- if (nodeName === 'HTML') {
- fromEl = document.documentElement;
- } else if (nodeName === 'BODY') {
- fromEl = document.body;
- } else if (nodeName === 'HEAD') {
- fromEl = document.head;
- } else {
- fromEl = fromEls[targetEl.id];
- }
-
- if (fromEl) {
- morphdom(
- fromEl,
- targetEl,
- globalComponentsContext,
- onNodeAdded,
- onBeforeElUpdated,
- onBeforeNodeDiscarded,
- onNodeDiscarded,
- onBeforeElChildrenUpdated);
- }
-
- targetEl = targetEl.___nextSibling;
- }
- }
+ morphdom(
+ startNode.parentNode,
+ startNode,
+ endNodeNextSibling,
+ targetNode,
+ doc,
+ componentsContext);
result.afterInsert(doc);
-
- out.emit('___componentsInitialized');
});
this.___reset();
},
- ___getRootEls: function(rootEls) {
- var i, len;
+ ___detach: function() {
+ var fragment = this.___document.createDocumentFragment();
+ this.___forEachNode(fragment.appendChild.bind(fragment));
+ return fragment;
+ },
- var componentEls = this.els;
+ ___forEachNode: function(callback) {
+ var currentNode = this.___startNode;
+ var endNode = this.___endNode;
- for (i=0, len=componentEls.length; i 1) {
- var fragment = component.___document.createDocumentFragment();
- els.forEach(function(el) {
- fragment.appendChild(el);
- });
- return fragment;
- } else {
- return els[0];
- }
+ return component.___detach();
},
function afterInsert(component) {
return component;
diff --git a/src/components/ComponentDef.js b/src/components/ComponentDef.js
index e1ef621ee..b005d376b 100644
--- a/src/components/ComponentDef.js
+++ b/src/components/ComponentDef.js
@@ -3,48 +3,50 @@ var repeatedRegExp = /\[\]$/;
var componentUtil = require('./util');
var attachBubblingEvent = componentUtil.___attachBubblingEvent;
var extend = require('raptor-util/extend');
+var KeySequence = require('./KeySequence');
+
+/*
+var FLAG_WILL_RERENDER_IN_BROWSER = 1;
+var FLAG_HAS_BODY_EL = 2;
+var FLAG_HAS_HEAD_EL = 4;
+*/
/**
* A ComponentDef is used to hold the metadata collected at runtime for
* a single component and this information is used to instantiate the component
* later (after the rendered HTML has been added to the DOM)
*/
-function ComponentDef(component, componentId, globalComponentsContext, componentStack, componentStackLen) {
+function ComponentDef(component, componentId, globalComponentsContext) {
this.___globalComponentsContext = globalComponentsContext; // The AsyncWriter that this component is associated with
- this.___componentStack = componentStack;
- this.___componentStackLen = componentStackLen;
this.___component = component;
this.id = componentId;
- this.___roots = null; // IDs of root elements if there are multiple root elements
- this.___children = null; // An array of nested ComponentDef instances
this.___domEvents = undefined; // An array of DOM events that need to be added (in sets of three)
this.___isExisting = false;
- this.___willRerenderInBrowser = false;
+ this.___renderBoundary = false;
+ this.___flags = 0;
this.___nextIdIndex = 0; // The unique integer to use for the next scoped ID
+
+ this.___keySequence = null;
+
+ this.___preservedDOMNodes = null;
}
ComponentDef.prototype = {
- ___end: function() {
- this.___componentStack.length = this.___componentStackLen;
+
+ ___nextKey: function(key) {
+ var keySequence = this.___keySequence || (this.___keySequence = new KeySequence());
+ return keySequence.___nextKey(key);
},
- /**
- * Register a nested component for this component. We maintain a tree of components
- * so that we can instantiate nested components before their parents.
- */
- ___addChild: function (componentDef) {
- var children = this.___children;
-
- if (children) {
- children.push(componentDef);
- } else {
- this.___children = [componentDef];
- }
+ ___preserveDOMNode: function(key, bodyOnly) {
+ var lookup = this.___preservedDOMNodes || (this.___preservedDOMNodes = {});
+ lookup[key] = bodyOnly ? 2 : 1;
},
+
/**
* This helper method generates a unique and fully qualified DOM element ID
* that is unique within the scope of the current component. This method prefixes
@@ -88,15 +90,15 @@ ComponentDef.prototype = {
* Returns the next auto generated unique ID for a nested DOM element or nested DOM component
*/
___nextComponentId: function() {
- var id = this.id;
-
- return id === null ?
- this.___globalComponentsContext.___nextComponentId(this.___out) :
- id + '-c' + (this.___nextIdIndex++);
+ return this.id + '-c' + (this.___nextIdIndex++);
},
d: function(handlerMethodName, extraArgs) {
return attachBubblingEvent(this, handlerMethodName, extraArgs);
+ },
+
+ get ___type() {
+ return this.___component.___type;
}
};
@@ -146,10 +148,11 @@ ComponentDef.___deserialize = function(o, types, globals, registry) {
component.___global = globals;
return {
+ id: id,
___component: component,
- ___roots: extra.r,
+ ___boundary: extra.r,
___domEvents: extra.d,
- ___willRerenderInBrowser: extra._ === 1
+ ___flags: extra.f || 0
};
};
diff --git a/src/components/ComponentsContext.js b/src/components/ComponentsContext.js
index abf7545eb..32a6f4fd7 100644
--- a/src/components/ComponentsContext.js
+++ b/src/components/ComponentsContext.js
@@ -1,117 +1,47 @@
'use strict';
+var GlobalComponentsContext = require('./GlobalComponentsContext');
-var ComponentDef = require('./ComponentDef');
-var componentsUtil = require('./util');
+function ComponentsContext(out, parentComponentsContext) {
+ var globalComponentsContext;
+ var componentDef;
+ var components;
-var beginComponent = require('./beginComponent');
+ if (parentComponentsContext) {
+ components = parentComponentsContext.___components;
+ globalComponentsContext = parentComponentsContext.___globalContext;
+ componentDef = parentComponentsContext.___componentDef;
+ } else {
+ globalComponentsContext = out.global.___components;
+ if (globalComponentsContext === undefined) {
+ out.global.___components = globalComponentsContext = new GlobalComponentsContext(out);
+ }
+ components = [];
+ }
-var EMPTY_OBJECT = {};
-
-function GlobalComponentsContext(out) {
- this.___roots = [];
- this.___preserved = EMPTY_OBJECT;
- this.___preservedBodies = EMPTY_OBJECT;
- this.___componentsById = {};
+ this.___globalContext = globalComponentsContext;
+ this.___components = components;
this.___out = out;
- this.___rerenderComponent = undefined;
- this.___nextIdLookup = null;
- this.___nextComponentId = componentsUtil.___nextComponentIdProvider(out);
+ this.___componentDef = componentDef;
}
-GlobalComponentsContext.prototype = {
+ComponentsContext.prototype = {
___initComponents: function(doc) {
- var topLevelComponentDefs = null;
+ var componentDefs = this.___components;
- this.___roots.forEach(function(root) {
- var children = root.___children;
- if (children) {
- // NOTE: ComponentsContext.___initClientRendered is provided by
- // index-browser.js to avoid a circular dependency
- ComponentsContext.___initClientRendered(children, doc);
- if (topLevelComponentDefs === null) {
- topLevelComponentDefs = children;
- } else {
- topLevelComponentDefs = topLevelComponentDefs.concat(children);
- }
- }
- });
+ ComponentsContext.___initClientRendered(componentDefs, doc);
- this.___roots = null;
+ this.___out.emit('___componentsInitialized');
// Reset things stored in global since global is retained for
// future renders
this.___out.global.___components = undefined;
- return topLevelComponentDefs;
+ return componentDefs;
},
- ___preserveDOMNode: function(elId, bodyOnly) {
- var preserved = bodyOnly === true ? this.___preservedBodies : this.___preserved;
- if (preserved === EMPTY_OBJECT) {
- if (bodyOnly === true) {
- preserved = this.___preservedBodies = {};
- } else {
- preserved = this.___preserved = {};
- }
- }
- preserved[elId] = true;
- },
- ___nextRepeatedId: function(parentId, id) {
- var nextIdLookup = this.___nextIdLookup || (this.___nextIdLookup = {});
-
- var indexLookupKey = parentId + '-' + id;
- var currentIndex = nextIdLookup[indexLookupKey];
- if (currentIndex == null) {
- currentIndex = nextIdLookup[indexLookupKey] = 0;
- } else {
- currentIndex = ++nextIdLookup[indexLookupKey];
- }
-
- return indexLookupKey.slice(0, -2) + '[' + currentIndex + ']';
- }
-};
-
-function ComponentsContext(out, parentComponentsContext, shouldAddGlobalRoot) {
- var root;
-
- var globalComponentsContext;
-
- if (parentComponentsContext === undefined) {
- globalComponentsContext = out.global.___components;
- if (globalComponentsContext === undefined) {
- out.global.___components = globalComponentsContext = new GlobalComponentsContext(out);
- }
-
- root = new ComponentDef(null, null, globalComponentsContext);
-
- if (shouldAddGlobalRoot !== false) {
- globalComponentsContext.___roots.push(root);
- }
- } else {
- globalComponentsContext = parentComponentsContext.___globalContext;
- var parentComponentStack = parentComponentsContext.___componentStack;
- root = parentComponentStack[parentComponentStack.length-1];
- }
-
- this.___globalContext = globalComponentsContext;
- this.___out = out;
- this.___componentStack = [root];
-}
-
-ComponentsContext.prototype = {
- ___createNestedComponentsContext: function(nestedOut) {
- return new ComponentsContext(nestedOut, this);
- },
- ___beginComponent: beginComponent,
-
- ___nextComponentId: function() {
- var componentStack = this.___componentStack;
- var parentComponentDef = componentStack[componentStack.length - 1];
- return parentComponentDef.___nextComponentId();
- }
};
function getComponentsContext(out) {
- return out.data.___components || (out.data.___components = new ComponentsContext(out));
+ return out.___components || (out.___components = new ComponentsContext(out));
}
module.exports = exports = ComponentsContext;
diff --git a/src/components/GlobalComponentsContext.js b/src/components/GlobalComponentsContext.js
new file mode 100644
index 000000000..1e2188372
--- /dev/null
+++ b/src/components/GlobalComponentsContext.js
@@ -0,0 +1,18 @@
+var nextComponentIdProvider = require('./util').___nextComponentIdProvider;
+var KeySequence = require('./KeySequence');
+
+function GlobalComponentsContext(out) {
+ this.___preservedEls = {};
+ this.___preservedElBodies = {};
+ this.___renderedComponentsById = {};
+ this.___rerenderComponent = undefined;
+ this.___nextComponentId = nextComponentIdProvider(out);
+}
+
+GlobalComponentsContext.prototype = {
+ ___createKeySequence: function() {
+ return new KeySequence();
+ }
+};
+
+module.exports = GlobalComponentsContext;
diff --git a/src/components/KeySequence.js b/src/components/KeySequence.js
new file mode 100644
index 000000000..ed73040da
--- /dev/null
+++ b/src/components/KeySequence.js
@@ -0,0 +1,27 @@
+ function KeySequence() {
+ this.___lookup = {};
+}
+
+KeySequence.prototype = {
+ ___nextKey: function(key) {
+ // var len = key.length;
+ // var lastChar = key[len-1];
+ // if (lastChar === ']') {
+ // key = key.substring(0, len-2);
+ // }
+ var lookup = this.___lookup;
+
+ var currentIndex = lookup[key]++;
+ if (!currentIndex) {
+ lookup[key] = 1;
+ currentIndex = 0;
+ return key;
+ } else {
+ return key + '_' + currentIndex;
+ }
+
+
+ }
+};
+
+module.exports = KeySequence;
diff --git a/src/components/attach-detach.js b/src/components/attach-detach.js
index 21309a2b0..8d82e0eff 100644
--- a/src/components/attach-detach.js
+++ b/src/components/attach-detach.js
@@ -2,14 +2,15 @@ var eventDelegation = require('./event-delegation');
var delegateEvent = eventDelegation.___delegateEvent;
var getEventFromEl = eventDelegation.___getEventFromEl;
-var componentsUtil = require('./util');
-var destroyElRecursive = componentsUtil.___destroyElRecursive;
-var destroyComponentForEl = componentsUtil.___destroyComponentForEl;
+// var componentsUtil = require('./util');
+// var destroyNodeRecursive = componentsUtil.___destroyNodeRecursive;
+// var destroyComponentForNode = componentsUtil.___destroyComponentForNode;
-function handleNodeAttach(node, out) {
+function handleNodeAttach(node, componentsContext) {
if (node.nodeType === 1) {
var target = getEventFromEl(node, 'onattach');
if (target) {
+ var out = componentsContext.___out;
var data = out.data;
var attachTargets = data.___attachTargets;
@@ -39,8 +40,6 @@ function handleNodeDetach(node) {
delegateEvent(node, target, {
preventDefault: function() {
allowDetach = false;
- destroyComponentForEl(node);
- destroyElRecursive(node);
},
detach: function() {
var parentNode = node.parentNode;
diff --git a/src/components/beginComponent-browser.js b/src/components/beginComponent-browser.js
index c4ba643a9..b4e61922f 100644
--- a/src/components/beginComponent-browser.js
+++ b/src/components/beginComponent-browser.js
@@ -1,17 +1,14 @@
var ComponentDef = require('./ComponentDef');
-module.exports = function(component, isSplitComponent) {
- var componentStack = this.___componentStack;
- var origLength = componentStack.length;
- var parentComponentDef = componentStack[origLength - 1];
-
+module.exports = function beginComponent(componentsContext, component, isSplitComponen, parentComponentDeft) {
var componentId = component.id;
- var componentDef = new ComponentDef(component, componentId, this.___globalContext, componentStack, origLength);
- parentComponentDef.___addChild(componentDef);
- this.___globalContext.___componentsById[componentId] = componentDef;
-
- componentStack.push(componentDef);
+ var globalContext = componentsContext.___globalContext;
+ var componentDef = componentsContext.___componentDef = new ComponentDef(component, componentId, globalContext);
+ globalContext.___renderedComponentsById[componentId] = true;
+ componentsContext.___components.push(componentDef);
+ var out = componentsContext.___out;
+ out.bc(component);
return componentDef;
};
diff --git a/src/components/beginComponent.js b/src/components/beginComponent.js
index ee9f67b53..dba1c8766 100644
--- a/src/components/beginComponent.js
+++ b/src/components/beginComponent.js
@@ -3,8 +3,13 @@
const ComponentDef = require('./ComponentDef');
const hasRenderBodyKey = Symbol.for("hasRenderBody");
+var FLAG_WILL_RERENDER_IN_BROWSER = 1;
+// var FLAG_HAS_BODY_EL = 2;
+// var FLAG_HAS_HEAD_EL = 4;
+
function isInputSerializable(component) {
var input = component.___input;
+
if (!input) {
return true;
}
@@ -16,29 +21,33 @@ function isInputSerializable(component) {
}
}
-module.exports = function beginComponent(component, isSplitComponent) {
- var componentStack = this.___componentStack;
- var origLength = componentStack.length;
- var parentComponentDef = componentStack[origLength - 1];
+module.exports = function beginComponent(componentsContext, component, isSplitComponent, parentComponentDef) {
+ var globalContext = componentsContext.___globalContext;
var componentId = component.id;
- var componentDef = new ComponentDef(component, componentId, this.___globalContext, componentStack, origLength);
+ var componentDef = componentsContext.___componentDef = new ComponentDef(component, componentId, globalContext);
// On the server
- if (parentComponentDef.___willRerenderInBrowser === true) {
- componentDef.___willRerenderInBrowser = true;
- } else {
- parentComponentDef.___addChild(componentDef);
- if (isSplitComponent === false &&
- this.___out.global.noBrowserRerender !== true &&
- isInputSerializable(component)) {
-
- componentDef.___willRerenderInBrowser = true;
- }
+ if (parentComponentDef && (parentComponentDef.___flags & FLAG_WILL_RERENDER_IN_BROWSER)) {
+ componentDef.___flags |= FLAG_WILL_RERENDER_IN_BROWSER;
+ return componentDef;
}
- componentStack.push(componentDef);
+ componentsContext.___components.push(componentDef);
+
+ let out = componentsContext.___out;
+
+ componentDef.___renderBoundary = true;
+
+ if (isSplitComponent === false &&
+ out.global.noBrowserRerender !== true &&
+ isInputSerializable(component)) {
+ componentDef.___flags |= FLAG_WILL_RERENDER_IN_BROWSER;
+ out.w('');
+ } else {
+ out.w('');
+ }
return componentDef;
};
diff --git a/src/components/endComponent-browser.js b/src/components/endComponent-browser.js
new file mode 100644
index 000000000..eae21057a
--- /dev/null
+++ b/src/components/endComponent-browser.js
@@ -0,0 +1,5 @@
+'use strict';
+
+module.exports = function endComponent(out) {
+ out.ee(); // endElement() (also works for VComponent nodes pushed on to the stack)
+};
diff --git a/src/components/endComponent.js b/src/components/endComponent.js
new file mode 100644
index 000000000..34d224b94
--- /dev/null
+++ b/src/components/endComponent.js
@@ -0,0 +1,7 @@
+'use strict';
+
+module.exports = function endComponent(out, componentDef) {
+ if (componentDef.___renderBoundary) {
+ out.w('');
+ }
+};
diff --git a/src/components/index.js b/src/components/index.js
index 6da4a563d..dacf6d7d5 100644
--- a/src/components/index.js
+++ b/src/components/index.js
@@ -3,12 +3,34 @@
var warp10 = require('warp10');
var escapeEndingScriptTagRegExp = /<\//g;
-function flattenHelper(components, flattened, typesArray, typesLookup) {
- for (var i = 0, len = components.length; i < len; i++) {
+
+
+function getRenderedComponents(out, shouldIncludeAll) {
+ var componentsContext = out.___components;
+ if (componentsContext === null) {
+ return;
+ }
+
+ // console.log('componentsContext:', componentsContext);
+
+ var components = componentsContext.___components;
+ if (components.length === 0) {
+ return;
+ }
+
+ // console.log('components:', components.map((componentDef) => {
+ // return { id: componentDef.id, type: componentDef.type};
+ // }));
+
+ var componentsFinal = [];
+ var typesLookup = {};
+ var typesArray = [];
+
+ for (var i = components.length - 1; i >= 0; i--) {
var componentDef = components[i];
var id = componentDef.id;
var component = componentDef.___component;
- var rerenderInBrowser = componentDef.___willRerenderInBrowser;
+ var flags = componentDef.___flags;
var state = component.state;
var input = component.input;
var typeName = component.typeName;
@@ -36,14 +58,6 @@ function flattenHelper(components, flattened, typesArray, typesLookup) {
typesLookup[typeName] = typeIndex;
}
- var children = componentDef.___children;
-
- if (children !== null) {
- // Depth-first search (children should be initialized before parent)
- flattenHelper(children, flattened, typesArray, typesLookup);
- componentDef.___children = null;
- }
-
var hasProps = false;
let componentKeys = Object.keys(component);
@@ -81,71 +95,23 @@ function flattenHelper(components, flattened, typesArray, typesLookup) {
b: bubblingDomEvents,
d: componentDef.___domEvents,
e: customEvents,
+ f: flags ? flags : undefined,
p: customEvents && scope, // Only serialize scope if we need to attach custom events
- r: componentDef.___roots,
+ r: componentDef.___boundary,
s: state,
u: undefinedPropNames,
- w: hasProps ? component : undefined,
- _: rerenderInBrowser ? 1 : undefined
+ w: hasProps ? component : undefined
};
- flattened.push([
+ componentsFinal.push([
id, // 0 = id
typeIndex, // 1 = type
input, // 2 = input
extra // 3
]);
}
-}
-function getRenderedComponents(out, shouldIncludeAll) {
- var componentDefs;
- var globalComponentsContext;
- var outGlobal = out.global;
-
- if (shouldIncludeAll === true) {
- globalComponentsContext = outGlobal.___components;
-
- if (globalComponentsContext === undefined) {
- return undefined;
- }
- } else {
- let componentsContext = out.data.___components;
- if (componentsContext === undefined) {
- return undefined;
- }
- let rootComponentDef = componentsContext.___componentStack[0];
- componentDefs = rootComponentDef.___children;
-
- if (componentDefs === null) {
- return undefined;
- }
-
- rootComponentDef.___children = null;
- }
-
- var flattened = [];
- var typesLookup = {};
- var typesArray = [];
-
- if (shouldIncludeAll === true) {
- let roots = globalComponentsContext.___roots;
- for (let i=0, len=roots.length; i=0; i--) {
var componentDef = componentDefs[i];
-
- if (componentDef.___children) {
- initClientRendered(componentDef.___children, doc);
- }
-
initComponent(
componentDef,
doc);
@@ -172,9 +178,12 @@ function initServerRendered(renderedComponents, doc) {
return;
}
+
+ doc = doc || defaultDocument;
+
// Ensure that event handlers to handle delegating events are
// always attached before initializing any components
- eventDelegation.___init(doc || defaultDocument);
+ eventDelegation.___init(doc);
renderedComponents = warp10Finalize(renderedComponents);
@@ -188,6 +197,65 @@ function initServerRendered(renderedComponents, doc) {
componentDefs.forEach(function(componentDef) {
componentDef = ComponentDef.___deserialize(componentDef, typesArray, serverRenderedGlobals, registry);
+ var componentId = componentDef.id;
+ var component = componentDef.___component;
+
+ var startNode;
+ var endNode;
+ var flags = componentDef.___flags;
+ if ((flags & 6) === 6) {
+ startNode = document.head;
+ endNode = document.body;
+ } else if (flags & FLAG_HAS_BODY_EL) {
+ startNode = endNode = document.body;
+ } else if (flags & FLAG_HAS_HEAD_EL) {
+ startNode = endNode = document.head;
+ } else {
+ var startNodeComment = serverComponentStartNodes[componentId];
+ if (!startNodeComment) {
+ indexServerComponentBoundaries(doc);
+ startNodeComment = serverComponentStartNodes[componentId];
+ }
+ var endNodeComment = serverComponentEndNodes[componentId];
+
+ startNode = startNodeComment.nextSibling;
+
+ if (startNode === endNodeComment) {
+ // Component has no output nodes so just mount to the start comment node
+ // and we will remove the end comment node
+ startNode = endNode = startNodeComment;
+ } else {
+ startNodeComment.parentNode.removeChild(startNodeComment);
+
+ if (startNode.parentNode === document) {
+ endNode = startNode = document.documentElement;
+ } else {
+ // Remove the start and end comment nodes and use the inner nodes
+ // as the boundary
+ endNode = endNodeComment.previousSibling;
+ }
+ }
+
+ if (endNodeComment) {
+ endNodeComment.parentNode.removeChild(endNodeComment);
+ }
+ }
+
+ component.___keyedElements = keyedElementsByComponentId[componentId] || {};
+ component.___startNode = startNode;
+ component.___endNode = endNode;
+
+ delete keyedElementsByComponentId[componentId];
+
+ // Mark the start node so that we know we need to skip past this
+ // node when matching up children
+ startNode.___startNode = true;
+
+ // Mark the end node so that when we attempt to find boundaries
+ // for nested UI components we don't accidentally go outside the boundary
+ // of the parent component
+ endNode.___endNode = true;
+
initComponent(componentDef, doc || defaultDocument);
});
}
diff --git a/src/components/jquery.js b/src/components/jquery.js
index 33495b3b2..88d98126e 100644
--- a/src/components/jquery.js
+++ b/src/components/jquery.js
@@ -23,11 +23,11 @@ exports.patchComponent = function(jQuery) {
var match = idRegExp.exec(arg);
//Reset the search to 0 so the next call to exec will start from the beginning for the new string
if (match != null) {
- var componentElId = match[1];
+ var key = match[1];
if (match[2] == null) {
- return jQuery(self.getEl(componentElId));
+ return jQuery(self.getEl(key));
} else {
- return jQuery('#' + self.getElId(componentElId) + match[2]);
+ return jQuery(match[2].trim(), self.getEl(key));
}
} else {
var rootEl = self.getEl();
diff --git a/src/components/legacy/renderer-legacy.js b/src/components/legacy/renderer-legacy.js
index 62ce853ff..ae6202672 100644
--- a/src/components/legacy/renderer-legacy.js
+++ b/src/components/legacy/renderer-legacy.js
@@ -4,8 +4,9 @@ var componentLookup = componentsUtil.___componentLookup;
var registry = require('../registry');
var modernRenderer = require('../renderer');
var resolveComponentKey = modernRenderer.___resolveComponentKey;
-var preserveComponentEls = modernRenderer.___preserveComponentEls;
var handleBeginAsync = modernRenderer.___handleBeginAsync;
+var beginComponent = require('../beginComponent');
+var endComponent = require('../endComponent');
var WIDGETS_BEGIN_ASYNC_ADDED_KEY = '$wa';
@@ -13,6 +14,7 @@ function createRendererFunc(templateRenderFunc, componentProps) {
var typeName = componentProps.type;
var roots = componentProps.roots;
var assignedId = componentProps.id;
+ var isSplit = componentProps.split === true;
return function renderer(input, out, renderingLogic) {
var outGlobal = out.global;
@@ -44,39 +46,46 @@ function createRendererFunc(templateRenderFunc, componentProps) {
var globalComponentsContext = componentsContext.___globalContext;
var component = globalComponentsContext.___rerenderComponent;
- var fakeComponent;
+
var isRerender = component !== undefined;
var id = assignedId;
var isExisting;
var customEvents;
var scope;
+ var parentComponentDef;
if (component) {
id = component.id;
isExisting = true;
globalComponentsContext.___rerenderComponent = null;
} else {
+ parentComponentDef = componentsContext.___componentDef;
var componentArgs = out.___componentArgs;
if (componentArgs) {
+ scope = parentComponentDef.id;
out.___componentArgs = null;
- scope = componentArgs[0];
- if (scope) {
- scope = scope.id;
+ var key;
+
+ if (typeof componentArgs === 'string') {
+ key = componentArgs;
+ } else {
+ key = componentArgs[0];
+ customEvents = componentArgs[1];
}
- var ref = componentArgs[1];
- if (ref != null) {
- ref = ref.toString();
+ if (key != null) {
+ key = key.toString();
}
- id = id || resolveComponentKey(globalComponentsContext, ref, scope);
- customEvents = componentArgs[2];
+ id = id || resolveComponentKey(globalComponentsContext, key, parentComponentDef);
+ } else if (parentComponentDef) {
+ id = parentComponentDef.___nextComponentId();
+ } else {
+ id = globalComponentsContext.___nextComponentId();
}
}
- id = id || componentsContext.___nextComponentId();
-
if (registry.___isServer && typeName) {
component = { id:id, typeName:typeName };
} else {
@@ -101,6 +110,10 @@ function createRendererFunc(templateRenderFunc, componentProps) {
}
}
+ if (component) {
+ component.___updateQueued = true;
+ }
+
if (input) {
if (getWidgetConfig) {
// If getWidgetConfig() was implemented then use that to
@@ -151,14 +164,23 @@ function createRendererFunc(templateRenderFunc, componentProps) {
component.___setCustomEvents(customEvents, scope);
}
- preserveComponentEls(component, out, globalComponentsContext);
+ // We put a placeholder element in the output stream to ensure that the existing
+ // DOM node is matched up correctly when using morphdom. We flag the VElement
+ // node to track that it is a preserve marker
+ out.___preserveComponent(component);
+ globalComponentsContext.___renderedComponentsById[id] = true;
+ component.___reset(); // The component is no longer dirty so reset internal flags
return;
}
}
+ var isFakeComponent = false;
+
if (!component) {
- fakeComponent = {
- id: id
+ isFakeComponent = true;
+ component = {
+ id: id,
+ ___keyedElements: {}
};
} else {
componentState = component.___rawState || componentState;
@@ -168,17 +190,22 @@ function createRendererFunc(templateRenderFunc, componentProps) {
getTemplateData(componentState, input, out) :
componentState || input || {};
- var componentDef = componentsContext.___beginComponent(component || fakeComponent);
+ var componentDef = beginComponent(componentsContext, component, isSplit, parentComponentDef);
+
+ // This is a hack, but we have to swap out the component instance stored with this node
+ var vComponentNode = out.___parent;
+
componentDef.___roots = roots;
- componentDef.___component = fakeComponent ? null : component;
+ componentDef.___component = isFakeComponent ? null : component;
componentDef.___isExisting = isExisting;
componentDef.b = componentBody;
componentDef.c = function(widgetConfig) {
component.$c = widgetConfig;
};
+
componentDef.t = function(typeName) {
if (typeName) {
- this.___component = component = registry.___createComponent(typeName, fakeComponent.id);
+ vComponentNode.___component = this.___component = component = registry.___createComponent(typeName, component.id);
}
};
@@ -188,7 +215,7 @@ function createRendererFunc(templateRenderFunc, componentProps) {
// Render the template associated with the component using the final template
// data that we constructed
- templateRenderFunc(templateInput, out, componentDef, componentDef);
+ templateRenderFunc(templateInput, out, componentDef, componentDef, component);
if (customEvents && componentDef.___component) {
if (registry.___isServer) {
@@ -199,7 +226,8 @@ function createRendererFunc(templateRenderFunc, componentProps) {
}
}
- componentDef.___end();
+ endComponent(out, componentDef);
+ componentsContext.___componentDef = parentComponentDef;
};
}
diff --git a/src/components/package.json b/src/components/package.json
index 88c5cbfe6..180abc321 100644
--- a/src/components/package.json
+++ b/src/components/package.json
@@ -1,6 +1,7 @@
{
"browser": {
"./beginComponent.js": "./beginComponent-browser.js",
+ "./endComponent.js": "./endComponent-browser.js",
"./helpers.js": "./helpers-browser.js",
"./index.js": "./index-browser.js",
"./init-components.js": "./init-components-browser.js",
diff --git a/src/components/renderer.js b/src/components/renderer.js
index 68f08b5ad..b5ff19282 100644
--- a/src/components/renderer.js
+++ b/src/components/renderer.js
@@ -4,50 +4,26 @@ var emitLifecycleEvent = componentsUtil.___emitLifecycleEvent;
var ComponentsContext = require('./ComponentsContext');
var getComponentsContext = ComponentsContext.___getComponentsContext;
-var repeatedRegExp = /\[\]$/;
var registry = require('./registry');
var copyProps = require('raptor-util/copyProps');
var isServer = componentsUtil.___isServer === true;
+var beginComponent = require('./beginComponent');
+var endComponent = require('./endComponent');
var COMPONENT_BEGIN_ASYNC_ADDED_KEY = '$wa';
-function resolveComponentKey(globalComponentsContext, key, scope) {
- if (key[0] == '#') {
+function resolveComponentKey(globalComponentsContext, key, parentComponentDef) {
+ if (key[0] === '#') {
return key.substring(1);
} else {
- var resolvedId;
-
- if (repeatedRegExp.test(key)) {
- resolvedId = globalComponentsContext.___nextRepeatedId(scope, key);
- } else {
- resolvedId = scope + '-' + key;
- }
-
- return resolvedId;
+ return parentComponentDef.id + '-' + parentComponentDef.___nextKey(key);
}
}
-function preserveComponentEls(existingComponent, out, globalComponentsContext) {
- var rootEls = existingComponent.___getRootEls({});
-
- for (var elId in rootEls) {
- var el = rootEls[elId];
-
- // We put a placeholder element in the output stream to ensure that the existing
- // DOM node is matched up correctly when using morphdom.
- out.element(el.tagName, { id: elId });
-
- globalComponentsContext.___preserveDOMNode(elId); // Mark the element as being preserved (for morphdom)
- }
-
- existingComponent.___reset(); // The component is no longer dirty so reset internal flags
- return true;
-}
-
function handleBeginAsync(event) {
var parentOut = event.parentOut;
var asyncOut = event.out;
- var componentsContext = parentOut.data.___components;
+ var componentsContext = parentOut.___components;
if (componentsContext !== undefined) {
// All of the components in this async block should be
@@ -58,8 +34,7 @@ function handleBeginAsync(event) {
// stack (to begin with). This will result in top-level components
// of the async block being added as children of the component in the
// parent block.
- var nestedComponentsContext = componentsContext.___createNestedComponentsContext(asyncOut);
- asyncOut.data.___components = nestedComponentsContext;
+ asyncOut.___components = new ComponentsContext(asyncOut, componentsContext);
}
// Carry along the component arguments
asyncOut.___componentArgs = parentOut.___componentArgs;
@@ -69,8 +44,6 @@ function createRendererFunc(templateRenderFunc, componentProps, renderingLogic)
renderingLogic = renderingLogic || {};
var onInput = renderingLogic.onInput;
var typeName = componentProps.type;
- var roots = componentProps.roots;
- var assignedId = componentProps.id;
var isSplit = componentProps.split === true;
var shouldApplySplitMixins = isSplit;
@@ -89,38 +62,46 @@ function createRendererFunc(templateRenderFunc, componentProps, renderingLogic)
var component = globalComponentsContext.___rerenderComponent;
var isRerender = component !== undefined;
- var id = assignedId;
+ var id;
var isExisting;
var customEvents;
var scope;
+ var parentComponentDef;
if (component) {
id = component.id;
isExisting = true;
globalComponentsContext.___rerenderComponent = null;
} else {
+ parentComponentDef = componentsContext.___componentDef;
var componentArgs = out.___componentArgs;
-
if (componentArgs) {
+ // console.log('componentArgs:', componentArgs);
+ scope = parentComponentDef.id;
out.___componentArgs = null;
- scope = componentArgs[0];
+ var key;
- if (scope) {
- scope = scope.id;
+ if (typeof componentArgs === 'string') {
+ key = componentArgs;
+ } else {
+ key = componentArgs[0];
+ customEvents = componentArgs[1];
}
- var key = componentArgs[1];
if (key != null) {
key = key.toString();
+ id = resolveComponentKey(globalComponentsContext, key, parentComponentDef);
+ } else {
+ id = parentComponentDef.___nextComponentId();
}
- id = id || resolveComponentKey(globalComponentsContext, key, scope);
- customEvents = componentArgs[2];
+ } else if (parentComponentDef) {
+ id = parentComponentDef.___nextComponentId();
+ } else {
+ id = globalComponentsContext.___nextComponentId();
}
}
- id = id || componentsContext.___nextComponentId();
-
if (isServer) {
component = registry.___createComponent(
renderingLogic,
@@ -134,12 +115,10 @@ function createRendererFunc(templateRenderFunc, componentProps, renderingLogic)
component.___updatedInput = undefined; // We don't want ___updatedInput to be serialized to the browser
} else {
if (!component) {
- if (isRerender) {
- // Look in in the DOM to see if a component with the same ID and type already exists.
- component = componentLookup[id];
- if (component && component.___type !== typeName) {
- component = undefined;
- }
+ if (isRerender && (component = componentLookup[id]) && component.___type !== typeName) {
+ // Destroy the existing component since
+ component.destroy();
+ component = undefined;
}
if (component) {
@@ -178,7 +157,12 @@ function createRendererFunc(templateRenderFunc, componentProps, renderingLogic)
if (isExisting === true) {
if (component.___isDirty === false || component.shouldUpdate(input, component.___state) === false) {
- preserveComponentEls(component, out, globalComponentsContext);
+ // We put a placeholder element in the output stream to ensure that the existing
+ // DOM node is matched up correctly when using morphdom. We flag the VElement
+ // node to track that it is a preserve marker
+ out.___preserveComponent(component);
+ globalComponentsContext.___renderedComponentsById[id] = true;
+ component.___reset(); // The component is no longer dirty so reset internal flags
return;
}
}
@@ -189,15 +173,17 @@ function createRendererFunc(templateRenderFunc, componentProps, renderingLogic)
emitLifecycleEvent(component, 'render', out);
}
- var componentDef = componentsContext.___beginComponent(component, isSplit);
- componentDef.___roots = roots;
+ var componentDef =
+ beginComponent(componentsContext, component, isSplit, parentComponentDef);
+
componentDef.___isExisting = isExisting;
// Render the template associated with the component using the final template
// data that we constructed
templateRenderFunc(input, out, componentDef, component, component.___rawState);
- componentDef.___end();
+ endComponent(out, componentDef);
+ componentsContext.___componentDef = parentComponentDef;
};
}
@@ -205,5 +191,4 @@ module.exports = createRendererFunc;
// exports used by the legacy renderer
createRendererFunc.___resolveComponentKey = resolveComponentKey;
-createRendererFunc.___preserveComponentEls = preserveComponentEls;
createRendererFunc.___handleBeginAsync = handleBeginAsync;
diff --git a/src/components/taglib/TransformHelper/ComponentArgs.js b/src/components/taglib/TransformHelper/ComponentArgs.js
index 793331db9..dad09d7cf 100644
--- a/src/components/taglib/TransformHelper/ComponentArgs.js
+++ b/src/components/taglib/TransformHelper/ComponentArgs.js
@@ -2,23 +2,15 @@
class ComponentArgs {
constructor() {
- this.id = null;
+ this.key = null;
this.customEvents = null;
- this.empty = true;
}
- setId(id) {
- this.empty = false;
- this.id = id;
- }
-
- getId() {
- return this.id;
+ setKey(key) {
+ this.key = key;
}
addCustomEvent(eventType, targetMethod, extraArgs) {
- this.empty = false;
-
if (!this.customEvents) {
this.customEvents = [];
}
@@ -27,7 +19,7 @@ class ComponentArgs {
}
compile(transformHelper) {
- if (this.empty) {
+ if (!this.key && !this.customEvents) {
return;
}
@@ -35,29 +27,14 @@ class ComponentArgs {
var builder = transformHelper.builder;
- var id = this.id;
- var customEvents = this.customEvents;
+ var args;
- // Make sure the nested component has access to the ID of the containing
- // component if it is needed
- var shouldProvideScope = id || customEvents;
-
- var args = [];
-
- if (shouldProvideScope) {
- args.push(builder.identifier('__component'));
+ if (this.key && this.customEvents) {
+ args = builder.literal([ this.key, builder.literal(this.customEvents) ]);
+ } else if (this.customEvents) {
+ args = builder.literal([ builder.literalNull(), builder.literal(this.customEvents) ]);
} else {
- args.push(builder.literalNull());
- }
-
- if (id != null) {
- args.push(id);
- } else {
- args.push(builder.literalNull());
- }
-
- if (customEvents) {
- args.push(builder.literal(customEvents));
+ args = this.key;
}
if (el.type === 'CustomTag') {
@@ -66,7 +43,7 @@ class ComponentArgs {
el.generateRenderTagCode = function(codegen, tagVar, tagArgs) {
tagArgs = [tagVar].concat(tagArgs);
- tagArgs.push(builder.literal(args));
+ tagArgs.push(args);
return codegen.builder.functionCall(
renderComponentHelper,
@@ -75,7 +52,7 @@ class ComponentArgs {
} else {
el.onBeforeGenerateCode((event) => {
let funcTarget = builder.memberExpression(builder.identifierOut(), builder.identifier('c'));
- let funcArgs = [builder.literal(args)];
+ let funcArgs = [args];
event.insertCode(builder.functionCall(funcTarget, funcArgs));
});
diff --git a/src/components/taglib/TransformHelper/assignComponentId.js b/src/components/taglib/TransformHelper/assignComponentId.js
index aabb801da..c538587ec 100644
--- a/src/components/taglib/TransformHelper/assignComponentId.js
+++ b/src/components/taglib/TransformHelper/assignComponentId.js
@@ -12,7 +12,11 @@ module.exports = function assignComponentId(isRepeated) {
var context = this.context;
var builder = this.builder;
- let componentRef;
+ if (el.noOutput || (el.tagDef && el.tagDef.noOutput)) {
+ return;
+ }
+
+ let assignedKey;
var nestedIdExpression;
var idExpression;
@@ -27,6 +31,9 @@ module.exports = function assignComponentId(isRepeated) {
this.getMarkoComponentsRequirePath('marko/components/taglib/helpers/getCurrentComponent'));
context.addVar('__component', builder.functionCall(getCurrentComponentVar, [builder.identifierOut()]));
+ context.addVar('component', builder.memberExpression(
+ builder.identifier('__component'),
+ builder.identifier('___component')));
}
}
@@ -42,72 +49,80 @@ module.exports = function assignComponentId(isRepeated) {
// 3) The HTML does not have an "id" or "ref" attribute. We must add
// an "id" attribute with a unique ID.
- var isCustomTag = el.type !== 'HtmlElement';
+ var isHtmlElement = el.type === 'HtmlElement';
+ var isCustomTag = el.type === 'CustomTag';
- if (el.hasAttribute('key')) {
- componentRef = el.getAttributeValue('key');
- el.removeAttribute('key');
- } else if (el.hasAttribute('ref')) {
- context.deprecate('The "ref" attribute is deprecated. Please use "key" instead.');
- componentRef = el.getAttributeValue('ref');
- el.removeAttribute('ref');
+ // LEGACY -- Remove in Marko 5.0
+ if (!isCustomTag && el.tagName === 'invoke') {
+ isCustomTag = true;
+ }
+
+ if (!isCustomTag && !isHtmlElement) {
+ return;
}
if (el.hasAttribute('w-id')) {
context.deprecate('The "w-id" attribute is deprecated. Please use "key" instead.');
- if (componentRef) {
- this.addError('The "w-id" attribute cannot be used in conjuction with the "ref" or "key" attributes.');
+ if (el.hasAttribute('key')) {
+ this.addError('The "w-id" attribute cannot be used in conjunction with the "key" attributes.');
return;
}
- componentRef = el.getAttributeValue('w-id');
+ if (el.hasAttribute('ref')) {
+ this.addError('The "w-id" attribute cannot be used in conjunction with the "ref" attributes.');
+ return;
+ }
+
+ assignedKey = el.getAttributeValue('w-id');
el.removeAttribute('w-id');
+ } else if (el.hasAttribute('key')) {
+ assignedKey = el.getAttributeValue('key');
+ el.removeAttribute('key');
+ } else if (el.hasAttribute('ref')) {
+ context.deprecate('The "ref" attribute is deprecated. Please use "key" instead.');
+ assignedKey = el.getAttributeValue('ref');
+ el.removeAttribute('ref');
}
- if (componentRef) {
- idExpression = this.buildComponentElIdFunctionCall(componentRef);
-
- nestedIdExpression = componentRef;
+ if (assignedKey) {
+ nestedIdExpression = assignedKey;
if (isCustomTag) {
+ idExpression = this.buildComponentElIdFunctionCall(assignedKey);
// The element is a custom tag
- this.getComponentArgs().setId(nestedIdExpression);
+ this.getComponentArgs().setKey(nestedIdExpression);
} else {
- if (el.hasAttribute('id')) {
- this.addError('The "ref", "key", and "w-id" attributes cannot be used in conjuction with the "id" attribute.');
- return;
+ idExpression = assignedKey;
+ if (context.data.hasLegacyForKey && el.data.userAssignedKey !== false) {
+ el.setAttributeValue('id', this.buildComponentElIdFunctionCall(assignedKey));
}
- el.setAttributeValue('id', idExpression);
- }
- } else if (el.hasAttribute('id')) {
- idExpression = el.getAttributeValue('id');
- if (el.isFlagSet('hasComponentBind')) {
- // We have to attach a listener to the root element of the component
- // We will use an empty string as an indicator that it is the root component
- // element.
- nestedIdExpression = builder.literal('');
- } else {
- // Convert the raw String to a JavaScript expression. we need to prefix
- // with '#' to make it clear this is a fully resolved element ID
- nestedIdExpression = builder.concat(
- builder.literal('#'),
- idExpression);
+ if (context.isServerTarget()) {
+ var markoKeyAttrVar = context.importModule('marko_keyAttr',
+ this.getMarkoComponentsRequirePath('marko/components/taglib/helpers/markoKeyAttr'));
+
+ el.setAttributeValue('data-marko-key', builder.functionCall(markoKeyAttrVar, [
+ idExpression,
+ builder.identifier('__component')
+ ]));
+ }
+
+ el.setKey(assignedKey);
}
} else {
// Case 3 - We need to add a unique "id" attribute
let uniqueElId = this.nextUniqueId();
- nestedIdExpression = isRepeated ? builder.literal(uniqueElId + '[]') : builder.literal(uniqueElId);
+ nestedIdExpression = isRepeated ? builder.literal(uniqueElId + '[]') : builder.literal(uniqueElId.toString());
- idExpression = this.buildComponentElIdFunctionCall(nestedIdExpression);
+ idExpression = builder.literal(uniqueElId.toString());
if (isCustomTag) {
- this.getComponentArgs().setId(nestedIdExpression);
+ this.getComponentArgs().setKey(nestedIdExpression);
} else {
- el.setAttributeValue('id', idExpression);
+ el.setKey(idExpression);
}
}
@@ -123,13 +138,17 @@ module.exports = function assignComponentId(isRepeated) {
}
let uniqueElId = transformHelper.nextUniqueId();
- let idVarName = '__componentId' + uniqueElId;
+ let idVarName = '__key' + uniqueElId;
let idVar = builder.identifier(idVarName);
this.idVarNode = builder.vars([
{
id: idVarName,
- init: idExpression
+ init: builder.functionCall(
+ builder.memberExpression(
+ builder.identifier('__component'),
+ builder.identifier('___nextKey')),
+ [ idExpression ])
}
]);
@@ -140,9 +159,9 @@ module.exports = function assignComponentId(isRepeated) {
idVar);
if (isCustomTag) {
- transformHelper.getComponentArgs().setId(nestedIdExpression);
+ transformHelper.getComponentArgs().setKey(nestedIdExpression);
} else {
- el.setAttributeValue('id', idExpression);
+ el.setKey(idExpression);
}
return this.idVarNode;
diff --git a/src/components/taglib/TransformHelper/convertToComponent.js b/src/components/taglib/TransformHelper/convertToComponent.js
new file mode 100644
index 000000000..2d6ab557c
--- /dev/null
+++ b/src/components/taglib/TransformHelper/convertToComponent.js
@@ -0,0 +1,158 @@
+'use strict';
+const generateRegisterComponentCode = require('../util/generateRegisterComponentCode');
+
+// var FLAG_WILL_RERENDER_IN_BROWSER = 1;
+var FLAG_HAS_BODY_EL = 2;
+var FLAG_HAS_HEAD_EL = 4;
+
+module.exports = function handleComponentBind(options) {
+ if (this.firstBind) {
+ return;
+ }
+
+ this.firstBind = true;
+
+ let context = this.context;
+ let builder = this.builder;
+
+ let isLegacyComponent = this.isLegacyComponent = options.isLegacyComponent === true;
+ let componentModule = options.componentModule;
+ let rendererModule = options.rendererModule;
+ let componentProps = options.componentProps || {};
+ let rootNodes = options.rootNodes;
+ let isLegacyInnerBind = options.isLegacyInnerBind;
+
+ var isSplit = false;
+
+ if ((rendererModule && rendererModule !== componentModule) ||
+ (!rendererModule && componentModule)) {
+ componentProps.split = isSplit = true;
+ }
+
+ if (componentModule) {
+ let componentTypeNode;
+ let dependencyModule = isLegacyComponent || isSplit ? componentModule : this.getTemplateModule();
+
+ if (dependencyModule.requirePath) {
+ context.addDependency({ type:'require', path: dependencyModule.requirePath });
+ }
+
+ if (isSplit) {
+ context.addDependency({ type:'require', path: context.markoModulePrefix + 'components' });
+ }
+
+ componentTypeNode = context.addStaticVar(
+ 'marko_componentType',
+ generateRegisterComponentCode(componentModule, this, isSplit));
+
+ componentProps.type = componentTypeNode;
+ }
+
+ if (isLegacyInnerBind) {
+ let el = rootNodes[0];
+ el.setAttributeValue('id',
+ builder.memberExpression(
+ builder.identifier('__component'),
+ builder.identifier('id')));
+
+ // TODO Deprecation warning for inner binds
+ let componentNode = context.createNodeForEl('_component', {
+ props: builder.literal(componentProps)
+ });
+ el.wrapWith(componentNode);
+ return;
+ }
+
+ var flags = 0;
+
+
+
+ rootNodes.forEach((rootNode) => {
+ if (rootNode.type === 'HtmlElement') {
+ if (rootNode.tagName === 'body') {
+ flags |= FLAG_HAS_BODY_EL;
+ } else if (rootNode.tagName === 'head') {
+ flags |= FLAG_HAS_HEAD_EL;
+ }
+ }
+ });
+
+ if (flags) {
+ context.root.appendChild(
+ builder.assignment(
+ builder.memberExpression(
+ builder.identifier('__component'),
+ builder.identifier('___flags')),
+ builder.literal(flags),
+ '|='));
+ }
+
+ let markoComponentVar;
+
+ if (rendererModule) {
+ if (rendererModule.inlineId) {
+ markoComponentVar = rendererModule.inlineId;
+ } else {
+ markoComponentVar = context.addStaticVar('marko_component', builder.require(builder.literal(rendererModule.requirePath)));
+ }
+ }
+
+ this.setHasBoundComponentForTemplate();
+
+ var rendererHelper = isLegacyComponent ?
+ this.context.helper('rendererLegacy') :
+ this.context.helper('renderer');
+
+ var defineComponentHelper;
+
+ if (!isSplit && !isLegacyComponent) {
+ defineComponentHelper = this.context.helper('defineComponent');
+ }
+
+ this.context.on('beforeGenerateCode:TemplateRoot', function(eventArgs) {
+ eventArgs.node.addRenderFunctionParam(builder.identifier('__component'));
+
+ if (isLegacyComponent) {
+ eventArgs.node.addRenderFunctionParam(builder.identifier('widget'));
+ eventArgs.node.addRenderFunctionParam(builder.identifier('component'));
+ } else {
+ eventArgs.node.addRenderFunctionParam(builder.identifier('component'));
+ eventArgs.node.addRenderFunctionParam(builder.identifier('state'));
+ }
+
+ eventArgs.node.generateAssignRenderCode = (eventArgs) => {
+ const nodes = [];
+ const templateVar = eventArgs.templateVar;
+ const templateRendererMember = eventArgs.templateRendererMember;
+ const renderFunctionVar = eventArgs.renderFunctionVar;
+
+ const createRendererArgs = [
+ renderFunctionVar,
+ builder.literal(componentProps)
+ ];
+
+ if (markoComponentVar) {
+ createRendererArgs.push(markoComponentVar);
+ }
+
+ nodes.push(builder.assignment(
+ templateRendererMember,
+ builder.functionCall(
+ rendererHelper,
+ createRendererArgs)));
+
+ if (!isSplit && !isLegacyComponent) {
+ nodes.push(builder.assignment(
+ builder.memberExpression(templateVar, builder.identifier('Component')),
+ builder.functionCall(
+ defineComponentHelper,
+ [
+ markoComponentVar,
+ templateRendererMember
+ ])));
+ }
+
+ return nodes;
+ };
+ });
+};
diff --git a/src/components/taglib/TransformHelper/handleComponentBind.js b/src/components/taglib/TransformHelper/handleComponentBind.js
deleted file mode 100644
index 41c25d475..000000000
--- a/src/components/taglib/TransformHelper/handleComponentBind.js
+++ /dev/null
@@ -1,287 +0,0 @@
-'use strict';
-const resolveFrom = require('resolve-from');
-const generateRegisterComponentCode = require('../util/generateRegisterComponentCode');
-
-function legacyGetDefaultComponentModule(dirname) {
- var filename;
- var legacy = true;
-
- if ((filename = resolveFrom(dirname, './widget'))) {
- return {
- filename,
- requirePath: './widget',
- legacy
- };
- } else if ((filename = resolveFrom(dirname, './component'))) {
- return {
- filename,
- requirePath: './component',
- legacy
- };
- } else if ((filename = resolveFrom(dirname, './'))) {
- return {
- filename,
- requirePath: './',
- legacy
- };
- } else {
- return null;
- }
-}
-
-function checkIsInnerBind(el) {
- var curNode = el;
-
- while (true) {
- if (curNode.data.hasBoundComponent) {
- return true;
- }
-
- curNode = curNode.parentNode;
-
- if (!curNode) {
- break;
- }
- }
-
- return false;
-}
-
-module.exports = function handleComponentBind() {
- let el = this.el;
- let context = this.context;
- let builder = this.builder;
-
- let componentModule;
- let rendererModulePath;
- let rendererModule = this.getRendererModule();
- let isLegacyComponent = false;
-
- if (el.hasAttribute('w-bind')) {
- let bindAttr = el.getAttribute('w-bind');
-
- context.deprecate('Legacy components using w-bind and defineRenderer/defineComponent or defineComponent are deprecated. See: https://github.com/marko-js/marko/issues/421');
- this.isLegacyComponent = isLegacyComponent = true;
-
- // Remove the w-bind attribute since we don't want it showing up in the output DOM
- el.removeAttribute('w-bind');
-
- // Read the value for the w-bind attribute. This will be an AST node for the parsed JavaScript
- let bindAttrValue = bindAttr.value;
-
- const hasWidgetTypes = context.isFlagSet('hasWidgetTypes');
-
- if (hasWidgetTypes) {
- context.deprecate('The tag is deprecated. Please remove it. See: https://github.com/marko-js/marko/issues/514');
- }
-
- if (bindAttrValue == null) {
- componentModule = legacyGetDefaultComponentModule(this.dirname);
- if (!componentModule) {
- this.addError('No corresponding JavaScript module found in the same directory (either "component.js" or "index.js").');
- return;
- }
- } else if (bindAttr.isLiteralValue()) {
- if (typeof bindAttr.literalValue !== 'string') {
- this.addError('The value for the "w-bind" attribute should be a string. Actual: ' + componentModule);
- return;
- }
-
- let requirePath = bindAttr.literalValue;
- let filename = resolveFrom(this.dirname, requirePath);
-
- if (!filename) {
- this.addError('Target file not found: ' + requirePath + ' (from: ' + this.dirname + ')');
- return;
- }
-
- componentModule = {
- legacy: true,
- filename,
- requirePath
- };
- } else {
- // This is a dynamic expression. The should have been found.
- if (!hasWidgetTypes) {
- this.addError('The tag must be used to declare components when the value of the "w-bind" attribute is a dynamic expression.');
- return;
- }
-
- el.insertSiblingBefore(
- builder.functionCall(
- builder.memberExpression(builder.identifier('__component'), builder.identifier('t')),
- [
- builder.memberExpression(
- builder.identifier('marko_componentTypes'),
- bindAttrValue,
- true /* computed */)
- ]));
- }
- } else if (el.isFlagSet('hasComponentBind')) {
- componentModule = this.getComponentModule();
- rendererModulePath = this.getRendererModule();
-
-
- if (context.isFlagSet('hasWidgetTypes')) {
- context.addError('The tag is no longer supported. See: https://github.com/marko-js/marko/issues/514');
- }
- } else {
- return;
- }
-
- this.setHasBoundComponentForTemplate();
-
- let isInnerBind = checkIsInnerBind(el.parentNode);
-
- el.data.hasBoundComponent = true;
-
- // A component is bound to the el...
-
- var componentProps = isInnerBind ? {} : this.getComponentProps();
- let transformHelper = this;
-
- var isSplit = false;
-
- if ((rendererModule && rendererModule !== componentModule) ||
- (!rendererModule && componentModule)) {
- componentProps.split = isSplit = true;
- }
-
- if (componentModule) {
- let componentTypeNode;
- let dependencyModule = isLegacyComponent || isSplit ? componentModule : this.getTemplateModule();
-
- if (dependencyModule.requirePath) {
- context.addDependency({ type:'require', path: dependencyModule.requirePath });
- }
-
- if (isSplit) {
- context.addDependency({ type:'require', path: context.markoModulePrefix + 'components' });
- }
-
- componentTypeNode = context.addStaticVar(
- 'marko_componentType',
- generateRegisterComponentCode(componentModule, this, isSplit));
-
- componentProps.type = componentTypeNode;
- }
-
- if (el.hasAttribute('w-config')) {
- el.insertSiblingBefore(
- builder.functionCall(
- builder.memberExpression(builder.identifier('__component'), builder.identifier('c')),
- [
- el.getAttributeValue('w-config')
- ]));
-
- el.removeAttribute('w-config');
- }
-
- let id = el.getAttributeValue('id');
-
- if (id) {
- componentProps.id = id;
- }
-
- let markoComponentVar;
-
- if (rendererModule) {
- if (rendererModule.inlineId) {
- markoComponentVar = rendererModule.inlineId;
- } else {
- markoComponentVar = context.addStaticVar('marko_component', builder.require(builder.literal(rendererModule.requirePath)));
- }
- }
-
- if (isInnerBind) {
- el.setAttributeValue('id',
- builder.memberExpression(
- builder.identifier('__component'),
- builder.identifier('id')));
-
- // TODO Deprecation warning for inner binds
- let componentNode = context.createNodeForEl('_component', {
- props: builder.literal(componentProps)
- });
- el.wrapWith(componentNode);
- return;
- }
-
- if (this.firstBind) {
-
- var rendererHelper = isLegacyComponent ?
- transformHelper.context.helper('rendererLegacy') :
- transformHelper.context.helper('renderer');
-
- var defineComponentHelper;
-
- if (!isSplit && !isLegacyComponent) {
- defineComponentHelper = transformHelper.context.helper('defineComponent');
- }
-
- this.context.on('beforeGenerateCode:TemplateRoot', function(eventArgs) {
- eventArgs.node.addRenderFunctionParam(builder.identifier('__component'));
-
- if (isLegacyComponent) {
- eventArgs.node.addRenderFunctionParam(builder.identifier('widget'));
- } else {
- eventArgs.node.addRenderFunctionParam(builder.identifier('component'));
- eventArgs.node.addRenderFunctionParam(builder.identifier('state'));
- }
-
- eventArgs.node.generateAssignRenderCode = function(eventArgs) {
- let nodes = [];
- let templateVar = eventArgs.templateVar;
- let templateRendererMember = eventArgs.templateRendererMember;
- let renderFunctionVar = eventArgs.renderFunctionVar;
-
- let createRendererArgs = [
- renderFunctionVar,
- builder.literal(componentProps)
- ];
-
- if (markoComponentVar) {
- createRendererArgs.push(markoComponentVar);
- }
-
- nodes.push(builder.assignment(
- templateRendererMember,
- builder.functionCall(
- rendererHelper,
- createRendererArgs)));
-
- if (!isSplit && !isLegacyComponent) {
- nodes.push(builder.assignment(
- builder.memberExpression(templateVar, builder.identifier('Component')),
- builder.functionCall(
- defineComponentHelper,
- [
- markoComponentVar,
- templateRendererMember
- ])));
- }
-
- return nodes;
- };
- });
- }
-
- if (el.hasAttribute('key')) {
- if (!componentProps.roots) {
- componentProps.roots = [];
- }
- var key = el.getAttributeValue('key');
- componentProps.roots.push(key);
- } else if (el.hasAttribute('ref')) {
- if (!componentProps.roots) {
- componentProps.roots = [];
- }
- var ref = el.getAttributeValue('ref');
- componentProps.roots.push(ref);
- } else {
- el.setAttributeValue('id',
- builder.memberExpression(
- builder.identifier('__component'),
- builder.identifier('id')));
- }
-};
diff --git a/src/components/taglib/TransformHelper/handleComponentKeyAttrs.js b/src/components/taglib/TransformHelper/handleComponentKeyAttrs.js
deleted file mode 100644
index d0f2a8f01..000000000
--- a/src/components/taglib/TransformHelper/handleComponentKeyAttrs.js
+++ /dev/null
@@ -1,51 +0,0 @@
-'use strict';
-
-const keySuffix = ':key';
-
-module.exports = function handleComponentKeyAttrs() {
- let el = this.el;
- let context = this.context;
-
- const filePosition = el.pos ? context.getPosInfo(el.pos) : context.filename;
-
- // BEGIN support for deprecated for attributes
-
- let deprecatedForAttributes = ['for-ref', 'for-key', 'w-for'];
-
- deprecatedForAttributes.forEach(attributeName => {
- if (el.hasAttribute(attributeName)) {
- context.deprecate(`The "${attributeName}" tag is deprecated. Please use "for:key" instead.`);
-
- let incompatibleAttributes = ['for', 'for:key']
- .concat(deprecatedForAttributes.filter(a => a != attributeName))
- .filter(a => el.hasAttribute(a));
-
- if (incompatibleAttributes.length) {
- this.addError(`The "${attributeName}" tag cannot be used with "${incompatibleAttributes.join('" or "')}". (${filePosition})`);
- } else {
- el.setAttributeValue('for:key', el.getAttributeValue(attributeName));
- }
-
- el.removeAttribute(attributeName);
- }
- });
-
- // END support for deprecated for attributes
-
- el.attributes.forEach(attribute => {
- const attributeName = attribute.name;
-
- if (attributeName && attributeName !== keySuffix && attributeName.endsWith(keySuffix)) {
- const unfixedName = attributeName.replace(keySuffix, '');
-
- el.removeAttribute(attributeName);
- if (el.hasAttribute(unfixedName)) {
- this.addError(`The "${attributeName}" attribute cannot be used in conjuction with the "${unfixedName}" attribute. (${filePosition})`);
- } else {
- el.setAttributeValue(
- unfixedName,
- this.buildComponentElIdFunctionCall(attribute.value));
- }
- }
- });
-};
diff --git a/src/components/taglib/TransformHelper/handleComponentPreserve.js b/src/components/taglib/TransformHelper/handleComponentPreserve.js
index ac9684f56..6ef5f32d4 100644
--- a/src/components/taglib/TransformHelper/handleComponentPreserve.js
+++ b/src/components/taglib/TransformHelper/handleComponentPreserve.js
@@ -15,12 +15,17 @@ function addPreserve(transformHelper, bodyOnly, condition) {
preserveAttrs['if'] = condition;
}
- let componentIdInfo = transformHelper.assignComponentId(true /* repeated */);
+ let componentIdInfo = transformHelper.assignComponentId();
let idVarNode = componentIdInfo.idVarNode ? null : componentIdInfo.createIdVarNode();
- preserveAttrs.id = transformHelper.getIdExpression();
+ if (el.type === 'HtmlElement') {
+ preserveAttrs.key = transformHelper.getIdExpression();
+ } else {
+ preserveAttrs.cid = transformHelper.getIdExpression();
+ }
- let preserveNode = context.createNodeForEl('w-preserve', preserveAttrs);
+
+ let preserveNode = context.createNodeForEl('_preserve', preserveAttrs);
let idVarNodeTarget;
if (bodyOnly) {
diff --git a/src/components/taglib/TransformHelper/handleIncludeNode.js b/src/components/taglib/TransformHelper/handleIncludeNode.js
deleted file mode 100644
index c9c9189e9..000000000
--- a/src/components/taglib/TransformHelper/handleIncludeNode.js
+++ /dev/null
@@ -1,75 +0,0 @@
-'use strict';
-
-var includeTagForComponents = require.resolve('../include-tag');
-
-module.exports = function(includeNode) {
- var context = this.context;
-
- if (!this.hasBoundComponentForTemplate()) {
- return;
- }
-
- var parentNode = includeNode.parentNode;
-
- if (!parentNode.hasAttribute) {
- return;
- }
-
- parentNode._normalizeChildTextNodes(context, true /* force trim */);
-
- if (parentNode.childCount === 1) {
- if (includeNode.hasAttribute('key') || includeNode.hasAttribute('ref')) {
- this.assignComponentId();
- }
-
- let parentTransformHelper = this.getTransformHelper(parentNode);
-
- if (includeNode.data.bodySlot) {
- parentTransformHelper.assignComponentId(false /* not repeated */);
- var componentProps = this.getComponentProps();
- componentProps.body = parentTransformHelper.getNestedIdExpression();
- } else {
- let componentIdInfo = parentTransformHelper.assignComponentId(true /* repeated */);
- if (!componentIdInfo.idVarNode) {
- let idVarNode = componentIdInfo.createIdVarNode();
- parentNode.onBeforeGenerateCode((event) => {
- event.insertCode(idVarNode);
- });
- }
- }
-
- includeNode.setRendererPath(includeTagForComponents);
-
- includeNode.onBeforeGenerateCode(function() {
- includeNode.addProp('_elId', parentTransformHelper.getIdExpression());
- });
- }
-
-
-
- // includeNode.generateCodeForDynamicInclude = (options, codegen) => {
- // var target = options.target;
- // var data = options.data;
- //
- // if (!data) {
- // data = builder.literal(null);
- // }
- //
- // let includeVar = context.importModule('marko_component_include', this.getMarkoComponentsRequirePath('marko/components/taglib/helpers/include'));
- //
- // let includeArgs = [
- // target,
- // builder.identifierOut(),
- // data
- // ];
- //
- // if (parentTransformHelper) {
- // includeArgs = includeArgs.concat([
- // parentTransformHelper.getIdExpression(),
- //
- // ]);
- // }
- //
- // return builder.functionCall(includeVar, includeArgs);
- // };
-};
diff --git a/src/components/taglib/TransformHelper/handleLegacyBind.js b/src/components/taglib/TransformHelper/handleLegacyBind.js
new file mode 100644
index 000000000..82897ed5a
--- /dev/null
+++ b/src/components/taglib/TransformHelper/handleLegacyBind.js
@@ -0,0 +1,155 @@
+'use strict';
+const resolveFrom = require('resolve-from');
+
+function legacyGetDefaultComponentModule(dirname) {
+ var filename;
+ var legacy = true;
+
+ if ((filename = resolveFrom(dirname, './widget'))) {
+ return {
+ filename,
+ requirePath: './widget',
+ legacy
+ };
+ } else if ((filename = resolveFrom(dirname, './component'))) {
+ return {
+ filename,
+ requirePath: './component',
+ legacy
+ };
+ } else if ((filename = resolveFrom(dirname, './'))) {
+ return {
+ filename,
+ requirePath: './',
+ legacy
+ };
+ } else {
+ return null;
+ }
+}
+
+function checkIsInnerBind(el) {
+ var curNode = el;
+
+ while (true) {
+ if (curNode.data.hasBoundComponent) {
+ return true;
+ }
+
+ curNode = curNode.parentNode;
+
+ if (!curNode) {
+ break;
+ }
+ }
+
+ return false;
+}
+
+module.exports = function handleLegacyBind() {
+ let el = this.el;
+ let context = this.context;
+ let builder = this.builder;
+
+ let componentModule;
+ let rendererModule;
+
+ let isLegacyComponent = false;
+
+ if (el.hasAttribute('w-bind')) {
+ let bindAttr = el.getAttribute('w-bind');
+
+ context.deprecate('Legacy components using w-bind and defineRenderer/defineComponent or defineComponent are deprecated. See: https://github.com/marko-js/marko/issues/421');
+ this.isLegacyComponent = isLegacyComponent = true;
+
+ // Remove the w-bind attribute since we don't want it showing up in the output DOM
+ el.removeAttribute('w-bind');
+
+ // Read the value for the w-bind attribute. This will be an AST node for the parsed JavaScript
+ let bindAttrValue = bindAttr.value;
+
+ const hasWidgetTypes = context.isFlagSet('hasWidgetTypes');
+
+ if (hasWidgetTypes) {
+ context.deprecate('The tag is deprecated. Please remove it. See: https://github.com/marko-js/marko/issues/514');
+ }
+
+ if (bindAttrValue == null) {
+ componentModule = legacyGetDefaultComponentModule(this.dirname);
+ if (!componentModule) {
+ this.addError('No corresponding JavaScript module found in the same directory (either "component.js" or "index.js").');
+ return;
+ }
+ } else if (bindAttr.isLiteralValue()) {
+ if (typeof bindAttr.literalValue !== 'string') {
+ this.addError('The value for the "w-bind" attribute should be a string. Actual: ' + componentModule);
+ return;
+ }
+
+ let requirePath = bindAttr.literalValue;
+ let filename = resolveFrom(this.dirname, requirePath);
+
+ if (!filename) {
+ this.addError('Target file not found: ' + requirePath + ' (from: ' + this.dirname + ')');
+ return;
+ }
+
+ componentModule = {
+ legacy: true,
+ filename,
+ requirePath
+ };
+ } else {
+ // This is a dynamic expression. The should have been found.
+ if (!hasWidgetTypes) {
+ this.addError('The tag must be used to declare components when the value of the "w-bind" attribute is a dynamic expression.');
+ return;
+ }
+
+ el.insertSiblingBefore(
+ builder.functionCall(
+ builder.memberExpression(builder.identifier('__component'), builder.identifier('t')),
+ [
+ builder.memberExpression(
+ builder.identifier('marko_componentTypes'),
+ bindAttrValue,
+ true /* computed */)
+ ]));
+ }
+ } else {
+ return;
+ }
+
+ let isLegacyInnerBind = checkIsInnerBind(el.parentNode);
+
+ el.data.hasBoundComponent = true;
+
+ // A component is bound to the el...
+
+ if (el.hasAttribute('w-config')) {
+ el.insertSiblingBefore(
+ builder.functionCall(
+ builder.memberExpression(builder.identifier('__component'), builder.identifier('c')),
+ [
+ el.getAttributeValue('w-config')
+ ]));
+
+ el.removeAttribute('w-config');
+ }
+
+ let componentProps = {};
+
+ let id = el.getAttributeValue('id');
+
+ if (id) {
+ componentProps.id = id;
+ }
+
+ this.convertToComponent({
+ isLegacyInnerBind,
+ componentModule,
+ rendererModule,
+ isLegacyComponent: true,
+ rootNodes: [el]
+ });
+};
diff --git a/src/components/taglib/TransformHelper/handleRootNodes.js b/src/components/taglib/TransformHelper/handleRootNodes.js
index d92631fde..0294de34f 100644
--- a/src/components/taglib/TransformHelper/handleRootNodes.js
+++ b/src/components/taglib/TransformHelper/handleRootNodes.js
@@ -1,7 +1,7 @@
'use strict';
-var path = require('path');
-var getComponentFiles = require('./getComponentFiles');
+let path = require('path');
+let getComponentFiles = require('./getComponentFiles');
const esprima = require('esprima');
const escodegen = require('escodegen');
@@ -12,16 +12,16 @@ function handleStyleElement(styleEl, transformHelper) {
return;
}
- var attrs = styleEl.attributes;
+ let attrs = styleEl.attributes;
- var styleCode;
- var lang = 'css';
+ let styleCode;
+ let lang = 'css';
- var hasStyleBlock = false;
+ let hasStyleBlock = false;
- for (var i=attrs.length-1; i>=0; i--) {
- var attr = attrs[i];
- var name = attr.name;
+ for (let i=attrs.length-1; i>=0; i--) {
+ let attr = attrs[i];
+ let name = attr.name;
if (name.startsWith('{')) {
hasStyleBlock = true;
@@ -47,7 +47,7 @@ function handleStyleElement(styleEl, transformHelper) {
styleEl.setFlag(FLAG_COMPONENT_STYLE);
if (styleCode) {
- var context = transformHelper.context;
+ let context = transformHelper.context;
context.addDependency({
type: lang,
code: styleCode,
@@ -95,15 +95,15 @@ function classToObject(cls, el, transformHelper) {
function handleClassDeclaration(classEl, transformHelper) {
let tree;
- var wrappedSrc = '('+classEl.tagString+'\n)';
+ let wrappedSrc = '('+classEl.tagString+'\n)';
try {
tree = esprima.parse(wrappedSrc);
} catch(err) {
- var message = 'Unable to parse JavaScript for component class. ' + err;
+ let message = 'Unable to parse JavaScript for component class. ' + err;
if (err.index != null) {
- var errorIndex = err.index;
+ let errorIndex = err.index;
// message += '\n' + err.description;
if (errorIndex != null && errorIndex >= 0) {
transformHelper.context.addError({
@@ -127,36 +127,28 @@ function handleClassDeclaration(classEl, transformHelper) {
let object = classToObject(expression, classEl, transformHelper);
let componentVar = transformHelper.context.addStaticVar('marko_component', escodegen.generate(object));
- if (transformHelper.getRendererModule() != null) {
- transformHelper.context.addError(classEl, 'The component has both an inline component `class` and a separate `component.js`. This is not allowed. See: https://github.com/marko-js/marko/wiki/Error:-Component-inline-and-external');
- return;
- }
-
- var moduleInfo = {
+ let moduleInfo = {
inlineId: componentVar,
filename: transformHelper.filename,
requirePath: './' + path.basename(transformHelper.filename)
};
- if (transformHelper.getComponentModule() == null) {
- transformHelper.setComponentModule(moduleInfo);
- }
-
- transformHelper.setRendererModule(moduleInfo);
-
classEl.detach();
+
+ return moduleInfo;
}
module.exports = function handleRootNodes() {
- var context = this.context;
- var builder = this.builder;
+ let context = this.context;
- var componentFiles = getComponentFiles(context.filename);
+ let componentFiles = getComponentFiles(context.filename);
if (!componentFiles) {
return;
}
- var dirname = context.dirname;
+ let componentModule;
+ let rendererModule;
+ let dirname = context.dirname;
if (componentFiles.package) {
context.addDependency('package: ./' + componentFiles.package);
@@ -174,67 +166,78 @@ module.exports = function handleRootNodes() {
requirePath: './'+file.slice(0, file.lastIndexOf('.'))
};
- this.setComponentModule(moduleInfo);
- this.setRendererModule(moduleInfo);
+ componentModule = rendererModule = moduleInfo;
}
if (componentFiles.browserFile) {
let file = componentFiles.browserFile;
- this.setComponentModule({
+
+ componentModule = {
filename: path.join(dirname, file),
requirePath: './' + file.slice(
0, file.lastIndexOf('.'))
- });
+ };
}
- var templateRoot = this.el;
+ let templateRoot = this.el;
- var rootNodes = [];
- var hasLegacyExplicitBind = false;
- var hasIdCount = 0;
- var nodeWithAssignedId;
- var assignedId;
- var transformHelper = this;
+ let rootNodes = [];
+ let hasIdCount = 0;
+ let nodeWithAssignedId;
+ let assignedId;
+ let transformHelper = this;
let walker = context.createWalker({
enter(node) {
let tagName = node.tagName && node.tagName.toLowerCase();
- let tag = node.tagName && context.taglibLookup.getTag(node.tagName);
if (node.type === 'TemplateRoot' || !node.type) {
- // Don't worry about the TemplateRoot or a Container node
- // But continue into the node to look at its children for root elements
- } else if (tag && tag.noOutput) {
- walker.skip();
+ // Don't worry about the TemplateRoot or an Container node
} else if (node.type === 'HtmlElement') {
- if (node.hasAttribute('w-bind')) {
- transformHelper.setHasBoundComponentForTemplate();
- hasLegacyExplicitBind = true;
- } else {
- if (node.hasAttribute('id')) {
- hasIdCount++;
- nodeWithAssignedId = node;
- assignedId = node.getAttributeValue('id');
- }
+ if (node.hasAttribute('id')) {
+ hasIdCount++;
+ nodeWithAssignedId = node;
+ assignedId = node.getAttributeValue('id');
+ }
- if (tagName === 'style') {
- handleStyleElement(node, transformHelper);
- }
+ if (tagName === 'style') {
+ handleStyleElement(node, transformHelper);
+ }
- if (!node.isFlagSet(FLAG_COMPONENT_STYLE)) {
- rootNodes.push(node);
- }
+ if (!node.isFlagSet(FLAG_COMPONENT_STYLE)) {
+ rootNodes.push(node);
+ }
+ walker.skip();
+
+ return;
+ } else if (node.type === 'CustomTag') {
+ let tag = context.taglibLookup.getTag(node.tagName);
+
+ if (!tag.noOutput) {
+ rootNodes.push(node);
}
walker.skip();
return;
- } else if (node.type === 'CustomTag') {
- rootNodes.push(node);
+ } else if (node.type === 'Scriptlet') {
walker.skip();
return;
} else {
if (tagName === 'class') {
- handleClassDeclaration(node, transformHelper);
+ let classComponentModule = handleClassDeclaration(node, transformHelper);
+
+ if (rendererModule != null) {
+ transformHelper.context.addError(node, 'The component has both an inline component `class` and a separate `component.js`. This is not allowed. See: https://github.com/marko-js/marko/wiki/Error:-Component-inline-and-external');
+ return;
+ }
+
+ if (componentModule == null) {
+ componentModule = classComponentModule;
+ }
+
+ rendererModule = classComponentModule;
+ } else if (!node.noOutput) {
+ rootNodes.push(node);
}
walker.skip();
@@ -245,43 +248,29 @@ module.exports = function handleRootNodes() {
walker.walk(templateRoot);
- if (hasLegacyExplicitBind) {
- //There is an explicit bind so nothing to do
+ if (!componentModule) {
return;
}
- if (!this.hasBoundComponentForTemplate()) {
- return;
+ if (context.isFlagSet('hasWidgetTypes')) {
+ context.addError('The tag is no longer supported. See: https://github.com/marko-js/marko/issues/514');
}
+ templateRoot._normalizeChildTextNodes(context, true);
+
+ // After normalizing the text nodes to remove whitespace we may have detached
+ // some of the root text nodes so remove those from our list
+ rootNodes = rootNodes.filter((rootNode) => {
+ return rootNode.isDetached() !== true;
+ });
+
if (rootNodes.length === 0) {
return;
}
- if (rootNodes.length > 1 && hasIdCount > 0) {
- // We can only bind a component to multiple top-level elements if we can assign
- // all of the IDs
- return;
- }
-
- transformHelper.setHasBoundComponentForTemplate();
-
- var nextKey = 0;
-
- rootNodes.forEach((curNode, i) => {
- let id = curNode.getAttributeValue('id');
-
- if (id && id.type !== 'Literal') {
- context.addError('Root HTML element should not have a dynamic `id` attribute. See: https://github.com/marko-js/marko/wiki/Error:-Dynamic-root-HTML-element-id-attribute');
- return;
- }
-
- curNode.setFlag('hasComponentBind');
-
- if (!curNode.hasAttribute('key') && !curNode.hasAttribute('ref')) {
- if (curNode.type === 'CustomTag' || rootNodes.length > 1) {
- curNode.setAttributeValue('key', builder.literal('_r' + (nextKey++)));
- }
- }
+ this.convertToComponent({
+ rootNodes,
+ componentModule,
+ rendererModule
});
};
diff --git a/src/components/taglib/TransformHelper/handleScopedAttrs.js b/src/components/taglib/TransformHelper/handleScopedAttrs.js
new file mode 100644
index 000000000..ae58d5a17
--- /dev/null
+++ b/src/components/taglib/TransformHelper/handleScopedAttrs.js
@@ -0,0 +1,75 @@
+'use strict';
+
+const deprecatedKeySuffix = ':key';
+const scopedSuffix = ':scoped';
+const deprecatedAttrs = {
+ 'for-ref': true,
+ 'for-key': true,
+ 'w-for': true
+};
+
+module.exports = function handleComponentKeyAttrs() {
+ let el = this.el;
+ let context = this.context;
+ let builder = this.builder;
+ const filePosition = el.pos ? context.getPosInfo(el.pos) : context.filename;
+
+ var attrs = el.attributes.concat([]);
+
+ attrs.forEach(attribute => {
+ const attributeName = attribute.name;
+ if (!attributeName) {
+ return;
+ }
+
+ let fixedAttributeName = attributeName;
+
+ // BEGIN support for deprecated for attributes
+ if (deprecatedAttrs[attributeName]) {
+ context.deprecate(`The "${attributeName}" attribute is deprecated. Please use "for:key" instead.`);
+
+ let incompatibleAttributes = ['for', 'for:key']
+ .concat(Object.keys(deprecatedAttrs).filter(a => a != attributeName))
+ .filter(a => el.hasAttribute(a));
+
+ if (incompatibleAttributes.length) {
+ this.addError(`The "${attributeName}" attribute cannot be used in conjunction with "${incompatibleAttributes.join('" or "')}". (${filePosition})`);
+ return;
+ } else {
+ fixedAttributeName = 'for:scoped';
+ }
+ } else if (attributeName !== deprecatedKeySuffix && attributeName.endsWith(deprecatedKeySuffix)) {
+ context.deprecate(`The "${attributeName}" attribute is deprecated. Please use "for:scoped" instead.`);
+ fixedAttributeName = attributeName.slice(0, 0-deprecatedKeySuffix.length) + ':scoped';
+ }
+ // END support for deprecated for attributes
+
+ if (fixedAttributeName !== scopedSuffix && fixedAttributeName.endsWith(scopedSuffix)) {
+ el.removeAttribute(attributeName);
+
+ let finalAttributeName = fixedAttributeName.slice(0, 0-scopedSuffix.length);
+
+ if (el.hasAttribute(finalAttributeName)) {
+ this.addError(`The "${attributeName}" attribute cannot be used in conjunction with the "${finalAttributeName}" attribute. (${filePosition})`);
+ return;
+ }
+
+ let uniqueElId = this.nextUniqueId();
+ let idVarName = 'marko_' + finalAttributeName + '_key' + uniqueElId;
+
+
+ let varNode = builder.var(idVarName, attribute.value);
+
+ el.insertSiblingBefore(varNode);
+ let varIdNode = builder.identifier(idVarName);
+
+ el.setAttributeValue(finalAttributeName, this.buildComponentElIdFunctionCall(varIdNode));
+
+ if (!el.hasAttribute('key') && !el.hasAttribute('w-id') && !el.hasAttribute('ref')) {
+ // The scoped attribute should be suitable for a key
+ el.setAttributeValue('key', varIdNode);
+ el.data.userAssignedKey = false;
+ }
+ }
+ });
+};
diff --git a/src/components/taglib/TransformHelper/index.js b/src/components/taglib/TransformHelper/index.js
index c699acd87..6167048da 100644
--- a/src/components/taglib/TransformHelper/index.js
+++ b/src/components/taglib/TransformHelper/index.js
@@ -24,25 +24,11 @@ class TransformHelper {
}
setHasBoundComponentForTemplate() {
+ this.context.isComponent = true;
+
this.context.data[HAS_COMPONENT_KEY] = true;
}
- setComponentModule(value) {
- this.context.data.componentModule = value;
- }
-
- getComponentModule() {
- return this.context.data.componentModule;
- }
-
- setRendererModule(value) {
- this.context.data.rendererModule = value;
- }
-
- getRendererModule() {
- return this.context.data.rendererModule;
- }
-
getTemplateModule() {
return {
requirePath:this.context.getRequirePath(this.filename)
@@ -55,15 +41,6 @@ class TransformHelper {
this.context.data[WIDGET_PROPS_KEY] != null;
}
- getComponentProps() {
- var componentProps = this.context.data[WIDGET_PROPS_KEY];
- if (!componentProps) {
- this.firstBind = true;
- componentProps = this.context.data[WIDGET_PROPS_KEY] = {};
- }
- return componentProps;
- }
-
addError(message, code) {
this.context.addError(this.el, message, code);
}
@@ -81,11 +58,6 @@ class TransformHelper {
return (this.context.data.componentNextElId++);
}
- getNestedIdExpression() {
- this.assignComponentId();
- return this.getComponentIdInfo().nestedIdExpression;
- }
-
getIdExpression() {
this.assignComponentId();
return this.getComponentIdInfo().idExpression;
@@ -129,11 +101,19 @@ class TransformHelper {
buildComponentElIdFunctionCall(id) {
var builder = this.builder;
- var componentElId = builder.memberExpression(
- builder.identifier('__component'),
- builder.identifier('elId'));
+ if (id.type === 'Literal' && id.value === '') {
+ let componentElId = builder.memberExpression(
+ builder.identifier('__component'),
+ builder.identifier('id'));
- return builder.functionCall(componentElId, arguments.length === 0 ? [] : [ id ]);
+ return componentElId;
+ } else {
+ let componentElId = builder.memberExpression(
+ builder.identifier('__component'),
+ builder.identifier('elId'));
+
+ return builder.functionCall(componentElId, arguments.length === 0 ? [] : [ id ]);
+ }
}
getTransformHelper(el) {
@@ -142,12 +122,12 @@ class TransformHelper {
}
TransformHelper.prototype.assignComponentId = require('./assignComponentId');
+TransformHelper.prototype.convertToComponent = require('./convertToComponent');
TransformHelper.prototype.handleRootNodes = require('./handleRootNodes');
-TransformHelper.prototype.handleIncludeNode = require('./handleIncludeNode');
TransformHelper.prototype.handleComponentEvents = require('./handleComponentEvents');
TransformHelper.prototype.handleComponentPreserve = require('./handleComponentPreserve');
TransformHelper.prototype.handleComponentPreserveAttrs = require('./handleComponentPreserveAttrs');
-TransformHelper.prototype.handleComponentBind = require('./handleComponentBind');
-TransformHelper.prototype.handleComponentKeyAttrs = require('./handleComponentKeyAttrs');
+TransformHelper.prototype.handleScopedAttrs = require('./handleScopedAttrs');
+TransformHelper.prototype.handleLegacyBind = require('./handleLegacyBind');
module.exports = TransformHelper;
diff --git a/src/components/taglib/components-transformer.js b/src/components/taglib/components-transformer.js
index 7df27ecb8..07996cd2e 100644
--- a/src/components/taglib/components-transformer.js
+++ b/src/components/taglib/components-transformer.js
@@ -23,10 +23,15 @@ module.exports = function transform(el, context) {
}
if (el.hasAttribute('w-body')) {
+ // This is a legacy code block and should be removed in Marko v5
context.deprecate('The "w-body" attribute is deprecated. Please use "": {
+ "<_preserve>": {
"renderer": "./preserve-tag.js",
- "@id": "string",
- "@if": "expression",
- "@body-only": "expression",
- "autocomplete": [],
- "deprecated": true
+ "@key": "string",
+ "@cid": "string",
+ "@if": "boolean",
+ "@body-only": "boolean",
+ "autocomplete": []
},
"": {
"renderer": "./preserve-tag.js",
diff --git a/src/components/taglib/preserve-tag-browser.js b/src/components/taglib/preserve-tag-browser.js
index 99d8631e4..c90d79fce 100644
--- a/src/components/taglib/preserve-tag-browser.js
+++ b/src/components/taglib/preserve-tag-browser.js
@@ -1,34 +1,46 @@
-var getElementById = require('../util').___getElementById;
+var componentsUtil = require('../util');
+var componentLookup = componentsUtil.___componentLookup;
module.exports = function render(input, out) {
- var globalComponentsContext = out.global.___components;
-
- if (globalComponentsContext && globalComponentsContext.___isRerenderInBrowser !== true && globalComponentsContext.___rerenderComponent !== undefined) {
- var id = input.id;
+ var componentsContext = out.___components;
+ if (componentsContext) {
// See if the DOM node with the given ID already exists.
// If so, then reuse the existing DOM node instead of re-rendering
// the children. We have to put a placeholder node that will get
// replaced out if we find that the DOM node has already been rendered
if (!('if' in input) || input['if']) {
- var existingEl = getElementById(out.___document, id);
- if (existingEl) {
- var bodyOnly = input.bodyOnly === true;
- // Don't actually render anything since the element is already in the DOM,
- // but keep track that the node is being preserved so that we can ignore
- // it while transforming the old DOM
+ var component = componentsContext.___componentDef.___component;
+ var globalComponentsContext = componentsContext.___globalContext;
+ var key = input.key;
+ var componentId;
- if (!bodyOnly) {
- var tagName = existingEl.tagName;
- // If we are preserving the entire DOM node (not just the body)
- // then that means that we have need to render a placeholder to
- // mark the target location. We can then replace the placeholder
- // node with the existing DOM node
- out.element(tagName, { id: id });
+ if (key) {
+ if (component.___keyedElements[key]) {
+ var bodyOnly = input.bodyOnly === true;
+ // Don't actually render anything since the element is already in the DOM,
+ // but keep track that the node is being preserved so that we can ignore
+ // it while transforming the old DOM
+ if (bodyOnly) {
+ globalComponentsContext.___preservedElBodies[key] = true;
+ } else {
+ // If we are preserving the entire DOM node (not just the body)
+ // then that means that we have need to render a placeholder to
+ // mark the target location. We can then replace the placeholder
+ // node with the existing DOM node
+ out.element('', null, key, null, 0, 8 /* FLAG_PRESERVE */);
+ globalComponentsContext.___preservedEls[key] = true;
+ }
+
+ return;
+ }
+ } else if ((componentId = input.cid)) {
+ var existingComponent = componentLookup[componentId];
+ if (existingComponent) {
+ out.___preserveComponent(existingComponent);
+ globalComponentsContext.___renderedComponentsById[componentId] = true;
+ return;
}
-
- globalComponentsContext.___preserveDOMNode(id, bodyOnly);
- return;
}
}
}
diff --git a/src/components/util-browser.js b/src/components/util-browser.js
index d4dd6417f..65c921130 100644
--- a/src/components/util-browser.js
+++ b/src/components/util-browser.js
@@ -1,12 +1,6 @@
-var extend = require('raptor-util/extend');
-var markoGlobal = extend(window.$MG, {
- uid: 0
-});
-
-window.$MG = markoGlobal;
-
-var runtimeId = markoGlobal.uid++;
+var markoUID = window.$MUID || (window.$MUID = { i: 0 });
+var runtimeId = markoUID.i++;
var componentLookup = {};
@@ -17,18 +11,7 @@ function getComponentForEl(el, doc) {
if (el) {
var node = typeof el == 'string' ? (doc || defaultDocument).getElementById(el) : el;
if (node) {
- var component = node._w;
-
- while(component) {
- var rootFor = component.___rootFor;
- if (rootFor) {
- component = rootFor;
- } else {
- break;
- }
- }
-
- return component;
+ return node.___markoComponent;
}
}
}
@@ -69,26 +52,26 @@ function emitLifecycleEvent(component, eventType, eventArg1, eventArg2) {
component.emit(eventType, eventArg1, eventArg2);
}
-function destroyComponentForEl(el) {
- var componentToDestroy = el._w;
+function destroyComponentForNode(node) {
+ var componentToDestroy = node.___markoComponent;
if (componentToDestroy) {
componentToDestroy.___destroyShallow();
- el._w = null;
-
- while ((componentToDestroy = componentToDestroy.___rootFor)) {
- componentToDestroy.___rootFor = null;
- componentToDestroy.___destroyShallow();
- }
}
}
-function destroyElRecursive(el) {
- var curChild = el.firstChild;
- while(curChild) {
- if (curChild.nodeType === 1) {
- destroyComponentForEl(curChild);
- destroyElRecursive(curChild);
+function destroyNodeRecursive(node, component) {
+ if (node.nodeType === 1) {
+ var key;
+
+ if (component && (key = node.___markoKey)) {
+ delete component.___keyedElements[key];
+ }
+
+ var curChild = node.firstChild;
+ while(curChild) {
+ destroyComponentForNode(curChild);
+ destroyNodeRecursive(curChild, component);
+ curChild = curChild.nextSibling;
}
- curChild = curChild.nextSibling;
}
}
@@ -97,52 +80,36 @@ function nextComponentId() {
// marko runtimes. This allows multiple instances of marko to be
// loaded in the same window and they should all place nice
// together
- return 'b' + ((markoGlobal.uid)++);
+ return 'b' + (markoUID.i++);
}
-function nextComponentIdProvider(out) {
+function nextComponentIdProvider() {
return nextComponentId;
}
-function getElementById(doc, id) {
- return doc.getElementById(id);
-}
-
function attachBubblingEvent(componentDef, handlerMethodName, extraArgs) {
if (handlerMethodName) {
- var id = componentDef.id;
+ var componentId = componentDef.id;
if (extraArgs) {
- var isRerenderInBrowser = componentDef.___globalComponentsContext.___isRerenderInBrowser;
-
- if (isRerenderInBrowser === true) {
- // If we are bootstrapping a page rendered on the server
- // we need to put the actual event args on the UI component
- // since we will not actually be updating the DOM
- var component = componentDef.___component;
-
- var bubblingDomEvents = component.___bubblingDomEvents ||
- ( component.___bubblingDomEvents = [] );
-
- bubblingDomEvents.push(extraArgs);
-
- return;
- } else {
- return [handlerMethodName, id, extraArgs];
- }
+ return [handlerMethodName, componentId, extraArgs];
} else {
- return [handlerMethodName, id];
+ return [handlerMethodName, componentId];
}
}
}
function getMarkoPropsFromEl(el) {
- var virtualProps = el._vprops;
- if (virtualProps === undefined) {
- virtualProps = el.getAttribute('data-marko');
- if (virtualProps) {
- virtualProps = JSON.parse(virtualProps);
+ var vElement = el.___markoVElement;
+ var virtualProps;
+
+ if (vElement) {
+ virtualProps = vElement.___properties;
+ } else {
+ virtualProps = el.___markoVProps;
+ if (!virtualProps) {
+ virtualProps = el.getAttribute('data-marko');
+ el.___markoVProps = virtualProps = virtualProps ? JSON.parse(virtualProps) : EMPTY_OBJECT;
}
- el._vprops = virtualProps = virtualProps || EMPTY_OBJECT;
}
return virtualProps;
@@ -152,9 +119,8 @@ exports.___runtimeId = runtimeId;
exports.___componentLookup = componentLookup;
exports.___getComponentForEl = getComponentForEl;
exports.___emitLifecycleEvent = emitLifecycleEvent;
-exports.___destroyComponentForEl = destroyComponentForEl;
-exports.___destroyElRecursive = destroyElRecursive;
+exports.___destroyComponentForNode = destroyComponentForNode;
+exports.___destroyNodeRecursive = destroyNodeRecursive;
exports.___nextComponentIdProvider = nextComponentIdProvider;
-exports.___getElementById = getElementById;
exports.___attachBubblingEvent = attachBubblingEvent;
exports.___getMarkoPropsFromEl = getMarkoPropsFromEl;
diff --git a/src/components/util.js b/src/components/util.js
index 401ec4096..532904bf4 100644
--- a/src/components/util.js
+++ b/src/components/util.js
@@ -1,3 +1,7 @@
+var FLAG_WILL_RERENDER_IN_BROWSER = 1;
+// var FLAG_HAS_BODY_EL = 2;
+// var FLAG_HAS_HEAD_EL = 4;
+
function nextComponentIdProvider(out) {
var prefix = out.global.componentIdPrefix || 's'; // "s" is for server (we use "b" for the browser)
var nextId = 0;
@@ -21,7 +25,7 @@ function attachBubblingEvent(componentDef, handlerMethodName, extraArgs) {
// where the extra args will be found when the UI component is
// rerendered in the browser
- if (componentDef.___willRerenderInBrowser === false) {
+ if (componentDef.___flags & FLAG_WILL_RERENDER_IN_BROWSER) {
if (eventIndex === 0) {
component.___bubblingDomEvents = [extraArgs];
} else {
@@ -40,3 +44,5 @@ function attachBubblingEvent(componentDef, handlerMethodName, extraArgs) {
exports.___nextComponentIdProvider = nextComponentIdProvider;
exports.___isServer = true;
exports.___attachBubblingEvent = attachBubblingEvent;
+exports.___destroyComponentForNode = function noop() {};
+exports.___destroyNodeRecursive = function noop() {};
diff --git a/src/morphdom/index.js b/src/morphdom/index.js
index 3758701c9..952b74371 100644
--- a/src/morphdom/index.js
+++ b/src/morphdom/index.js
@@ -1,325 +1,539 @@
'use strict';
-var defaultDoc = typeof document == 'undefined' ? undefined : document;
var specialElHandlers = require('./specialElHandlers');
-
-var morphAttrs = require('../runtime/vdom/VElement').___morphAttrs;
+var componentsUtil = require('../components/util');
+var existingComponentLookup = componentsUtil.___componentLookup;
+var destroyComponentForNode = componentsUtil.___destroyComponentForNode;
+var destroyNodeRecursive = componentsUtil.___destroyNodeRecursive;
+var VElement = require('../runtime/vdom/vdom').___VElement;
+var virtualizeElement = VElement.___virtualize;
+var morphAttrs = VElement.___morphAttrs;
+var eventDelegation = require('../components/event-delegation');
var ELEMENT_NODE = 1;
var TEXT_NODE = 3;
var COMMENT_NODE = 8;
+var COMPONENT_NODE = 2;
+
+// var FLAG_IS_SVG = 1;
+// var FLAG_IS_TEXTAREA = 2;
+// var FLAG_SIMPLE_ATTRS = 4;
+var FLAG_PRESERVE = 8;
function compareNodeNames(fromEl, toEl) {
- return fromEl.nodeName === toEl.___nodeName;
+ return fromEl.___nodeName === toEl.___nodeName;
}
+function onBeforeNodeDiscarded(node) {
+ return eventDelegation.___handleNodeDetach(node);
+}
-function getElementById(doc, id) {
- return doc.getElementById(id);
+function onNodeAdded(node, componentsContext) {
+ if (node.nodeType === 1) {
+ eventDelegation.___handleNodeAttach(node, componentsContext);
+ }
+}
+
+function insertBefore(node, referenceNode, parentNode) {
+ return parentNode.insertBefore(node, referenceNode);
+}
+
+function insertAfter(node, referenceNode, parentNode) {
+ return parentNode.insertBefore(node, referenceNode && referenceNode.nextSibling);
}
function morphdom(
- fromNode,
+ parentNode,
+ startNode,
+ endNode,
toNode,
- context,
- onNodeAdded,
- onBeforeElUpdated,
- onBeforeNodeDiscarded,
- onNodeDiscarded,
- onBeforeElChildrenUpdated
+ doc,
+ componentsContext
) {
+ var globalComponentsContext;
+ var isRerenderInBrowser = false;
- var doc = fromNode.ownerDocument || defaultDoc;
-
- // This object is used as a lookup to quickly find all keyed elements in the original DOM tree.
- var removalList = [];
- var foundKeys = {};
-
- function walkDiscardedChildNodes(node) {
- onNodeDiscarded(node);
- var curChild = node.firstChild;
-
- while (curChild) {
- walkDiscardedChildNodes(curChild);
- curChild = curChild.nextSibling;
- }
+ if (componentsContext) {
+ globalComponentsContext = componentsContext.___globalContext;
+ isRerenderInBrowser = globalComponentsContext.___isRerenderInBrowser;
}
+ function createMarkerComment(referenceNode, parentNode) {
+ return doc.createComment('$marko');
+ }
- function addVirtualNode(vEl, parentEl) {
- var realEl = vEl.___actualize(doc);
+ function insertVirtualNodeBefore(vNode, key, referenceEl, parentEl, component, keySequence) {
+ var realNode = vNode.___actualize(doc);
+ insertBefore(realNode, referenceEl, parentEl);
- if (parentEl) {
- parentEl.appendChild(realEl);
- }
-
- onNodeAdded(realEl, context);
-
- var vCurChild = vEl.___firstChild;
- while (vCurChild) {
- var realCurChild = null;
-
- var key = vCurChild.id;
+ if (vNode.___nodeType === ELEMENT_NODE) {
if (key) {
- var unmatchedFromEl = getElementById(doc, key);
- if (unmatchedFromEl && compareNodeNames(vCurChild, unmatchedFromEl)) {
- morphEl(unmatchedFromEl, vCurChild, false);
- realEl.appendChild(realCurChild = unmatchedFromEl);
- }
+ realNode.___markoKey = key;
+ (component = vNode.___component || component).___keyedElements[key] = realNode;
}
- if (!realCurChild) {
- addVirtualNode(vCurChild, realEl);
- }
-
- vCurChild = vCurChild.___nextSibling;
+ morphChildren(realNode, null, null, vNode, component, keySequence);
}
- if (vEl.___nodeType === 1) {
- var elHandler = specialElHandlers[vEl.nodeName];
- if (elHandler !== undefined) {
- elHandler(realEl, vEl);
- }
- }
-
- return realEl;
+ onNodeAdded(realNode, componentsContext);
}
- function morphEl(fromEl, toEl, childrenOnly) {
- var toElKey = toEl.id;
- var nodeName = toEl.___nodeName;
+ function insertVirtualComponentBefore(vComponent, referenceNode, referenceNodeParentEl) {
+ var component = vComponent.___component;
+ component.___startNode = component.___endNode = insertBefore(createMarkerComment(), referenceNode, referenceNodeParentEl);
+ morphComponent(referenceNodeParentEl, component, vComponent);
+ }
- if (childrenOnly === false) {
- if (toElKey) {
- // If an element with an ID is being morphed then it is will be in the final
- // DOM so clear it out of the saved elements collection
- foundKeys[toElKey] = true;
+ function resolveComponentEndNode(startNode, vChild, parentNode) {
+ var endNode = startNode;
+
+ // We track text nodes because multiple adjacent VText nodes should
+ // be treated as a single VText node for purposes of pairing with HTML
+ // that was rendered on the server since browsers will only see
+ // a single text node
+ var isPrevText = vChild.___nodeType === TEXT_NODE;
+
+ while((vChild = vChild.___nextSibling)) {
+ var nextRealNode = endNode.nextSibling;
+
+ // We stop when there are no more corresponding real nodes or when
+ // we reach the end boundary for our UI component
+ if (!nextRealNode || nextRealNode.___endNode) {
+ break;
}
+ var isText = vChild.___nodeType === TEXT_NODE;
+ if (isText && isPrevText) {
+ // Pretend like we didn't see this VText node since it
+ // the previous vnode was also a VText node
+ continue;
+ }
+ endNode = nextRealNode;
+ isPrevText = isText;
+ }
- var constId = toEl.___constId;
- if (constId !== undefined) {
- var otherProps = fromEl._vprops;
- if (otherProps !== undefined && constId === otherProps.c) {
- return;
+ return endNode;
+ }
+
+ function morphComponent(parentFromNode, component, vComponent) {
+ // We create a key sequence to generate unique keys since a key
+ // can be repeated
+ var keySequence = globalComponentsContext.___createKeySequence();
+
+ var startNode = component.___startNode;
+ var endNode = component.___endNode;
+ startNode.___markoComponent = undefined;
+ endNode.___endNode = undefined;
+
+ var beforeChild = startNode.previousSibling;
+ var afterChild = endNode.nextSibling;
+
+ morphChildren(parentFromNode, startNode, afterChild, vComponent, component, keySequence);
+
+ endNode = undefined;
+
+ if (beforeChild) {
+ startNode = beforeChild.nextSibling;
+ if (!startNode || startNode === afterChild) {
+ startNode = endNode = insertAfter(createMarkerComment(), beforeChild, parentFromNode);
+ }
+ } else {
+ startNode = parentFromNode.firstChild;
+ if (!startNode) {
+ startNode = endNode = insertAfter(createMarkerComment(), beforeChild, parentFromNode);
+ }
+ }
+
+ if (!endNode) {
+ if (afterChild) {
+ endNode = afterChild.previousSibling;
+ } else {
+ endNode = parentFromNode.lastChild;
+ }
+ }
+
+ // Make sure we don't use a detached node as the component boundary and
+ // we can't use a node that is already the boundary node for another component
+ if (startNode.___markoDetached !== undefined || startNode.___markoComponent) {
+ startNode = insertBefore(createMarkerComment(), startNode, parentFromNode);
+ }
+
+ if (endNode.___markoDetached !== undefined || endNode.___endNode) {
+ endNode = insertAfter(createMarkerComment(), endNode, parentFromNode);
+ }
+
+ startNode.___markoComponent = component;
+ endNode.___endNode = true;
+
+ component.___startNode = startNode;
+ component.___endNode = endNode;
+
+ return afterChild;
+ }
+
+ var detachedNodes = [];
+
+ function detachNode(node, parentNode, component) {
+ if (node.nodeType === ELEMENT_NODE) {
+ detachedNodes.push(node);
+ node.___markoDetached = component || true;
+ } else {
+ destroyNodeRecursive(node);
+ parentNode.removeChild(node);
+ }
+ }
+
+ function destroyComponent(component) {
+ component.destroy(onBeforeNodeDiscarded);
+ }
+
+ function morphChildren(parentFromNode, startNode, endNode, toNode, component, keySequence) {
+ var curFromNodeChild = startNode;
+ var curToNodeChild = toNode.___firstChild;
+
+ var curToNodeKey;
+ var curFromNodeKey;
+ var curToNodeType;
+
+ var fromNextSibling;
+ var toNextSibling;
+ var matchingFromEl;
+ var matchingFromComponent;
+ var toComponent;
+ var curVFromNodeChild;
+ var fromComponent;
+
+ outer: while (curToNodeChild) {
+ toNextSibling = curToNodeChild.___nextSibling;
+ curToNodeType = curToNodeChild.___nodeType;
+
+ if (curToNodeType === COMPONENT_NODE) {
+ toComponent = curToNodeChild.___component;
+
+ if ((matchingFromComponent = existingComponentLookup[toComponent.id]) === undefined) {
+ if (isRerenderInBrowser === true) {
+ var firstVChild = curToNodeChild.___firstChild;
+ if (firstVChild) {
+ if (!curFromNodeChild) {
+ curFromNodeChild = insertBefore(createMarkerComment(), null, parentFromNode);
+ }
+
+ toComponent.___startNode = curFromNodeChild;
+ toComponent.___endNode = resolveComponentEndNode(curFromNodeChild, firstVChild, parentFromNode);
+
+ } else {
+ toComponent.___startNode = toComponent.___endNode = insertBefore(createMarkerComment(), curFromNodeChild, parentFromNode);
+ }
+
+ curFromNodeChild = morphComponent(parentFromNode, toComponent, curToNodeChild);
+ } else {
+ insertVirtualComponentBefore(curToNodeChild, curFromNodeChild, parentFromNode);
+ }
+ } else {
+ if (matchingFromComponent.___startNode !== curFromNodeChild) {
+ if (curFromNodeChild &&
+ (fromComponent = curFromNodeChild.___markoComponent) &&
+ globalComponentsContext.___renderedComponentsById[fromComponent.id] === undefined) {
+
+ // The component associated with the current real DOM node was not rendered
+ // so we should just remove it out of the real DOM by destroying it
+ curFromNodeChild = fromComponent.___endNode.nextSibling;
+ destroyComponent(fromComponent);
+ continue;
+ }
+
+ // We need to move the existing component into
+ // the correct location
+ insertBefore(matchingFromComponent.___detach(), curFromNodeChild, parentFromNode);
+ }
+
+ if (curToNodeChild.___preserve) {
+ curFromNodeChild = matchingFromComponent.___endNode.nextSibling;
+ } else {
+ curFromNodeChild = morphComponent(parentFromNode, toComponent, curToNodeChild);
+ }
}
- }
- if (onBeforeElUpdated(fromEl, toElKey, context) === true) {
- return;
- }
+ curToNodeChild = toNextSibling;
+ continue;
+ } else if ((curToNodeKey = curToNodeChild.___key)) {
+ curVFromNodeChild = undefined;
+ curFromNodeKey = undefined;
- morphAttrs(fromEl, toEl);
- }
+ // We have a keyed element. This is the fast path for matching
+ // up elements
+ curToNodeKey = keySequence.___nextKey(curToNodeKey);
- if (onBeforeElChildrenUpdated(fromEl, toElKey, context) === true) {
- return;
- }
+ if (curFromNodeChild) {
+ if (curFromNodeChild !== endNode) {
+ curFromNodeKey = curFromNodeChild.___markoKey;
+ curVFromNodeChild = curFromNodeChild.___markoVElement;
+ fromNextSibling = curFromNodeChild.nextSibling;
+ }
+ }
- if (nodeName !== 'TEXTAREA') {
- var curToNodeChild = toEl.___firstChild;
- var curFromNodeChild = fromEl.firstChild;
- var curToNodeKey;
- var curFromNodeKey;
-
- var fromNextSibling;
- var toNextSibling;
- var matchingFromEl;
-
- outer: while (curToNodeChild) {
- toNextSibling = curToNodeChild.___nextSibling;
- curToNodeKey = curToNodeChild.id;
-
- while (curFromNodeChild) {
- fromNextSibling = curFromNodeChild.nextSibling;
-
- curFromNodeKey = curFromNodeChild.id;
-
- var curFromNodeType = curFromNodeChild.nodeType;
-
- var isCompatible = undefined;
-
- if (curFromNodeType === curToNodeChild.___nodeType) {
- if (curFromNodeType === ELEMENT_NODE) {
- // Both nodes being compared are Element nodes
-
- if (curToNodeKey) {
- // The target node has a key so we want to match it up with the correct element
- // in the original DOM tree
- if (curToNodeKey !== curFromNodeKey) {
- // The current element in the original DOM tree does not have a matching key so
- // let's check our lookup to see if there is a matching element in the original
- // DOM tree
- if ((matchingFromEl = getElementById(doc, curToNodeKey))) {
- if (curFromNodeChild.nextSibling === matchingFromEl) {
- // Special case for single element removals. To avoid removing the original
- // DOM node out of the tree (since that can break CSS transitions, etc.),
- // we will instead discard the current node and wait until the next
- // iteration to properly match up the keyed target element with its matching
- // element in the original tree
- isCompatible = false;
- } else {
- // We found a matching keyed element somewhere in the original DOM tree.
- // Let's moving the original DOM node into the current position and morph
- // it.
-
- // NOTE: We use insertBefore instead of replaceChild because we want to go through
- // the `removeNode()` function for the node that is being discarded so that
- // all lifecycle hooks are correctly invoked
+ if (curFromNodeKey === curToNodeKey) {
+ // Elements line up. Now we just have to make sure they are compatible
+ if ((curToNodeChild.___flags & FLAG_PRESERVE) === 0) {
+ // We just skip over the fromNode if it is preserved
- fromEl.insertBefore(matchingFromEl, curFromNodeChild);
+ if (compareNodeNames(curToNodeChild, curVFromNodeChild)) {
+ morphEl(curFromNodeChild, curVFromNodeChild, curToNodeChild, component, keySequence);
+ } else {
+ // Remove the old node
+ detachNode(curFromNodeChild, parentFromNode, component);
- fromNextSibling = curFromNodeChild.nextSibling;
- removalList.push(curFromNodeChild);
-
- curFromNodeChild = matchingFromEl;
- }
- } else {
- // The nodes are not compatible since the "to" node has a key and there
- // is no matching keyed node in the source tree
- isCompatible = false;
- }
- }
- } else if (curFromNodeKey) {
- // The original has a key
- isCompatible = false;
- }
-
- isCompatible = isCompatible !== false && compareNodeNames(curFromNodeChild, curToNodeChild) === true;
-
- if (isCompatible === true) {
- // We found compatible DOM elements so transform
- // the current "from" node to match the current
- // target DOM node.
- morphEl(curFromNodeChild, curToNodeChild, false);
- }
-
- } else if (curFromNodeType === TEXT_NODE || curFromNodeType === COMMENT_NODE) {
- // Both nodes being compared are Text or Comment nodes
- isCompatible = true;
- // Simply update nodeValue on the original node to
- // change the text value
- curFromNodeChild.nodeValue = curToNodeChild.___nodeValue;
+ // Incompatible nodes. Just move the target VNode into the DOM at this position
+ insertVirtualNodeBefore(curToNodeChild, curToNodeKey, curFromNodeChild, parentFromNode, component, keySequence);
}
}
-
- if (isCompatible === true) {
- // Advance both the "to" child and the "from" child since we found a match
- curToNodeChild = toNextSibling;
- curFromNodeChild = fromNextSibling;
- continue outer;
- }
-
- // No compatible match so remove the old node from the DOM and continue trying to find a
- // match in the original DOM. However, we only do this if the from node is not keyed
- // since it is possible that a keyed node might match up with a node somewhere else in the
- // target tree and we don't want to discard it just yet since it still might find a
- // home in the final DOM tree. After everything is done we will remove any keyed nodes
- // that didn't find a home
- removalList.push(curFromNodeChild);
-
- curFromNodeChild = fromNextSibling;
- }
-
- // If we got this far then we did not find a candidate match for
- // our "to node" and we exhausted all of the children "from"
- // nodes. Therefore, we will just append the current "to" node
- // to the end
- if (curToNodeKey && (matchingFromEl = getElementById(doc, curToNodeKey)) && compareNodeNames(matchingFromEl, curToNodeChild)) {
- fromEl.appendChild(matchingFromEl);
- morphEl(matchingFromEl, curToNodeChild, false);
} else {
- addVirtualNode(curToNodeChild, fromEl);
+ if ((matchingFromEl = component.___keyedElements[curToNodeKey]) === undefined) {
+ if (isRerenderInBrowser === true && curFromNodeChild &&
+ curFromNodeChild.nodeType === ELEMENT_NODE &&
+ curFromNodeChild.nodeName === curToNodeChild.___nodeName) {
+ curVFromNodeChild = virtualizeElement(curFromNodeChild);
+ curFromNodeChild.___markoKey = curToNodeKey;
+ morphEl(curFromNodeChild, curVFromNodeChild, curToNodeChild, component, keySequence);
+ curToNodeChild = toNextSibling;
+ curFromNodeChild = fromNextSibling;
+ continue;
+ }
+
+ insertVirtualNodeBefore(curToNodeChild, curToNodeKey, curFromNodeChild, parentFromNode, component, keySequence);
+ fromNextSibling = curFromNodeChild;
+ } else {
+ if (matchingFromEl.___markoDetached !== undefined) {
+ matchingFromEl.___markoDetached = undefined;
+ }
+ curVFromNodeChild = matchingFromEl.___markoVElement;
+
+ if (compareNodeNames(curVFromNodeChild, curToNodeChild)) {
+ if (fromNextSibling === matchingFromEl) {
+ // Single element removal:
+ // A <-> A
+ // B <-> C <-- We are here
+ // C D
+ // D
+ //
+ // Single element swap:
+ // A <-> A
+ // B <-> C <-- We are here
+ // C B
+
+ if (toNextSibling && toNextSibling.___key === curFromNodeKey) {
+ // Single element swap
+
+ // We want to stay on the current real DOM node
+ fromNextSibling = curFromNodeChild;
+
+ // But move the matching element into place
+ insertBefore(matchingFromEl, curFromNodeChild, parentFromNode);
+ } else {
+ // Single element removal
+
+ // We need to remove the current real DOM node
+ // and the matching real DOM node will fall into
+ // place. We will continue diffing with next sibling
+ // after the real DOM node that just fell into place
+ fromNextSibling = fromNextSibling.nextSibling;
+
+ if (curFromNodeChild) {
+ detachNode(curFromNodeChild, parentFromNode, component);
+ }
+ }
+
+ } else {
+ // A <-> A
+ // B <-> D <-- We are here
+ // C
+ // D
+
+ // We need to move the matching node into place
+ insertAfter(matchingFromEl, curFromNodeChild, parentFromNode);
+
+ if (curFromNodeChild) {
+ detachNode(curFromNodeChild, parentFromNode, component);
+ }
+ }
+
+ if ((curToNodeChild.___flags & FLAG_PRESERVE) === 0) {
+ morphEl(matchingFromEl, curVFromNodeChild, curToNodeChild, component, keySequence);
+ }
+
+
+ } else {
+ insertVirtualNodeBefore(curToNodeChild, curToNodeKey, curFromNodeChild, parentFromNode, component, keySequence);
+ detachNode(matchingFromEl, parentFromNode, component);
+ }
+ }
}
curToNodeChild = toNextSibling;
curFromNodeChild = fromNextSibling;
+ continue;
}
- // We have processed all of the "to nodes". If curFromNodeChild is
- // non-null then we still have some from nodes left over that need
- // to be removed
- while (curFromNodeChild) {
- removalList.push(curFromNodeChild);
- curFromNodeChild = curFromNodeChild.nextSibling;
+ // The know the target node is not a VComponent node and we know
+ // it is also not a preserve node. Let's now match up the HTML
+ // element, text node, comment, etc.
+ while (curFromNodeChild && curFromNodeChild !== endNode) {
+ if ((fromComponent = curFromNodeChild.___markoComponent) && fromComponent !== component) {
+ // The current "to" element is not associated with a component,
+ // but the current "from" element is associated with a component
+
+ // Even if we destroy the current component in the original
+ // DOM or not, we still need to skip over it since it is
+ // not compatible with the current "to" node
+ curFromNodeChild = fromComponent.___endNode.nextSibling;
+
+ if (!globalComponentsContext.___renderedComponentsById[fromComponent.id]) {
+ destroyComponent(fromComponent);
+ }
+
+ continue; // Move to the next "from" node
+ }
+
+ fromNextSibling = curFromNodeChild.nextSibling;
+
+ var curFromNodeType = curFromNodeChild.nodeType;
+
+ var isCompatible = undefined;
+
+ if (curFromNodeType === curToNodeType) {
+ if (curFromNodeType === ELEMENT_NODE) {
+ // Both nodes being compared are Element nodes
+ curVFromNodeChild = curFromNodeChild.___markoVElement;
+ if (curVFromNodeChild === undefined) {
+ if (isRerenderInBrowser === true) {
+ curVFromNodeChild = virtualizeElement(curFromNodeChild);
+ } else {
+ // Skip over nodes that don't look like ours...
+ curFromNodeChild = fromNextSibling;
+ continue;
+ }
+ } else if ((curFromNodeKey = curVFromNodeChild.___key)) {
+ // We have a keyed element here but our target VDOM node
+ // is not keyed so this not doesn't belong
+ isCompatible = false;
+ }
+
+ isCompatible = isCompatible !== false && compareNodeNames(curVFromNodeChild, curToNodeChild) === true;
+
+ if (isCompatible === true) {
+ // We found compatible DOM elements so transform
+ // the current "from" node to match the current
+ // target DOM node.
+ morphEl(curFromNodeChild, curVFromNodeChild, curToNodeChild, component, keySequence);
+ }
+
+ } else if (curFromNodeType === TEXT_NODE || curFromNodeType === COMMENT_NODE) {
+ // Both nodes being compared are Text or Comment nodes
+ isCompatible = true;
+ // Simply update nodeValue on the original node to
+ // change the text value
+ curFromNodeChild.nodeValue = curToNodeChild.___nodeValue;
+ }
+ }
+
+ if (isCompatible === true) {
+ // Advance both the "to" child and the "from" child since we found a match
+ curToNodeChild = toNextSibling;
+ curFromNodeChild = fromNextSibling;
+ continue outer;
+ }
+
+ if (curFromNodeKey) {
+ if (globalComponentsContext.___preservedEls[curFromNodeKey] === undefined) {
+ detachNode(curFromNodeChild, parentFromNode, component);
+ }
+ } else {
+ detachNode(curFromNodeChild, parentFromNode, component);
+ }
+
+ curFromNodeChild = fromNextSibling;
+ } // END: while (curFromNodeChild)
+
+ // If we got this far then we did not find a candidate match for
+ // our "to node" and we exhausted all of the children "from"
+ // nodes. Therefore, we will just append the current "to" node
+ // to the end
+ insertVirtualNodeBefore(curToNodeChild, curToNodeKey, curFromNodeChild, parentFromNode, component, keySequence);
+
+ curToNodeChild = toNextSibling;
+ curFromNodeChild = fromNextSibling;
+ }
+
+ // We have processed all of the "to nodes". If curFromNodeChild is
+ // non-null then we still have some from nodes left over that need
+ // to be removed
+ while (curFromNodeChild && (endNode === null || curFromNodeChild !== endNode)) {
+ fromNextSibling = curFromNodeChild.nextSibling;
+
+ if ((fromComponent = curFromNodeChild.___markoComponent)) {
+ if (globalComponentsContext.___renderedComponentsById[fromComponent.id]) {
+ // Skip over this component since it was rendered in the target VDOM
+ // and will be moved into place later
+ curFromNodeChild = fromComponent.___endNode.nextSibling;
+ continue;
+ }
}
+
+ detachNode(curFromNodeChild, parentFromNode, component);
+
+ curFromNodeChild = fromNextSibling;
+ }
+ }
+
+ function morphEl(fromEl, vFromEl, toEl, component, keySequence) {
+ var toElKey = toEl.___key;
+ var nodeName = toEl.___nodeName;
+
+ component = toEl.___component || component;
+
+ if (isRerenderInBrowser === true && toElKey) {
+ component.___keyedElements[toElKey] = fromEl;
+ }
+
+ var constId = toEl.___constId;
+ if (constId !== undefined && vFromEl.___constId === constId) {
+ return;
+ }
+
+ morphAttrs(fromEl, vFromEl, toEl);
+
+ if (toElKey && globalComponentsContext.___preservedElBodies[toElKey] === true) {
+ // Don't morph the children since they are preserved
+ return;
+ }
+
+ if (nodeName !== 'TEXTAREA') {
+ morphChildren(fromEl, fromEl.firstChild, null, toEl, component, keySequence);
}
var specialElHandler = specialElHandlers[nodeName];
- if (specialElHandler) {
+ if (specialElHandler !== undefined) {
specialElHandler(fromEl, toEl);
}
} // END: morphEl(...)
- var morphedNode = fromNode;
- var fromNodeType = morphedNode.nodeType;
- var toNodeType = toNode.___nodeType;
- var morphChildrenOnly = false;
- var shouldMorphEl = true;
- var newNode;
+ morphChildren(parentNode, startNode, endNode, toNode);
- // Handle the case where we are given two DOM nodes that are not
- // compatible (e.g. -->
or --> TEXT)
- if (fromNodeType == ELEMENT_NODE) {
- if (toNodeType == ELEMENT_NODE) {
- if (!compareNodeNames(fromNode, toNode)) {
- newNode = toNode.___actualize(doc);
- morphChildrenOnly = true;
- removalList.push(fromNode);
- }
- } else {
- // Going from an element node to a text or comment node
- removalList.push(fromNode);
- newNode = toNode.___actualize(doc);
- shouldMorphEl = false;
- }
- } else if (fromNodeType == TEXT_NODE || fromNodeType == COMMENT_NODE) { // Text or comment node
- if (toNodeType == fromNodeType) {
- morphedNode.nodeValue = toNode.___nodeValue;
- return morphedNode;
- } else {
- // Text node to something else
- removalList.push(fromNode);
- newNode = addVirtualNode(toNode);
- shouldMorphEl = false;
- }
- }
+ detachedNodes.forEach(function(node) {
+ var detachedFromComponent = node.___markoDetached;
- if (shouldMorphEl === true) {
- morphEl(newNode || morphedNode, toNode, morphChildrenOnly);
- }
+ if (detachedFromComponent !== undefined) {
+ node.___markoDetached = undefined;
- if (newNode) {
- if (fromNode.parentNode) {
- fromNode.parentNode.replaceChild(newNode, fromNode);
- }
- }
+ destroyComponentForNode(node);
+ destroyNodeRecursive(node, detachedFromComponent !== true && detachedFromComponent);
- // We now need to loop over any keyed nodes that might need to be
- // removed. We only do the removal if we know that the keyed node
- // never found a match. When a keyed node is matched up we remove
- // it out of fromNodesLookup and we use fromNodesLookup to determine
- // if a keyed node has been matched up or not
- for (var i=0, len=removalList.length; i
= 0; --i) {
- var attr = oldAttributesList[i];
-
- if (attr.specified !== false) {
- attrName = attr.name;
- if (attrName !== 'data-marko') {
- var attrNamespaceURI = attr.namespaceURI;
- if (attrNamespaceURI === NS_XLINK) {
- oldAttrs[ATTR_XLINK_HREF] = attr.value;
- } else {
- oldAttrs[attrName] = attr.value;
- }
- }
- }
- }
-
- // We don't want preserved attributes to show up in either the old
- // or new attribute map.
- removePreservedAttributes(oldAttrs, props, false);
}
- fromEl._vattrs = attrs;
-
var attrValue;
- var flags = toEl.___flags;
- var oldFlags;
+ var toFlags = toEl.___flags;
- if (flags & FLAG_SIMPLE_ATTRS && ((oldFlags = fromEl._vflags) & FLAG_SIMPLE_ATTRS)) {
+
+ if (toFlags & FLAG_SIMPLE_ATTRS && fromFlags & FLAG_SIMPLE_ATTRS) {
if (oldAttrs['class'] !== (attrValue = attrs['class'])) {
fromEl.className = attrValue;
}
@@ -309,6 +331,7 @@ VElement.___morphAttrs = function(fromEl, toEl) {
return;
}
+
// In some cases we only want to set an attribute value for the first
// render or we don't want certain attributes to be touched. To support
// that use case we delete out all of the preserved attributes
@@ -352,7 +375,7 @@ VElement.___morphAttrs = function(fromEl, toEl) {
// was not a virtualized node (i.e., a node that was not rendered by a
// Marko template, but rather a node that was created from an HTML
// string or a real DOM node).
- if (!attrs.id || props.___virtualized === true) {
+ if (toEl.___key === null) {
for (attrName in oldAttrs) {
if (!(attrName in attrs)) {
if (attrName === ATTR_XLINK_HREF) {
diff --git a/src/runtime/vdom/VNode.js b/src/runtime/vdom/VNode.js
index 01e161a9a..937a2c706 100644
--- a/src/runtime/vdom/VNode.js
+++ b/src/runtime/vdom/VNode.js
@@ -1,6 +1,4 @@
/* jshint newcap:false */
-var specialElHandlers = require('../../morphdom/specialElHandlers');
-
function VNode() {}
VNode.prototype = {
@@ -13,6 +11,8 @@ VNode.prototype = {
this.___nextSiblingInternal = null;
},
+ ___component: null,
+
get ___firstChild() {
var firstChild = this.___firstChildInternal;
@@ -49,10 +49,10 @@ VNode.prototype = {
___appendChild: function(child) {
this.___childCount++;
- if (this.___isTextArea) {
+ if (this.___isTextArea === true) {
if (child.___Text) {
var childValue = child.___nodeValue;
- this.___value = (this.___value || '') + childValue;
+ this.___valueInternal = (this.___valueInternal || '') + childValue;
} else {
throw TypeError();
}
@@ -74,32 +74,13 @@ VNode.prototype = {
},
___finishChild: function finishChild() {
- if (this.___childCount == this.___finalChildCount && this.___parentNode) {
+ if (this.___childCount === this.___finalChildCount && this.___parentNode) {
return this.___parentNode.___finishChild();
} else {
return this;
}
},
- actualize: function(doc) {
- var actualNode = this.___actualize(doc);
-
- var curChild = this.___firstChild;
-
- while(curChild) {
- actualNode.appendChild(curChild.actualize(doc));
- curChild = curChild.___nextSibling;
- }
-
- if (this.___nodeType === 1) {
- var elHandler = specialElHandlers[this.___nodeName];
- if (elHandler !== undefined) {
- elHandler(actualNode, this);
- }
- }
-
- return actualNode;
- }
// ,toJSON: function() {
// var clone = Object.assign({
diff --git a/src/runtime/vdom/helpers.js b/src/runtime/vdom/helpers.js
index 5561bf765..e6ae716b3 100644
--- a/src/runtime/vdom/helpers.js
+++ b/src/runtime/vdom/helpers.js
@@ -10,8 +10,8 @@ var extend = require('raptor-util/extend');
var classList = commonHelpers.cl;
var helpers = extend({
- e: function(tagName, attrs, childCount, flags, props) {
- return new VElement(tagName, attrs, childCount, flags, props);
+ e: function(tagName, attrs, key, component, childCount, flags, props) {
+ return new VElement(tagName, attrs, key, component, childCount, flags, props);
},
t: function(value) {
diff --git a/src/runtime/vdom/preserve-attrs.js b/src/runtime/vdom/preserve-attrs.js
index 6704b654a..314078786 100644
--- a/src/runtime/vdom/preserve-attrs.js
+++ b/src/runtime/vdom/preserve-attrs.js
@@ -1,11 +1,9 @@
var extend = require('raptor-util/extend');
-function removePreservedAttributes(attrs, props, clone) {
+function removePreservedAttributes(attrs, props) {
var preservedAttrs = props && props.noupdate;
if (preservedAttrs) {
- if (clone) {
- attrs = extend({}, attrs);
- }
+ attrs = extend({}, attrs);
preservedAttrs.forEach(function(preservedAttrName) {
delete attrs[preservedAttrName];
});
diff --git a/src/runtime/vdom/vdom.js b/src/runtime/vdom/vdom.js
index e13d938ac..111e533db 100644
--- a/src/runtime/vdom/vdom.js
+++ b/src/runtime/vdom/vdom.js
@@ -3,12 +3,11 @@ var VComment = require('./VComment');
var VDocumentFragment = require('./VDocumentFragment');
var VElement = require('./VElement');
var VText = require('./VText');
+var VComponent = require('./VComponent');
-var FLAG_IS_TEXTAREA = 2;
var defaultDocument = typeof document != 'undefined' && document;
var specialHtmlRegexp = /[&<]/;
-var xmlnsRegExp = /^xmlns(:|$)/;
-var virtualizedProps = { ___virtualized: true };
+
function virtualizeChildNodes(node, vdomParent) {
var curChild = node.firstChild;
@@ -18,44 +17,10 @@ function virtualizeChildNodes(node, vdomParent) {
}
}
-function virtualize(node) {
+function virtualize(node, shallow) {
switch(node.nodeType) {
case 1:
- var attributes = node.attributes;
- var attrCount = attributes.length;
-
- var attrs;
-
- if (attrCount) {
- attrs = {};
- for (var i=0; i": {
"code-generator": "./import-tag",
+ "no-output": true,
"parse-options": {
"relaxRequireCommas": true
}
@@ -135,7 +136,8 @@
{
"descriptionMoreURL": "http://markojs.com/docs/marko/language-guide/#macros"
}
- ]
+ ],
+ "no-output": true
},
"": {
"code-generator": "./macro-body-tag",
@@ -167,6 +169,7 @@
},
"": {
"code-generator": "./static-tag",
+ "no-output": true,
"parse-options": {
"ignoreAttributes": true
}
diff --git a/src/taglibs/html/marko.json b/src/taglibs/html/marko.json
index f1437d13a..0fbb84080 100644
--- a/src/taglibs/html/marko.json
+++ b/src/taglibs/html/marko.json
@@ -1127,7 +1127,8 @@
"html": true,
"attribute-groups": [
"html-attributes"
- ]
+ ],
+ "openTagOnly": true
},
"attribute-groups": {
"html-attributes": {
diff --git a/test/autotests/async-render/components-await-title/expected.html b/test/autotests/async-render/components-await-title/expected.html
index aeec1ed10..3b556a035 100644
--- a/test/autotests/async-render/components-await-title/expected.html
+++ b/test/autotests/async-render/components-await-title/expected.html
@@ -1 +1 @@
-Welcome Frank Hello
+Welcome Frank Hello
\ No newline at end of file
diff --git a/test/autotests/compiler-browser/dynamic-tag-name/expected.js b/test/autotests/compiler-browser/dynamic-tag-name/expected.js
index f4fa9a269..084c32b24 100644
--- a/test/autotests/compiler-browser/dynamic-tag-name/expected.js
+++ b/test/autotests/compiler-browser/dynamic-tag-name/expected.js
@@ -5,7 +5,7 @@ var marko_template = module.exports = require("marko/src/vdom").t();
function render(input, out) {
var data = input;
- out.ed(foo ? "foo" : "bar", null, 0);
+ out.ed(foo ? "foo" : "bar", null, null, null, 0);
}
marko_template._ = render;
diff --git a/test/autotests/compiler-browser/simple/expected.js b/test/autotests/compiler-browser/simple/expected.js
index 74b988d42..1728e7744 100644
--- a/test/autotests/compiler-browser/simple/expected.js
+++ b/test/autotests/compiler-browser/simple/expected.js
@@ -6,12 +6,12 @@ var marko_template = module.exports = require("marko/src/vdom").t(),
marko_createElement = marko_helpers.e,
marko_const = marko_helpers.const,
marko_const_nextId = marko_const("295cea"),
- marko_node0 = marko_createElement("DIV", null, 1, 0, {
- c: marko_const_nextId()
+ marko_node0 = marko_createElement("DIV", null, null, null, 1, 0, {
+ i: marko_const_nextId()
})
.t("No colors!"),
- marko_node1 = marko_createElement("DIV", null, 1, 0, {
- c: marko_const_nextId()
+ marko_node1 = marko_createElement("DIV", null, null, null, 1, 0, {
+ i: marko_const_nextId()
})
.t("No colors!");
@@ -28,7 +28,7 @@ function render(input, out) {
out.be("UL");
marko_forEach(input.colors, function(color) {
- out.e("LI", null, 1)
+ out.e("LI", null, null, null, 1)
.t(color);
});
@@ -41,7 +41,7 @@ function render(input, out) {
out.be("UL");
marko_forEach(input.colors, function(color) {
- out.e("LI", null, 1)
+ out.e("LI", null, null, null, 1)
.t(color);
});
diff --git a/test/autotests/compiler-browser/svg-anchor/expected.js b/test/autotests/compiler-browser/svg-anchor/expected.js
index b07a88e5a..affb7cd0f 100644
--- a/test/autotests/compiler-browser/svg-anchor/expected.js
+++ b/test/autotests/compiler-browser/svg-anchor/expected.js
@@ -8,13 +8,13 @@ var marko_template = module.exports = require("marko/src/vdom").t(),
marko_node0 = marko_createElement("svg", {
width: "140",
height: "30"
- }, 1, 1, {
- c: marko_const_nextId()
+ }, null, null, 1, 1, {
+ i: marko_const_nextId()
})
.e("a", {
"xlink:href": "https://developer.mozilla.org/en-US/docs/SVG",
target: "_blank"
- }, 0, 1);
+ }, null, null, 0, 1);
function render(input, out) {
var data = input;
diff --git a/test/autotests/compiler-browser/svg-dynamic-tag-name/expected.js b/test/autotests/compiler-browser/svg-dynamic-tag-name/expected.js
index 7cb14e5c5..2ab10723f 100644
--- a/test/autotests/compiler-browser/svg-dynamic-tag-name/expected.js
+++ b/test/autotests/compiler-browser/svg-dynamic-tag-name/expected.js
@@ -15,8 +15,8 @@ function render(input, out) {
var isCircle = true;
- out.e("svg", marko_attrs0, 1, 1)
- .e(isCircle ? "circle" : "square", marko_attrs1, 0, 1);
+ out.e("svg", marko_attrs0, null, null, 1, 1)
+ .e(isCircle ? "circle" : "square", marko_attrs1, null, null, 0, 1);
}
marko_template._ = render;
diff --git a/test/autotests/compiler-browser/svg/expected.js b/test/autotests/compiler-browser/svg/expected.js
index da0db4cea..8fac00093 100644
--- a/test/autotests/compiler-browser/svg/expected.js
+++ b/test/autotests/compiler-browser/svg/expected.js
@@ -8,14 +8,14 @@ var marko_template = module.exports = require("marko/src/vdom").t(),
marko_node0 = marko_createElement("svg", {
viewBox: "0 0 200 200",
xmlns: "http://www.w3.org/2000/svg"
- }, 1, 1, {
- c: marko_const_nextId()
+ }, null, null, 1, 1, {
+ i: marko_const_nextId()
})
.e("circle", {
cx: "100",
cy: "100",
r: "100"
- }, 0, 1);
+ }, null, null, 0, 1);
function render(input, out) {
var data = input;
diff --git a/test/autotests/components-browser/repeated-with-label-ref/index.marko b/test/autotests/components-browser-deprecated/for-key-repeated/index.marko
similarity index 90%
rename from test/autotests/components-browser/repeated-with-label-ref/index.marko
rename to test/autotests/components-browser-deprecated/for-key-repeated/index.marko
index a87bf72d7..6bf35bc95 100644
--- a/test/autotests/components-browser/repeated-with-label-ref/index.marko
+++ b/test/autotests/components-browser-deprecated/for-key-repeated/index.marko
@@ -2,11 +2,11 @@ class {
}
-
+
\ No newline at end of file
+
diff --git a/test/autotests/components-browser-deprecated/for-key-repeated/test.js b/test/autotests/components-browser-deprecated/for-key-repeated/test.js
new file mode 100644
index 000000000..8451257d3
--- /dev/null
+++ b/test/autotests/components-browser-deprecated/for-key-repeated/test.js
@@ -0,0 +1,31 @@
+var expect = require('chai').expect;
+
+module.exports = function(helpers) {
+ var fields = [
+ {
+ value: 'name',
+ label: 'Name'
+ },
+ {
+ value: 'age',
+ label: 'Age'
+ }
+ ];
+
+ var component = helpers.mount(require('./index'), {
+ fields: fields
+ });
+
+ var inputs = component.getEl('root').querySelectorAll('input');
+ var labels = component.getEl('root').querySelectorAll('label');
+
+ expect(inputs.length).to.equal(fields.length);
+
+ for (var i=0; i${input.count}
diff --git a/test/autotests/components-browser/diffpatch-boundary-inner-component-only/index.marko b/test/autotests/components-browser/diffpatch-boundary-inner-component-only/index.marko
new file mode 100644
index 000000000..c320bebf8
--- /dev/null
+++ b/test/autotests/components-browser/diffpatch-boundary-inner-component-only/index.marko
@@ -0,0 +1,9 @@
+class {
+ onCreate() {
+ this.state = {
+ count: 0
+ }
+ }
+}
+
+
diff --git a/test/autotests/components-browser/diffpatch-boundary-inner-component-only/test.js b/test/autotests/components-browser/diffpatch-boundary-inner-component-only/test.js
new file mode 100644
index 000000000..f000b76e8
--- /dev/null
+++ b/test/autotests/components-browser/diffpatch-boundary-inner-component-only/test.js
@@ -0,0 +1,21 @@
+var expect = require('chai').expect;
+
+module.exports = function(helpers) {
+ var component = helpers.mount(require('./index'), { });
+ var innerComponent = component.getComponent('inner');
+
+ expect(innerComponent.___startNode.className).to.equal('inner');
+ expect(component.___startNode).to.not.equal(innerComponent.___startNode);
+ expect(component.___endNode).to.not.equal(innerComponent.___endNode);
+ expect(helpers.targetEl.querySelector('.inner').innerHTML).to.equal('0');
+
+ component.state.count++;
+ component.update();
+
+ innerComponent = component.getComponent('inner');
+
+ expect(innerComponent.___startNode.className).to.equal('inner');
+ expect(component.___startNode).to.not.equal(innerComponent.___startNode);
+ expect(component.___endNode).to.not.equal(innerComponent.___endNode);
+ expect(helpers.targetEl.querySelector('.inner').innerHTML).to.equal('1');
+};
diff --git a/test/autotests/components-browser/diffpatch-component-mismatch-append/components/bar/index.marko b/test/autotests/components-browser/diffpatch-component-mismatch-append/components/bar/index.marko
new file mode 100644
index 000000000..c991c39bb
--- /dev/null
+++ b/test/autotests/components-browser/diffpatch-component-mismatch-append/components/bar/index.marko
@@ -0,0 +1,3 @@
+class {}
+
+${input.name}
diff --git a/test/autotests/components-browser/diffpatch-component-mismatch-append/components/foo/index.marko b/test/autotests/components-browser/diffpatch-component-mismatch-append/components/foo/index.marko
new file mode 100644
index 000000000..ccb76c5f3
--- /dev/null
+++ b/test/autotests/components-browser/diffpatch-component-mismatch-append/components/foo/index.marko
@@ -0,0 +1,3 @@
+class {}
+
+${input.name}
diff --git a/test/autotests/components-browser/diffpatch-component-mismatch-append/index.marko b/test/autotests/components-browser/diffpatch-component-mismatch-append/index.marko
new file mode 100644
index 000000000..9face8ab1
--- /dev/null
+++ b/test/autotests/components-browser/diffpatch-component-mismatch-append/index.marko
@@ -0,0 +1,19 @@
+class {
+ onCreate() {
+ this.state = {
+ count: 0
+ }
+ }
+}
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/autotests/components-browser/diffpatch-component-mismatch-append/test.js b/test/autotests/components-browser/diffpatch-component-mismatch-append/test.js
new file mode 100644
index 000000000..bb602878f
--- /dev/null
+++ b/test/autotests/components-browser/diffpatch-component-mismatch-append/test.js
@@ -0,0 +1,21 @@
+var expect = require('chai').expect;
+
+module.exports = function(helpers) {
+ var component = helpers.mount(require('./index'), { });
+
+ var children;
+
+ children = component.getEl('root').children;
+ expect(children.length).to.equal(2);
+ expect(children[0].innerHTML).to.equal('foo-a');
+ expect(children[1].innerHTML).to.equal('bar-b');
+
+ component.state.count++;
+ component.update();
+
+ children = component.getEl('root').children;
+ expect(children.length).to.equal(3);
+ expect(children[0].innerHTML).to.equal('foo-a');
+ expect(children[1].innerHTML).to.equal('foo-b');
+ expect(children[2].innerHTML).to.equal('bar-c');
+};
diff --git a/test/autotests/components-browser/diffpatch-component-mismatch/components/bar/index.marko b/test/autotests/components-browser/diffpatch-component-mismatch/components/bar/index.marko
new file mode 100644
index 000000000..3aeda96a5
--- /dev/null
+++ b/test/autotests/components-browser/diffpatch-component-mismatch/components/bar/index.marko
@@ -0,0 +1,3 @@
+class {}
+
+bar ${component.id}
diff --git a/test/autotests/components-browser/diffpatch-component-mismatch/components/foo/index.marko b/test/autotests/components-browser/diffpatch-component-mismatch/components/foo/index.marko
new file mode 100644
index 000000000..f8cf2747c
--- /dev/null
+++ b/test/autotests/components-browser/diffpatch-component-mismatch/components/foo/index.marko
@@ -0,0 +1,3 @@
+class {}
+
+foo ${component.id}
diff --git a/test/autotests/components-browser/diffpatch-component-mismatch/index.marko b/test/autotests/components-browser/diffpatch-component-mismatch/index.marko
new file mode 100644
index 000000000..8b50860b8
--- /dev/null
+++ b/test/autotests/components-browser/diffpatch-component-mismatch/index.marko
@@ -0,0 +1,19 @@
+class {
+ onCreate() {
+ this.state = {
+ count: 0
+ }
+ }
+}
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/autotests/components-browser/diffpatch-component-mismatch/test.js b/test/autotests/components-browser/diffpatch-component-mismatch/test.js
new file mode 100644
index 000000000..ce46d6e7e
--- /dev/null
+++ b/test/autotests/components-browser/diffpatch-component-mismatch/test.js
@@ -0,0 +1,16 @@
+var expect = require('chai').expect;
+
+module.exports = function(helpers) {
+ var component = helpers.mount(require('./index'), { });
+
+ var children;
+
+ children = component.getEl('root').children;
+ expect(children.length).to.equal(3);
+
+ component.state.count++;
+ component.update();
+
+ children = component.getEl('root').children;
+ expect(children.length).to.equal(2);
+};
diff --git a/test/autotests/components-browser/diffpatch-component-toplevel-surrounded/components/bar/index.marko b/test/autotests/components-browser/diffpatch-component-toplevel-surrounded/components/bar/index.marko
new file mode 100644
index 000000000..0da5dadae
--- /dev/null
+++ b/test/autotests/components-browser/diffpatch-component-toplevel-surrounded/components/bar/index.marko
@@ -0,0 +1,16 @@
+class {
+ onCreate() {
+ this.state = {
+ count: 1
+ }
+ }
+
+ increment() {
+ this.state.count++;
+ }
+}
+
+
+
+ ${i}
+
diff --git a/test/autotests/components-browser/diffpatch-component-toplevel-surrounded/components/foo/index.marko b/test/autotests/components-browser/diffpatch-component-toplevel-surrounded/components/foo/index.marko
new file mode 100644
index 000000000..5b678b3d0
--- /dev/null
+++ b/test/autotests/components-browser/diffpatch-component-toplevel-surrounded/components/foo/index.marko
@@ -0,0 +1,3 @@
+class {}
+
+foo ${component.id}
diff --git a/test/autotests/components-browser/diffpatch-component-toplevel-surrounded/index.marko b/test/autotests/components-browser/diffpatch-component-toplevel-surrounded/index.marko
new file mode 100644
index 000000000..583b55637
--- /dev/null
+++ b/test/autotests/components-browser/diffpatch-component-toplevel-surrounded/index.marko
@@ -0,0 +1,12 @@
+class {
+ incrementBar() {
+ this.getComponent('bar').increment();
+ this.getComponent('bar').update();
+ }
+}
+
+
+
+
+
+
diff --git a/test/autotests/components-browser/diffpatch-component-toplevel-surrounded/test.js b/test/autotests/components-browser/diffpatch-component-toplevel-surrounded/test.js
new file mode 100644
index 000000000..ba78313e0
--- /dev/null
+++ b/test/autotests/components-browser/diffpatch-component-toplevel-surrounded/test.js
@@ -0,0 +1,10 @@
+var expect = require('chai').expect;
+
+module.exports = function(helpers) {
+ var component = helpers.mount(require('./index'), { });
+
+ component.incrementBar();
+
+ var children = component.getEl('root').children;
+ expect(children[children.length-1].nodeName).to.equal('SPAN');
+};
diff --git a/test/autotests/components-browser/diffpatch-destroy-child/components/hello/index.marko b/test/autotests/components-browser/diffpatch-destroy-child/components/hello/index.marko
new file mode 100644
index 000000000..34c5c995f
--- /dev/null
+++ b/test/autotests/components-browser/diffpatch-destroy-child/components/hello/index.marko
@@ -0,0 +1,5 @@
+class {
+
+}
+
+Hello${input.count}
diff --git a/test/autotests/components-browser/diffpatch-destroy-child/index.marko b/test/autotests/components-browser/diffpatch-destroy-child/index.marko
new file mode 100644
index 000000000..0c06259d6
--- /dev/null
+++ b/test/autotests/components-browser/diffpatch-destroy-child/index.marko
@@ -0,0 +1,15 @@
+class {
+ onCreate() {
+ this.state = {
+ count: 0,
+ renderHello: true
+ };
+ }
+}
+
+
+ [ROOT]
+
+
+
+
diff --git a/test/autotests/components-browser/diffpatch-destroy-child/test.js b/test/autotests/components-browser/diffpatch-destroy-child/test.js
new file mode 100644
index 000000000..350d682af
--- /dev/null
+++ b/test/autotests/components-browser/diffpatch-destroy-child/test.js
@@ -0,0 +1,21 @@
+var expect = require('chai').expect;
+
+module.exports = function(helpers) {
+ var component = helpers.mount(require('./index'), { });
+ var rootEl = component.el;
+ var helloCountEl = rootEl.querySelector('span.hello-count');
+ var helloComponent = component.getComponent('hello');
+
+ component.state.count = 1;
+ component.update();
+
+ expect(component.el).to.equal(rootEl);
+ expect(rootEl.querySelector('span.hello-count').innerHTML).to.equal('1');
+ expect(rootEl.querySelector('span.hello-count')).to.equal(helloCountEl);
+
+ component.state.renderHello = false;
+ component.update();
+
+ expect(helloComponent.isDestroyed()).to.equal(true);
+ expect(helloComponent.el == null).to.equal(true);
+};
diff --git a/test/autotests/components-browser/diffpatch-insert-el-before-component/components/hello/index.marko b/test/autotests/components-browser/diffpatch-insert-el-before-component/components/hello/index.marko
new file mode 100644
index 000000000..285be66ab
--- /dev/null
+++ b/test/autotests/components-browser/diffpatch-insert-el-before-component/components/hello/index.marko
@@ -0,0 +1,5 @@
+class {
+
+}
+
+Hello: ${input.count}
diff --git a/test/autotests/components-browser/diffpatch-insert-el-before-component/index.marko b/test/autotests/components-browser/diffpatch-insert-el-before-component/index.marko
new file mode 100644
index 000000000..2c560f54b
--- /dev/null
+++ b/test/autotests/components-browser/diffpatch-insert-el-before-component/index.marko
@@ -0,0 +1,21 @@
+class {
+ onCreate() {
+ this.state = {
+ insertEl: false,
+ count: 0
+ };
+ }
+}
+
+
+
+
+
+
+ New element
+
+
+
+
+
+
diff --git a/test/autotests/components-browser/diffpatch-insert-el-before-component/test.js b/test/autotests/components-browser/diffpatch-insert-el-before-component/test.js
new file mode 100644
index 000000000..915f2df74
--- /dev/null
+++ b/test/autotests/components-browser/diffpatch-insert-el-before-component/test.js
@@ -0,0 +1,17 @@
+var expect = require('chai').expect;
+
+module.exports = function(helpers) {
+ var component = helpers.mount(require('./index'), { });
+ // var rootEl = component.el;
+ var helloComponent = component.getComponent('hello');
+ expect(component.getEl('root').querySelector('.hello') != null).to.equal(true);
+ // expect(helloComponent.el.parentNode).to.equal(rootEl);
+
+ component.state.insertEl = true;
+ component.state.count++;
+ component.update();
+
+ expect(component.getEl('root').querySelector('.hello') != null).to.equal(true);
+
+ expect(component.getComponent('hello')).to.equal(helloComponent);
+};
diff --git a/test/autotests/components-browser/diffpatch-insert-unkeyed-el-before-component/components/hello/index.marko b/test/autotests/components-browser/diffpatch-insert-unkeyed-el-before-component/components/hello/index.marko
new file mode 100644
index 000000000..285be66ab
--- /dev/null
+++ b/test/autotests/components-browser/diffpatch-insert-unkeyed-el-before-component/components/hello/index.marko
@@ -0,0 +1,5 @@
+class {
+
+}
+
+Hello: ${input.count}
diff --git a/test/autotests/components-browser/diffpatch-insert-unkeyed-el-before-component/index.marko b/test/autotests/components-browser/diffpatch-insert-unkeyed-el-before-component/index.marko
new file mode 100644
index 000000000..8f94ed243
--- /dev/null
+++ b/test/autotests/components-browser/diffpatch-insert-unkeyed-el-before-component/index.marko
@@ -0,0 +1,19 @@
+class {
+ onCreate() {
+ this.state = {
+ insertEl: false,
+ count: 0
+ };
+ }
+}
+
+
+
+
+
+
+ $!{'
'}
+
+
+
+
diff --git a/test/autotests/components-browser/diffpatch-insert-unkeyed-el-before-component/test.js b/test/autotests/components-browser/diffpatch-insert-unkeyed-el-before-component/test.js
new file mode 100644
index 000000000..915f2df74
--- /dev/null
+++ b/test/autotests/components-browser/diffpatch-insert-unkeyed-el-before-component/test.js
@@ -0,0 +1,17 @@
+var expect = require('chai').expect;
+
+module.exports = function(helpers) {
+ var component = helpers.mount(require('./index'), { });
+ // var rootEl = component.el;
+ var helloComponent = component.getComponent('hello');
+ expect(component.getEl('root').querySelector('.hello') != null).to.equal(true);
+ // expect(helloComponent.el.parentNode).to.equal(rootEl);
+
+ component.state.insertEl = true;
+ component.state.count++;
+ component.update();
+
+ expect(component.getEl('root').querySelector('.hello') != null).to.equal(true);
+
+ expect(component.getComponent('hello')).to.equal(helloComponent);
+};
diff --git a/test/autotests/components-browser/diffpatch-insert-unkeyed-el-before-preserved-component/components/hello/index.marko b/test/autotests/components-browser/diffpatch-insert-unkeyed-el-before-preserved-component/components/hello/index.marko
new file mode 100644
index 000000000..285be66ab
--- /dev/null
+++ b/test/autotests/components-browser/diffpatch-insert-unkeyed-el-before-preserved-component/components/hello/index.marko
@@ -0,0 +1,5 @@
+class {
+
+}
+
+Hello: ${input.count}
diff --git a/test/autotests/components-browser/diffpatch-insert-unkeyed-el-before-preserved-component/index.marko b/test/autotests/components-browser/diffpatch-insert-unkeyed-el-before-preserved-component/index.marko
new file mode 100644
index 000000000..826e713d9
--- /dev/null
+++ b/test/autotests/components-browser/diffpatch-insert-unkeyed-el-before-preserved-component/index.marko
@@ -0,0 +1,19 @@
+class {
+ onCreate() {
+ this.state = {
+ insertEl: false,
+ count: 0
+ };
+ }
+}
+
+
+
+
+
+
+ $!{'
'}
+
+
+
+
diff --git a/test/autotests/components-browser/diffpatch-insert-unkeyed-el-before-preserved-component/test.js b/test/autotests/components-browser/diffpatch-insert-unkeyed-el-before-preserved-component/test.js
new file mode 100644
index 000000000..915f2df74
--- /dev/null
+++ b/test/autotests/components-browser/diffpatch-insert-unkeyed-el-before-preserved-component/test.js
@@ -0,0 +1,17 @@
+var expect = require('chai').expect;
+
+module.exports = function(helpers) {
+ var component = helpers.mount(require('./index'), { });
+ // var rootEl = component.el;
+ var helloComponent = component.getComponent('hello');
+ expect(component.getEl('root').querySelector('.hello') != null).to.equal(true);
+ // expect(helloComponent.el.parentNode).to.equal(rootEl);
+
+ component.state.insertEl = true;
+ component.state.count++;
+ component.update();
+
+ expect(component.getEl('root').querySelector('.hello') != null).to.equal(true);
+
+ expect(component.getComponent('hello')).to.equal(helloComponent);
+};
diff --git a/test/autotests/components-browser/diffpatch-rearrange-keyed-components/components/hello/index.marko b/test/autotests/components-browser/diffpatch-rearrange-keyed-components/components/hello/index.marko
new file mode 100644
index 000000000..aab3a0e19
--- /dev/null
+++ b/test/autotests/components-browser/diffpatch-rearrange-keyed-components/components/hello/index.marko
@@ -0,0 +1,3 @@
+class {}
+$ var value = input.value;
+${value}
diff --git a/test/autotests/components-browser/diffpatch-rearrange-keyed-components/index.marko b/test/autotests/components-browser/diffpatch-rearrange-keyed-components/index.marko
new file mode 100644
index 000000000..e9eee07ae
--- /dev/null
+++ b/test/autotests/components-browser/diffpatch-rearrange-keyed-components/index.marko
@@ -0,0 +1,9 @@
+class {
+}
+
+
+
Nested components:
+
+
+
+
diff --git a/test/autotests/components-browser/diffpatch-rearrange-keyed-components/test.js b/test/autotests/components-browser/diffpatch-rearrange-keyed-components/test.js
new file mode 100644
index 000000000..c0af89149
--- /dev/null
+++ b/test/autotests/components-browser/diffpatch-rearrange-keyed-components/test.js
@@ -0,0 +1,96 @@
+var expect = require('chai').expect;
+
+module.exports = function(helpers) {
+ var component = helpers.mount(require('./index'), {});
+
+ var previousComponents = {};
+
+ function checkOrder(letters) {
+ component.input = { letters: letters };
+ component.update();
+
+ var divs = component.getEl('root').querySelectorAll('div');
+
+ expect(divs.length).to.equal(letters.length);
+
+ var newComponents = {};
+
+ for(var i=0; i
+ ${letter}
+
diff --git a/test/autotests/components-browser/diffpatch-rearrange-keyed-els/test.js b/test/autotests/components-browser/diffpatch-rearrange-keyed-els/test.js
new file mode 100644
index 000000000..ff80815e5
--- /dev/null
+++ b/test/autotests/components-browser/diffpatch-rearrange-keyed-els/test.js
@@ -0,0 +1,69 @@
+var expect = require('chai').expect;
+
+module.exports = function(helpers) {
+ var component = helpers.mount(require('./index'), {});
+ expect(helpers.targetEl.querySelectorAll('div').length).to.equal(0);
+
+ function checkOrder(letters) {
+ component.input = { letters: letters };
+ component.update();
+
+ var divs = helpers.targetEl.querySelectorAll('div');
+
+ expect(divs.length).to.equal(letters.length);
+
+ for(var i=0; i
+ Header
+ Body
+
diff --git a/test/autotests/components-browser/diffpatch-remove-all-els/test.js b/test/autotests/components-browser/diffpatch-remove-all-els/test.js
new file mode 100644
index 000000000..617564fc0
--- /dev/null
+++ b/test/autotests/components-browser/diffpatch-remove-all-els/test.js
@@ -0,0 +1,16 @@
+var expect = require('chai').expect;
+
+module.exports = function(helpers) {
+ var component = helpers.mount(require('./index'), { });
+
+ expect(helpers.targetEl.querySelector('h1') != null).to.equal(true);
+ expect(helpers.targetEl.querySelector('div') != null).to.equal(true);
+
+ component.state.show = false;
+ component.update();
+
+ expect(helpers.targetEl.querySelector('h1') == null).to.equal(true);
+ expect(helpers.targetEl.querySelector('div') == null).to.equal(true);
+
+ expect(component.isDestroyed()).to.equal(false);
+};
diff --git a/test/autotests/components-browser/diffpatch-remove-end-el/index.marko b/test/autotests/components-browser/diffpatch-remove-end-el/index.marko
new file mode 100644
index 000000000..c8be2bb7c
--- /dev/null
+++ b/test/autotests/components-browser/diffpatch-remove-end-el/index.marko
@@ -0,0 +1,12 @@
+class {
+ onCreate() {
+ this.state = {
+ showFooter: true
+ };
+ }
+}
+
+Body
+
diff --git a/test/autotests/components-browser/diffpatch-remove-end-el/test.js b/test/autotests/components-browser/diffpatch-remove-end-el/test.js
new file mode 100644
index 000000000..6fca56434
--- /dev/null
+++ b/test/autotests/components-browser/diffpatch-remove-end-el/test.js
@@ -0,0 +1,14 @@
+var expect = require('chai').expect;
+
+module.exports = function(helpers) {
+ var component = helpers.mount(require('./index'), { });
+
+ expect(helpers.targetEl.querySelector('footer') != null).to.equal(true);
+ expect(helpers.targetEl.querySelector('div') != null).to.equal(true);
+
+ component.state.showFooter = false;
+ component.update();
+
+ expect(helpers.targetEl.querySelector('footer') == null).to.equal(true);
+ expect(helpers.targetEl.querySelector('div') != null).to.equal(true);
+};
diff --git a/test/autotests/components-browser/diffpatch-remove-start-el/index.marko b/test/autotests/components-browser/diffpatch-remove-start-el/index.marko
new file mode 100644
index 000000000..8eea05059
--- /dev/null
+++ b/test/autotests/components-browser/diffpatch-remove-start-el/index.marko
@@ -0,0 +1,12 @@
+class {
+ onCreate() {
+ this.state = {
+ showHeader: true
+ };
+ }
+}
+
+
+ Header
+
+Body
diff --git a/test/autotests/components-browser/diffpatch-remove-start-el/test.js b/test/autotests/components-browser/diffpatch-remove-start-el/test.js
new file mode 100644
index 000000000..a4c277797
--- /dev/null
+++ b/test/autotests/components-browser/diffpatch-remove-start-el/test.js
@@ -0,0 +1,14 @@
+var expect = require('chai').expect;
+
+module.exports = function(helpers) {
+ var component = helpers.mount(require('./index'), { });
+
+ expect(helpers.targetEl.querySelector('h1') != null).to.equal(true);
+ expect(helpers.targetEl.querySelector('div') != null).to.equal(true);
+
+ component.state.showHeader = false;
+ component.update();
+
+ expect(helpers.targetEl.querySelector('h1') == null).to.equal(true);
+ expect(helpers.targetEl.querySelector('div') != null).to.equal(true);
+};
diff --git a/test/autotests/components-browser/diffpatch-simple/index.marko b/test/autotests/components-browser/diffpatch-simple/index.marko
new file mode 100644
index 000000000..44e7a2bdf
--- /dev/null
+++ b/test/autotests/components-browser/diffpatch-simple/index.marko
@@ -0,0 +1,13 @@
+class MyComponent {
+ onCreate() {
+ this.state = {
+ count: 0
+ };
+ }
+}
+
+
+
+ ${state.count}
+
+
diff --git a/test/autotests/components-browser/diffpatch-simple/test.js b/test/autotests/components-browser/diffpatch-simple/test.js
new file mode 100644
index 000000000..a6951610b
--- /dev/null
+++ b/test/autotests/components-browser/diffpatch-simple/test.js
@@ -0,0 +1,14 @@
+var expect = require('chai').expect;
+
+module.exports = function(helpers) {
+ var component = helpers.mount(require('./index'), { });
+ var rootEl = component.el;
+ var spanEl = rootEl.querySelector('span');
+
+ component.state.count = 1;
+ component.update();
+
+ expect(component.el).to.equal(rootEl);
+ expect(rootEl.querySelector('span').innerHTML).to.equal('1');
+ expect(rootEl.querySelector('span')).to.equal(spanEl);
+};
diff --git a/test/autotests/components-browser/diffpatch-swap-components-dynamic/components/hello/index.marko b/test/autotests/components-browser/diffpatch-swap-components-dynamic/components/hello/index.marko
new file mode 100644
index 000000000..44a3156d1
--- /dev/null
+++ b/test/autotests/components-browser/diffpatch-swap-components-dynamic/components/hello/index.marko
@@ -0,0 +1,5 @@
+class {
+
+}
+
+${input.count}
diff --git a/test/autotests/components-browser/diffpatch-swap-components-dynamic/components/world/index.marko b/test/autotests/components-browser/diffpatch-swap-components-dynamic/components/world/index.marko
new file mode 100644
index 000000000..d72af3219
--- /dev/null
+++ b/test/autotests/components-browser/diffpatch-swap-components-dynamic/components/world/index.marko
@@ -0,0 +1,5 @@
+class {
+
+}
+
+${input.count}
diff --git a/test/autotests/components-browser/diffpatch-swap-components-dynamic/index.marko b/test/autotests/components-browser/diffpatch-swap-components-dynamic/index.marko
new file mode 100644
index 000000000..f444872ad
--- /dev/null
+++ b/test/autotests/components-browser/diffpatch-swap-components-dynamic/index.marko
@@ -0,0 +1,19 @@
+class {
+ onCreate() {
+ this.state = {
+ swapped: false,
+ count: 0
+ };
+ }
+}
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/autotests/components-browser/diffpatch-swap-components-dynamic/test.js b/test/autotests/components-browser/diffpatch-swap-components-dynamic/test.js
new file mode 100644
index 000000000..66e5151b8
--- /dev/null
+++ b/test/autotests/components-browser/diffpatch-swap-components-dynamic/test.js
@@ -0,0 +1,24 @@
+var expect = require('chai').expect;
+
+module.exports = function(helpers) {
+ var component = helpers.mount(require('./index'), { });
+ var rootEl = component.el;
+
+ var nestedDivs = rootEl.querySelectorAll('div');
+ expect(nestedDivs[0].className).to.equal('hello');
+ expect(nestedDivs[1].className).to.equal('world');
+ expect(nestedDivs[0].innerHTML).to.equal('0');
+ expect(nestedDivs[1].innerHTML).to.equal('0');
+
+ component.state.swapped = true;
+ component.state.count = 1;
+ component.update();
+
+ var nestedDivsAfter = rootEl.querySelectorAll('div');
+ expect(nestedDivsAfter[0].className).to.equal('world');
+ expect(nestedDivsAfter[1].className).to.equal('hello');
+ expect(nestedDivsAfter[0].innerHTML).to.equal('1');
+ expect(nestedDivsAfter[1].innerHTML).to.equal('1');
+ expect(nestedDivsAfter[0]).to.not.equal(nestedDivs[1]);
+ expect(nestedDivsAfter[1]).to.not.equal(nestedDivs[0]);
+};
diff --git a/test/autotests/components-browser/diffpatch-swap-components-keyed-dynamic/components/hello/index.marko b/test/autotests/components-browser/diffpatch-swap-components-keyed-dynamic/components/hello/index.marko
new file mode 100644
index 000000000..44a3156d1
--- /dev/null
+++ b/test/autotests/components-browser/diffpatch-swap-components-keyed-dynamic/components/hello/index.marko
@@ -0,0 +1,5 @@
+class {
+
+}
+
+${input.count}
diff --git a/test/autotests/components-browser/diffpatch-swap-components-keyed-dynamic/components/world/index.marko b/test/autotests/components-browser/diffpatch-swap-components-keyed-dynamic/components/world/index.marko
new file mode 100644
index 000000000..d72af3219
--- /dev/null
+++ b/test/autotests/components-browser/diffpatch-swap-components-keyed-dynamic/components/world/index.marko
@@ -0,0 +1,5 @@
+class {
+
+}
+
+${input.count}
diff --git a/test/autotests/components-browser/diffpatch-swap-components-keyed-dynamic/index.marko b/test/autotests/components-browser/diffpatch-swap-components-keyed-dynamic/index.marko
new file mode 100644
index 000000000..bb9cda28e
--- /dev/null
+++ b/test/autotests/components-browser/diffpatch-swap-components-keyed-dynamic/index.marko
@@ -0,0 +1,19 @@
+class {
+ onCreate() {
+ this.state = {
+ swapped: false,
+ count: 0
+ };
+ }
+}
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/autotests/components-browser/diffpatch-swap-components-keyed-dynamic/test.js b/test/autotests/components-browser/diffpatch-swap-components-keyed-dynamic/test.js
new file mode 100644
index 000000000..0c952b174
--- /dev/null
+++ b/test/autotests/components-browser/diffpatch-swap-components-keyed-dynamic/test.js
@@ -0,0 +1,32 @@
+var expect = require('chai').expect;
+
+module.exports = function(helpers) {
+ var component = helpers.mount(require('./index'), { });
+ var rootEl = component.el;
+ var helloComponent = component.getComponent('hello');
+ var worldComponent = component.getComponent('world');
+ expect(helloComponent.el.parentNode).to.equal(rootEl);
+ expect(worldComponent.el.parentNode).to.equal(rootEl);
+
+ var nestedDivs = rootEl.querySelectorAll('div');
+ expect(nestedDivs[0].className).to.equal('hello');
+ expect(nestedDivs[1].className).to.equal('world');
+ expect(nestedDivs[0].innerHTML).to.equal('0');
+ expect(nestedDivs[1].innerHTML).to.equal('0');
+
+ component.state.swapped = true;
+ component.state.count = 1;
+ component.update();
+
+ expect(component.getComponent('hello')).to.equal(helloComponent);
+ expect(component.getComponent('world')).to.equal(worldComponent);
+
+ var nestedDivsAfter = rootEl.querySelectorAll('div');
+ expect(nestedDivsAfter[0].className).to.equal('world');
+ expect(nestedDivsAfter[1].className).to.equal('hello');
+ expect(nestedDivs[0].innerHTML).to.equal('1');
+ expect(nestedDivs[1].innerHTML).to.equal('1');
+
+ expect(nestedDivsAfter[0]).to.equal(nestedDivs[1]);
+ expect(nestedDivsAfter[1]).to.equal(nestedDivs[0]);
+};
diff --git a/test/autotests/components-browser/diffpatch-swap-components-keyed/components/hello/index.marko b/test/autotests/components-browser/diffpatch-swap-components-keyed/components/hello/index.marko
new file mode 100644
index 000000000..8d6867d77
--- /dev/null
+++ b/test/autotests/components-browser/diffpatch-swap-components-keyed/components/hello/index.marko
@@ -0,0 +1,5 @@
+class {
+
+}
+
+Hello: ${input.count}
diff --git a/test/autotests/components-browser/diffpatch-swap-components-keyed/components/world/index.marko b/test/autotests/components-browser/diffpatch-swap-components-keyed/components/world/index.marko
new file mode 100644
index 000000000..556f14608
--- /dev/null
+++ b/test/autotests/components-browser/diffpatch-swap-components-keyed/components/world/index.marko
@@ -0,0 +1,5 @@
+class {
+
+}
+
+World: ${input.count}
diff --git a/test/autotests/components-browser/diffpatch-swap-components-keyed/index.marko b/test/autotests/components-browser/diffpatch-swap-components-keyed/index.marko
new file mode 100644
index 000000000..5f75fff2e
--- /dev/null
+++ b/test/autotests/components-browser/diffpatch-swap-components-keyed/index.marko
@@ -0,0 +1,18 @@
+class {
+ onCreate() {
+ this.state = {
+ swapped: false
+ };
+ }
+}
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/autotests/components-browser/diffpatch-swap-components-keyed/test.js b/test/autotests/components-browser/diffpatch-swap-components-keyed/test.js
new file mode 100644
index 000000000..c22e4e10c
--- /dev/null
+++ b/test/autotests/components-browser/diffpatch-swap-components-keyed/test.js
@@ -0,0 +1,27 @@
+var expect = require('chai').expect;
+
+module.exports = function(helpers) {
+ var component = helpers.mount(require('./index'), { });
+ var rootEl = component.el;
+ var helloComponent = component.getComponent('hello');
+ var worldComponent = component.getComponent('world');
+ expect(helloComponent.el.parentNode).to.equal(rootEl);
+ expect(worldComponent.el.parentNode).to.equal(rootEl);
+
+ var nestedDivs = rootEl.querySelectorAll('div');
+ expect(nestedDivs[0].className).to.equal('hello');
+ expect(nestedDivs[1].className).to.equal('world');
+
+ component.state.swapped = true;
+ component.update();
+
+ expect(component.getComponent('hello')).to.equal(helloComponent);
+ expect(component.getComponent('world')).to.equal(worldComponent);
+
+ var nestedDivsAfter = rootEl.querySelectorAll('div');
+ expect(nestedDivsAfter[0].className).to.equal('world');
+ expect(nestedDivsAfter[1].className).to.equal('hello');
+
+ expect(nestedDivsAfter[0]).to.equal(nestedDivs[1]);
+ expect(nestedDivsAfter[1]).to.equal(nestedDivs[0]);
+};
diff --git a/test/autotests/components-browser/diffpatch-swap-keyed-el/index.marko b/test/autotests/components-browser/diffpatch-swap-keyed-el/index.marko
new file mode 100644
index 000000000..fe6606330
--- /dev/null
+++ b/test/autotests/components-browser/diffpatch-swap-keyed-el/index.marko
@@ -0,0 +1,18 @@
+class {
+ onCreate() {
+ this.state = {
+ swapped: false
+ }
+ }
+}
+
+
+
+ bar
+ foo
+
+
+ foo
+ bar
+
+
diff --git a/test/autotests/components-browser/diffpatch-swap-keyed-el/test.js b/test/autotests/components-browser/diffpatch-swap-keyed-el/test.js
new file mode 100644
index 000000000..82f943243
--- /dev/null
+++ b/test/autotests/components-browser/diffpatch-swap-keyed-el/test.js
@@ -0,0 +1,18 @@
+var expect = require('chai').expect;
+
+module.exports = function(helpers) {
+ var component = helpers.mount(require('./index'), { });
+
+ var fooEl = component.getEl('foo');
+ var barEl = component.getEl('bar');
+
+ expect(fooEl.nextSibling).to.equal(barEl);
+
+ component.state.swapped = true;
+ component.update();
+
+ expect(component.getEl('foo')).to.equal(fooEl);
+ expect(component.getEl('bar')).to.equal(barEl);
+
+ expect(barEl.nextSibling).to.equal(fooEl);
+};
diff --git a/test/autotests/components-browser/diffpatch-swap-unkeyed-el/index.marko b/test/autotests/components-browser/diffpatch-swap-unkeyed-el/index.marko
new file mode 100644
index 000000000..e89dd1feb
--- /dev/null
+++ b/test/autotests/components-browser/diffpatch-swap-unkeyed-el/index.marko
@@ -0,0 +1,18 @@
+class {
+ onCreate() {
+ this.state = {
+ swapped: false
+ }
+ }
+}
+
+
+
+ bar
+ foo
+
+
+ foo
+ bar
+
+
diff --git a/test/autotests/components-browser/diffpatch-swap-unkeyed-el/test.js b/test/autotests/components-browser/diffpatch-swap-unkeyed-el/test.js
new file mode 100644
index 000000000..7b6916948
--- /dev/null
+++ b/test/autotests/components-browser/diffpatch-swap-unkeyed-el/test.js
@@ -0,0 +1,26 @@
+var expect = require('chai').expect;
+
+module.exports = function(helpers) {
+ var component = helpers.mount(require('./index'), { });
+ var children = component.getEl('root').children;
+ expect(children.length).to.equal(2);
+
+ expect(children[0].dataset.foo).to.equal('true');
+ expect(children[0].dataset.bar).to.equal(undefined);
+
+ expect(children[1].dataset.foo).to.equal(undefined);
+ expect(children[1].dataset.bar).to.equal('true');
+
+ component.state.swapped = true;
+ component.update();
+
+ children = component.getEl('root').children;
+
+ expect(children.length).to.equal(2);
+
+ expect(children[0].dataset.foo).to.equal(undefined);
+ expect(children[0].dataset.bar).to.equal('true');
+
+ expect(children[1].dataset.foo).to.equal('true');
+ expect(children[1].dataset.bar).to.equal(undefined);
+};
diff --git a/test/autotests/components-browser/event-attach-el/test.js b/test/autotests/components-browser/event-attach-el/test.js
index 665f8ce57..25d46323a 100644
--- a/test/autotests/components-browser/event-attach-el/test.js
+++ b/test/autotests/components-browser/event-attach-el/test.js
@@ -5,7 +5,11 @@ module.exports = function(helpers) {
colors: ['red']
});
- expect(component.events.length).to.equal(0);
+ expect(component.events.length).to.equal(1);
+
+
+ expect(component.events[0].color).to.equal('red');
+ expect(component.events[0].node).to.equal(component.el.querySelectorAll('li')[0]);
component.input = {
@@ -14,9 +18,9 @@ module.exports = function(helpers) {
component.update();
- expect(component.events.length).to.equal(1);
- expect(component.events[0].color).to.equal('blue');
- expect(component.events[0].node).to.equal(component.el.querySelectorAll('li')[1]);
+ expect(component.events.length).to.equal(2);
+ expect(component.events[1].color).to.equal('blue');
+ expect(component.events[1].node).to.equal(component.el.querySelectorAll('li')[1]);
component.input = {
colors: ['red', 'green', 'blue']
@@ -24,7 +28,7 @@ module.exports = function(helpers) {
component.update();
- expect(component.events.length).to.equal(2);
- expect(component.events[1].color).to.equal('green');
- expect(component.events[1].node).to.equal(component.el.querySelectorAll('li')[1]);
-};
\ No newline at end of file
+ expect(component.events.length).to.equal(3);
+ expect(component.events[2].color).to.equal('green');
+ expect(component.events[2].node).to.equal(component.el.querySelectorAll('li')[1]);
+};
diff --git a/test/autotests/components-browser/extend-component/test.js b/test/autotests/components-browser/extend-component/test.js
index 87fac4731..53c2e9c65 100644
--- a/test/autotests/components-browser/extend-component/test.js
+++ b/test/autotests/components-browser/extend-component/test.js
@@ -1,17 +1,15 @@
var expect = require('chai').expect;
-var markoComponents = require('marko/components');
module.exports = function(helpers) {
- var checkboxComponent = helpers.mount(require('./components/app-checkbox'), {
+ helpers.mount(require('./components/app-checkbox'), {
checked: true,
'class': 'my-checkbox',
data: 123
});
+ expect(helpers.targetEl.children.length).to.equal(1);
+ expect(helpers.targetEl.children[0].nodeName).to.equal('BUTTON');
+
var el = helpers.targetEl.querySelector('.my-checkbox');
expect(el != null).to.equal(true);
-
- var componentForEl = markoComponents.getComponentForEl(el);
-
- expect(componentForEl).to.equal(checkboxComponent);
-};
\ No newline at end of file
+};
diff --git a/test/autotests/components-browser/forceUpdate/index.marko b/test/autotests/components-browser/forceUpdate/index.marko
index fc8fce307..cbef375a3 100644
--- a/test/autotests/components-browser/forceUpdate/index.marko
+++ b/test/autotests/components-browser/forceUpdate/index.marko
@@ -17,4 +17,4 @@ class {
}
}
-${counter++}
\ No newline at end of file
+${counter++}
diff --git a/test/autotests/components-browser/include-root/index.marko b/test/autotests/components-browser/include-root/index.marko
new file mode 100644
index 000000000..1ef76f2e6
--- /dev/null
+++ b/test/autotests/components-browser/include-root/index.marko
@@ -0,0 +1,13 @@
+class {
+ onClick() {
+ this.clicked = true;
+ }
+}
+
+
+ <@body>
+
+ Test link
+
+ @body>
+
diff --git a/test/autotests/components-browser/include-root/modal.marko b/test/autotests/components-browser/include-root/modal.marko
new file mode 100644
index 000000000..380096be1
--- /dev/null
+++ b/test/autotests/components-browser/include-root/modal.marko
@@ -0,0 +1,9 @@
+class {
+
+}
+
+
diff --git a/test/autotests/components-browser/include-root/test.js b/test/autotests/components-browser/include-root/test.js
new file mode 100644
index 000000000..7afdb7e3b
--- /dev/null
+++ b/test/autotests/components-browser/include-root/test.js
@@ -0,0 +1,7 @@
+var expect = require('chai').expect;
+
+module.exports = function(helpers) {
+ var component = helpers.mount(require('./index'), { });
+ helpers.triggerMouseEvent(component.getEl('button'), 'click');
+ expect(component.clicked).to.equal(true);
+};
diff --git a/test/autotests/components-browser/label-for-scoped-repeated/index.marko b/test/autotests/components-browser/label-for-scoped-repeated/index.marko
new file mode 100644
index 000000000..608aa5f10
--- /dev/null
+++ b/test/autotests/components-browser/label-for-scoped-repeated/index.marko
@@ -0,0 +1,12 @@
+class {
+
+}
+
+
+
+
+
+ ${field.label}
+
+
+
diff --git a/test/autotests/components-browser/label-for-scoped-repeated/test.js b/test/autotests/components-browser/label-for-scoped-repeated/test.js
new file mode 100644
index 000000000..aca3601b6
--- /dev/null
+++ b/test/autotests/components-browser/label-for-scoped-repeated/test.js
@@ -0,0 +1,32 @@
+var expect = require('chai').expect;
+
+module.exports = function(helpers) {
+ var fields = [
+ {
+ value: 'name',
+ label: 'Name'
+ },
+ {
+ value: 'age',
+ label: 'Age'
+ }
+ ];
+
+ var component = helpers.mount(require('./index'), {
+ fields: fields
+ });
+
+ var inputs = component.getEl('root').querySelectorAll('input');
+ var labels = component.getEl('root').querySelectorAll('label');
+
+ expect(inputs.length).to.equal(fields.length);
+
+ for (var i=0; i
+
Hello ${input.name}!
diff --git a/test/autotests/components-browser/lifecyle-hooks-destroy/index.marko b/test/autotests/components-browser/lifecyle-hooks-destroy/index.marko
index d735e05cf..a2076e1e3 100644
--- a/test/autotests/components-browser/lifecyle-hooks-destroy/index.marko
+++ b/test/autotests/components-browser/lifecyle-hooks-destroy/index.marko
@@ -3,11 +3,13 @@ import { expect } from 'chai';
class {
onDestroy(){
- expect(document.getElementById(this.id)).to.equal(this.el);
+ // The nodes should still be attached
+ var rootNode = this.getEl('root');
+ expect(rootNode.parentNode != null).to.equal(true);
hooks.record('root:destroy');
}
}
-
+
diff --git a/test/autotests/components-browser/morphdom-node-added-nested-keyed/test.js b/test/autotests/components-browser/morphdom-node-added-nested-keyed/test.js
index 1681d86f1..5b0db4845 100644
--- a/test/autotests/components-browser/morphdom-node-added-nested-keyed/test.js
+++ b/test/autotests/components-browser/morphdom-node-added-nested-keyed/test.js
@@ -2,13 +2,12 @@ var expect = require('chai').expect;
module.exports = function(helpers) {
var component = helpers.mount(require('./index'), { });
- var nameInput = component.getEl('nameInput');
+
+ expect(component.getEl('nameInput').parentNode.nodeName).to.equal('DIV');
component.state.renderCount++;
-
component.update();
- expect(component.getEl('nameInput')).to.equal(nameInput);
- expect(component.getEl('nameInput').value).to.equal('2');
expect(component.getEl('nameInput').parentNode.nodeName).to.equal('P');
+ expect(component.getEl('nameInput').value).to.equal('2');
};
diff --git a/test/autotests/components-browser/repeated-with-label-ref/test.js b/test/autotests/components-browser/repeated-with-label-ref/test.js
deleted file mode 100644
index 3d524eecb..000000000
--- a/test/autotests/components-browser/repeated-with-label-ref/test.js
+++ /dev/null
@@ -1,20 +0,0 @@
-var expect = require('chai').expect;
-
-module.exports = function(helpers) {
- var component = helpers.mount(require('./index'), {
- fields: [
- {
- value: 'name',
- label: 'Name'
- },
- {
- value: 'age',
- label: 'Age'
- }
- ]
- });
-
- var inputs = component.getEls('field');
- expect(inputs.length).to.equal(2);
- expect(inputs[0].value).to.equal('name');
-};
\ No newline at end of file
diff --git a/test/autotests/components-browser/rerender-multiple-roots/index.marko b/test/autotests/components-browser/rerender-multiple-roots/index.marko
index d11f27edc..cf03f9871 100644
--- a/test/autotests/components-browser/rerender-multiple-roots/index.marko
+++ b/test/autotests/components-browser/rerender-multiple-roots/index.marko
@@ -4,5 +4,5 @@ class {
}
}
-
--- in that case - i have error
+
${state.count}
+-- [${state.count}]
diff --git a/test/autotests/components-browser/rerender-multiple-roots/test.js b/test/autotests/components-browser/rerender-multiple-roots/test.js
index a40a16937..bda02db5f 100644
--- a/test/autotests/components-browser/rerender-multiple-roots/test.js
+++ b/test/autotests/components-browser/rerender-multiple-roots/test.js
@@ -1,5 +1,3 @@
-var expect = require('chai').expect;
-
// Test for issues #749 #690. Using multiple root nodes or style tag at the root would
// cause an infinite loop on rerender. This code does not perform any assertions
// but it will get caught in an infinite loop without the changes in PR #751
diff --git a/test/autotests/components-browser/rerender-same-id/index.marko b/test/autotests/components-browser/rerender-same-id/index.marko
deleted file mode 100644
index aae6c994d..000000000
--- a/test/autotests/components-browser/rerender-same-id/index.marko
+++ /dev/null
@@ -1,3 +0,0 @@
-
- ${input.label}
-
\ No newline at end of file
diff --git a/test/autotests/components-browser/rerender-same-id/component.js b/test/autotests/components-browser/rerender-same-root/component.js
similarity index 100%
rename from test/autotests/components-browser/rerender-same-id/component.js
rename to test/autotests/components-browser/rerender-same-root/component.js
diff --git a/test/autotests/components-browser/rerender-same-root/index.marko b/test/autotests/components-browser/rerender-same-root/index.marko
new file mode 100644
index 000000000..bec7fe120
--- /dev/null
+++ b/test/autotests/components-browser/rerender-same-root/index.marko
@@ -0,0 +1,3 @@
+
+ ${input.label}
+
diff --git a/test/autotests/components-browser/rerender-same-id/test.js b/test/autotests/components-browser/rerender-same-root/test.js
similarity index 70%
rename from test/autotests/components-browser/rerender-same-id/test.js
rename to test/autotests/components-browser/rerender-same-root/test.js
index 8d2c9b598..29c666d6a 100644
--- a/test/autotests/components-browser/rerender-same-id/test.js
+++ b/test/autotests/components-browser/rerender-same-root/test.js
@@ -5,12 +5,12 @@ module.exports = function(helpers) {
label: 'Foo'
});
- var oldId = component.id;
+ var rootEl = component.getEl('root');
component.input = {
label: 'Bar'
};
component.update();
- expect(component.el.id).to.equal(oldId);
-};
\ No newline at end of file
+ expect(component.getEl('root')).to.equal(rootEl);
+};
diff --git a/test/autotests/components-browser/transclusion-include-from-state/components/app-button/index.marko b/test/autotests/components-browser/transclusion-include-from-state/components/app-button/index.marko
deleted file mode 100644
index 7c90ae6e2..000000000
--- a/test/autotests/components-browser/transclusion-include-from-state/components/app-button/index.marko
+++ /dev/null
@@ -1,29 +0,0 @@
-class {
- onInput(input) {
- this.state = {
- size: input.size || 'normal',
- variant: input.variant || 'primary',
- body: input.label || input.renderBody
- };
- }
-
- // Add any other methods here
- setVariant(variant) {
- this.state.variant = variant;
- }
-
- setSize(size) {
- this.state.size = size;
- }
-
- setLabel(label) {
- this.state.label = label;
- }
-}
-
-$ var variantClassName=(state.variant !== 'primary' && 'app-button-' + state.variant)
-$ var sizeClassName=(state.size !== 'normal' && 'app-button-' + state.size)
-
-
-
-
\ No newline at end of file
diff --git a/test/autotests/components-browser/transclusion-include-from-state/index.marko b/test/autotests/components-browser/transclusion-include-from-state/index.marko
deleted file mode 100644
index 23def10ab..000000000
--- a/test/autotests/components-browser/transclusion-include-from-state/index.marko
+++ /dev/null
@@ -1,9 +0,0 @@
-class {
-
-}
-
-
\ No newline at end of file
diff --git a/test/autotests/components-browser/transclusion-include-from-state/test.js b/test/autotests/components-browser/transclusion-include-from-state/test.js
deleted file mode 100644
index bcc5ce2ad..000000000
--- a/test/autotests/components-browser/transclusion-include-from-state/test.js
+++ /dev/null
@@ -1,31 +0,0 @@
-var expect = require('chai').expect;
-
-module.exports = function(helpers) {
- var component = helpers.mount(require('./index'), {
- name: 'Frank'
- });
-
- var buttonComponent = component.getComponent('button');
- expect(buttonComponent.el.innerHTML).to.contain('Frank');
- expect(buttonComponent.el.className).to.equal('app-button app-button-small');
-
- component.input = { name: 'John '};
- component.update();
-
- expect(buttonComponent.el.innerHTML).to.contain('John');
-
- buttonComponent.setSize('large');
- buttonComponent.update();
- expect(buttonComponent.el.innerHTML).to.contain('John');
- expect(buttonComponent.el.className).to.equal('app-button app-button-large');
-
- buttonComponent.input = {
- size: 'small',
- variant: 'secondary'
- // NOTE: We aren't including renderBody() but we expect that content to be preserved
- };
- buttonComponent.update();
-
- expect(buttonComponent.el.innerHTML).to.contain('John');
- expect(buttonComponent.el.className).to.equal('app-button app-button-secondary app-button-small');
-};
\ No newline at end of file
diff --git a/test/autotests/components-browser/widget-include-ref/index.marko b/test/autotests/components-browser/widget-include-ref/index.marko
index f31c7b095..a0d8cfbdc 100644
--- a/test/autotests/components-browser/widget-include-ref/index.marko
+++ b/test/autotests/components-browser/widget-include-ref/index.marko
@@ -2,4 +2,5 @@ import barComponent from './components/app-bar';
-
\ No newline at end of file
+
+
diff --git a/test/autotests/components-browser/widget-jQuery-proxy/test.js b/test/autotests/components-browser/widget-jQuery-proxy/test.js
index cfdf1fe4c..af0665ce7 100644
--- a/test/autotests/components-browser/widget-jQuery-proxy/test.js
+++ b/test/autotests/components-browser/widget-jQuery-proxy/test.js
@@ -6,7 +6,6 @@ module.exports = function(helpers, done) {
try {
var component = helpers.mount(require('./index'), {});
- expect(component.$().attr('id')).to.equal(component.id);
expect(component.$().attr('class')).to.equal('app-jquery-proxy');
expect(component.$('#foo').html()).to.equal('foo');
expect(component.$('#fooText').html()).to.equal('fooText');
diff --git a/test/autotests/components-browser/widget-rerender-init-order/test.js b/test/autotests/components-browser/widget-rerender-init-order/test.js
index 5cc96ec70..eef53d019 100644
--- a/test/autotests/components-browser/widget-rerender-init-order/test.js
+++ b/test/autotests/components-browser/widget-rerender-init-order/test.js
@@ -7,7 +7,7 @@ module.exports = function(helpers) {
version: 0
});
- expect(window.rerenderInitOrder).to.deep.equal(['childA', 'childB', 'parent']);
+ expect(window.rerenderInitOrder).to.deep.equal(['childB', 'childA', 'parent']);
window.rerenderInitOrder = [];
@@ -15,7 +15,7 @@ module.exports = function(helpers) {
component.update();
// console.log('ACTUAL ORDER: ', window.rerenderInitOrder);
- expect(window.rerenderInitOrder).to.deep.equal(['childA', 'childB', 'parent']);
+ expect(window.rerenderInitOrder).to.deep.equal(['childB', 'childA', 'parent']);
delete window.rerenderInitOrder;
-};
\ No newline at end of file
+};
diff --git a/test/autotests/components-browser/widget-rerender-reuse-stateful/component.js b/test/autotests/components-browser/widget-rerender-reuse-stateful/component.js
deleted file mode 100644
index b8e3e8e4e..000000000
--- a/test/autotests/components-browser/widget-rerender-reuse-stateful/component.js
+++ /dev/null
@@ -1,11 +0,0 @@
-module.exports = {
- onInput: function(input) {
- this.state = {
- buttonSize: input.buttonSize || 'normal'
- };
- },
-
- setButtonSize: function(size) {
- this.setState('buttonSize', size);
- }
-};
\ No newline at end of file
diff --git a/test/autotests/components-browser/widget-rerender-reuse-stateful/components/app-stateful-button/component.js b/test/autotests/components-browser/widget-rerender-reuse-stateful/components/app-stateful-button/component.js
deleted file mode 100644
index dac980872..000000000
--- a/test/autotests/components-browser/widget-rerender-reuse-stateful/components/app-stateful-button/component.js
+++ /dev/null
@@ -1,16 +0,0 @@
-module.exports = {
- onInput: function(input) {
- this.state = {
- size: input.size || 'normal',
- label: input.label || '(no label)'
- };
- },
-
- setSize: function(newSize) {
- this.state.size = newSize;
- },
-
- setLabel: function(newLabel) {
- this.state.label = newLabel;
- }
-};
\ No newline at end of file
diff --git a/test/autotests/components-browser/widget-rerender-reuse-stateful/components/app-stateful-button/index.marko b/test/autotests/components-browser/widget-rerender-reuse-stateful/components/app-stateful-button/index.marko
deleted file mode 100644
index f81857540..000000000
--- a/test/autotests/components-browser/widget-rerender-reuse-stateful/components/app-stateful-button/index.marko
+++ /dev/null
@@ -1,4 +0,0 @@
-
- ${__filename}
- ${state.label}
-
\ No newline at end of file
diff --git a/test/autotests/components-browser/widget-rerender-reuse-stateful/components/app-stateful-button/marko-tag.json b/test/autotests/components-browser/widget-rerender-reuse-stateful/components/app-stateful-button/marko-tag.json
deleted file mode 100644
index 1937298d5..000000000
--- a/test/autotests/components-browser/widget-rerender-reuse-stateful/components/app-stateful-button/marko-tag.json
+++ /dev/null
@@ -1,19 +0,0 @@
-{
- "attributes": {
- "label": "string",
- "href": "string",
- "variant": {
- "type": "string",
- "description": "primary | secondary"
- },
- "size": {
- "type": "string",
- "description": "small | normal | large"
- },
- "class": {
- "type": "string",
- "description": "Additional CSS class names"
- },
- "*": "string"
- }
-}
\ No newline at end of file
diff --git a/test/autotests/components-browser/widget-rerender-reuse-stateful/components/hello/index.marko b/test/autotests/components-browser/widget-rerender-reuse-stateful/components/hello/index.marko
new file mode 100644
index 000000000..5482964d8
--- /dev/null
+++ b/test/autotests/components-browser/widget-rerender-reuse-stateful/components/hello/index.marko
@@ -0,0 +1,13 @@
+class {
+ onCreate() {
+ window.helloInstances.push(this);
+ }
+
+ onInput(input) {
+ this.state = {
+ name: input.name
+ }
+ }
+}
+
+Hello ${state.name}
diff --git a/test/autotests/components-browser/widget-rerender-reuse-stateful/index.marko b/test/autotests/components-browser/widget-rerender-reuse-stateful/index.marko
index f9d168d61..582b566a8 100644
--- a/test/autotests/components-browser/widget-rerender-reuse-stateful/index.marko
+++ b/test/autotests/components-browser/widget-rerender-reuse-stateful/index.marko
@@ -1,4 +1,12 @@
-
-
${state.buttonSize}
-
button2
-
\ No newline at end of file
+class {
+ onCreate() {
+ this.state = {
+ count: 0
+ };
+ }
+}
+
+
+
+
+
diff --git a/test/autotests/components-browser/widget-rerender-reuse-stateful/test.js b/test/autotests/components-browser/widget-rerender-reuse-stateful/test.js
index 6ccdf55b2..6a7dcf839 100644
--- a/test/autotests/components-browser/widget-rerender-reuse-stateful/test.js
+++ b/test/autotests/components-browser/widget-rerender-reuse-stateful/test.js
@@ -1,38 +1,35 @@
var expect = require('chai').expect;
module.exports = function(helpers) {
-
+ window.helloInstances = [];
var component = helpers.mount(require('./index'), {});
- var oldButton1Component = component.getComponent('button1');
- var oldButton2Component = component.getEl('button2').__component;
- var oldButton1El = oldButton1Component.el;
- var oldButton2El = component.getEl('button2');
+ expect(window.helloInstances.length).to.equal(2);
- expect(component.getComponent('button1').el.className).to.contain('normal');
+ var helloEls = component.getEl('root').querySelectorAll('.hello');
+ expect(helloEls[0].innerHTML).to.equal('Hello Jane');
+ expect(helloEls[1].innerHTML).to.equal('Hello John0');
- var self = component;
+ var hello1 = component.getComponent('hello1');
+ var hello2 = component.getComponent('hello2');
- self.setButtonSize('small');
- self.update();
+ component.state.count++;
+ component.update();
- var newButton1El = component.getComponent('button1').el;
- var newButton2El = component.getEl('button2');
+ // Make sure no more instances of the nested components were created
+ expect(window.helloInstances.length).to.equal(2);
- // // Both button components should be reused
- expect(component.getComponent('button1')).to.equal(oldButton1Component);
- expect(component.getEl('button2').__component).to.equal(oldButton2Component);
+ // Make sure the UI components received the new state as part of onInput()
+ expect(hello1.state.name).to.equal('Jane');
+ expect(hello2.state.name).to.equal('John1');
- expect(component.getComponent('button1').el.className).to.contain('small');
+ // Make sure the HTML elements were reused
+ var helloElsAfter = component.getEl('root').querySelectorAll('.hello');
+ expect(helloElsAfter[0]).to.equal(helloEls[0]);
+ expect(helloElsAfter[1]).to.equal(helloEls[1]);
-
- // // State changed for button1 so it should have a new el
- // // since it re-renders to update its view
- // console.log('newButton1El: ', newButton1El);
- expect(newButton1El === oldButton1El).to.equal(true);
-
- //
- // // State didn't change for button2 so it should be the same el
- expect(newButton2El).to.equal(oldButton2El);
-};
\ No newline at end of file
+ // Make sure the DOM was updated
+ expect(helloElsAfter[0].innerHTML).to.equal('Hello Jane');
+ expect(helloElsAfter[1].innerHTML).to.equal('Hello John1');
+};
diff --git a/test/autotests/components-browser/widget-stateful-reuse-widgets/component.js b/test/autotests/components-browser/widget-stateful-reuse-widgets/component.js
deleted file mode 100644
index 8d60154ca..000000000
--- a/test/autotests/components-browser/widget-stateful-reuse-widgets/component.js
+++ /dev/null
@@ -1,13 +0,0 @@
-module.exports = {
- onInput: function(input) {
- this.state = {
- buttonSize: input.buttonSize || 'normal'
- };
- },
-
- setButtonSize: function(size) {
- this.setState('buttonSize', size);
- },
- onMount: function() {
- }
-};
\ No newline at end of file
diff --git a/test/autotests/components-browser/widget-stateful-reuse-widgets/components/app-stateful-button/component.js b/test/autotests/components-browser/widget-stateful-reuse-widgets/components/app-stateful-button/component.js
deleted file mode 100644
index 42ca16331..000000000
--- a/test/autotests/components-browser/widget-stateful-reuse-widgets/components/app-stateful-button/component.js
+++ /dev/null
@@ -1,8 +0,0 @@
-module.exports = {
- onInput: function(input) {
- this.state = {
- size: input.size || 'normal',
- label: input.label || '(no label)'
- };
- }
-};
\ No newline at end of file
diff --git a/test/autotests/components-browser/widget-stateful-reuse-widgets/components/app-stateful-button/index.marko b/test/autotests/components-browser/widget-stateful-reuse-widgets/components/app-stateful-button/index.marko
deleted file mode 100644
index f81857540..000000000
--- a/test/autotests/components-browser/widget-stateful-reuse-widgets/components/app-stateful-button/index.marko
+++ /dev/null
@@ -1,4 +0,0 @@
-
- ${__filename}
- ${state.label}
-
\ No newline at end of file
diff --git a/test/autotests/components-browser/widget-stateful-reuse-widgets/components/app-stateful-button/marko-tag.json b/test/autotests/components-browser/widget-stateful-reuse-widgets/components/app-stateful-button/marko-tag.json
deleted file mode 100644
index 1937298d5..000000000
--- a/test/autotests/components-browser/widget-stateful-reuse-widgets/components/app-stateful-button/marko-tag.json
+++ /dev/null
@@ -1,19 +0,0 @@
-{
- "attributes": {
- "label": "string",
- "href": "string",
- "variant": {
- "type": "string",
- "description": "primary | secondary"
- },
- "size": {
- "type": "string",
- "description": "small | normal | large"
- },
- "class": {
- "type": "string",
- "description": "Additional CSS class names"
- },
- "*": "string"
- }
-}
\ No newline at end of file
diff --git a/test/autotests/components-browser/widget-stateful-reuse-widgets/index.marko b/test/autotests/components-browser/widget-stateful-reuse-widgets/index.marko
deleted file mode 100644
index f9d168d61..000000000
--- a/test/autotests/components-browser/widget-stateful-reuse-widgets/index.marko
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
${state.buttonSize}
-
button2
-
\ No newline at end of file
diff --git a/test/autotests/components-browser/widget-stateful-reuse-widgets/test.js b/test/autotests/components-browser/widget-stateful-reuse-widgets/test.js
deleted file mode 100644
index e3c464e45..000000000
--- a/test/autotests/components-browser/widget-stateful-reuse-widgets/test.js
+++ /dev/null
@@ -1,36 +0,0 @@
-var expect = require('chai').expect;
-
-module.exports = function(helpers) {
- var component = helpers.mount(require('./index'), {});
-
- var oldButton1Component = component.getComponent('button1');
- var oldButton2Component = component.getEl('button2').__component;
- var oldButton1El = oldButton1Component.el;
- var oldButton2El = component.getEl('button2');
-
- expect(component.getComponent('button1').el.className).to.contain('normal');
-
- var self = component;
-
- self.setButtonSize('small');
- self.update();
-
- var newButton1El = component.getComponent('button1').el;
- var newButton2El = component.getEl('button2');
-
- // // Both button components should be reused
- expect(component.getComponent('button1')).to.equal(oldButton1Component);
- expect(component.getEl('button2').__component).to.equal(oldButton2Component);
-
- expect(component.getComponent('button1').el.className).to.contain('small');
-
-
- // // State changed for button1 so it should have a new el
- // // since it re-renders to update its view
- // console.log('newButton1El: ', newButton1El);
- expect(newButton1El === oldButton1El).to.equal(true);
-
- //
- // // State didn't change for button2 so it should be the same el
- expect(newButton2El).to.equal(oldButton2El);
-};
\ No newline at end of file
diff --git a/test/autotests/components-compilation-deprecated/bind-component/expected.js b/test/autotests/components-compilation-deprecated/bind-component/expected.js
index 8c28af17b..1dc5efce5 100644
--- a/test/autotests/components-compilation-deprecated/bind-component/expected.js
+++ b/test/autotests/components-compilation-deprecated/bind-component/expected.js
@@ -8,16 +8,12 @@ var marko_template = module.exports = require("marko/src/html").t(__filename),
marko_componentType = marko_registerComponent("/marko-test$1.0.0/autotests/components-compilation-deprecated/bind-component/index", function() {
return marko_defineWidget_legacy(require("./"));
}),
- marko_rendererLegacy = legacy_helpers.r,
- marko_helpers = require("marko/src/runtime/html/helpers"),
- marko_attr = marko_helpers.a;
+ marko_rendererLegacy = legacy_helpers.r;
-function render(input, out, __component, widget) {
+function render(input, out, __component, widget, component) {
var data = input;
- out.w("
");
+ out.w("
");
}
marko_template._ = marko_rendererLegacy(render, {
diff --git a/test/autotests/components-compilation-deprecated/bind-widget/expected.js b/test/autotests/components-compilation-deprecated/bind-widget/expected.js
index 0add5bad9..16fee9562 100644
--- a/test/autotests/components-compilation-deprecated/bind-widget/expected.js
+++ b/test/autotests/components-compilation-deprecated/bind-widget/expected.js
@@ -8,16 +8,12 @@ var marko_template = module.exports = require("marko/src/html").t(__filename),
marko_componentType = marko_registerComponent("/marko-test$1.0.0/autotests/components-compilation-deprecated/bind-widget/widget", function() {
return marko_defineWidget_legacy(require("./widget"));
}),
- marko_rendererLegacy = legacy_helpers.r,
- marko_helpers = require("marko/src/runtime/html/helpers"),
- marko_attr = marko_helpers.a;
+ marko_rendererLegacy = legacy_helpers.r;
-function render(input, out, __component, widget) {
+function render(input, out, __component, widget, component) {
var data = input;
- out.w("
");
+ out.w("
");
}
marko_template._ = marko_rendererLegacy(render, {
diff --git a/test/autotests/components-compilation-deprecated/component-include-attr/expected.js b/test/autotests/components-compilation-deprecated/component-include-attr/expected.js
index 1a11c21fb..be8699cca 100644
--- a/test/autotests/components-compilation-deprecated/component-include-attr/expected.js
+++ b/test/autotests/components-compilation-deprecated/component-include-attr/expected.js
@@ -9,23 +9,30 @@ var marko_template = module.exports = require("marko/src/html").t(__filename),
return marko_defineWidget_legacy(require("./"));
}),
marko_rendererLegacy = legacy_helpers.r,
+ marko_renderComponent = require("marko/src/components/taglib/helpers/renderComponent"),
marko_helpers = require("marko/src/runtime/html/helpers"),
marko_loadTag = marko_helpers.t,
- include_tag = marko_loadTag(require("marko/src/components/taglib/include-tag")),
- marko_attr = marko_helpers.a;
+ include_tag = marko_loadTag(require("marko/src/taglibs/core/include-tag")),
+ _preserve_tag = marko_loadTag(require("marko/src/components/taglib/preserve-tag"));
-function render(input, out, __component, widget) {
+function render(input, out, __component, widget, component) {
var data = input;
- out.w("Header ");
+ out.w("
Header ");
- include_tag({
- _target: __component.b,
- _elId: __component.elId(0)
+ var __key3 = __component.___nextKey("2");
+
+ out.w("
");
+
+ _preserve_tag({
+ bodyOnly: true,
+ if: !__component.b,
+ key: __key3,
+ renderBody: function renderBody(out) {
+ marko_renderComponent(include_tag, {
+ _target: __component.b
+ }, out, "4");
+ }
}, out);
out.w("
");
@@ -33,8 +40,7 @@ function render(input, out, __component, widget) {
marko_template._ = marko_rendererLegacy(render, {
split: true,
- type: marko_componentType,
- body: 0
+ type: marko_componentType
});
marko_template.meta = {
@@ -49,6 +55,7 @@ marko_template.meta = {
}
],
tags: [
- "marko/src/components/taglib/include-tag"
+ "marko/src/taglibs/core/include-tag",
+ "marko/src/components/taglib/preserve-tag"
]
};
diff --git a/test/autotests/components-compilation-deprecated/component-template-entry/expected.js b/test/autotests/components-compilation-deprecated/component-template-entry/expected.js
index f407a1a04..e83cfb82c 100644
--- a/test/autotests/components-compilation-deprecated/component-template-entry/expected.js
+++ b/test/autotests/components-compilation-deprecated/component-template-entry/expected.js
@@ -3,31 +3,36 @@
var marko_template = module.exports = require("marko/src/html").t(__filename),
components_helpers = require("marko/src/components/helpers"),
marko_registerComponent = components_helpers.rc,
- legacy_helpers = require("marko/src/components/legacy/helpers"),
- marko_defineWidget_legacy = legacy_helpers.w,
- marko_componentType = marko_registerComponent("/marko-test$1.0.0/autotests/components-compilation-deprecated/component-template-entry/component", function() {
- return marko_defineWidget_legacy(require("./component"));
+ marko_componentType = marko_registerComponent("/marko-test$1.0.0/autotests/components-compilation-deprecated/component-template-entry/index.marko", function() {
+ return module.exports;
}),
marko_component = require("./component"),
- marko_rendererLegacy = legacy_helpers.r,
- marko_helpers = require("marko/src/runtime/html/helpers"),
- marko_attr = marko_helpers.a;
+ marko_renderer = components_helpers.r,
+ marko_defineComponent = components_helpers.c,
+ legacy_helpers = require("marko/src/components/legacy/helpers"),
+ marko_defineWidget_legacy = legacy_helpers.w,
+ marko_componentType2 = marko_registerComponent("/marko-test$1.0.0/autotests/components-compilation-deprecated/component-template-entry/component", function() {
+ return marko_defineWidget_legacy(require("./component"));
+ }),
+ marko_rendererLegacy = legacy_helpers.r;
-function render(input, out, __component, widget) {
+function render(input, out, __component, component, state, __component, widget, component) {
var data = input;
- out.w("
");
+ out.w("
");
}
marko_template._ = marko_rendererLegacy(render, {
split: true,
- type: marko_componentType
- }, marko_component);
+ type: marko_componentType2
+ });
marko_template.meta = {
deps: [
+ {
+ type: "require",
+ path: "./"
+ },
{
type: "require",
path: "./component"
diff --git a/test/autotests/components-compilation-deprecated/widget-types/expected.js b/test/autotests/components-compilation-deprecated/widget-types/expected.js
index df1ce7369..526558488 100644
--- a/test/autotests/components-compilation-deprecated/widget-types/expected.js
+++ b/test/autotests/components-compilation-deprecated/widget-types/expected.js
@@ -13,18 +13,14 @@ var marko_template = module.exports = require("marko/src/html").t(__filename),
mobile: marko_registerComponent("/marko-test$1.0.0/autotests/components-compilation-deprecated/widget-types/component-mobile", function() {
return marko_defineWidget_legacy(require("./component-mobile"));
})
- },
- marko_helpers = require("marko/src/runtime/html/helpers"),
- marko_attr = marko_helpers.a;
+ };
-function render(input, out, __component, widget) {
+function render(input, out, __component, widget, component) {
var data = input;
__component.t(marko_componentTypes[data.isMobile ? "default" : "mobile"]);
- out.w("
");
+ out.w("
");
}
marko_template._ = marko_rendererLegacy(render, {});
diff --git a/test/autotests/components-compilation-vdom/boundary-el-if-el/components/my-component/index.marko b/test/autotests/components-compilation-vdom/boundary-el-if-el/components/my-component/index.marko
new file mode 100644
index 000000000..e69de29bb
diff --git a/test/autotests/components-compilation-vdom/boundary-el-if-el/expected.js b/test/autotests/components-compilation-vdom/boundary-el-if-el/expected.js
new file mode 100644
index 000000000..69ba27647
--- /dev/null
+++ b/test/autotests/components-compilation-vdom/boundary-el-if-el/expected.js
@@ -0,0 +1,51 @@
+"use strict";
+
+var marko_template = module.exports = require("marko/src/vdom").t(__filename),
+ marko_component = {},
+ components_helpers = require("marko/src/components/helpers"),
+ marko_registerComponent = components_helpers.rc,
+ marko_componentType = marko_registerComponent("/marko-test$1.0.0/autotests/components-compilation-vdom/boundary-el-if-el/index.marko", function() {
+ return module.exports;
+ }),
+ marko_renderer = components_helpers.r,
+ marko_defineComponent = components_helpers.c,
+ marko_helpers = require("marko/src/runtime/vdom/helpers"),
+ marko_createElement = marko_helpers.e,
+ marko_const = marko_helpers.const,
+ marko_const_nextId = marko_const("441cd5"),
+ marko_node0 = marko_createElement("H1", null, "0", null, 0, 0, {
+ i: marko_const_nextId()
+ }),
+ marko_node1 = marko_createElement("DIV", null, "2", null, 0, 0, {
+ i: marko_const_nextId()
+ }),
+ marko_node2 = marko_createElement("SPAN", null, "1", null, 0, 0, {
+ i: marko_const_nextId()
+ });
+
+function render(input, out, __component, component, state) {
+ var data = input;
+
+ out.n(marko_node0, component);
+
+ if (input.someCondition) {
+ out.n(marko_node2, component);
+ }
+
+ out.n(marko_node1, component);
+}
+
+marko_template._ = marko_renderer(render, {
+ type: marko_componentType
+ }, marko_component);
+
+marko_template.Component = marko_defineComponent(marko_component, marko_template._);
+
+marko_template.meta = {
+ deps: [
+ {
+ type: "require",
+ path: "./"
+ }
+ ]
+ };
diff --git a/test/autotests/components-compilation-vdom/boundary-el-if-el/index.marko b/test/autotests/components-compilation-vdom/boundary-el-if-el/index.marko
new file mode 100644
index 000000000..2cbc93319
--- /dev/null
+++ b/test/autotests/components-compilation-vdom/boundary-el-if-el/index.marko
@@ -0,0 +1,7 @@
+class {}
+
+
+
+
+
+
diff --git a/test/autotests/components-compilation-vdom/boundary-el-if/expected.js b/test/autotests/components-compilation-vdom/boundary-el-if/expected.js
new file mode 100644
index 000000000..ca09539ef
--- /dev/null
+++ b/test/autotests/components-compilation-vdom/boundary-el-if/expected.js
@@ -0,0 +1,46 @@
+"use strict";
+
+var marko_template = module.exports = require("marko/src/vdom").t(__filename),
+ marko_component = {},
+ components_helpers = require("marko/src/components/helpers"),
+ marko_registerComponent = components_helpers.rc,
+ marko_componentType = marko_registerComponent("/marko-test$1.0.0/autotests/components-compilation-vdom/boundary-el-if/index.marko", function() {
+ return module.exports;
+ }),
+ marko_renderer = components_helpers.r,
+ marko_defineComponent = components_helpers.c,
+ marko_helpers = require("marko/src/runtime/vdom/helpers"),
+ marko_createElement = marko_helpers.e,
+ marko_const = marko_helpers.const,
+ marko_const_nextId = marko_const("ee4c02"),
+ marko_node0 = marko_createElement("H1", null, "0", null, 0, 0, {
+ i: marko_const_nextId()
+ }),
+ marko_node1 = marko_createElement("SPAN", null, "1", null, 0, 0, {
+ i: marko_const_nextId()
+ });
+
+function render(input, out, __component, component, state) {
+ var data = input;
+
+ out.n(marko_node0, component);
+
+ if (input.someCondition) {
+ out.n(marko_node1, component);
+ }
+}
+
+marko_template._ = marko_renderer(render, {
+ type: marko_componentType
+ }, marko_component);
+
+marko_template.Component = marko_defineComponent(marko_component, marko_template._);
+
+marko_template.meta = {
+ deps: [
+ {
+ type: "require",
+ path: "./"
+ }
+ ]
+ };
diff --git a/test/autotests/components-compilation-vdom/boundary-el-if/index.marko b/test/autotests/components-compilation-vdom/boundary-el-if/index.marko
new file mode 100644
index 000000000..cd6729875
--- /dev/null
+++ b/test/autotests/components-compilation-vdom/boundary-el-if/index.marko
@@ -0,0 +1,6 @@
+class {}
+
+
+
+
+
diff --git a/test/autotests/components-compilation-vdom/boundary-el-no-output-tag/components/test-no-output/marko-tag.json b/test/autotests/components-compilation-vdom/boundary-el-no-output-tag/components/test-no-output/marko-tag.json
new file mode 100644
index 000000000..1dc5f764d
--- /dev/null
+++ b/test/autotests/components-compilation-vdom/boundary-el-no-output-tag/components/test-no-output/marko-tag.json
@@ -0,0 +1,4 @@
+{
+ "renderer": "./renderer",
+ "noOutput": true
+}
\ No newline at end of file
diff --git a/test/autotests/components-compilation-vdom/boundary-el-no-output-tag/components/test-no-output/renderer.js b/test/autotests/components-compilation-vdom/boundary-el-no-output-tag/components/test-no-output/renderer.js
new file mode 100644
index 000000000..2d87648d8
--- /dev/null
+++ b/test/autotests/components-compilation-vdom/boundary-el-no-output-tag/components/test-no-output/renderer.js
@@ -0,0 +1,3 @@
+module.exports = function(input, out) {
+
+};
\ No newline at end of file
diff --git a/test/autotests/components-compilation-vdom/boundary-el-no-output-tag/expected.js b/test/autotests/components-compilation-vdom/boundary-el-no-output-tag/expected.js
new file mode 100644
index 000000000..7b806c3a3
--- /dev/null
+++ b/test/autotests/components-compilation-vdom/boundary-el-no-output-tag/expected.js
@@ -0,0 +1,46 @@
+"use strict";
+
+var marko_template = module.exports = require("marko/src/vdom").t(__filename),
+ marko_component = {},
+ components_helpers = require("marko/src/components/helpers"),
+ marko_registerComponent = components_helpers.rc,
+ marko_componentType = marko_registerComponent("/marko-test$1.0.0/autotests/components-compilation-vdom/boundary-el-no-output-tag/index.marko", function() {
+ return module.exports;
+ }),
+ marko_renderer = components_helpers.r,
+ marko_defineComponent = components_helpers.c,
+ marko_helpers = require("marko/src/runtime/vdom/helpers"),
+ marko_loadTag = marko_helpers.t,
+ test_no_output_tag = marko_loadTag(require("./components/test-no-output/renderer")),
+ marko_createElement = marko_helpers.e,
+ marko_const = marko_helpers.const,
+ marko_const_nextId = marko_const("4529ef"),
+ marko_node0 = marko_createElement("DIV", null, "0", null, 0, 0, {
+ i: marko_const_nextId()
+ });
+
+function render(input, out, __component, component, state) {
+ var data = input;
+
+ out.n(marko_node0, component);
+
+ test_no_output_tag({}, out);
+}
+
+marko_template._ = marko_renderer(render, {
+ type: marko_componentType
+ }, marko_component);
+
+marko_template.Component = marko_defineComponent(marko_component, marko_template._);
+
+marko_template.meta = {
+ deps: [
+ {
+ type: "require",
+ path: "./"
+ }
+ ],
+ tags: [
+ "./components/test-no-output/renderer"
+ ]
+ };
diff --git a/test/autotests/components-compilation-vdom/boundary-el-no-output-tag/index.marko b/test/autotests/components-compilation-vdom/boundary-el-no-output-tag/index.marko
new file mode 100644
index 000000000..2dee9522a
--- /dev/null
+++ b/test/autotests/components-compilation-vdom/boundary-el-no-output-tag/index.marko
@@ -0,0 +1,4 @@
+class {}
+
+
+
diff --git a/test/autotests/components-compilation-vdom/boundary-html-tag/expected.js b/test/autotests/components-compilation-vdom/boundary-html-tag/expected.js
new file mode 100644
index 000000000..2251795e8
--- /dev/null
+++ b/test/autotests/components-compilation-vdom/boundary-html-tag/expected.js
@@ -0,0 +1,71 @@
+"use strict";
+
+var marko_template = module.exports = require("marko/src/vdom").t(__filename),
+ marko_component = {},
+ components_helpers = require("marko/src/components/helpers"),
+ marko_registerComponent = components_helpers.rc,
+ marko_componentType = marko_registerComponent("/marko-test$1.0.0/autotests/components-compilation-vdom/boundary-html-tag/index.marko", function() {
+ return module.exports;
+ }),
+ marko_renderer = components_helpers.r,
+ marko_defineComponent = components_helpers.c,
+ marko_renderComponent = require("marko/src/components/taglib/helpers/renderComponent"),
+ marko_helpers = require("marko/src/runtime/vdom/helpers"),
+ marko_loadTag = marko_helpers.t,
+ component_globals_tag = marko_loadTag(require("marko/src/components/taglib/component-globals-tag")),
+ init_components_tag = marko_loadTag(require("marko/src/components/taglib/init-components-tag")),
+ await_reorderer_tag = marko_loadTag(require("marko/src/taglibs/async/await-reorderer-tag")),
+ marko_createElement = marko_helpers.e,
+ marko_const = marko_helpers.const,
+ marko_const_nextId = marko_const("5b1bc3"),
+ marko_node0 = marko_createElement("HEAD", null, "1", null, 1, 0, {
+ i: marko_const_nextId()
+ })
+ .e("TITLE", null, "2", null, 1)
+ .t("Hello"),
+ marko_node1 = marko_createElement("H1", null, "4", null, 1, 0, {
+ i: marko_const_nextId()
+ })
+ .t("Hello");
+
+function render(input, out, __component, component, state) {
+ var data = input;
+
+ out.be("HTML", null, "0", component);
+
+ out.n(marko_node0, component);
+
+ out.be("BODY", null, "3", component);
+
+ component_globals_tag({}, out);
+
+ out.n(marko_node1, component);
+
+ init_components_tag({}, out);
+
+ marko_renderComponent(await_reorderer_tag, {}, out, "5");
+
+ out.ee();
+
+ out.ee();
+}
+
+marko_template._ = marko_renderer(render, {
+ type: marko_componentType
+ }, marko_component);
+
+marko_template.Component = marko_defineComponent(marko_component, marko_template._);
+
+marko_template.meta = {
+ deps: [
+ {
+ type: "require",
+ path: "./"
+ }
+ ],
+ tags: [
+ "marko/src/components/taglib/component-globals-tag",
+ "marko/src/components/taglib/init-components-tag",
+ "marko/src/taglibs/async/await-reorderer-tag"
+ ]
+ };
diff --git a/test/autotests/components-compilation-vdom/boundary-html-tag/index.marko b/test/autotests/components-compilation-vdom/boundary-html-tag/index.marko
new file mode 100644
index 000000000..f2353b0c6
--- /dev/null
+++ b/test/autotests/components-compilation-vdom/boundary-html-tag/index.marko
@@ -0,0 +1,9 @@
+class {}
+
+
+
Hello
+
+
+
Hello
+
+
diff --git a/test/autotests/components-compilation-vdom/boundary-if-el/expected.js b/test/autotests/components-compilation-vdom/boundary-if-el/expected.js
new file mode 100644
index 000000000..0ff64302f
--- /dev/null
+++ b/test/autotests/components-compilation-vdom/boundary-if-el/expected.js
@@ -0,0 +1,46 @@
+"use strict";
+
+var marko_template = module.exports = require("marko/src/vdom").t(__filename),
+ marko_component = {},
+ components_helpers = require("marko/src/components/helpers"),
+ marko_registerComponent = components_helpers.rc,
+ marko_componentType = marko_registerComponent("/marko-test$1.0.0/autotests/components-compilation-vdom/boundary-if-el/index.marko", function() {
+ return module.exports;
+ }),
+ marko_renderer = components_helpers.r,
+ marko_defineComponent = components_helpers.c,
+ marko_helpers = require("marko/src/runtime/vdom/helpers"),
+ marko_createElement = marko_helpers.e,
+ marko_const = marko_helpers.const,
+ marko_const_nextId = marko_const("5854ef"),
+ marko_node0 = marko_createElement("H1", null, "1", null, 0, 0, {
+ i: marko_const_nextId()
+ }),
+ marko_node1 = marko_createElement("SPAN", null, "0", null, 0, 0, {
+ i: marko_const_nextId()
+ });
+
+function render(input, out, __component, component, state) {
+ var data = input;
+
+ if (input.someCondition) {
+ out.n(marko_node1, component);
+ }
+
+ out.n(marko_node0, component);
+}
+
+marko_template._ = marko_renderer(render, {
+ type: marko_componentType
+ }, marko_component);
+
+marko_template.Component = marko_defineComponent(marko_component, marko_template._);
+
+marko_template.meta = {
+ deps: [
+ {
+ type: "require",
+ path: "./"
+ }
+ ]
+ };
diff --git a/test/autotests/components-compilation-vdom/boundary-if-el/index.marko b/test/autotests/components-compilation-vdom/boundary-if-el/index.marko
new file mode 100644
index 000000000..a47f7c3e9
--- /dev/null
+++ b/test/autotests/components-compilation-vdom/boundary-if-el/index.marko
@@ -0,0 +1,6 @@
+class {}
+
+
+
+
+
diff --git a/test/autotests/components-compilation-vdom/boundary-if-root/components/my-component/index.marko b/test/autotests/components-compilation-vdom/boundary-if-root/components/my-component/index.marko
new file mode 100644
index 000000000..e69de29bb
diff --git a/test/autotests/components-compilation-vdom/boundary-if-root/expected.js b/test/autotests/components-compilation-vdom/boundary-if-root/expected.js
new file mode 100644
index 000000000..ab8c6ed96
--- /dev/null
+++ b/test/autotests/components-compilation-vdom/boundary-if-root/expected.js
@@ -0,0 +1,41 @@
+"use strict";
+
+var marko_template = module.exports = require("marko/src/vdom").t(__filename),
+ marko_component = {},
+ components_helpers = require("marko/src/components/helpers"),
+ marko_registerComponent = components_helpers.rc,
+ marko_componentType = marko_registerComponent("/marko-test$1.0.0/autotests/components-compilation-vdom/boundary-if-root/index.marko", function() {
+ return module.exports;
+ }),
+ marko_renderer = components_helpers.r,
+ marko_defineComponent = components_helpers.c,
+ marko_helpers = require("marko/src/runtime/vdom/helpers"),
+ marko_createElement = marko_helpers.e,
+ marko_const = marko_helpers.const,
+ marko_const_nextId = marko_const("0ec49e"),
+ marko_node0 = marko_createElement("DIV", null, "0", null, 0, 0, {
+ i: marko_const_nextId()
+ });
+
+function render(input, out, __component, component, state) {
+ var data = input;
+
+ if (input.someCondition) {
+ out.n(marko_node0, component);
+ }
+}
+
+marko_template._ = marko_renderer(render, {
+ type: marko_componentType
+ }, marko_component);
+
+marko_template.Component = marko_defineComponent(marko_component, marko_template._);
+
+marko_template.meta = {
+ deps: [
+ {
+ type: "require",
+ path: "./"
+ }
+ ]
+ };
diff --git a/test/autotests/components-compilation-vdom/boundary-if-root/index.marko b/test/autotests/components-compilation-vdom/boundary-if-root/index.marko
new file mode 100644
index 000000000..1f11313dc
--- /dev/null
+++ b/test/autotests/components-compilation-vdom/boundary-if-root/index.marko
@@ -0,0 +1,5 @@
+class {}
+
+
+
+
diff --git a/test/autotests/components-compilation-vdom/boundary-multi-root-html-el-custom-tag-no-key/components/my-component/index.marko b/test/autotests/components-compilation-vdom/boundary-multi-root-html-el-custom-tag-no-key/components/my-component/index.marko
new file mode 100644
index 000000000..e69de29bb
diff --git a/test/autotests/components-compilation-vdom/boundary-multi-root-html-el-custom-tag-no-key/expected.js b/test/autotests/components-compilation-vdom/boundary-multi-root-html-el-custom-tag-no-key/expected.js
new file mode 100644
index 000000000..7afe5ff3e
--- /dev/null
+++ b/test/autotests/components-compilation-vdom/boundary-multi-root-html-el-custom-tag-no-key/expected.js
@@ -0,0 +1,46 @@
+"use strict";
+
+var marko_template = module.exports = require("marko/src/vdom").t(__filename),
+ marko_component = {},
+ components_helpers = require("marko/src/components/helpers"),
+ marko_registerComponent = components_helpers.rc,
+ marko_componentType = marko_registerComponent("/marko-test$1.0.0/autotests/components-compilation-vdom/boundary-multi-root-html-el-custom-tag-no-key/index.marko", function() {
+ return module.exports;
+ }),
+ marko_renderer = components_helpers.r,
+ marko_defineComponent = components_helpers.c,
+ marko_keyAttr = require("marko/src/components/taglib/helpers/markoKeyAttr"),
+ marko_renderComponent = require("marko/src/components/taglib/helpers/renderComponent"),
+ marko_loadTemplate = require("marko/src/runtime/helper-loadTemplate"),
+ my_component_template = marko_loadTemplate(require.resolve("./components/my-component")),
+ marko_helpers = require("marko/src/runtime/vdom/helpers"),
+ marko_loadTag = marko_helpers.t,
+ my_component_tag = marko_loadTag(my_component_template);
+
+function render(input, out, __component, component, state) {
+ var data = input;
+
+ out.e("H1", {
+ "data-marko-key": marko_keyAttr(input.myStartKey, __component)
+ }, input.myStartKey, component, 0);
+
+ marko_renderComponent(my_component_tag, {}, out, "0");
+}
+
+marko_template._ = marko_renderer(render, {
+ type: marko_componentType
+ }, marko_component);
+
+marko_template.Component = marko_defineComponent(marko_component, marko_template._);
+
+marko_template.meta = {
+ deps: [
+ {
+ type: "require",
+ path: "./"
+ }
+ ],
+ tags: [
+ "./components/my-component"
+ ]
+ };
diff --git a/test/autotests/components-compilation-vdom/boundary-multi-root-html-el-custom-tag-no-key/index.marko b/test/autotests/components-compilation-vdom/boundary-multi-root-html-el-custom-tag-no-key/index.marko
new file mode 100644
index 000000000..76eda8c84
--- /dev/null
+++ b/test/autotests/components-compilation-vdom/boundary-multi-root-html-el-custom-tag-no-key/index.marko
@@ -0,0 +1,3 @@
+class {}
+
+
diff --git a/test/autotests/components-compilation-vdom/boundary-multi-root-html-el-id-custom-tag-key/components/my-component/index.marko b/test/autotests/components-compilation-vdom/boundary-multi-root-html-el-id-custom-tag-key/components/my-component/index.marko
new file mode 100644
index 000000000..e69de29bb
diff --git a/test/autotests/components-compilation-vdom/boundary-multi-root-html-el-id-custom-tag-key/expected.js b/test/autotests/components-compilation-vdom/boundary-multi-root-html-el-id-custom-tag-key/expected.js
new file mode 100644
index 000000000..9c6525237
--- /dev/null
+++ b/test/autotests/components-compilation-vdom/boundary-multi-root-html-el-id-custom-tag-key/expected.js
@@ -0,0 +1,51 @@
+"use strict";
+
+var marko_template = module.exports = require("marko/src/vdom").t(__filename),
+ marko_component = {},
+ components_helpers = require("marko/src/components/helpers"),
+ marko_registerComponent = components_helpers.rc,
+ marko_componentType = marko_registerComponent("/marko-test$1.0.0/autotests/components-compilation-vdom/boundary-multi-root-html-el-id-custom-tag-key/index.marko", function() {
+ return module.exports;
+ }),
+ marko_renderer = components_helpers.r,
+ marko_defineComponent = components_helpers.c,
+ marko_renderComponent = require("marko/src/components/taglib/helpers/renderComponent"),
+ marko_loadTemplate = require("marko/src/runtime/helper-loadTemplate"),
+ my_component_template = marko_loadTemplate(require.resolve("./components/my-component")),
+ marko_helpers = require("marko/src/runtime/vdom/helpers"),
+ marko_loadTag = marko_helpers.t,
+ my_component_tag = marko_loadTag(my_component_template),
+ marko_createElement = marko_helpers.e,
+ marko_const = marko_helpers.const,
+ marko_const_nextId = marko_const("0a6612"),
+ marko_node0 = marko_createElement("H1", {
+ id: "myStart"
+ }, "0", null, 0, 0, {
+ i: marko_const_nextId()
+ });
+
+function render(input, out, __component, component, state) {
+ var data = input;
+
+ out.n(marko_node0, component);
+
+ marko_renderComponent(my_component_tag, {}, out, "myEnd");
+}
+
+marko_template._ = marko_renderer(render, {
+ type: marko_componentType
+ }, marko_component);
+
+marko_template.Component = marko_defineComponent(marko_component, marko_template._);
+
+marko_template.meta = {
+ deps: [
+ {
+ type: "require",
+ path: "./"
+ }
+ ],
+ tags: [
+ "./components/my-component"
+ ]
+ };
diff --git a/test/autotests/components-compilation-vdom/boundary-multi-root-html-el-id-custom-tag-key/index.marko b/test/autotests/components-compilation-vdom/boundary-multi-root-html-el-id-custom-tag-key/index.marko
new file mode 100644
index 000000000..561428614
--- /dev/null
+++ b/test/autotests/components-compilation-vdom/boundary-multi-root-html-el-id-custom-tag-key/index.marko
@@ -0,0 +1,3 @@
+class {}
+
+
diff --git a/test/autotests/components-compilation-vdom/boundary-multi-root-html-els-ids-dynamic/expected.js b/test/autotests/components-compilation-vdom/boundary-multi-root-html-els-ids-dynamic/expected.js
new file mode 100644
index 000000000..fa9938877
--- /dev/null
+++ b/test/autotests/components-compilation-vdom/boundary-multi-root-html-els-ids-dynamic/expected.js
@@ -0,0 +1,38 @@
+"use strict";
+
+var marko_template = module.exports = require("marko/src/vdom").t(__filename),
+ marko_component = {},
+ components_helpers = require("marko/src/components/helpers"),
+ marko_registerComponent = components_helpers.rc,
+ marko_componentType = marko_registerComponent("/marko-test$1.0.0/autotests/components-compilation-vdom/boundary-multi-root-html-els-ids-dynamic/index.marko", function() {
+ return module.exports;
+ }),
+ marko_renderer = components_helpers.r,
+ marko_defineComponent = components_helpers.c;
+
+function render(input, out, __component, component, state) {
+ var data = input;
+
+ out.e("H1", {
+ id: input.myStart
+ }, "0", component, 0, 4);
+
+ out.e("DIV", {
+ id: input.myEnd
+ }, "1", component, 0, 4);
+}
+
+marko_template._ = marko_renderer(render, {
+ type: marko_componentType
+ }, marko_component);
+
+marko_template.Component = marko_defineComponent(marko_component, marko_template._);
+
+marko_template.meta = {
+ deps: [
+ {
+ type: "require",
+ path: "./"
+ }
+ ]
+ };
diff --git a/test/autotests/components-compilation-vdom/boundary-multi-root-html-els-ids-dynamic/index.marko b/test/autotests/components-compilation-vdom/boundary-multi-root-html-els-ids-dynamic/index.marko
new file mode 100644
index 000000000..b74e6c740
--- /dev/null
+++ b/test/autotests/components-compilation-vdom/boundary-multi-root-html-els-ids-dynamic/index.marko
@@ -0,0 +1,3 @@
+class {}
+