?nodefine — a pattern to skip Custom Element definitions
The following code is a minimum viable custom element:
class Nimble extends HTMLElement {}
customElements.define("nim-ble", Nimble);
The define call is typically packaged up in the component code (for ease of use) and not in your application code. I typically adapt this into a static function like so:
class Nimble extends HTMLElement {
static define(tagName) {
customElements.define(tagName || "nim-ble", this);
}
}
Nimble.define();
On first glance, this might not offer much benefit, but depending on the platform features I’m using I may also include a Cut-the-Mustard style feature test in there:
class Nimble extends HTMLElement {
static define(tagName) {
// Baseline 2020: Chrome 71 Safari 12.1 Firefox 65
// (extended browser support on top of ESM and Custom Elements)
if(typeof globalThis !== "undefined") {
customElements.define(tagName || "nim-ble", this);
}
}
}
Nimble.define();
Automatic Definition
The biggest drawback I’m struggling with in these patterns is how to allow folks to opt-out of the customElements.define call when they import or bundle the script. Perhaps they might want to use a different tag name (you can only define a class once in the registry), or run some additional advanced configuration before definition.
Here’s how you would typically import the script code (or import "./nimble.js" works too):
<!-- <nim-ble> is defined for you -->
<script type="module" src="nimble.js"></script>
Here’s a new idea I’m experimenting with to allow opt-out of define() by adding a ?nodefine query parameter to the script URL:
<!-- <nim-ble> is *not* defined for you -->
<script type="module" src="nimble.js?nodefine"></script>
<script type="module">
Nimble.define("some-other-name");
</script>
And then in your component code you’ll check for ?nodefine before auto defining the custom element:
class Nimble extends HTMLElement {
static define(tagName) {
customElements.define(tagName || "nim-ble", this);
}
}
// This line is the magic:
if(!(new URL(import.meta.url)).searchParams.has("nodefine")) {
Nimble.define();
}
window.Nimble = Nimble;
export { Nimble };
This pattern works with import too:
<script type="module">
// <nim-ble> is *not* defined for you
import { Nimble } from "./nimble.js?nodefine";
Nimble.define("some-other-name");
// Or directly:
customElements.define("nim-ble", Nimble);
</script>
This would also work on any Custom Element code adopting this pattern nestled inside a larger bundle:
<script type="module" src="bundle-398720.js?nodefine"></script>
What do y’all think?
Community Addendums
Updated February 14, 2025 I somehow missed these excellent links which cover similar ground:


