?nodefine — a pattern to skip automatic Custom Element definitions
Zach Leatherman 7676 replies 7575 likes1111 reposts11 quote Original on MastodonOriginal on BlueskyAlso posted to Mastodon.
76 replies
i arrived at a very similar place (and @knowler.dev before me, linked in the post)
Amazing! How strongly do you feel about opt-in versus opt-out?
oh, dang you allow the tag name to be passed in the query param — interesting
i like opt-in! and i think i actually prefer ?nodefine to ?define=false
wait i meant opt-out lol
-
That’s a good argument!
yeah i think i feel more strongly about the principle of least surprise than about whether to define by default or not
like, i personally prefer define by default, but if there were a community expectation of opt-in definition via a query string param i’d choose that pattern over my own preference
I *feel* like it should be opt-in to avoid unwanted surprises, but assuming the query string arguments work well with all major bundlers, it's easy enough to opt-out that defaulting to define isn't a major hang-up for me.
I've been craving standardization one layer up (when should the script be fetched) -- I see several useful settings: (1) deferring script loading until the (not yet defined) custom element nears the viewport, (2) defer until used in DOM, (3) defer until idle, (4)...
(4) "immediate" in the sense that the script tag would be added, but timing may be influenced by any of the <script> policies -- async, defer, blocking=render, fetchpriority, etc.
@zachleat @knowler did the same in reverse! https://knowler.dev/blog/to-define-custom-elements-or-not-when-distributing-them
@zachleat seems sensible to me. i had the thought in the past of using ?tag=another-name, but exposing everything directly is more flexible
@wavebeem yeah I’m seeing that in a few places too!
@zachleat i've been a little unsure how this works out in practice though... like what's the use case for defining it with a new name? i guess if you just don't like the name that could be useful, and as long as it's not part of a larger system of components with dependencies on each other...
@wavebeem for sure — it’s just a preference for small projects. I can see the argument that you might not want renames for larger projects
-
thanks Tony!
@jakelazaroff.com has some lovely ideas documented here that overlap: til.jakelazaroff.com/html/define-...
I read it as Node Fine. 🙃
How about camel casing it?
?noDefine
I personally don’t like relying on case sensitivity in query param keys (acknowledging that they are case sensitive) 😅
-
@zachleat FYI the static `define` pattern you describe is a proposed community protocol.
https://github.com/webcomponents-cg/community-protocols/blob/main/proposals/on-demand-definitions.md
My hope is that tools and frameworks can use this knowledge to auto-generate the `.define()` call when using a custom element such that you don't need to worry about something like `?nodefine`.
@develwithoutacause ooh this is lovely — thank you! I’ll link it up from my post
@develwithoutacause @zachleat wow this is an awesome proposal! Here's hoping it really gets traction 🤞
@zachleat I'm curious, how does this pattern work with bundlers?
I guess you can still `import { Nimble } from './nimble.js?nodefine';` and presumably `import.meta` will be set correctly. Will bundlers tree-shake the `customElements.define` call? Will they tree-shake the class definition entirely if the imported symbol is not used?
@develwithoutacause some good discussion on the bundler question documented here from @jakelazaroff which I somehow missed: https://til.jakelazaroff.com/html/define-a-custom-element/
@zachleat @jakelazaroff Ooh duplicating the module between `?define` and not seems pretty rough, though it seems like that's an issue even outside a bundler?
Limiting all consumers to either `?define` or not seems pretty constraining at scale.
@develwithoutacause @jakelazaroff well if you’re wanting both mechanisms you probably wouldn’t do import "./nimble.js" with import "./nimble.js?define", you’d do import "./nimble.js" with `Nimble.define()`—right?
(which to me seems like yet another vote for opt-in rather than opt-out)
@zachleat @jakelazaroff Sure, you can not use the feature, but you need to *know* you can't use it in that situation.
Any given file can import `?define` but it needs to be confident that no *other* file imports without `?define`, which seems hard to enforce.
I guess you could codify this pattern into your bundler or something and rewrite the import / throw an error, but it's still complexity which needs to be managed.
@develwithoutacause @jakelazaroff you lost me at bundler *runs away*
@zachleat @jakelazaroff That just makes it all the more critical to choose `?define` or not. 😅
-
"And all of the bundlers cried out in agony"
I don’t know her!!
Super nifty. Is there a reason you assigned to the window?
-
-
Mostly this was an artifact from <is-land> (which I’ve been working on) which has some static APIs it exposes around fallback content and initialization extensions — wanted those to be available globally. I don’t think I’d go so far as to say it makes sense everywhere!
-
-
@zachleat This came across my feed again and I randomly read it as `node fine?`
@develwithoutacause node is fine tho 😅
> opt-out of define
> want different tag name
> run script before definitionWhy create a behemoth of a Web Component
that caters for IFs that will never happen?it is OOP
𝙘𝙪𝙨𝙩𝙤𝙢𝙀𝙡𝙚𝙢𝙚𝙣𝙩𝙨.𝙙𝙚𝙛𝙞𝙣𝙚("𝙢𝙮-𝙘𝙤𝙢𝙥𝙤𝙣𝙚𝙣𝙩",
𝙘𝙡𝙖𝙨𝙨 𝙚𝙭𝙩𝙚𝙣𝙙𝙨 𝙘𝙪𝙨𝙩𝙤𝙢𝙀𝙡𝙚𝙢𝙚𝙣𝙩𝙨.𝙜𝙚𝙩("𝙮𝙤𝙪𝙧-𝙘𝙤𝙢𝙥𝙤𝙣𝙚𝙣𝙩"){ ... })I extended Josh Wardle his Wordle (written with Web Components)
This thread has me thinking, thanks! github.com/11ty/is-land...
Love this! And it looks like it would make 3rd party components more immediately compatible with my little definer.js script if I changed it to add `.js?nodefine&define=false` (so it works with both your and @jakelazaroff.com’s patterns) instead of just adding the .js extension
Interesting! You may be interested in this discussion too: github.com/11ty/is-land...
I’ll take a look, thanks
I like the idea of self-contained definers, especially if you have custom logic for defining components.
Because of the side-effect that comes with defining components in the same module as where the logic is, I've been a big proponent of splitting them into separate modules.
That way you can import the module as-is from the definer from one module or you can import the class from another module if you want to customize the tag or extend the class.
it feels a little heavy for one-off components for me but I can see the allure for larger systems!
The way I've handled this is by making a secondary/separate import that auto defines the custom element.
you and @stuffbreaker.bsky.social should be friends 😅 bsky.app/profile/stuf...
Looks like im in good company...
// Auto registers as <light-editor>
import "light-pen/exports/components/light-editor/light-editor-register.js"// Manual Register
import LightEditor "light-pen/exports/components/light-editor/light-editor.js"
LightEditor.define()
// => Registers as <light-editor>oh interesting, I don’t mind this pattern in your *-register.js
```
import LightEditor from "./light-editor.js";
export default LightEditor;
LightEditor.define();
```Should probably also have an
export * from "./light-editor.js"
Just in case anything else gets exported 🤷♂️
but yea it's the pattern I've hated the least. Although you are right in that it incurs an extra network request on CDNs just to call the registering function
tfw the CE legends are all aligning in the replies bsky.app/profile/konn...
Such a lovely blast from the past!
It hadn’t crossed my mind that the ”import.meta.url” could be a replacement for the old way of finding the data by checking the DOM for script tags (and when synchronously loading a script, grabbing the last available element in the DOM or whatever it was)
tbh I’d still like a document.currentScript for modules that don’t have a `src` though
Why not just have two separate modules, one self-registering and one not?
self-registering requires two request chain if you don’t want to duplicate code and you aren’t using a bundler, unless there is a technique I’m not aware of!
That, but also I would probably just do one module that just exports the class, and give the snippet with the define call in documentation.
Teach people to fish, etc.
I don't know, I guess I've been burned by jQuery plugins and other frontend scripts that perform side effects on the page automatically and don't let you take control. So now I'd rather import the class and register myself. 🫠
If you really want to do a query parameter thing, I'd make the side effects opt-in.
?define
?define=custom-nameThat'll break if used with two different parameter values on the same page.
This only strengthens my impression that auto-registering is bad. 😅
Auto registration is the only way to ensure the required 1-1 class to tagname relationship.
Users could always add a second manual registration, which would break, so nothing is ensured?
Even if we consider that `customElements.define` is a footgun, should we handle this by having each library do its own bespoke mitigation pattern? That sounds nightmarish.
Yeah, so two modules. Simple. Works with and without scoped registries.