SvelteJS vs ReactJS rendering difference (repaint / reflow) - javascript

Here's my naive understanding of how the DOM and browser works
Whenever something in the DOM ( the real dom ) changes the browser repaints or reflows that the DOM. So in simpler terms every time the DOM changes browser needs to recalculate the CSS, do a layout and repaint the web page. This is what takes time in real dom.
So React comes with this virtual DOM and what it actually does is it batches the changes and call applies them on real-dom in one go. Thus, minimizing the re-flow and re-paint.
Then what about Svelte. If it is manipulating the DOM directly how does it controls the repaint/reflow of the browser.

In addition to the (correct) answer above: Svelte "compiles" the code that you provide to it, so the final code can be executed without a library runtime (in contrast to React). And it creates rather readable code, so it is absolutely possible to understand the inner workings.
Note: This will be a bit longer answer - and still leave out many minute details about what is going on under the hood of Svelte. But I hope it helps to demystify some of the aspects what is going on under the hood. Also, this is how Svelte does things as of v3.16.x. Since this is internal, it might change. However, I still find it always rewarding to understand what is really going on.
So, here we go.
First and foremost: The Svelte tutorial has a useful feature to let you see the generated code (right next to the "Result" pane). It might look a bit intimidating at first, but you quickly get the hang of it.
Below code will be based on this example (but further simplified): Svelte tutorial - reactivity/assignments
Our example component definition (i.e. App.svelte) looks like this:
<script>
let count = 0;
function handleClick() {
count += 1;
}
</script>
<button on:click={handleClick}>{count}</button>
Based on this component definition, the Svelte compiler creates a function which will create a "fragment" which receives and interacts with a "context".
function create_fragment(ctx) {
let button;
let t;
let dispose;
return {
c() {
button = element("button");
t = text(/*count*/ ctx[0]);
dispose = listen(button, "click", /*handleClick*/ ctx[1]);
},
m(target, anchor) {
insert(target, button, anchor);
append(button, t);
},
p(ctx, [dirty]) {
if (dirty & /*count*/ 1) set_data(t, /*count*/ ctx[0]);
},
i: noop,
o: noop,
d(detaching) {
if (detaching) detach(button);
dispose();
}
};
}
The fragment is responsible to interact with the DOM and will be passed around with the component instance. In a nutshell, the code inside
"c" will be run on create (creating the DOM elements in memory and also setting up the event handlers)
"m" will run on mount (attaching the elements to the DOM)
"p" will run on update, i.e. when something (including props) changes
"i" / "o" are related to intro/outro (i.e. transitions)
"d" will be run on destroy
Note: The functions like element or set_data are actually very approachable.
For example, the function element is just a wrapper around document.createElement:
function element(name) {
return document.createElement(name);
}
The context (ctx) will hold all instance variables as well as functions. It is nothing more than a simple array. Since Svelte "knows" what each index means at compile time, it can make hard references to the indices at other places.
This code essentially defines the instance context:
function instance($$self, $$props, $$invalidate) {
let count = 0;
function handleClick() {
$$invalidate(0, count += 1);
}
return [count, handleClick];
}
Both the instance method as well as the create_fragment will be called from another function call init. It's a bit more involved, so instead of copy and pasting it here, you can have a look at this link to the source.
The $$invalidate will make sure that the count variable is set as dirty and schedule an update. When the next update runs, it will look at all "dirty" components and update them. How this is happening is really more of an implementation detail. If interested, set a breakpoint in the flush function.
In fact, if you really want to go a bit deeper, I recommend to clone the template app, then create a simple component, have it compiled and then inspect "bundle.js". You can also debug the actual code if you either delete the source maps or deactivate them.
So, for example set the rollup.config.js like so:
output: {
sourcemap: false,
format: 'iife',
name: 'app',
file: 'public/build/bundle.js'
},
plugins: [
svelte({
dev: false,
Note: As shown above, I recommend to also set dev mode to false since this will create more concise code.
One neat feature: Once our app is running, you can also access the app variable (it is assigned to the global window object since it is bundled as an immediately-invoked function expression).
So, you can open your console and simply say
console.dir(app)
which will produce something like this
App
$$:
fragment: {c: ƒ, m: ƒ, p: ƒ, i: ƒ, o: ƒ, …}
ctx: (2) [0, ƒ]
props: {count: 0}
update: ƒ noop()
not_equal: ƒ safe_not_equal(a, b)
bound: {}
on_mount: []
on_destroy: []
before_update: []
after_update: []
context: Map(0) {}
callbacks: {}
dirty: [-1]
__proto__: Object
$set: $$props => {…}
One cool feature is that you can the $set method yourself to update the instance. For example like so:
app.$set({count: 10})
There are also Svelte DevTools which try to make the internals of Svelte more approachable. Somehow, they seemed to affect the render performance of my apps when I personally tried them out, so I don't use them myself. But certainly something to watch.
Well, there you have it. I know this is still rather technical, but I hope it helped to get a better understanding on what compiled Svelte code is doing.

Both libraries minimize how many changes need to be made to the dom. The difference is the way that they figure out what that minimal set of changes is.
React's approach is to have a representation of the dom in memory (the virtual dom). When you set state, it runs the render process again to create another virtual dom. It compares the before and after, finds what changed, and then any changes get pushed to the real dom.
Svelte's approach is that when you set a variable, it sets a flag marking that variable as having changed. It knows which variables are dependent on other variables, so it then steps through any dependent variables and recalculates them, building up a list of what needs to change. Then these changes get pushed to the dom.

Related

When in the Chrome Debugger, is there anyway to reference data or functions inside an anonymous function block?

I'm trying to debug something live on a customer website and my code is all inside an anonymous function block. I don't know if there's anyway to reach that code to execute functions or look at variables in there. I can't put a breakpoint either because this code is dynamically generated each time the page is refreshed and the breakpoint doesn't stick.
(function() {
var Date = "14 September 2022 14:44:55"; // different every refresh for example
var Holder = {
var Items = {
item1: "Value1",
item2: "Value2"
};
function getItem(name) {
return Items[name];
};
function setItem(name, value) {
Items[name] = value;
};
setTimeout(DoSomething(), 2000);
})();
That's not the actual code, just a bare minimum example to illustrate the problem.
Is there anyway to get reach getItem() or Items?
Without a breakpoint that code probably runs to completion then POOF it's all gone anyway.
Redefine setTimeout
If it really is the case that the code inside the anonymous function calls other browser methods, you might be able to insert a detour at runtime that you can then put a breakpoint on.
For this to work, you will need to be able to inject new code into the page before the anonymous code, because there's no other way to invoke the IIFE.
Your example code uses setTimeout, so here's what I would try to insert:
let realSetTimeout = window.setTimeout
window.setTimeout = (...args) => {
debugger
return realSetTimeout(...args)
}
Lots of unrelated code might be calling setTimeout, in which case this could break the page or just make debugging really tedious. In that case, you might make it only debug if one of the setTimeout args has a value that's used in your example, e.g.:
// only break for our timeout
if(args[1] === 2000) debugger
Something like that might not trigger for only your code, but it would hugely reduce the number of other codepaths that get interrupted on their journey through the commonly-used browser capability.
Alternatively, use Charles Proxy to rewrite the body of the HTML page before it enters your browser. You could manually insert a debugger call directly into the anonymous function. Charles is not free, but I think they have a demo that might let you do this. If you do this professionally, it's probably a good purchase anyway. Your employer might even pay for the license.
If you can't use Charles (or a similar tool), you could instead set up a local proxy server using Node which does the rewrite for you. Something like that might only take an hour to throw together. But that is a bigger task, and deserves its own question if you need help with that.
No unfortunately.
The variables inside of the anonymous object are created in a scope which is inaccessible from the outside.
One of the main benefits of using a closure!
You’ll have to find a way to insert your own code inside of it by modifying the function that is generating those objects. If you can’t do that, then you’ll have to take the fork in the road and find another way.

Using modular design pattern in Javascript with DOM selections

I have been following the Modular Design Pattern for quite some time now and find it extremely useful as it helps in the well maintenance of code & separation of blocks into modules.
Regular usage of the module structure with jQuery has led to most of my applications/code following the below structure:
(function() {
var chat = {
websocket: new WebSocket("ws://echo.websocket.org/"),
that: this,
init: function() {
this.scrollToBottom();
this.bindEvents();
this.webSocketHandlers();
},
bindEvents: function() {
this.toggleChat();
this.filterPeople();
this.compose();
},
elements: {
indicator: $(".indicator"),
statusText: $(".status-text"),
chatHeadNames: $(".people li .name"),
filterInput: $("#filter-input"),
msgInput: $("#msg-input"),
sendBtn: $(".send")
},
...
...
...
filterPeople: function() {
var that = this;
this.elements.chatHeadNames.each(function() {
$(this).attr('data-search-term', $(this).text().toLowerCase());
});
},
...
...
};
chat.init();
})();
What I would like to know is whether referencing all my elements via jQuery as part of a single variable chat.elements is a good practice?
One part of me tells that it indeed is a good way to reference all your selectors at once and cache them in variables so that multiple usages of the same element can be done with the cached variables (instead of multiple DOM selections).
Another part of me tells that this might be an anti-pattern and specific elements should be selected and cached locally when required.
I have used similar structures throughout and have got mixed responses about the code, but nothing solid. Any help would be appreciated. Thanks!
Caching the selectors is a good thing. Hanging on to them is a good idea. It improves performance over repeatedly querying the DOM for the same thing. The code you have above looks very similar to BackboneJS and MarionetteJS code.
I do have some warnings for you though:
This pattern could cause memory leaks. Consider the case where you destory a subview, but you keep a reference to something that selected it. This is called a dangling pointer. The view won't really disappear. All bindings will remain. Events will continue to fire behind the scenes.
You will eventually run into a bug where you decided to re-render part of your screen. Cleaning up all your bindings is then required and you need to remember to delete and selectors. If you don't do this you will almost certainly run into issues where you wonder why an event is indeed firing but nothing appears to happen on screen.... (this will be because its happening off screen, to the element that you tried to delete, that still exists... sorta).
The current way you are querying for elements causes searches against the entire page. Take a look at https://api.jquery.com/find/. If you cache one selector and then perform searches within that selector it may gain you a little performance bump.
I think, If the chat module has selectors only for its children, then it's a good pattern. Like:
<div id="chat-module">
<div class="indicator">...</div>
<div class="status-text">...<div>
...
</div>
<script src="and your chat module.js"></script>
// your chat module selecting .indicator:
// $('#chat-module.indicator')
Also, add a shut-down function to your module. So, when you remove it from the view (as in a single-page-app), you can nullify your selectors and detaching event handlers, like: delete this.elements.indicator and event detaching code.
There are also other/better patterns for this, like, when a user types something, you fire an event, and catch that event in your module. To separate UI and the code.

How do reactive streams in JS work?

I'm novice in reactive streams and now trying to understand them. The idea looks pretty clear and simple, but on practice I can't understand what's really going on there.
For now I'm playing with most.js, trying to implement a simple dispatcher. The scan method seems to be exactly what I need for this.
My code:
var dispatch;
// expose method for pushing events to stream:
var events = require("most").create(add => dispatch = add);
// initialize stream, so callback in `create` above is actually called
events.drain();
events.observe(v => console.log("new event", v));
dispatch(1);
var scaner = events.scan(
(state, patch) => {
console.log("scaner", patch);
// update state here
return state;
},
{ foo: 0 }
);
scaner.observe(v => console.log("scaner state", v));
dispatch(2);
As I understand, the first observer should be called twice (once per event), and scaner callback and second observer – once each (because they were added after triggering first event).
On practice, however, console shows this:
new event 1
new event 2
scaner state { foo: 0 }
Scaner is never called, no matter how much events I push in stream.
But if I remove first dispatch call (before creating scaner), everything works just as I expected.
Why is this? I'm reading docs, reading articles, but so far didn't found anything even similar to this problem. Where am I wrong in my suggestions?
Most probably, you have studied examples like this from the API:
most.from(['a', 'b', 'c', 'd'])
.scan(function(string, letter) {
return string + letter;
}, '')
.forEach(console.log.bind(console));
They are suggesting a step-by-step execution like this:
Get an array ['a', 'b', 'c', 'd'] and feed its values into the stream.
The values fed are transformed by scan().
... and consumed by forEach().
But this is not entirely true. This is why your code doesn't work.
Here in the most.js source code, you see at line 1340 ff.:
exports.from = from;
function from(a) {
if(Array.isArray(a) || isArrayLike(a)) {
return fromArray(a);
}
...
So from() is forwarding to some fromArray(). Then, fromArray() (below in the code) is creating a new Stream:
...
function fromArray (a) {
return new Stream(new ArraySource(a));
}
...
If you follow through, you will come from Stream to sink.event(0, array[i]);, having 0 for timeout millis. There is no setTimeout in the code, but if you search the code further for .event = function, you will find a lot of additional code that uncovers more. Specially, around line 4692 there is the Scheduler with delay() and timestamps.
To sum it up: the array in the example above is fed into the stream asynchronously, after some time, even if the time seems to be 0 millis.
Which means you have to assume that somehow, the stream is first built, and then used. Even if the program code doesn't look that way. But hey, isn't it always the target to hide complexity :-) ?
Now you can check this with your own code. Here is a fiddle based on your snippet:
https://jsfiddle.net/aak18y0m/1/
Look at your dispatch() calls in the fiddle. I have wrapped them with setTimeout():
setTimeout( function() { dispatch( 1 /* or 2 */); }, 0);
By doing so, I force them also to be asynchronous calls, like the array values in the example actually are.
In order to run the fiddle, you need to open the browser debugger (to see the console) and then press the run button above. The console output shows that your scanner is now called three times:
doc ready
(index):61 Most loaded: [object Object]
(index):82 scanner state Object {foo: 0}
(index):75 scanner! 1
(index):82 scanner state Object {foo: 0}
(index):75 scanner! 2
(index):82 scanner state Object {foo: 0}
First for drain(), then for each event.
You can also reach a valid result (but it's not the same behind scenes) if you use dispatch() synchronously, having them added at the end, after JavaScript was able to build the whole stream. Just uncomment the lines after // Alternative solution, run again and watch the result.
Well, my question appears to be not so general as it sounds. It's just a lib-specific one.
First – approach from topic is not valid for most.js. They argue to 'take a declarative, rather than imperative, approach'.
Second – I tried Kefir.js lib, and with it code from topic works perfect. Just works. Even more, the same approach which is not supported in most.js, is explicitly recommended for Kefir.js.
So, the problem is in a particular lib implementation, not in my head.

How to trace function calls in Marionette

Somewhere in the global scope:
let App = Backbone.Marionette.Application.extend({});
window.Ext = new App();
Inside Module A:
Ext.vent.trigger('Tracked:Email:send', {
from_email: tracking_data.sender,
to_emails: tracking_data.to,
cc_emails: tracking_data.cc,
email_id: tracking_data.email_id || '',
template: tracking_data.template_id || '',
subject: tracking_data.subject
});
Inside Module B:
Ext.vent.on('all', function (evt_name, params) {
// something else...
console.log(params);
}
The object's properties (from_email, to_emails & cc_emails) are undefined when I call console.log in Module B.
I've tried to debug this using console.trace, but the console doesn't show any functions that are involved in changing the object. I've also tried using Object.observe to capture the act of changing the object but no changes are detected.
Can some please teach me some debugging techniques to trace function calls and events in Marionette.
The scenario is:
our codebase is huge.
I'm a new guy at our company so I'm not sure if there are other functions or events that are involved.
I'm the only front end developer right now.
The good news: there's nothing special about Marionette here. Everyday JavaScript debugging techniques will help you out.
The bad news: as you're aware, debugging large, event-driven applications is difficult.
You could take these steps in Chrome dev tools (YMMV with other browsers):
Split the App.trigger line to get a reference to the parameters object:
let options = { from_email: ... }
App.vent.trigger('Tracked:Email:send', options);
Set a breakpoint on Ext.vent.trigger and run your code.
When execution pauses at the breakpoint, check that options contains the expected values. If not, the problem is somewhere before App.vent.trigger. Look up the call stack for any functions that affect tracking_data. You may need to check the Async checkbox if it is populated by asynchronous code.
If options contains the values you expect, carry on...
Whilst execution is paused use the console to save a global reference to your parameters object:
> OPTIONS = options
Add OPTIONS as a watch expression. Expand the item so you can watch changes to its properties.
Step through (including Marionette and Backbone code) until you see the OPTIONS properties change. If you don't see Marionette code when you step into App.vent.trigger you may need to remove Marionette from your black boxed libraries.

Since Meteor upgrade to 0.8.0, Template "rendered" callback is not fired when Session variable dependancy changes

I'm stuck with a problem since I upgraded to 0.8.0.
The Template rendered is not being fired anymore (except the first time).
I followed the recommendations as in:
https://github.com/avital/meteor-ui-new-rendered-callback/blob/master/new2/client/each.js
This didn't helped, and so I finally made this small piece of code (by modifying the new2 example).
The main difference is that the update is triggered by a Session variable change instead of a DB change.
This perfectly shows the problem, as rendered is fired only twice with this example:
client/each.js
Template.list.items = function () {
return (Session.get('items') || 'None')
};
var renderCount = 1;
var logRender = function () {
console.log("rendered #" + renderCount);
renderCount++;
};
Template.list.rendered = function () {
logRender();
};
Template.justName.rendered = function () {
logRender();
};
setInterval(function () {
Session.set('items', {name: Random.choice(["one", "two", "three"])});
}, 1000);
client/each.html
<body>
{{> list}}
</body>
<template name="list">
{{#with items}}
{{> justName}}
{{/with}}
</template>
<template name="justName">
{{name}}
</template>
How can I do to get the Template.justName.rendered callback properly fired when content update is triggered by a Session.set?
Thanks,
I do have an instant solution for you, but it probably requires a tiny bit of re-thinking your actual code. This is by the way the same problem as here:
Meteor 0.8.0 - Failed to operate DOM in rendered callback
But the question is posed in such a different context that it makes sense to answer it twice.
So why does it not trigger the rendered callback? Because it does not re-render.
Blaze treats the whole thing of "how to react on a changed dependencies" very differently, "better" one might say: it will identify the DOM node where your "one", "two" or "three" (in your case it's the template itself) was stored in and just replace the part that has changed, which is the text content "one", "two" or "three". The DOM node itself as well as the template stay completely intact. That also means, that everything you could have been doing with this DOM node won't have to be re-done in almost every practical scenario. I.e. if you animate it, change it's text color using jQuery, the color and animation will just stay on the screen, so you won't need the rendered callback to re-do that.
In your case, the problem is easily solved by just rearanging what you want to do on "rerender":
var whatever = function(){
// whatever you want to do on data-change, in your case calling "logRender" (which needs to be renamed with Blaze, anyway..)
logRender();
}
And then the only thing you have to do is firing it whenever your data change, either manually, like this:
setInterval(function () {
Session.set('items', {name: Random.choice(["one", "two", "three"])});
// calling the function when changing the data, knowing that it WON'T destroy the DOM node it affects
whatever();
}, 1000);
or reactively, like this:
Deps.autorun(function(){
Session.get("items"); // our dependency, just has to be there, but you can also use it
whatever(); // will be fired whenever dependency changes
});
The core idea is to eliminate the need to re-do something you did in the rendered callback, since the DOM and the identity of its objects (and all the beautiful jQuery effects) are still intact. So all that is left to re-do is something that only would depend on the particular reactive data-change, which is why there is the Deps.autorun().
In your particular example, your "logRender" function did not have any reactive dependencies, but if you add some and put it into the Deps.autorun(), it will be reliably re-run whenever the dependency changes.
As a conclusion, Meteor 0.7.x and below drove us to make the mistake of treating the "rendered" callback function as a general purpose autorun function, which is why we are running into trouble now and have to fix our apps.
As noted in the comments, this is a indeed a design change with Meteor.
Prior to Meteor 0.8, a template was a function that generated HTML. This function would be re-computed whenever any of its reactive dependencies changed, which resulted in a recreation of all the DOM nodes generated by the template (apart from any sub-templates or isolated nodes). Whenever this re-draw happened, the rendered callback was triggered.
This behavior creates quite a performance hit because it requires re-rendering of potentially a lot of HTML, including for identifiers and helpers depending on data that hadn't changed. Additionally, it made it difficult to use other libraries like jQuery to modify the DOM elements that were created, because Meteor basically had control of the entire process and the jQuery code would have to be carefully re-run each time.
Meteor 0.8 fixes this by only rendering the pieces of the DOM that have actually changed, down to the granularity of the identifiers in your template - it is much more fine-grained. As a result, the template's rendered callback is only triggered once when your template hits the page, and is never called again afterward. This solves a lot of performance issues and allows jQuery and other DOM manipulations to work seamlessly with Meteor, but also means that you won't get the automatic callback signalling when something has changed. You can, however, achieve this with helpers that use reactive variables for specific things that change.
For a more detailed listing of how Spacebars, the new Handlebars replacement, works, see https://github.com/meteor/meteor/blob/devel/packages/spacebars/README.md
See also the new documentation about the rendered callback: http://docs.meteor.com/#template_rendered
So, I was doing a lot of digging yesterday to try and figure out basically the exact same issues you are having. I am still digging, but I did come across this Devshop Talk about Integrating Other Clientside JS Libraries. In it Ted Blackman describes a package he made to trigger events when a Session variable changed. It sounds like what you need. This talk was given prior to 0.8.0 so I am not sure how the package would be effected, but it might be worth a shot.
Devshop Talk - https://www.youtube.com/watch?v=NdBPY98o6eM
Session Extras - https://atmospherejs.com/package/session-extras
Event Horizon - https://atmospherejs.com/package/event-horizon

Categories

Resources