diff --git a/CHANGELOG.md b/CHANGELOG.md index dd07cf9..af6ab0c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ * Don't let `Hawkejs.doNextSync(promise)` swallow errors * Use methods for getting (and proxy traps for setting) variables on the `Variables` class * Refactor variable access in compiled template code +* Only clone certain variables once, when they are initially set ## 2.3.19 (2024-04-13) diff --git a/lib/core/base.js b/lib/core/base.js index 457874a..2e9a543 100644 --- a/lib/core/base.js +++ b/lib/core/base.js @@ -474,7 +474,6 @@ Hawkejs.CONSTRUCTED = Symbol('constructed'); Hawkejs.SERIALIZING = Symbol('serializing'); Hawkejs.VARIABLES = Symbol('variables'); Hawkejs.PRE_TASKS = Symbol('pre_tasks'); -Hawkejs.PRE_CLONE = Symbol('pre_clone'); Hawkejs.RC_CACHE = Symbol('rc_cache'); Hawkejs.RESULT = Symbol('result'); Hawkejs.BLOCK = Symbol('block'); diff --git a/lib/core/renderer.js b/lib/core/renderer.js index b4e171e..68ffbac 100644 --- a/lib/core/renderer.js +++ b/lib/core/renderer.js @@ -8,7 +8,7 @@ const TASK_GROUP = Symbol('task_group'), * * @author Jelle De Loecker * @since 1.0.0 - * @version 2.2.12 + * @version 2.4.0 * * @param {Hawkejs} hawkejs */ @@ -44,9 +44,6 @@ const Renderer = Fn.inherits('Hawkejs.Base', function Renderer(hawkejs) { // Templates to render this.queued_templates = []; - // The clone symbol - this.clone_symbol = Symbol('clone'); - // Created dialogs this.dialogs = []; @@ -205,6 +202,19 @@ Renderer.enforceRootProperty(function expose_to_scene(value) { return Hawkejs.Variables.cast(value, this); }); +/** + * The weakmap used for cloning variables + * + * @author Jelle De Loecker + * @since 2.4.0 + * @version 2.4.0 + * + * @type {WeakMap} + */ +Renderer.enforceRootProperty(function weakmap_for_cloning(value) { + return value || new WeakMap(); +}); + /** * Items to preload * @@ -821,7 +831,7 @@ Renderer.setMethod(function toJSON() { * * @author Jelle De Loecker * @since 2.2.11 - * @version 2.3.16 + * @version 2.4.0 */ Renderer.setMethod(function _prepareClone(wm, custom_method) { @@ -832,8 +842,8 @@ Renderer.setMethod(function _prepareClone(wm, custom_method) { let blocks = this.blocks._prepareClone(this, wm, custom_method); const result = { - variables : this._prepareVariables(this.variables, wm, custom_method), - expose_to_scene : this._prepareVariables(this.expose_to_scene, wm, custom_method), + variables : this.variables, + expose_to_scene : this.expose_to_scene, request : this.request, blocks : blocks, page_title : this.page_title, @@ -945,58 +955,14 @@ Renderer.setMethod(function createSubRenderer() { * * @author Jelle De Loecker * @since 2.0.0 - * @version 2.2.11 + * @version 2.4.0 * * @param {Object} variables * - * @return {Object} + * @return {Hawkejs.Variables} */ Renderer.setMethod(function prepareVariables(variables) { - return this._prepareVariables(variables); -}); - -/** - * Prepare variables - * - * @author Jelle De Loecker - * @since 2.2.11 - * @version 2.2.14 - * - * @param {Object} variables - * @param {WeakMap} wm - * - * @return {Object} - */ -Renderer.setMethod(function _prepareVariables(variables, wm, custom_method) { - - if (!variables) { - return null; - } - - variables = Hawkejs.Variables.cast(variables, this); - - let clone = variables.getExistingCloneIfValid(Hawkejs.PRE_CLONE); - - if (clone) { - return clone; - } - - clone = variables.getExistingCloneIfValid(this.clone_symbol); - - if (clone) { - return clone; - } - - if (!custom_method) { - custom_method = 'toHawkejs'; - } - - clone = Bound.JSON.clone(variables, custom_method, [this], wm); - - variables[this.clone_symbol] = clone; - clone[this.clone_symbol] = clone; - - return clone; + return Hawkejs.Variables.cast(variables, this); }); /** @@ -1242,7 +1208,7 @@ Renderer.setMethod(function serializeForClientSideRender() { * * @author Jelle De Loecker * @since 2.0.0 - * @version 2.3.15 + * @version 2.4.0 * * @param {Hawkejs.Templates} templates * @param {Object} variables @@ -1266,7 +1232,7 @@ Renderer.setMethod(function renderTemplate(templates, variables, main_name_modif } if (variables) { - templates.variables = variables; + templates.variables = Hawkejs.Variables.cast(variables, this); } // This still has to remain async for now @@ -1277,12 +1243,6 @@ Renderer.setMethod(function renderTemplate(templates, variables, main_name_modif return next(); } - that.variables = that.prepareVariables(that.variables); - - if (templates.variables) { - that.prepareVariables(templates.variables); - } - // Clone the variables, use the toHawkejs method if available let variables = templates.variables || that.variables; @@ -2242,7 +2202,7 @@ Renderer.setMethod(function applyElementOptions(element, options, for_sync_rende if (options.variables) { if (!element[Hawkejs.VARIABLES]) { - element[Hawkejs.VARIABLES] = this.prepareVariables({}); + element[Hawkejs.VARIABLES] = this.prepareVariables(); } for (i = 0; i < options.variables.length; i++) { @@ -4017,7 +3977,7 @@ Renderer.setCommand(function set(name, value) { return this.variables.get(name); } - this.variables.set(name, value); + this.variables.setShouldTransform(name, value); }); /** @@ -4112,7 +4072,7 @@ Renderer.setCommand(function expose(name, value) { return; } - this.expose_to_scene.set(name, value); + this.expose_to_scene.setShouldTransform(name, value); }); /** @@ -4137,7 +4097,7 @@ Renderer.setCommand(function serverVar(name, value) { return this.server_variables.get(name); } - this.server_variables.set(name, value); + this.server_variables.setShouldTransform(name, value); }); /** diff --git a/lib/core/variables.js b/lib/core/variables.js index 1ff4be3..bc26958 100644 --- a/lib/core/variables.js +++ b/lib/core/variables.js @@ -54,41 +54,27 @@ const TRAPS = { * @version 2.0.0 */ const Variables = Fn.inherits('Hawkejs.Base', function Variables(renderer, variables) { - this[Hawkejs.PRE_CLONE] = null; - this[renderer.clone_symbol] = null; this[PARENT] = null; this[RENDERER] = renderer; this[TRAPLESS] = this; - this[VALUES] = mapify(variables); -}); - -/** - * Turn the given input into a map - * - * @author Jelle De Loecker - * @since 2.4.0 - * @version 2.4.0 - * - * @param {Object|Map|Variables} input - * - * @return {Map} - */ -function mapify(input) { - if (!input) { - return new Map(); - } - - if (input instanceof Map) { - return new Map(input); - } + if (variables && typeof variables == 'object') { + if (variables instanceof Map) { + this[VALUES] = new Map(variables); + } else if (variables instanceof Variables) { + this[VALUES] = new Map(variables[TRAPLESS][VALUES]); + this[PARENT] = variables; + } else { + this[VALUES] = new Map(); - if (input instanceof Variables) { - return mapify(input[TRAPLESS][VALUES]); + for (let key in variables) { + this.setShouldTransform(key, variables[key]); + } + } + } else { + this[VALUES] = new Map(); } - - return new Map(Object.entries(input)); -} +}); /** * Make sure the result is a valid Variables instance @@ -221,7 +207,8 @@ Variables.setMethod(function setEphemeralGetter(key, getter) { }); /** - * Set a specific variable from a template + * Set a variable by name. + * The variable will be stored as is. * * @author Jelle De Loecker * @since 2.4.0 @@ -231,6 +218,31 @@ Variables.setMethod(function set(key, value) { return this[VALUES].set(key, value); }); +/** + * Set a variable by name. + * The variable will be converted to the proper hawkejs representation + * + * @author Jelle De Loecker + * @since 2.4.0 + * @version 2.4.0 + * + * @param {string} key + * @param {*} value + */ +Variables.setMethod(function setShouldTransform(key, value) { + + if (!value || typeof value != 'object') { + return this.set(key, value); + } + + const renderer = this[RENDERER], + weakmap = renderer.weakmap_for_cloning; + + let cloned = Bound.JSON.clone(value, 'toHawkejs', [renderer], weakmap); + + return this.set(key, cloned); +}); + /** * Set a specific variable from a template * @@ -287,7 +299,7 @@ Variables.setMethod(function getExistingCloneIfValid(clone_name) { * * @author Jelle De Loecker * @since 2.0.0 - * @version 2.0.0 + * @version 2.4.0 * * @param {WeakMap} wm * @@ -312,7 +324,7 @@ Variables.setMethod(function toHawkejs(wm) { dict = Bound.JSON.clone(dict, 'toHawkejs', wm); // Put the cloned values back into the result - result[VALUES] = mapify(dict); + result[VALUES] = new Map(Object.entries(dict)) return result; }); @@ -323,10 +335,10 @@ Variables.setMethod(function toHawkejs(wm) { * * @author Jelle De Loecker * @since 2.3.7 - * @version 2.3.7 + * @version 2.4.0 */ Variables.setMethod(function getShallowClone() { - let result = new Variables(this[RENDERER], this.toJSON()); + let result = new Variables(this[RENDERER], new Map(Object.entries(this.toJSON()))); return result; }); diff --git a/lib/element/custom_element.js b/lib/element/custom_element.js index 7758a6a..59ab4fc 100644 --- a/lib/element/custom_element.js +++ b/lib/element/custom_element.js @@ -1055,8 +1055,6 @@ function renderCustomTemplate(re_render) { render_vars.setFromTemplate('child_nodes', slot_data.child_nodes); render_vars.setFromTemplate('$ancestor_element', that); - render_vars[renderer.clone_symbol] = render_vars; - renderer.renderTemplate(template, render_vars, String(render_counter++)).done(next); }, function assembleBlock(next, template) { @@ -1926,7 +1924,7 @@ Element.setMethod(function setVariable(name, value) { let variables = this[Hawkejs.VARIABLES]; if (!variables) { - variables = this[Hawkejs.VARIABLES] = this.hawkejs_renderer.prepareVariables({}); + variables = this[Hawkejs.VARIABLES] = this.hawkejs_renderer.prepareVariables(); } variables.set(name, value); @@ -2531,7 +2529,7 @@ Element.setMethod(function applyCompiledTemplate(fnc) { if (this[Hawkejs.VARIABLES]) { render_vars = this[Hawkejs.VARIABLES].overlay(); } else { - render_vars = renderer.prepareVariables({}); + render_vars = renderer.prepareVariables(); } render_vars.setEphemeralGetter('self', () => { diff --git a/test/10-expressions.js b/test/10-expressions.js index 1cc3c12..d3fa72c 100644 --- a/test/10-expressions.js +++ b/test/10-expressions.js @@ -939,6 +939,10 @@ function createTests(tests) { } }; + CustomList.prototype.toHawkejs = function toHawkejs() { + return this; + }; + let my_deck = new __Protoblast.Classes.Deck(); my_deck.set('x', 'X'); my_deck.set('y', 'Y'); @@ -990,7 +994,7 @@ function createTests(tests) { empty_obj : {}, falsy : false, success : true, - error : new Error('Some error'), + error : 'some error',//,new Error('Some error'), stuff : 'stuff', opt_str : new Blast.Classes.Develry.Optional('truthy'), opt_empty : new Blast.Classes.Develry.Optional(), @@ -1106,10 +1110,14 @@ function createTests(tests) { } }; - vars.iterable = iterable; + let renderer = hawkejs.createRenderer(); + let variables = renderer.prepareVariables(vars); + // Set this later, so it won't get cloned + // (and lose the iterator property) + variables.set('iterable', iterable); - hawkejs.render(compiled, vars, function done(err, res) { + renderer.renderHTML(compiled, variables).done(function done(err, res) { if (err) { return next(err);