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):

    <zjs-component remote-src='counter-obj.zjsc' start-at='100'> </zjs-component>
    <zjs-component remote-src='counter-obj.zjsc' start-at='200'> </zjs-component>
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:

    <x-counter start-at="100"></x-counter>
    <x-counter start-at="200"></x-counter>
    <x-whatever></x-whatever>
With zjs you have to use the same tag for everything:

    <zjs-component remote-src='counter-obj.zjsc' start-at='100'></zjs-component>
    <zjs-component remote-src='counter-obj.zjsc' start-at='200'></zjs-component>
    <zjs-component remote-src='whatever-obj.zjsc'></zjs-component>
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:

    <div>
        Counter Value: <span name=counter-value>0</span>
    </div>
    <div>
        <button onclick='ZjsComponent.send(this, "increment", 1)'> +1 </button>
        <button onclick='ZjsComponent.send(this, "increment", 2)'> +2 </button>
        <button onclick='ZjsComponent.send(this, "increment", 5)'> +5 </button>
    </div>

    <script>
        function increment(amount) {
            const el = this.querySelector("[name='counter-value']");
            el.textContent = parseInt(el.textContent) + amount;
        }

        exports.increment = increment;
    </script>
Usage of zjs-component:

   <zjs-component remote-src=counter.zjsc> </zjs-component>
Implementation as a custom element web component:

    <!-- counter-component.js -->
    <script>
    class CounterComponent extends HTMLElement {
      constructor() {
        super();

        this.attachShadow({ mode: 'open' });

        this.shadowRoot.innerHTML = `
          <div>
            Counter Value: <span id="counter-value">0</span>
          </div>
          <div>
            <button data-amount="1"> +1 </button>
            <button data-amount="2"> +2 </button>
            <button data-amount="5"> +5 </button>
          </div>
        `;
      }

      connectedCallback() {
        this.shadowRoot.querySelectorAll('button').forEach(btn => {
          btn.addEventListener('click', () => {
            const amount = parseInt(btn.getAttribute('data-amount'), 10);
            this.increment(amount);
          });
        });
      }

      increment(amount) {
        const valueEl = this.shadowRoot.getElementById('counter-value');
        valueEl.textContent = parseInt(valueEl.textContent, 10) + amount;
      }
    }

    customElements.define('counter-component', CounterComponent);
    </script>
Usage of the custom element web component:

    <counter-component></counter-component>

I really like what you have done.

I know you are trying to avoid boiler plate but I'm wondering how technically difficult it would be to provide an alternative for those of us who really like named components? Something like:

  <script>
    ZjsComponent.register("counter-component", "counter.zjsc");
  </script>
Then I can just use in the named way, like:

  <counter-component start-at="100"><counter-component>

As an opt-in, probably not a bad idea. I'll look into it.