Hacker News new | past | comments | ask | show | jobs | submit login
Implementing single-file web components (ckeditor.com)
113 points by k0n0pka on March 23, 2020 | hide | past | favorite | 67 comments



I've been on the Polymer team, making web component libraries, for a long time. And while we used to promote "single file" style authoring in HTML files, we've come around (me very strongly) to the idea that it's better to do single-file components in JavaScript.

This has resulted in our newer lit-html and LitElement libraries, where the DOM rendering is done in JavaScript, but it's still possible to do single-file style:

    import {LitElement, css, html} from 'lit-element';

    export class MyElement {
      static styles = `
        :host {
          display: block;
          background: red;
        }
      `;

      static properties = {
        name: {},
      };

      render() {
        return html`
          <h1>Hello ${this.name}</h1>
        `;
      }
    }
    customElements.define('my-element', MyElement);
There are a number of reasons why this is better, IMO. Not requiring any builds and utilizing the JS module graph are big ones.

The most important though is that the essential job of templates is to interpolate data into DOM. It's much easier this by building DOM in JS, than by trying to access and transform data in HTML.

Templates need some kind of expression language. If the template is in HTML then you need to invent expression delimiters, the expression syntax and semantics, and a way to get data into HTML: HTML doesn't natively have a way to refer to JavaScript objects. This is all custom, needs code to implement, and you need to train developers on it.

On the other hand if you write templates in JS you can take advantage of full JavaScript expressions, imports, and natural lexical scoping... and the data is typically already in JavaScript.


> On the other hand if you write templates in JS

As a non-programmer (UX), I used to watch Polymer with so much interest, since it had HTML tag-like syntax. I copy paste component codes & construct an UI. There was simplicity in <googlemaps lat="89.2" long="123.2" draggable="false"></googlemaps>

Now with lit-html, it went from unique HTML/WebComponent for non-coders, to another one of the JS frameworks out there.


I'm only talking about the _implemention_ of web components. You can still use any custom element from HTML.

If <google-maps> was rewritten in LitElement, you'd still be able to write:

    <google-maps lat="89.2" long="123.2" draggable="false"></google-maps>


I used to like the idea of (originally zope's) metal/tal templates. In theory the templates where valid html, complete with example values - and could be edited directly.

Unfortunately this breaks down as soon as you start to break up the page into sections (you end up with a component that needs a "layout" to provide surrounding <html><body>(...) etc).

Ironically, now with html5 being rather loose and fast, you might actually get a decent preview of such a widget again... But it's still not right for "web applications" as opposed to "hypertext applications".

Full applications need full gui widgets - and html isn't a good fit.


You can absolutely still use React/Polymer/other JS frameworks in that same manner, but you have to learn a little bit of coding boilerplate.

I think one difference between lit-html and React/other JS frameworks is that lit-html knows which portions of its template are static, and can avoid reconciling static portions. This is the same optimization as what Svelte can do, but without the need to change your toolchain.

Disclaimer: I work at Google, but on a completely unrelated team.


Somehow, I don't know why especially, I've come to enjoy JSX/TSX syntax with styled components. It just is simpler for my brains, I guess. To think of all just as custom components, with their logic included as a single component. Nice to see though that also people working at Polymer do see the benefits of only JS.

But having separate style-sheets, I don't know. I have done Vue with SASS etc, but the abstraction I felt was a bit too vague. At least our team got a little lazy and wrote massive style sheets with nested classes, and similarly we then ended up fitting too much HTML to the template.

With JSX/TSX I can keep myself from doing those mistakes, and just make small simple components, that do not go overboard with logic. Although I have to say performance-wise React is still very much pain in the ass to debug, and I can't say how many working days I have wasted on some dumb rendering bug, where the components would rerender constantly.

And it's way, way too hard to find those problems and I think this is the weak point of React. No one likes figuring out how to performance optimize a simple list. It should work straight out-of-the-box! I hope that Polymer team will try to make their framework a little better than React on this part.


If you think it's bad in React (and I agree that this can be a pain point at times - although actually long lists are a weak point of the web platform in general), definitely don't try Polymer!

A colleague of mine went with Polymer for a prototype (against my advice), and switched to Angular within 2 weeks because the performance of Polymer was so bad. I think the web-component approach is inherently inefficient for large apps, because you can't batch DOM reads and write like the other frameworks do.

Were you using CSS modules with your SASS? I've found a few base styles in global SASS along with scoped styles in CSS (SASS) modules works pretty well for keeping things simple.


Polymer has generally been as fast or faster than Angular and React. We've seen that pretty consistently in Todo MVC, Hacker News, etc., shootouts.

But Polymer isn't our current generation library anymore. LitElement, which is based on the lit-html template library, is. LitElement measures clearly faster than Angular and React on shootouts.

There's nothing inherently inefficient about web components, and we do batching of DOM updates much like frameworks. LitElement is even able to yield between different component renders naturally, like React concurrent mode, to not jank the page.


Huh, well it figures that it isn't an easy problem to solve.

EDIT: I was looking at the wrong. I see, it seems like a nifty way to combine styles. But hmm, I guess this one of those things that what works for you, is good enough.

Somehow keeping the styles in the same file, inside the styled components, just makes everything easier to me. Granted the string interpolation is bit annoying at times for accessing the theme variables but ehh.


Remove a couple special characters and it's starting to look pretty much like QML (http://qmlbook.github.io/ch04-qmlstart/qmlstart.html) :p


With JS being the better way, why would people chose that over, say, Vue ? For a few ko you can do that and more. And transition to a full scale infra if needed later.


The unfortunate issue with components is that they're kind of like wysiwygs or bootstraps. They save a small amount of trouble in the beginning, but always bite you in the end.

That being said, if it's all in a single file, you can copy and paste that file into your project and then modify the thing, unless the single file is itself a bundle.

At my last company we tried to make a button component. But then we wanted to be able to change url with it. But then if you ctrl-click, it should open in a new tab. And then there were certain styles of button, with mixed and matched shapes.

Nowadays, I see components as single use, or specific to a project. And for anything that's more about a visual than a function (ie buttons), just use css classes and implement the underlying Elements from scratch.

Point being, everyone wishes they could model the perfect abstraction of a component, but then you start adding config to make it do everything until it's basically its own miniature programming language (with worse docs).


I've encountered this issue a lot. Really, a button is a very fine-grained bit of functionality to write a component for, in my opinion. A component is just the wrong level of abstraction for a button. For me, it's worked to make a component when I have a grouping of elements that I will re-use together. For instance, I wouldn't make a component for a form field, but I would make a component for an entire form, if I would use that form again.


I like the cut of your jib! I recently inherited a project with nested components and inheritance and all these insane levels of abstraction and it's impossible know what's going on anywhere. The crazy part is that all this abstraction is hiding incredibly simple HTML underneath it all, and the components are rarely used in more than one place.

Anytime I need to do some real work on this project, it's easier to just replace the call site of the component with the final HTML output, and move any required logic to the top level.

Turns out that across the multiple call sites, the final HTML output isn't really the same and this forgoes all the messy conditional logic and awkward data massaging that the original developer included.

As far as buttons go, the easiest is to just use

  <a href="#" class="button">Click Here</a>
and

  <a href="#" class="button button-primary">Click Here</a>
everywhere. Look at that, turns out CSS is a component framework already ;)


It is insane that HTML has no "include". None of all this would be needed if we could just do:

<include src="myComponent.html>


it was proposed and implemented, then the standards body did an about-face.

https://hacks.mozilla.org/2015/06/the-state-of-web-component... (ctrl+f "HTML Imports")

"HTML Imports do lend themselves well to a simpler/more declarative workflow. [...] Imports don’t offer enough control to be taken seriously as a dependency management solution. [...] Mozilla had a working implementation behind a flag, but struggled through an incomplete specification."

which to me reads a bit like: "js frameworks won't use it, and the spec didn't get enough love because everybody's only thinking about frameworks rather than vanilla-js."


Actually, they are waiting for module Imports to standardize and get some actual real-world usage. Once that happens, they may move onto HTML Imports. But I feel like they could probably get away with just doing `import Template from "./somefile.html"` using module resolvers.


It's disappointing that JS Frameworks are driving the HTML spec.


HTML Imports was not an <include> mechanism though.

It was much more akin to JS imports. HTML files would be side-effectful, then you could use the elements they defined, like <x-foo>.


Sigh.

A web that was built on documents fragments because the JS and server-side developers think writing raw HTML and CSS is gross.

Email developers would like a word with them.

This isn't going away, and at some point, adding more abstractions is actually harmful.

As I have little else to do at this point - Tl;dr When you work for a public library, but are closed the public, nothing happens - I'm rewriting a small microsite I made for job hunting purposes.

Other than a Service Worker cache, it's markup and styles all the way down, stitched together with Make.

For my blog, I've stuck with Liquid, because Jekyll - which I actually like (ducks) - will be interesting to see if this gets revived and actually lands one day.


_This_ front end developer _loves_ writing HTML and CSS, regardless of my previous several years of JS, React, Angular, Backbone etc.


would you mind sharing the source or stack for your microsite? your setup sounds like something i'd enjoy using.


Code is here - https://github.com/chrisfinazzo/reverse-job-posting

Started on GH Pages, but moved to Netlify which gives me a bit more fine grained control.

Pardon the dust;

First, I'm not a graphic designer and second this is actually an older deploy that I've "locked" so I can continue tinkering with the master branch behind the scenes. Will be a single page with just anchors to navigate when done.

Everything old is new again. Homepages, kids, ask your parents about them :)


I vaguely recall doing things with server-side includes and ".shtml" in the late '90s...


I still have a website that uses server-side includes fro the navbar and other common page elements.

https://en.wikipedia.org/wiki/Server_Side_Includes


There's a web component for that! https://github.com/justinfagnani/html-include-element

    <script type="module" src='https://unpkg.com/html-include-element'></script>
    <html-include src="./my-local-file.html"></html-include>


Back in the day we had something which was often used:

<!--#include virtual="/cgi-bin/counter.pl" -->


That was the original web components. Much better. Then they changed it to make it worse. :(


Yeah I remember reading about web components, getting excited and building a prototype. Then when I worked again on the project shadow DOM API changed and HTML imports were removed :| At this point I highly suspects the people behind the Web shit-show (spec writers and implementors) keep things bad on purpose as a way to get job security.


That, and much more, is implemented in SGML on which HTML is based.


Then why not a templating language with looping constructs until it is Turing complete? Because base HTML is a document not a document generation language. Um, until javascript. Oh well.

Thus JS library references basically enable HTML includes.


a document that supports embedding other documents is still just a document.


Ah, but a document that supports including itself, that's the bomb!


That wouldn't be a solution to everything, but at least it would be a starting point to defining a fully-declarative custom element solution so that custom elements can be used in a non-JS context.


question for HN: do single-file components _actually_ work better for you than having the script, markup, and styling in three separate files?

i've been doing a little Vue and a little WPF/XAML lately. with Vue SFCs, i find myself constantly scrolling up and back down in long files. with WPF, i'm switching between the XAML markup in one editor tab and the code-behind in another. i find the WPF workflow is better for me, because i lose my place less often.


I've spent the past 5 years with various blends of SASS, CSS, CSS-in-JS, Redux, Rx, React, Material-UI and a few other bits and bobs (and several years of front end dev with other tech before that, too).

I prefer by far keeping the CSS (or SASS) separate from the rest. React's mix of JS and HTML-ish is very comfortable, but I can't say I love it more than "native JS + some kind of templating". However, I'm absolutely certain that I prefer the CSS left outside the rest. My reasons include:

- you (probably) don't have to recompile the whole JS app to get your style changes - it's much easier to see the relevant JS when you don't have to skip over CSS or lengthy class names - it's much easier to see how bits of CSS relate to one another when they're in the same file (which I'll admit can make _some_ parts of that job harder, some of the time) - when the browser's dealing with "proper" CSS, you can use the browser tools to test changes before updating the source files, skipping around the DOM, watching all the selected elements change (which doesn't always happen when you have CSS Modules style scoping going on) - code editor tooling is usually better - you don't have to remember whether you're writing your CSS in native CSS, snake-case, or JS style camelCase (I know some of the recent CSS-in-JS tech helps here, but why add a library to do something the browser already does?) - you don't need to add libraries to do something the browsers already do


I find the single file preferable because it's less clutter. Less open files in the editor, less jumping between documents, and easier to reason about in code review and when dealing with merge conflicts (since the context of the changes are present in front of you instead of in another file somewhere). If you're editing 3 components at once that's 3 files open instead of 9. The benefits of this are more noticeable when you're trying to debug a frustrating issue but you're not exactly sure where exactly it originates in the code (files open but maybe they don't all get closed as you peruse through, more .

I also find that in a well-architected application, very large components with lots of markup and styles are rare compared to compact and reusable components used to compose the more complex ones which ultimately means the more complex ones also need less styles and markup; really big components might need to exist in some circumstances, but usually it's a code smell. 1 file also means there is less uptake necessary when bootstrapping new components, you don't need to create 3 new files with matching filenames and import statements (yes, you could get an IDE or a script to do it but its just one less thing to think about with the 1 file approach).

Finally, IMO, in the case of components the distinction is more superficial than logical, breaking up a single unit into 3 highly coupled modules that cannot be imported and reused anywhere else feels unnecessary. You wouldn't break up a class with 3 large methods into 3 different modules and import those into the class module.

In the case where I do have to edit a really large files with lots of coupled logic, my editor allows me to view the same file in multiple views at different positions.


I am with you on that. I recently had to write a bunch of Vue and scrolling up and down really annoyed me. But, fortunately it is becoming a moot point a bit, at least in React world, with more focus on presentational components. Right now I am working on a project where there is a UI Kit and the application is a separate entity and I really-really like this approach. With the `styled-system` library this produces quiet sane-looking styled code. Not sure how it translates into Vue world, but I would say - make presentational components with as little logic as possible.


I really don't care.

This is one of those things that usually boils down to arbitrary personal preference, often buttressed by people working backward from their desired result to construct objective-sounding "maintainability" rationales.

Pick a style and stick to it within the project's codebase. There's no wrong answer here, except "the only right answer is the way I personally prefer". I wish we had Prettier for file structure.


Yes, and for a very mundane reason. When an application is long-lived there’s inevitably some bit rot where one layer (template, style, or JavaScript) gets removed for some reason, and one or more of the corresponding layers is accidentally left in the code. This is especially likely to be CSS.

With single file components it’s much easier to track the dependencies and rebuild / scrap chunks of code without leaving cruft around accidentally.


In over 10 years I've never worked somewhere where anyone was trying to prune unused CSS, especially in the traditional approach where you just have large .css files that become append-only.

Not until I experienced component-level CSS did I see it happen since now it's relatively trivial to see unused CSS.


i would buy this argument, because i have seen this happen with WPF/XAML (person deletes the markup and code-behind, but accidentally leaves the viewmodel), except that in the webcomponents world i would imagine you'd have all three files living alone in a folder named after the component.


How is that possible when templates, style and js live side by side in a folder dedicated to that component only?


In my personal experience, generally yes given that you are able to carve your application logic into acceptably sized components. IMO what works best is not so much a hard single-file rule, but rather a looser single-location principle. If the component's logic is massive and necessitates many files, create a folder for that component and put them all in there. If a component is this large then it can also be a helpful signal that perhaps application logic could be factored differently so as to produce more easily understandable units. If there is enough discipline and alignment amongst the project developers, then the single-location thing lets you take advantage of making big logic easier to grok by allowing separation while keeping stuff that all belongs under a single umbrella grouped together.


Apparently, either people here do not use Vue or have not read the docs.

__Vue actually allows you to split the sections into seperate files if you require it.__

Source: https://vuejs.org/v2/guide/single-file-components.html#What-...


Can't you have the code-behind in a separate tab even if it's just a different part of the same file?


i guess, but then you've got a dependency on a specific text editor's behavior. for WPF/XAML, the markup and code-behind are two separate files, so you can use any text editor you want, but VS2019 shows them "grouped" together so you can open both files at once.


> do single-file components _actually_ work better for you than having the script, markup, and styling in three separate files?

Absolutely not for me, I much prefer Angular's style of separate files living in a dedicated folder. With Vue, I also find myself constantly scrolling up and down.


I guess it depends on the components size.

Small straight forward components -> sure.

Mid-sized components -> maybe.

Large component -> No, but probably you it's a good idea to see if they can be shrunk.

But I only have played around with this tech, so no real experience.


I've found that when the components are very large, I can almost always do something better (smaller components). I've done a couple of big Vue projects now.


For me, keeping all those things even closer together (react with hooks and some inline event handlers + css-in-js or tailwind) is so much better that I have no interest in single file components.


We are currently rewriting out frontend app, so can't talk about long term experience.

I don't see much of a benefit of having everything in a single file vs. many. We are going to split them up, as soon as they become unwieldy and people ending up spending too much time reading code not relevant for the task at hand.

What I do see a lot of benefit in, is having everything in one language (Typescript in our case) instead of three and all the IDE capabilities like autocomplete, linting, refactoring, etc. that come with it. But to be fair, I haven't done frontend in a while, so I'm not up to date what the state of the art integrations for html/css/js look like in comparison.


A couple of years ago I loved SFCs in Vue. Now I hate them with a passion.

The approach only works for very small components, not for real world use cases.


If it's all contained and does not impact any other component, absolutely.

That to me is the killer-feature.


but scoping everything to the component can be done just as easily with three separate files.


Over which you have to navigate by opening, closing, switching tabs etc.

I frequently find myself overwhelmed with the sheer number of files such a convention generates.

But the strengths of the single-file approach reveal themselves when you have a lot of small components - it enables you to just glance at a file and immediately know what's going on.

Also it puts a psychological barier against large, bloated components.


> it puts a psychological barrier against large, bloated components

this is the most compelling argument for me... but i still find myself having to work with overly-large components written by others. and that's when i end up doing lots and lots of scrolling and losing my place.

i guess that's the difference. single-file components are superior iff you have good team rigor around component size.


upvoted: it’s repost of a repost of a 2 year old article, but still great article. original from 2018 :https://medium.com/content-uneditable/implementing-single-fi...


The original article is actually this one as it was written by us and initially posted to our Medium account only to realize last week that we somehow forgot to publish it on our blog. Whoops.

So - this here is an updated, revised and improved version. Enjoy!


Thank you for posting this on a less hostile platform :)


Cappuccino does this. Compare the speeds of the work-in-progress (https://wip.laser-tags.net) and the same site (https://laser-tags.net) after doing a ‘jake release’ which pulls the 90 or so files into a single .sj one.

There’s also ‘jake press’, which goes through the JavaScript code, pulling out anything not actually referenced and binning it

(edit: and it occurred to me while in the shower, if anyone wanted to tell me where (roughly) you are located, and loading time for the 'released' site, it'd make me happy :)


Here's an example of single-file Web Component. Markup (JSX syntax with embedded JS expressions), CSS and JS are in one file:

https://github.com/wisercoder/uibuilder/blob/master/WebCompo...

All you need is this tiny 250-line lib: https://github.com/wisercoder/uibuilder


Polymer used to do the HTML Imports thing. Now LitElement does everything in JS files and it's.. better? Works great with bundlers like rollup, fits the npm module resolution thing much better.


'can your tool run a full-featured app from a single file' is a really important test of simplicity

esp given how people learn online, a single-file example is much more palatable on e.g. stack overflow vs three files

way friendlier to learners

not that anyone is building large production apps with single files, but tooling quality matters and this is one way to measure it


Why are the on-page examples images, instead of web components?


Flip the order of the sections and it's how I write Svelte components.


Angular




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: