I'm guess that since this is an academic paper and not say, a website or GitHub repo, that you're in school. But given the research paper nature of this, did you want to contrast and compare to other methods of authoring web components?
There are many existing and popular approaches out there. Is your take easier, faster, or more capable? That discussion would be interesting.
I always love seeing more done in the web component space. I think Lit has the no build process captured pretty well and they include things such as a router.
I do prefer the style of of your components more, where you separate out the script and styles with html tags. I don't know if one way or the other is superior for performance, I but just like the separation verse the templated strings in Lit.
With build tools being so straightforward now-a-days, I struggle to see the value in the build less approach. One use case I can think of is maybe a constrained environment where the application contains some kind of customizable user components fully in the browser like a reporting WYSIWIG of some kind.
Is there a particular reason you prefer this approach?
With the caveat that I’m generally outside of the web dev sphere aside from casual tinkering: no build process means no setup and one fewer thing that can break. That’s valuable in itself.
> Is there a particular reason you prefer this approach?
Compared to Lit? Firstly, I like Lit. My approach is better for me because there are far fewer things to know in order to create a reusable object and there are far fewer things to know in order to use an existing object.
IOW, my approach trades off functionality in favour of a lower cognitive burden for me. I just wanted something that had the minimum functionality I need - create an object and then reuse it. For me, anything outside of that goal is peripheral burden that I'd rather not get bogged down in.
I agree with many here that this is not an approach for them, maybe even for most, but I am certain that if I find something useful that increases my velocity of feature delivery, then maybe someone else will as well.
Zjs is a web component for doing client-side includes. The Zjs code itself is itself a web component that extends HTMLElement and registers itself as <zjs-component>. It's only 100 lines long. In its connectedCallback, it fetches the URL of the "remote-src" attribute, and injects its content in innerHTML.
Client-side includes are bad. The industry avoids them, for good reasons.
First, they hurt performance. The client can't start downloading a zjs-component content until its connectedCallback runs. If a zjs-component includes another zjs-component, the client can't start downloading the nested component until its parent component downloads and executes. If your components are nested at N layers, this means that the page won't finish loading until N serial non-parallelizable requests succeed. On cellular networks, where network latency can be measured in seconds, if you have four layers of components, you're looking at adding 10+ seconds to page load.
Second, each client-side include will cause a layout shift, as the <zjs-component> element starts out at 0px tall, and then will change in height as the component loads. This is bad. https://web.dev/articles/cls
Finally, in your article, you claim that reactivity is "out of scope for a component creation mechanism," but there is no universally recognized "scope" for component creation. You'd need to justify the claim that reactivity is out of scope.
If you think reactivity matters at all, then you'd need to demonstrate how to use client-side includes in the context of a framework that does provide reactivity.
HTMX is a lightweight framework that provides a reactivity framework around client-side includes. https://htmx.org/ You might especially appreciate the academic theoretical framework the HTMX team provides in their book, Hypermedia Systems. https://hypermedia.systems/
HTMX is pretty good for what it does; it's not clear that anyone should prefer ZJS over HTMX for anything. But it's also not clear that HTMX is better than more frameworky alternatives, especially React Server Components.
That's a lot you wrote, thank you for all that effort and time. I will do my best to address them:
> Client-side includes are bad. The industry avoids them, for good reasons.
The industry we are talking about does lots of things that has caused many to consider "the industry" to be something of a running joke.
You may not like client-side includes, but it really is something useful to have. Wanting to have client-side includes and not wanting to spin up an entire tech stack of 5 or so different technologies is not unreasonable.
> First, they hurt performance. [...] On cellular networks, where network latency can be measured in seconds, if you have four layers of components, you're looking at adding 10+ seconds to page load.
1. The first time, sure. It's 10s the first time those 4 components are downloaded. Any other component using any of those 4 components on the rest of that page, or if that page is reloaded, will have a 0s download.
2. On cellular networks I currently get longer wait times than that for SPAs used by the mainstream frameworks anyway today. I'm not losing any sleep over the pathological cases which affect mainstream frameworks and libraries as well.
3. I call it pathological because I feel that it would be an extreme case to have lots of components being downloaded, of which only a few get reused. The whole point to reusing is so that the component only gets downloaded once, even if used in multiple places.
> Second, each client-side include will cause a layout shift, as the <zjs-component> element starts out at 0px tall, and then will change in height as the component loads. This is bad. https://web.dev/articles/cls
I agree. It's a trade-off that many sites currently make, SPAs included. While I find it incredibly annoying, this component is for when that trade-off decision is already made.
> Finally, in your article, you claim that reactivity is "out of scope for a component creation mechanism," but there is no universally recognized "scope" for component creation. You'd need to justify the claim that reactivity is out of scope.
I'm a bit confused about this; if there is no universally recognised "scope" for creation of an object in an OO environment, why would I have to first define what scope is and then defend that decision? Most (all?) mainstream OO approaches don't have reactivity within scope for the definition of an object, so I feel that any claim that reactivity is within scope for this specific OO approach needs to provide some sort of reason why that an exception must be made for zjs-component so that it is measured against a higher bar than other OO approaches.
> HTMX is a lightweight framework that provides a reactivity framework around client-side includes. https://htmx.org/ You might especially appreciate the academic theoretical framework the HTMX team provides in their book, Hypermedia Systems. https://hypermedia.systems/
I've read those.
Regardless, if you are building an SPA with Facebook level number of components, then this is not for you. IOW, this may not suit your use-case, or even most of your use-cases. I find it suitable to an embarrassingly large number of use-cases, though.
I build Line-of-Business applications for internal use at companies. Even the most complex UI in these LoB applications have only a few different components, and at most two levels of nesting (these component objects are not like React components at all - they're meant to be instanced inline in HTML, which is both a drawback and an advantage).
Because it does one thing and one thing only: instantiate an HTML element object. You literally cannot write a book about zjs-component the way it was done for HTMX: you'd be done on page one after demonstrating how zjs-component works!
To me there is value in lowering the cognitive burden this significantly; while HTMX is indeed a lower burden than almost everything else, zjs-component is even lower and gets you 80% (or more) of the way there.
You may disagree that the trade-off is worth it, but in many contexts the trade-off is certainly worth it.
In any case, with the way I use zjs-component, all I use are static files for the fragments (fragments are not generated by the server).
When I used HTMX the fragments returned in response to a request were almost never static files, they were all generated on the server (in Go, if you must know) so values could be interpolated. This, by its very nature, is not reusable.
When I create a component that is a zjs-component, the calls it makes to hydrate itself gets data in the responses (JSON, or similar), not HTML. It means that I write an object that has a snippet of HTML, has Javascript that is scoped to that object, and has DOM calls that can be scoped to that specific instance of the object.
HTMX has no scoping or containment of of the code in the HTML, nor can code in a fragment be written to only nagivate/search the DOM in that specific instance because HTMX does not have instances of objects.
The whole reason I use zjs-component over something like HTMX is because zjs-component has the concept of "Objects" (containing HTML and methods), "Instances" which are isolated from each other and a way to instantiate these objects in a page using nothing more than `<zjs-component remote-src='some/remote/path/SomeObject.zjsc'>`.
It is clear to me that having an object containing HTML and methods, with instances isolated from each other, is not of value to you. Even though you do not find value in the trade-off it offers, you must understand that HTMX is not a replacement for zjs-component, and zjs-component is not a replacement for HTMX.
Some of what zjs-component offers cannot be done easily in HTMX (like having nothing but static files for defining objects, scoping DOM nav to the instance of the object that is running that method, etc), and some of what HTMX does cannot be done in zjs-component (such as interpolating values into a template) because it makes no sense to do so in the context of zjs-component.
Seeing as how neither is able to replace the other, why the fixation on HTMX as a replacement for zjs-component?
>No build process, no web-packer, no framework, no npm requirement. Just include the JS in your HTML and then you can create and include components.
How does the rest of your codebase look?
This is the primary problem with web components. No frameworks sounds nice in theory, but it only solves about 30% of the problem. The rest ends up an ad-hoc mixture of libraries and custom code for state management, routing, styling, cross-component communication, etc, to the point that you end up building your own framework that is brittle and unmaintainable. Applications like this generally end up as a huge confusing web of global event buses or with multiple tightly coupled layers of prop drilling because of that.
There was a dream that was web components once upon a time. It felt like the future. But the APIs ended up half implemented (poorly), and the spec was more or less abandoned by everyone but Google. Browser vendors could have done things right, and focused on pulling in the good things from the framework world (i.e. what happened with jQuery), but they didn't.
Disagree, done several custom frameworks for highly regulated areas and high load userbases - and as long as you keep to seperation of concerns it is a lot more nimble and long term viable - easy to wield that 200000 dependencies just to set a history state
> the spec was more or less abandoned by everyone but Google
This is very, very much not true.
All browsers are very up-to-date with web components APIs, and Safari is leading in some areas like scoped custom element registries, which they're about to ship first.
I think Google has done a great job of making Web Components seem both poorly implemented and way too complex. If Angular is the "Google blessed" way of writing Web Components and Angular makes a terrible hash of it, who can build good Web Components?
Angular and a couple other well known frameworks were targeting for far too long pre-spec Google-specific versions of Web Components and Web Components moved quickly past that and settled into cross-vendor specs with smarter defaults and simpler APIs a lot of the marketing damage was already done that Web Components were too complex and too poorly supported.
So much of the Web Components world is stuck with the quagmire of the Shadow DOM, and while there are some generalized use cases for the Shadow DOM, I have the harsh opinion that the Shadow DOM was mostly invented to be "better IFRAMES for Ad Publishers" and not much else. I don't think enough web developers yet realize that the Shadow DOM is entirely optional and its complexity is so much easier to ignore and toss than to try to utilize.
> focused on pulling in the good things from the framework world (i.e. what happened with jQuery), but they didn't.
I'm coming to the growing opinion that between Web Components and the TEMPLATE tag Browser vendors have finally caught up with some of the best ideas of the Knockout-era "Progressive Enhancement" world, assuming you ignore the Shadow DOM as mostly YAGNI.
Sure, a lot of cross-component communication and some of state management goes back to DOM APIs and DOM event management, but there used to be a lot of knowledge in those areas and maybe it past time to return to Vanilla JS ideas about some of that. In the time since jQuery and Knockout, all browsers today have much more consistent DOM APIs for event management, at least.
(Similar for routing. Vanilla routing, even "SPA" routing is easier than ever with modern APIs, even the API not fully standardized and cross-browser yet, but also the improved APIs for hash navigation. Also, Web Components seem a great reminder that MPA routing is great and brilliant, and so well tested in browsers and servers. With View Transitions now generally well supported, and getting better, many MPAs feel as good or better than SPAs even.)
(And I feel like most styling problems with Web Components are self-inflicted with and by the Shadow DOM. It's nice to embrace the cascade again, if you can.)
I was bullish on Web Components for a while, but most of my complaints have disappeared in recent months, and especially in realizing that I was very happy ignoring everything about the Shadow DOM. Sure, I'm not using Web Components without a "framework" but I'm using a far smaller "view engine" than a React or Angular, a lot more "Vanilla", and feeling a bit like it's a Revenge of Knockout sort of era.
> Sure, a lot of cross-component communication and some of state management goes back to DOM APIs and DOM event management, but there used to be a lot of knowledge in those areas and maybe it past time to return to Vanilla JS ideas about some of that.
My approach is, actually, an attempt to inject some Vanilla into front-end component development.
I think many of us, as this linked article that kicked off this discussion included, are thinking of various ways of using more Vanilla approaches. I appreciate the above poster's complaints that we aren't in some promised land of being able to do things 100% Vanilla without some form of reactivity management. That's still all in progress, no matter where you are in the Observables versus Signals "debate", Browsers are back to showing interest in native specs for one or both. (I personally think Signals are just "worse, poorly encapsulated Observables", but either proposal is a good building block for whatever the next steps are and thinner libraries to build on top of them will be.) But Web Components are mostly some steps in the right direction, I think, while that other work remains ongoing.
I also like gradual typing too much to expect to be 100% Vanilla any time soon. Typescript is too handy a verification/testing tool and while I'm still watching the native type stripping proposal with TC-39 with a lot of interest, it's certainly not on a fast track.
I haven't had too much trouble with Shadow DOM so far, and I was especially surprised to learn about `<template shadowrootmode="open">` recently. It makes SSR rendering of web components with shadow DOMs viable and appears to be well supported in browsers.
I'm aware of Lit and thought about mentioning some of its past versions too from the pre-spec era, but I think those are even more forgotten.
Around Angular 8-14 era Angular had a lot of "Angular is Web Components" marketing and assured devs at the time that the Angular way including CSS Modules support and its HTML Template language (and its super complex compiler) were "browser standards and the future of Web Components". Obviously none of that played out, CSS Modules are still in spec debate, the TEMPLATE tag was much simplified and no "template language" has been knighted as "the template language". But it was all so heavily marketed and it left scars in what people think of when they think of Web Components. People still expect CSS Modules. People still expect some sort of "blessed" template language out of the box for "Vanilla templating". Among other curious decisions by people marketing Angular to developers as a way to prepare for Web Components. (We can talk for hours about everything wrong with Zone.js and why it has taken Angular to version 20 to start to undo a lot of that [brain]damage.)
Lit was never quite so extreme in its worst pre-spec days, and it is useful for its templating language looked different enough from Angular's to, even at the time, prove that Angular's template language was neither HTML nor necessarily the template language to be knighted by Web Components. But it still had some versions with questionable Google-only decisions that are best forgotten today, and apparently easily forgotten today.
Of course, you don't have to take my anecdata on it, the sources still exist. If only Google made a search engine that works in 2025 maybe you could even find them.
A decorators proposal (and it was a different one than what Google was building stuff off of in 2019) didn't make it to Stage 3 until 2022 (and still hasn't made it to Stage 4).
This also contributed to the idea for years that Web Components were more complicated to support in non-Google browsers or without complicated build processes.
Lit has always supported plain JS ways of authoring components. Just because it also supported TypeScript and Babel decorators doesn't make it "pre-spec".
Lots of developers like decorators, we gave them the option. _shrug_
TLDR: Browser vendors made Shadow DOM for themselves.
Browser implementors use Shadow DOM extensively under the hood for built-in HTML elements with internal structure like range inputs, audio and video controls, etc. These elements absolutely need to work everywhere and be consistent, so extreme encapsulation and fixed api for styling them is an absolute must.
The Shadow DOM API is the browsers exposing, to developers, a foundational piece of functionality.
If you’re thinking about whether Shadow DOM is appropriate for your use case, consider how/why the vendors use it —- when an element’s API needs to be totally locked down to guarantee it works in contexts they have no control over. Conversely, if your potential use case is scoped to a single project, the encapsulation imposed (necessarily!) by Shadow DOM is probably overkill.
Web components are a decent way to make reusable UI, but if they don’t have strong encapsulation needs, you might avoid Shadow DOM.
This is great! A few comments/questions if you have time to respond:
1) I thought that React already worked this way until I learned it, and was horrified. Similar disillusionment with Ruby on Rails, Objective-C on iOS and Java on Android. Why someone would want to mix code and markup, rather than define components via code to be used within markup, blows my mind. Said as someone who uses PHP daily and sees JSX as its frontend analog.
2) I appreciate that async/await and promises were deliberately left out of the spec. I view their use as an anti-pattern, since I've programmed both async/nonblocking and sync/blocking sockets logic, and view sync as far superior if deterministic behavior is the goal. I know this is a contentious subject, but IMHO async will someday be considered the goto of the web and be deprecated across most languages. Better approaches such as scatter/gather arrays, coroutines and channels (like in Go) exist to remove the temporal/imperative aspect of logic and make it declarative without requiring red/blue functions.
3) The main problem I've encountered with React is that it's nearly untraceable in web inspector. It's impossible to tell which component object instance is associated with an HTML element. It's hard to tell where attribute values came from, unlike function arguments: are they props that aren't declared in a function prototype? are they magically mapped from redux state? are they derived neither in the constructor nor componentDidMount() or affected by an affect they weren't supposed to be? etc etc etc. Component methods are buried in dozens of anonymous functions and boilerplate. Promise values can't be inspected until their then() method without using tricks like Promise.resolve(), which don't always match intuition. Redux state can't be conveniently inspected without adding debug lines to the source code and/or installing a browser extension. Code gets mutated by the build pipeline so it's hard to find code snippets to set breakpoints. I could go on. Have you found ZjsComponent easier to debug, and if so, how?
4) I applaud your use of caching for client-side includes. IMHO much of the complexity of React is around its deliberate rejection of HTTP idioms and metaphors. We already solved these problems declaratively in the mid 1990s, why are we manually managing initialization and state, even if through Redux? That was a rhetorical question, but really, React doesn't seem nearly as reactive as something like a spreadsheet that just re-renders cells that reference cells that changed. Basically all of its component methods could be replaced by the render() method containing a JSX template, with the state boilerplate handled by the framework internally via declarative and data-driven techniques like observing variables for changes. Instead, they put the onus on developers, which wastes all of our time and sanity.
5) Do advanced use cases such as components built with other components, subresource integrity (SRI) and observing variables similarly to Vue.js watchers "just work"? I'd be curious to hear your vision for the future.
6) (Special mention) after driving myself crazy manually managing recycler views in iOS, Android and React, I wanted to highlight https://github.com/splinesoft/SSDataSources and https://github.com/SDWebImage/SDWebImage (no affiliation with either). The idea is to populate callbacks to handle the lifecycle of table view cells. That way, an array or generator in memory can be mapped to a table and automagically handle all scrolling and resizing edge cases. Including dynamically loading images and other async data with caching via SDWebImage, without crashing when navigating between view controllers that get disposed, while running as fast as possible. With these metaphors, all other GUI elements can be derived. These are the only frameworks that I've ever used that do all of this correctly. Which let me do declarative web-style programming natively by treating tables as already fully populated, rather than building rows and columns programmatically. Perhaps something in this could inspire a ZjsComponent that "just works" like an ordinary web table, but fast, without the memory hogging of the browser DOM or React's virtual DOM.
> Have you found ZjsComponent easier to debug, and if so, how?
I do find it easier to debug, mostly because the elements show up in the devtools. ISTR putting in a few extra lines to make debugging easier but I've never used the debug feature after putting it in.
It turned out that, due to the reduced functionality (and much reduced surface area) of ZjsComponent, my bugs are all shallow - i.e. they're all in JS methods which can be stepped through in the debugger.
> 5) Do advanced use cases such as components built with other components, subresource integrity (SRI) and observing variables similarly to Vue.js watchers "just work"? I'd be curious to hear your vision for the future.
There is no observability, but composing components with other existing components works quite well[1]. I used a `menu` component that contains subcomponents for client-app routing.
My vision for the future is "I wish someone will look at this and improve the developer experience slightly", just as I looked at custom element web components and improved the developer experience slightly :-)
> (Special mention) after driving myself crazy manually managing recycler views in iOS, Android and React, I wanted to highlight [...]
> The idea is to populate callbacks to handle the lifecycle of table view cells. That way, an array or generator in memory can be mapped to a table and automagically handle all scrolling and resizing edge cases. Including dynamically loading images and other async data with caching via SDWebImage, without crashing when navigating between view controllers that get disposed, while running as fast as possible.
This is quite interesting. I will have a look at the links when I have time, but I think that it might be a good experience for me to implement more complex components (such as active tables like you describe) using ZjsComponent. It would be a good experience to determine where my approach falls short.
----------------------------------------------------------
[1] TBH, the version I use in production might be different to the one published - I wrote the paper, archived the source, etc well over a year ago. I may need to update the github code with whatever I am using in production; even in 100 lines, there's a possibility of bugs.
I like vanilla components... Personally I think that instead of providing either frameworks or vanilla compnents, frameworks should provide a way to compile components to independant vanilla JS (similar to svelte not needing a "runtime").
This way it would not matter this much how you deploy components and this compiling step could probably have its own API with hooks and callbacks.
I came across Yoffee, also requires no specific build system or dependencies. Proposes to either code native web components, or not. No web components means instances are used to get fragments, and still support a state store. Not only one state store can be passed over to instance, as many as we want.
Can you compare with ZjsComponent?
Pure html and JavaScript from my experience is a relief, I still stuff the trauma of seeing jsx error cluttering the console with barely anything useful to troubleshoot.
How doé ZjsComponent compare, any advantage with your paradigm?
> OP doesn’t feel that novel. I read the code and it all has been done before.
Thank you for reading the code. Where was this done before?
A few projects were mentioned in this page (Yoffee, Lit, etc) but after closer examination none of them appear to be "define an object in simple terms, then use HTML to inline it", where object is "HTML fragment with methods in a script element that are scoped only to that specific instance of the fragment".
Thanks to the availability of ChatGPT and Claude, I have actually checked all those links prior to creating my own.
The ZjsComponent thing I use is unlike those things; while the goal of those (and other similar projects) is primarily to perform client-side includes, the goal of zjs-component is to provide client-side object instantiation using a remote object definition.
As you'll see from the above link, the goal was not to simply include remote fragments (with some execution behaviour), but to instantiate a local instance of an object defined in a remote file fragment and support instance-scoped code and HTML.
IOW, it's the scoping to a specific instance that is different from the existing `include` or `import` libraries.
It means when you do this (assuming you wrote a counter-obj.zjsc object):
You will get two independent instances of the counter object, each with their counter variables scoped to their own instance, and each with their HTML scoped to their own instance.
How is this different to the standard WebComponents then?
I mean, web components are JS with HTML in them and you have all the power of JS to manage state and functionality. In case of zjs you have HTML with JS in it and you get an entry point callback to set up your instance (and another to clean it up).
BTW, the API is a little awkward (`exports.onConnect = function () { /.../ }`) and easy to mix up with the standard syntax (`export function onConnect() { /.../ }`) which you can't actually use here.
In either case you have to include a script into your page to make the component work. But also when you have many different components in case of regular webcomponents you use a somewhat readable markup:
To me this looks not unlike the primordial div soup. And on top of it it's 1 more request as compared to regular webcomponents.
The point is if HTML inclusion is not the main feature, but rather the per-instance state then regular WebComponents seem like a better solution if only for being a standard. When a reader sees a WebComponent they immediately know what they're dealing with. zjs appears to solve the same problem but differently, yet uses WebComponint API itself.
> The point is if HTML inclusion is not the main feature, but rather the per-instance state then regular WebComponents seem like a better solution if only for being a standard.
Yes, they are better, but they also require more lines of code to express the same component; I consider zjs-component more as a low-code alternative to extending HTMLElement than as a replacement for HTMLElement.
For example, extending HTMLElement into a `<my-header>` element and a `<my-footer>` element is much more boilerplate than simply writing the header and footer fragments into an HTML file and doing `<zjs-component remote-src-'...'>` for `header.zjsc` and `footer.zjsc`. No class definition, no component registration, no shadow DOM/light DOM distinction.
At this point it is nothing more than a fancy include/import library.
Now once you have that, maybe for some of those remote sources you want some code to execute (headers for example may want to make an additional request to determine how many notification counts to display).
You can, in the header.zjsc file, export a constructor function. No boilerplate, no `class` keyword, no element registration, etc.
Maybe you need a `check for new notifications` button in the header at some later stage, then you can export a function `updateNotifications()` in header.zjsc, add a button somewhere in that file, and set the attribute `onclick="ZjsCompontent.send(this, 'updateNotifications')`.
So, sure, you can do all this with a little bit of boilerplate using standard web components, but I got tired of writing what is essentially 90% the same code for fairly trivial components.
I really really wanted client side includes, but I also really really wanted them to be cheap to write, as in "here's some HTML + JS that comprises an object, with exactly zero boilerplate".
With webcomponents I'd use custom events to ensure that any child element can invoke a method in the webcomponent (adding or removing itself from a menu, for example). Creating the custom event is unwieldy in HTML onclick attributes; can be done but is very much mostly boilerplate.
The zjs-component approach is to use the static method to dispatch an instance method name and arguments to that method call directly in the onclick (or other) attributes.
When I use terms like "lightweight" and "heavyweight", I do not mean in terms of runtime bloat/lack of bloat. I mean in amount of boilerplate lines that are repeated for every component that is created.
The last thing I did not like about web components was the potential for clashes. Where you see:
> To me this looks not unlike the primordial div soup.
because it's all the same tag, I see potential clashes because one library might register the same tagname as another, overwriting it. With web components there is no safe way to have two different menu libraries if both of them register the same tagname.
This problem does not exist if the component is written as a zjs-component because, by necessity alone, it is not possible to have two different components at the same `remote-src` path.
So, yeah, "it's all divs" is a valid complaint, but it does fix a potential clashing when using different components from different authors.
I see what you mean. I do understand the desire to reduce boilerplate. I'd go for a small framework like Lit (I'm sure there are even smaller ones out there) for that but I wouldn't fault anyone for writing their own. I guess, good for you you could get a paper out of it, too. It just doesn't feel particularly novel to warrant one.
> I see what you mean. I do understand the desire to reduce boilerplate. I'd go for a small framework like Lit (I'm sure there are even smaller ones out there) for that but I wouldn't fault anyone for writing their own. I guess, good for you you could get a paper out of it, too. It just doesn't feel particularly novel to warrant one.
I appreciate the sentiment; while this is, indeed, a paper, it is not a published or peer-reviewed paper. I wrote it with no intention of actually publishing it anywhere, and putting it on Arxiv is better long-term than putting it into Github or similar (I expect Arxiv to outlive any code forge).
In much the same way that I looked at web components and thought "What a nice idea. Here is how I can make this incrementally better and support client-side includes as well", I am hoping that this 100-lines of code will someday be looked at by someone else, who will (with the benefit of future knowledge and tech), then say "What a nice idea. Here is how I can make this incrementally better AND support <some future feature we cannot see right now>".
In any case, I thank you for your criticism and your time; your criticism can only make this better (for example, after reading your criticism, I think that showing a side-by-side comparison of my counter example with a custom element
doing the same thing will make it more obvious why I find zjs-components more pleasant to write and use than Custom Elements).
Cheers :-)
[EDIT: Here is the comparison, in case you are still curious]
Here is the small comparison; I gave ChatGPT the ZjsComponent README.md and
got it to write the example in the README as a custom element web component.
Is there some clean way to pass components or just html to components using this framework without having them in strings? This is issue I see with most of these approaches.
With java I can create new package let’s say under com.examle.plugins and extend my app, namely I can add nee classes, new features and have them working with my existing app. Are there such thing with frontend?
Seems to be taking how old frameworks used to work in the old days (download some html/js and run in an closure) and wraps it into webcomponents. Neat, but not sure why its a paper.
Yea, I feel like we are coming full circle with frontend JavaScript. To me this seems like a concept that has been around a while, but it’s being presented as a novel idea. I’m also baffled as to why it’s a paper.
I have a very good reason for that[1]. I will, however, concede that this approach might have been used somewhere public in the past, even though I have no knowledge of seeing this approach previously.
A poster downthread mentioned a link that I am going to read up when I have time, that seems like it is the same approach as zjs-component.
> Posting on Arxiv is a great way to signal that you don't intend to monetise something, aren't pretending to be an influencer and not posing as some thought leader.
In short, I'm not trying to build a community, I'm not trying to get followers, I'm not trying to build an email list.
I want to publish something to the world, but I'm not going to run the gauntlet of peer review publishing.
RSS XML file format could've also been written "before lunch". And yet it changed the world. Sometimes the right "simple thing" at the right time, used in the right way, can change the world.
My favorite example of this is XMLHttpRequest (Ajax). It existed for about 5 years before everyone started using it to create SPAs, which also changed the world forever, so it's always good to see people experiment with different patterns of existing ideas.
Shameless plug; I'm the author. Criticism welcome.
I use this for client-side includes and web components.
No build process, no web-packer, no framework, no npm requirement. Just include the JS in your HTML and then you can create and include components.
I'm guess that since this is an academic paper and not say, a website or GitHub repo, that you're in school. But given the research paper nature of this, did you want to contrast and compare to other methods of authoring web components?
There are many existing and popular approaches out there. Is your take easier, faster, or more capable? That discussion would be interesting.
Two that I help maintain are Lit (https://lit.dev) and Heximal (https://heximal.dev/). Of those, Heximal might share the most with Zjs.
> I'm guess that since this is an academic paper and not say, a website or GitHub repo,
It is a github repo. And a website too, IIRC. And now, finally, it is a paper as well.
I haven't been evangelising this as I have no burning desire for popularity. I posted the link here for discussion and criticism.
> that you're in school.
I wish. I've been professionally developing (i.e. getting paid for it) for close on to 30 years now (I am almost 50).
I like Lit, but was not aware of heximal. Will check it out.
Also Facet.js! Declarative to a fault.
[0]: https://github.com/kgscialdone/facet
I always love seeing more done in the web component space. I think Lit has the no build process captured pretty well and they include things such as a router.
I do prefer the style of of your components more, where you separate out the script and styles with html tags. I don't know if one way or the other is superior for performance, I but just like the separation verse the templated strings in Lit.
With build tools being so straightforward now-a-days, I struggle to see the value in the build less approach. One use case I can think of is maybe a constrained environment where the application contains some kind of customizable user components fully in the browser like a reporting WYSIWIG of some kind.
Is there a particular reason you prefer this approach?
With the caveat that I’m generally outside of the web dev sphere aside from casual tinkering: no build process means no setup and one fewer thing that can break. That’s valuable in itself.
> Is there a particular reason you prefer this approach?
Compared to Lit? Firstly, I like Lit. My approach is better for me because there are far fewer things to know in order to create a reusable object and there are far fewer things to know in order to use an existing object.
IOW, my approach trades off functionality in favour of a lower cognitive burden for me. I just wanted something that had the minimum functionality I need - create an object and then reuse it. For me, anything outside of that goal is peripheral burden that I'd rather not get bogged down in.
I agree with many here that this is not an approach for them, maybe even for most, but I am certain that if I find something useful that increases my velocity of feature delivery, then maybe someone else will as well.
One major benefit of not having a build process is that you can make changes and instantly see the result without delay.
Zjs is a web component for doing client-side includes. The Zjs code itself is itself a web component that extends HTMLElement and registers itself as <zjs-component>. It's only 100 lines long. In its connectedCallback, it fetches the URL of the "remote-src" attribute, and injects its content in innerHTML.
Client-side includes are bad. The industry avoids them, for good reasons.
First, they hurt performance. The client can't start downloading a zjs-component content until its connectedCallback runs. If a zjs-component includes another zjs-component, the client can't start downloading the nested component until its parent component downloads and executes. If your components are nested at N layers, this means that the page won't finish loading until N serial non-parallelizable requests succeed. On cellular networks, where network latency can be measured in seconds, if you have four layers of components, you're looking at adding 10+ seconds to page load.
Second, each client-side include will cause a layout shift, as the <zjs-component> element starts out at 0px tall, and then will change in height as the component loads. This is bad. https://web.dev/articles/cls
Finally, in your article, you claim that reactivity is "out of scope for a component creation mechanism," but there is no universally recognized "scope" for component creation. You'd need to justify the claim that reactivity is out of scope.
If you think reactivity matters at all, then you'd need to demonstrate how to use client-side includes in the context of a framework that does provide reactivity.
HTMX is a lightweight framework that provides a reactivity framework around client-side includes. https://htmx.org/ You might especially appreciate the academic theoretical framework the HTMX team provides in their book, Hypermedia Systems. https://hypermedia.systems/
HTMX is pretty good for what it does; it's not clear that anyone should prefer ZJS over HTMX for anything. But it's also not clear that HTMX is better than more frameworky alternatives, especially React Server Components.
Dan Abramov has written a series of articles explaining the theoretical framework behind RSC. Here's one that's relevant to you. https://overreacted.io/one-roundtrip-per-navigation/
That's a lot you wrote, thank you for all that effort and time. I will do my best to address them:
> Client-side includes are bad. The industry avoids them, for good reasons.
The industry we are talking about does lots of things that has caused many to consider "the industry" to be something of a running joke.
You may not like client-side includes, but it really is something useful to have. Wanting to have client-side includes and not wanting to spin up an entire tech stack of 5 or so different technologies is not unreasonable.
> First, they hurt performance. [...] On cellular networks, where network latency can be measured in seconds, if you have four layers of components, you're looking at adding 10+ seconds to page load.
1. The first time, sure. It's 10s the first time those 4 components are downloaded. Any other component using any of those 4 components on the rest of that page, or if that page is reloaded, will have a 0s download.
2. On cellular networks I currently get longer wait times than that for SPAs used by the mainstream frameworks anyway today. I'm not losing any sleep over the pathological cases which affect mainstream frameworks and libraries as well.
3. I call it pathological because I feel that it would be an extreme case to have lots of components being downloaded, of which only a few get reused. The whole point to reusing is so that the component only gets downloaded once, even if used in multiple places.
> Second, each client-side include will cause a layout shift, as the <zjs-component> element starts out at 0px tall, and then will change in height as the component loads. This is bad. https://web.dev/articles/cls
I agree. It's a trade-off that many sites currently make, SPAs included. While I find it incredibly annoying, this component is for when that trade-off decision is already made.
> Finally, in your article, you claim that reactivity is "out of scope for a component creation mechanism," but there is no universally recognized "scope" for component creation. You'd need to justify the claim that reactivity is out of scope.
I'm a bit confused about this; if there is no universally recognised "scope" for creation of an object in an OO environment, why would I have to first define what scope is and then defend that decision? Most (all?) mainstream OO approaches don't have reactivity within scope for the definition of an object, so I feel that any claim that reactivity is within scope for this specific OO approach needs to provide some sort of reason why that an exception must be made for zjs-component so that it is measured against a higher bar than other OO approaches.
> HTMX is a lightweight framework that provides a reactivity framework around client-side includes. https://htmx.org/ You might especially appreciate the academic theoretical framework the HTMX team provides in their book, Hypermedia Systems. https://hypermedia.systems/
I've read those.
Regardless, if you are building an SPA with Facebook level number of components, then this is not for you. IOW, this may not suit your use-case, or even most of your use-cases. I find it suitable to an embarrassingly large number of use-cases, though.
I build Line-of-Business applications for internal use at companies. Even the most complex UI in these LoB applications have only a few different components, and at most two levels of nesting (these component objects are not like React components at all - they're meant to be instanced inline in HTML, which is both a drawback and an advantage).
Why use ZJS instead of HTMX?
> Why use ZJS instead of HTMX?
Because it does one thing and one thing only: instantiate an HTML element object. You literally cannot write a book about zjs-component the way it was done for HTMX: you'd be done on page one after demonstrating how zjs-component works!
To me there is value in lowering the cognitive burden this significantly; while HTMX is indeed a lower burden than almost everything else, zjs-component is even lower and gets you 80% (or more) of the way there.
You may disagree that the trade-off is worth it, but in many contexts the trade-off is certainly worth it.
In any case, with the way I use zjs-component, all I use are static files for the fragments (fragments are not generated by the server).
When I used HTMX the fragments returned in response to a request were almost never static files, they were all generated on the server (in Go, if you must know) so values could be interpolated. This, by its very nature, is not reusable.
When I create a component that is a zjs-component, the calls it makes to hydrate itself gets data in the responses (JSON, or similar), not HTML. It means that I write an object that has a snippet of HTML, has Javascript that is scoped to that object, and has DOM calls that can be scoped to that specific instance of the object.
HTMX has no scoping or containment of of the code in the HTML, nor can code in a fragment be written to only nagivate/search the DOM in that specific instance because HTMX does not have instances of objects.
The whole reason I use zjs-component over something like HTMX is because zjs-component has the concept of "Objects" (containing HTML and methods), "Instances" which are isolated from each other and a way to instantiate these objects in a page using nothing more than `<zjs-component remote-src='some/remote/path/SomeObject.zjsc'>`.
It is clear to me that having an object containing HTML and methods, with instances isolated from each other, is not of value to you. Even though you do not find value in the trade-off it offers, you must understand that HTMX is not a replacement for zjs-component, and zjs-component is not a replacement for HTMX.
Some of what zjs-component offers cannot be done easily in HTMX (like having nothing but static files for defining objects, scoping DOM nav to the instance of the object that is running that method, etc), and some of what HTMX does cannot be done in zjs-component (such as interpolating values into a template) because it makes no sense to do so in the context of zjs-component.
Seeing as how neither is able to replace the other, why the fixation on HTMX as a replacement for zjs-component?
Excellent comment; it's well-informed, accurate, and actionable feedback. Agreed 100%.
Just use lit, and build to vanilla
>No build process, no web-packer, no framework, no npm requirement. Just include the JS in your HTML and then you can create and include components.
How does the rest of your codebase look?
This is the primary problem with web components. No frameworks sounds nice in theory, but it only solves about 30% of the problem. The rest ends up an ad-hoc mixture of libraries and custom code for state management, routing, styling, cross-component communication, etc, to the point that you end up building your own framework that is brittle and unmaintainable. Applications like this generally end up as a huge confusing web of global event buses or with multiple tightly coupled layers of prop drilling because of that.
There was a dream that was web components once upon a time. It felt like the future. But the APIs ended up half implemented (poorly), and the spec was more or less abandoned by everyone but Google. Browser vendors could have done things right, and focused on pulling in the good things from the framework world (i.e. what happened with jQuery), but they didn't.
Disagree, done several custom frameworks for highly regulated areas and high load userbases - and as long as you keep to seperation of concerns it is a lot more nimble and long term viable - easy to wield that 200000 dependencies just to set a history state
> the spec was more or less abandoned by everyone but Google
This is very, very much not true.
All browsers are very up-to-date with web components APIs, and Safari is leading in some areas like scoped custom element registries, which they're about to ship first.
> How does the rest of your codebase look?
Quit neat, compared to the React/Redux things I've maintained in the recent past.
> No frameworks sounds nice in theory, but it only solves about 30% of the problem.
"No framework" is not meant to be a prescription, it is an indication of the dependencies.
IOW, I mean to say "No framework required", I did not mean to say "Don't use a framework with zjs-component".
> the [web component] spec was more or less abandoned by everyone but Google.
Are you being sarcastic, maybe?
I think Google has done a great job of making Web Components seem both poorly implemented and way too complex. If Angular is the "Google blessed" way of writing Web Components and Angular makes a terrible hash of it, who can build good Web Components?
Angular and a couple other well known frameworks were targeting for far too long pre-spec Google-specific versions of Web Components and Web Components moved quickly past that and settled into cross-vendor specs with smarter defaults and simpler APIs a lot of the marketing damage was already done that Web Components were too complex and too poorly supported.
So much of the Web Components world is stuck with the quagmire of the Shadow DOM, and while there are some generalized use cases for the Shadow DOM, I have the harsh opinion that the Shadow DOM was mostly invented to be "better IFRAMES for Ad Publishers" and not much else. I don't think enough web developers yet realize that the Shadow DOM is entirely optional and its complexity is so much easier to ignore and toss than to try to utilize.
> focused on pulling in the good things from the framework world (i.e. what happened with jQuery), but they didn't.
I'm coming to the growing opinion that between Web Components and the TEMPLATE tag Browser vendors have finally caught up with some of the best ideas of the Knockout-era "Progressive Enhancement" world, assuming you ignore the Shadow DOM as mostly YAGNI.
Sure, a lot of cross-component communication and some of state management goes back to DOM APIs and DOM event management, but there used to be a lot of knowledge in those areas and maybe it past time to return to Vanilla JS ideas about some of that. In the time since jQuery and Knockout, all browsers today have much more consistent DOM APIs for event management, at least.
(Similar for routing. Vanilla routing, even "SPA" routing is easier than ever with modern APIs, even the API not fully standardized and cross-browser yet, but also the improved APIs for hash navigation. Also, Web Components seem a great reminder that MPA routing is great and brilliant, and so well tested in browsers and servers. With View Transitions now generally well supported, and getting better, many MPAs feel as good or better than SPAs even.)
(And I feel like most styling problems with Web Components are self-inflicted with and by the Shadow DOM. It's nice to embrace the cascade again, if you can.)
I was bullish on Web Components for a while, but most of my complaints have disappeared in recent months, and especially in realizing that I was very happy ignoring everything about the Shadow DOM. Sure, I'm not using Web Components without a "framework" but I'm using a far smaller "view engine" than a React or Angular, a lot more "Vanilla", and feeling a bit like it's a Revenge of Knockout sort of era.
> Sure, a lot of cross-component communication and some of state management goes back to DOM APIs and DOM event management, but there used to be a lot of knowledge in those areas and maybe it past time to return to Vanilla JS ideas about some of that.
My approach is, actually, an attempt to inject some Vanilla into front-end component development.
I think many of us, as this linked article that kicked off this discussion included, are thinking of various ways of using more Vanilla approaches. I appreciate the above poster's complaints that we aren't in some promised land of being able to do things 100% Vanilla without some form of reactivity management. That's still all in progress, no matter where you are in the Observables versus Signals "debate", Browsers are back to showing interest in native specs for one or both. (I personally think Signals are just "worse, poorly encapsulated Observables", but either proposal is a good building block for whatever the next steps are and thinner libraries to build on top of them will be.) But Web Components are mostly some steps in the right direction, I think, while that other work remains ongoing.
I also like gradual typing too much to expect to be 100% Vanilla any time soon. Typescript is too handy a verification/testing tool and while I'm still watching the native type stripping proposal with TC-39 with a lot of interest, it's certainly not on a fast track.
I haven't had too much trouble with Shadow DOM so far, and I was especially surprised to learn about `<template shadowrootmode="open">` recently. It makes SSR rendering of web components with shadow DOMs viable and appears to be well supported in browsers.
Angular has nothing to do with web components. It isn't even a way to write web components.
If anything is the "Google blessed" way of writing web components (Google doesn't really work that way), it's Lit: https://lit.dev/
I'm aware of Lit and thought about mentioning some of its past versions too from the pre-spec era, but I think those are even more forgotten.
Around Angular 8-14 era Angular had a lot of "Angular is Web Components" marketing and assured devs at the time that the Angular way including CSS Modules support and its HTML Template language (and its super complex compiler) were "browser standards and the future of Web Components". Obviously none of that played out, CSS Modules are still in spec debate, the TEMPLATE tag was much simplified and no "template language" has been knighted as "the template language". But it was all so heavily marketed and it left scars in what people think of when they think of Web Components. People still expect CSS Modules. People still expect some sort of "blessed" template language out of the box for "Vanilla templating". Among other curious decisions by people marketing Angular to developers as a way to prepare for Web Components. (We can talk for hours about everything wrong with Zone.js and why it has taken Angular to version 20 to start to undo a lot of that [brain]damage.)
Lit was never quite so extreme in its worst pre-spec days, and it is useful for its templating language looked different enough from Angular's to, even at the time, prove that Angular's template language was neither HTML nor necessarily the template language to be knighted by Web Components. But it still had some versions with questionable Google-only decisions that are best forgotten today, and apparently easily forgotten today.
Of course, you don't have to take my anecdata on it, the sources still exist. If only Google made a search engine that works in 2025 maybe you could even find them.
> Lit was never quite so extreme in its worst pre-spec days
"pre-spec days"? What are you even talking about?
Lit 1.0 used non-standard decorators in 2019: https://lit.dev/blog/2019-02-05-lit-element-and-lit-html-rel...
A decorators proposal (and it was a different one than what Google was building stuff off of in 2019) didn't make it to Stage 3 until 2022 (and still hasn't made it to Stage 4).
This also contributed to the idea for years that Web Components were more complicated to support in non-Google browsers or without complicated build processes.
Lit has always supported plain JS ways of authoring components. Just because it also supported TypeScript and Babel decorators doesn't make it "pre-spec".
Lots of developers like decorators, we gave them the option. _shrug_
TLDR: Browser vendors made Shadow DOM for themselves.
Browser implementors use Shadow DOM extensively under the hood for built-in HTML elements with internal structure like range inputs, audio and video controls, etc. These elements absolutely need to work everywhere and be consistent, so extreme encapsulation and fixed api for styling them is an absolute must.
The Shadow DOM API is the browsers exposing, to developers, a foundational piece of functionality.
If you’re thinking about whether Shadow DOM is appropriate for your use case, consider how/why the vendors use it —- when an element’s API needs to be totally locked down to guarantee it works in contexts they have no control over. Conversely, if your potential use case is scoped to a single project, the encapsulation imposed (necessarily!) by Shadow DOM is probably overkill.
Web components are a decent way to make reusable UI, but if they don’t have strong encapsulation needs, you might avoid Shadow DOM.
This is great! A few comments/questions if you have time to respond:
1) I thought that React already worked this way until I learned it, and was horrified. Similar disillusionment with Ruby on Rails, Objective-C on iOS and Java on Android. Why someone would want to mix code and markup, rather than define components via code to be used within markup, blows my mind. Said as someone who uses PHP daily and sees JSX as its frontend analog.
2) I appreciate that async/await and promises were deliberately left out of the spec. I view their use as an anti-pattern, since I've programmed both async/nonblocking and sync/blocking sockets logic, and view sync as far superior if deterministic behavior is the goal. I know this is a contentious subject, but IMHO async will someday be considered the goto of the web and be deprecated across most languages. Better approaches such as scatter/gather arrays, coroutines and channels (like in Go) exist to remove the temporal/imperative aspect of logic and make it declarative without requiring red/blue functions.
3) The main problem I've encountered with React is that it's nearly untraceable in web inspector. It's impossible to tell which component object instance is associated with an HTML element. It's hard to tell where attribute values came from, unlike function arguments: are they props that aren't declared in a function prototype? are they magically mapped from redux state? are they derived neither in the constructor nor componentDidMount() or affected by an affect they weren't supposed to be? etc etc etc. Component methods are buried in dozens of anonymous functions and boilerplate. Promise values can't be inspected until their then() method without using tricks like Promise.resolve(), which don't always match intuition. Redux state can't be conveniently inspected without adding debug lines to the source code and/or installing a browser extension. Code gets mutated by the build pipeline so it's hard to find code snippets to set breakpoints. I could go on. Have you found ZjsComponent easier to debug, and if so, how?
4) I applaud your use of caching for client-side includes. IMHO much of the complexity of React is around its deliberate rejection of HTTP idioms and metaphors. We already solved these problems declaratively in the mid 1990s, why are we manually managing initialization and state, even if through Redux? That was a rhetorical question, but really, React doesn't seem nearly as reactive as something like a spreadsheet that just re-renders cells that reference cells that changed. Basically all of its component methods could be replaced by the render() method containing a JSX template, with the state boilerplate handled by the framework internally via declarative and data-driven techniques like observing variables for changes. Instead, they put the onus on developers, which wastes all of our time and sanity.
5) Do advanced use cases such as components built with other components, subresource integrity (SRI) and observing variables similarly to Vue.js watchers "just work"? I'd be curious to hear your vision for the future.
6) (Special mention) after driving myself crazy manually managing recycler views in iOS, Android and React, I wanted to highlight https://github.com/splinesoft/SSDataSources and https://github.com/SDWebImage/SDWebImage (no affiliation with either). The idea is to populate callbacks to handle the lifecycle of table view cells. That way, an array or generator in memory can be mapped to a table and automagically handle all scrolling and resizing edge cases. Including dynamically loading images and other async data with caching via SDWebImage, without crashing when navigating between view controllers that get disposed, while running as fast as possible. With these metaphors, all other GUI elements can be derived. These are the only frameworks that I've ever used that do all of this correctly. Which let me do declarative web-style programming natively by treating tables as already fully populated, rather than building rows and columns programmatically. Perhaps something in this could inspire a ZjsComponent that "just works" like an ordinary web table, but fast, without the memory hogging of the browser DOM or React's virtual DOM.
I thank you for your kind feedback :-)
> Have you found ZjsComponent easier to debug, and if so, how?
I do find it easier to debug, mostly because the elements show up in the devtools. ISTR putting in a few extra lines to make debugging easier but I've never used the debug feature after putting it in.
It turned out that, due to the reduced functionality (and much reduced surface area) of ZjsComponent, my bugs are all shallow - i.e. they're all in JS methods which can be stepped through in the debugger.
> 5) Do advanced use cases such as components built with other components, subresource integrity (SRI) and observing variables similarly to Vue.js watchers "just work"? I'd be curious to hear your vision for the future.
There is no observability, but composing components with other existing components works quite well[1]. I used a `menu` component that contains subcomponents for client-app routing.
My vision for the future is "I wish someone will look at this and improve the developer experience slightly", just as I looked at custom element web components and improved the developer experience slightly :-)
> (Special mention) after driving myself crazy manually managing recycler views in iOS, Android and React, I wanted to highlight [...] > The idea is to populate callbacks to handle the lifecycle of table view cells. That way, an array or generator in memory can be mapped to a table and automagically handle all scrolling and resizing edge cases. Including dynamically loading images and other async data with caching via SDWebImage, without crashing when navigating between view controllers that get disposed, while running as fast as possible.
This is quite interesting. I will have a look at the links when I have time, but I think that it might be a good experience for me to implement more complex components (such as active tables like you describe) using ZjsComponent. It would be a good experience to determine where my approach falls short.
---------------------------------------------------------- [1] TBH, the version I use in production might be different to the one published - I wrote the paper, archived the source, etc well over a year ago. I may need to update the github code with whatever I am using in production; even in 100 lines, there's a possibility of bugs.
For anyone curious, the source code: https://github.com/lelanthran/ZjsComponent/
I like vanilla components... Personally I think that instead of providing either frameworks or vanilla compnents, frameworks should provide a way to compile components to independant vanilla JS (similar to svelte not needing a "runtime").
This way it would not matter this much how you deploy components and this compiling step could probably have its own API with hooks and callbacks.
Older Svelte versions may not have needed a runtime, but Svelte 5 definitely does.
Runtimes are good, the abilities of web components suck compared to most framework runtimes.
I came across Yoffee, also requires no specific build system or dependencies. Proposes to either code native web components, or not. No web components means instances are used to get fragments, and still support a state store. Not only one state store can be passed over to instance, as many as we want.
Can you compare with ZjsComponent?
Pure html and JavaScript from my experience is a relief, I still stuff the trauma of seeing jsx error cluttering the console with barely anything useful to troubleshoot.
How doé ZjsComponent compare, any advantage with your paradigm?
It’s remarkable that you can write a paper about a 100 lines of code (literally). Maybe I should try that some time.
One of John Nash's most famous paper's is 1 page: https://www.pnas.org/doi/abs/10.1073/pnas.36.1.48
Large systems is not a requirement for novelty.
OP doesn’t feel that novel. I read the code and it all has been done before.
> OP doesn’t feel that novel. I read the code and it all has been done before.
Thank you for reading the code. Where was this done before?
A few projects were mentioned in this page (Yoffee, Lit, etc) but after closer examination none of them appear to be "define an object in simple terms, then use HTML to inline it", where object is "HTML fragment with methods in a script element that are scoped only to that specific instance of the fragment".
I'm curious to see prior usage.
There are quite a few HTML include components out there. A quick GH search return a lot of results.
From very simple (https://github.com/include-html/include-html.github.io/blob/..., from 3 years ago), to more sophisticated, including script execution (https://github.com/SirPepe/html-import/blob/master/src/html-..., from 4 years ago; or even https://github.com/webcomponents/html-imports/blob/master/sr..., dating back 9 years). I haven't checked all of them but there's a decent chance there's something that covers each feature of OP, maybe even at once.
Thanks to the availability of ChatGPT and Claude, I have actually checked all those links prior to creating my own.
The ZjsComponent thing I use is unlike those things; while the goal of those (and other similar projects) is primarily to perform client-side includes, the goal of zjs-component is to provide client-side object instantiation using a remote object definition.
The paper itself may be a bit arcane and obscure, but as a quick example of what zjs-component was intended to do, see the GH page: https://github.com/lelanthran/ZjsComponent/tree/master?tab=r...
As you'll see from the above link, the goal was not to simply include remote fragments (with some execution behaviour), but to instantiate a local instance of an object defined in a remote file fragment and support instance-scoped code and HTML.
IOW, it's the scoping to a specific instance that is different from the existing `include` or `import` libraries.
It means when you do this (assuming you wrote a counter-obj.zjsc object):
You will get two independent instances of the counter object, each with their counter variables scoped to their own instance, and each with their HTML scoped to their own instance.How is this different to the standard WebComponents then?
I mean, web components are JS with HTML in them and you have all the power of JS to manage state and functionality. In case of zjs you have HTML with JS in it and you get an entry point callback to set up your instance (and another to clean it up).
BTW, the API is a little awkward (`exports.onConnect = function () { /.../ }`) and easy to mix up with the standard syntax (`export function onConnect() { /.../ }`) which you can't actually use here.
In either case you have to include a script into your page to make the component work. But also when you have many different components in case of regular webcomponents you use a somewhat readable markup:
With zjs you have to use the same tag for everything: To me this looks not unlike the primordial div soup. And on top of it it's 1 more request as compared to regular webcomponents.The point is if HTML inclusion is not the main feature, but rather the per-instance state then regular WebComponents seem like a better solution if only for being a standard. When a reader sees a WebComponent they immediately know what they're dealing with. zjs appears to solve the same problem but differently, yet uses WebComponint API itself.
> The point is if HTML inclusion is not the main feature, but rather the per-instance state then regular WebComponents seem like a better solution if only for being a standard.
Yes, they are better, but they also require more lines of code to express the same component; I consider zjs-component more as a low-code alternative to extending HTMLElement than as a replacement for HTMLElement.
For example, extending HTMLElement into a `<my-header>` element and a `<my-footer>` element is much more boilerplate than simply writing the header and footer fragments into an HTML file and doing `<zjs-component remote-src-'...'>` for `header.zjsc` and `footer.zjsc`. No class definition, no component registration, no shadow DOM/light DOM distinction.
At this point it is nothing more than a fancy include/import library.
Now once you have that, maybe for some of those remote sources you want some code to execute (headers for example may want to make an additional request to determine how many notification counts to display).
You can, in the header.zjsc file, export a constructor function. No boilerplate, no `class` keyword, no element registration, etc.
Maybe you need a `check for new notifications` button in the header at some later stage, then you can export a function `updateNotifications()` in header.zjsc, add a button somewhere in that file, and set the attribute `onclick="ZjsCompontent.send(this, 'updateNotifications')`.
So, sure, you can do all this with a little bit of boilerplate using standard web components, but I got tired of writing what is essentially 90% the same code for fairly trivial components.
I really really wanted client side includes, but I also really really wanted them to be cheap to write, as in "here's some HTML + JS that comprises an object, with exactly zero boilerplate".
With webcomponents I'd use custom events to ensure that any child element can invoke a method in the webcomponent (adding or removing itself from a menu, for example). Creating the custom event is unwieldy in HTML onclick attributes; can be done but is very much mostly boilerplate.
The zjs-component approach is to use the static method to dispatch an instance method name and arguments to that method call directly in the onclick (or other) attributes.
When I use terms like "lightweight" and "heavyweight", I do not mean in terms of runtime bloat/lack of bloat. I mean in amount of boilerplate lines that are repeated for every component that is created.
The last thing I did not like about web components was the potential for clashes. Where you see:
> To me this looks not unlike the primordial div soup.
because it's all the same tag, I see potential clashes because one library might register the same tagname as another, overwriting it. With web components there is no safe way to have two different menu libraries if both of them register the same tagname.
This problem does not exist if the component is written as a zjs-component because, by necessity alone, it is not possible to have two different components at the same `remote-src` path.
So, yeah, "it's all divs" is a valid complaint, but it does fix a potential clashing when using different components from different authors.
I see what you mean. I do understand the desire to reduce boilerplate. I'd go for a small framework like Lit (I'm sure there are even smaller ones out there) for that but I wouldn't fault anyone for writing their own. I guess, good for you you could get a paper out of it, too. It just doesn't feel particularly novel to warrant one.
> I see what you mean. I do understand the desire to reduce boilerplate. I'd go for a small framework like Lit (I'm sure there are even smaller ones out there) for that but I wouldn't fault anyone for writing their own. I guess, good for you you could get a paper out of it, too. It just doesn't feel particularly novel to warrant one.
I appreciate the sentiment; while this is, indeed, a paper, it is not a published or peer-reviewed paper. I wrote it with no intention of actually publishing it anywhere, and putting it on Arxiv is better long-term than putting it into Github or similar (I expect Arxiv to outlive any code forge).
In much the same way that I looked at web components and thought "What a nice idea. Here is how I can make this incrementally better and support client-side includes as well", I am hoping that this 100-lines of code will someday be looked at by someone else, who will (with the benefit of future knowledge and tech), then say "What a nice idea. Here is how I can make this incrementally better AND support <some future feature we cannot see right now>".
In any case, I thank you for your criticism and your time; your criticism can only make this better (for example, after reading your criticism, I think that showing a side-by-side comparison of my counter example with a custom element doing the same thing will make it more obvious why I find zjs-components more pleasant to write and use than Custom Elements).
Cheers :-)
[EDIT: Here is the comparison, in case you are still curious]
Here is the small comparison; I gave ChatGPT the ZjsComponent README.md and got it to write the example in the README as a custom element web component.
Here are the two implementations:
Implementation as a zjs-component:
Usage of zjs-component: Implementation as a custom element web component: Usage of the custom element web component:This kinda reminds me of a slimmed down version of Polymer.
I like the simplicity, and it might have potential to stack upon itself to provide the other missing elements like reactivity, etc.
Keep up the good work
Is there some clean way to pass components or just html to components using this framework without having them in strings? This is issue I see with most of these approaches.
> Is there some clean way to pass components or just html to components using this framework without having them in strings?
No, there isn't. Parameters are passed in as plain strings.
> This is issue I see with most of these approaches.
I didn't need to solve 100% of the problem, getting the most common 90% is sufficient.
This is cool. Remindes me of simplicity of https://tg.pl/drab which need phoenix/elixir.
With java I can create new package let’s say under com.examle.plugins and extend my app, namely I can add nee classes, new features and have them working with my existing app. Are there such thing with frontend?
Hate to be the one to pull a dang, but, remember:
> Be kind. Don't be snarky. Converse curiously; don't cross-examine. Edit out swipes.
— https://news.ycombinator.com/newsguidelines.html
Seems to be taking how old frameworks used to work in the old days (download some html/js and run in an closure) and wraps it into webcomponents. Neat, but not sure why its a paper.
Yea, I feel like we are coming full circle with frontend JavaScript. To me this seems like a concept that has been around a while, but it’s being presented as a novel idea. I’m also baffled as to why it’s a paper.
> Neat, but not sure why its a paper.
I have a very good reason for that[1]. I will, however, concede that this approach might have been used somewhere public in the past, even though I have no knowledge of seeing this approach previously.
A poster downthread mentioned a link that I am going to read up when I have time, that seems like it is the same approach as zjs-component.
-----------------------------------
[1] Not "done on a bet", but pretty similar.
Okay, but what is the reason? I didn't understand it from your comment.
As I said elsewhere:
> Posting on Arxiv is a great way to signal that you don't intend to monetise something, aren't pretending to be an influencer and not posing as some thought leader.
In short, I'm not trying to build a community, I'm not trying to get followers, I'm not trying to build an email list.
I want to publish something to the world, but I'm not going to run the gauntlet of peer review publishing.
Arxiv aligns with my goal for this.
He has maybe mentioned to someone that he'll write a paper on this topic
[flagged]
[flagged]
RSS XML file format could've also been written "before lunch". And yet it changed the world. Sometimes the right "simple thing" at the right time, used in the right way, can change the world.
My favorite example of this is XMLHttpRequest (Ajax). It existed for about 5 years before everyone started using it to create SPAs, which also changed the world forever, so it's always good to see people experiment with different patterns of existing ideas.