tailor
tailor copied to clipboard
Only include a static resource once per request, even if referenced by multiple fragments
Hopefully title is self-explanatory, but given the following fragments:
FragA -> style1.css, style2.css, script1.js, script2.js FragB -> style2.css, style3.css, script2.js, script3.js
then I would expect to see only the following files loaded by the browser: style1.css, style2.css, style3.css script1.js, script2.js, script3.js
Browser also does the job of avoiding the downloads(script/style) if the file is already loaded in the current navigation.
If script2.js from fragment 1 and script2.js from fragment 2 has the same URL, then the script would only be downloaded once.
Correct - but each time the script is referenced, it's re-executed in its entirety. ie: https://codepen.io/dazld/pen/zzBKwV - same script inserted a bunch of times in head. I'm trying really hard to think why someone would want to do that with tailor, but coming up a blank. It feels like scripting from a different time.
I'm hoping CSS is a bit more sane, and that multiple inserts of the same file has no effect.
@dazld But IMO this is a valid requirement as we use the same fragment multiple times on a page for recommendations on PDP page and therein the same resources are referenced multiple times. So, even if we avoid network overhead, the repeated execution time may still be a performance bottleneck
Correct, the script would only be downloaded once and re executed at all places where it is referenced.
@dazld @addityasingh Thinking out load, Not referencing the script to browser will cause these issues
- If fragment-1 is referenced twice in the template and wants to maintain a global state in script1.js and update the state on each exec call. This can also be an event bus trigger.
// script1.js - 1st exec
var ele = doc.getElementById("fragment-a");
if (ele) {
eventBus.trigger("fragment-a-loaded");
}
// script1.js - 2nd exec
var ele = doc.getElementById("fragment-b");
if (ele) {
eventBus.trigger("fragment-b-loaded");
}
This is totally valid and people might even do tracking based on this.
-
render
will never be called for the second time which means if fragment-1 is referenced twice in the template. Only the first one will render, all others will be ignored because the script that has the render function will never be executed.
I can't think of any cases for CSS though. we can safely avoid creating link tags for CSS that are flushed already, Browser does apply the changes to all the matching DOM elements.
IMO that should stay a concern of the fragments which trigger loading identical scripts.
that sounds like a buggy implementation - relying on the browser to execute the same script instead of cleanly separating the behaviour to a single place and invoking it multiple times.
@dazld Do you mean that we dedupe the fragment scripts if the fragment is repeated on the page, and just call it? If yes, Should that be the responsibility of Tailor? IMO it should be the responsibility of fragment owners to do that
@dazld Not always buggy. If we have recommendations fragment on the top and bottom of the page. Its valid to send the same script twice.
dependency handling is best left to dependency handling systems. I don't think injecting the same script tag twice is a great substitute, but I guess it works in a pinch.
I don't think injecting the same script tag twice is a great substitute
referencing it twice (by two fragments referencing it) should result in requesting it twice (taking browser caching out of the equation for the sake of brevity), anything else would violate SoC and would be invasive.
I think @dazld your use case might be served best with a Singleton pattern for the script which is prone to be referenced multiple times. It would come with the benefit of being expressive.
true, the architecture I have in mind is opinionated. it's definitely interesting to explore, but I get the impression that I've not expressed it well as yet, my apologies for this.
here's a plan - I'll implement a scratch approach with an example, and then there's something concrete to pick apart.
@dazld Did you have time to explore on the prototype implementation?
sorry, been totally snowed under. I still think the base idea is sound, though.
I tried out the examples, and I noticed that scripts only get included once, but stylesheets are still added per fragment. Why was this distinction made?
@JaroVDH There is no distinction as such, Can you give an example?
I ran the example basic-css-and-js and duplicated the fragment in the template. That resulted in the following output:
<!-- Tailor needs an index.html -->
<html>
<head>
<script type="text/javascript" charset="utf-8" async="" data-requirecontext="_" data-requiremodule="http://localhost:8081/script.js" src="http://localhost:8081/script.js"></script>
</head>
<body>
<h1>Basic css and js</h1>
<script src="https://cdnjs.cloudflare.com/ajax/libs/require.js/2.1.22/require.min.js" crossorigin=""></script>
<script>var Pipe=...</script>
<link rel="stylesheet" href="http://localhost:8081/styles.css">
<div>Fragment 1: <span id="c">7</span>s elapsed</div>
<link rel="stylesheet" href="http://localhost:8081/styles.css">
<div>Fragment 1: <span id="c">-1</span>s elapsed</div>
</body>
</html>
Two <link rel="stylesheet">
were created for the same resource.
You would see the same for javascript, If you do view source of that file.
<link rel="stylesheet" href="http://localhost:8081/styles.css"><script data-pipe>Pipe.start(0, "http://localhost:8081/script.js", {"primary":false,"id":0,"range":[0,0]})</script><div>Fragment 1: <span id="c">-1</span>s elapsed</div><script data-pipe>Pipe.end(0, "http://localhost:8081/script.js", {"primary":false,"id":0,"range":[0,0]})</script>
<link rel="stylesheet" href="http://localhost:8081/styles.css"><script data-pipe>Pipe.start(1, "http://localhost:8081/script.js", {"primary":false,"id":1,"range":[1,1]})</script><div>Fragment 1: <span id="c">-1</span>s elapsed</div><script data-pipe>Pipe.end(1, "http://localhost:8081/script.js", {"primary":false,"id":1,"range":[1,1]})</script>
In the end, You wont see two downloads, thats what matters.
The difference being the javascript is executed only once (as can be seen from the counter only updating once per second), and the actual script tag only shows up once. Because the stylesheet is added twice, the contents are included twice (even if it's only downloaded once) causing the priorities to change.