Use the new release of NudeElement#221
Conversation
✅ Deploy Preview for color-elements ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
6f6dfd0 to
3f26884
Compare
70737cb to
a9f2fdc
Compare
LeaVerou
left a comment
There was a problem hiding this comment.
LGTM. One thing is we need to update the docs around installation: linking straight from element.colorjs.io won't work anymore. We should recommend a CDN, e.g. esm.sh
| If you don't use a bundler, you can use [esm.sh](https://esm.sh): | ||
|
|
||
| ```html | ||
| <script src="https://esm.sh/color-elements/{{ name }}" type="module"></script> |
There was a problem hiding this comment.
We won't know until we release a new version with the exports. 😛
There was a problem hiding this comment.
We can see what happens with other packages that use similar patterns
3ed0573 to
7ad24ae
Compare
|
One issue left (when testing with bundlers) is that shadow styles are not applied. I don't know why. Investigating. EDIT: For Vite, the results are in the next comment. I asked Claude whether other bundlers suffer from the Vite issue, and it looks like they all do. Here is the Claude's comment: Build-time (affects ALL bundlers) Dev server (mostly Vite-specific) Summary:
The root issue is that the runtime |
Root Cause
static styles = "./color-picker.css";
static url = import.meta.url; // used to resolve the relative CSS pathAt runtime, This mechanism breaks with Vite in two different ways: 1. Dev server — Vite intercepts all 2. Production build — The SolutionAdd a small Vite plugin to // Before (what's in source):
static styles = "./color-picker.css";
// After (what Vite sees):
import __shadow_styles_0 from "./color-picker.css?inline";
static styles = [{ css: __shadow_styles_0 }];Vite's
function inlineShadowStyles() {
const RE = /\bstatic\s+styles\s*=\s*(['"])(\.\/[^'"]+\.css)\1/g;
return {
name: "inline-shadow-styles",
transform(code, id) {
if (!id.includes("/color-elements/") || !id.endsWith(".js")) return null;
RE.lastIndex = 0;
if (!RE.test(code)) return null;
RE.lastIndex = 0;
const imports = [];
let i = 0;
const newCode = code.replace(RE, (_match, _quote, cssPath) => {
const varName = `__shadow_styles_${i++}`;
imports.push(`import ${varName} from "${cssPath}?inline";`);
return `static styles = [{ css: ${varName} }]`;
});
return { code: imports.join("\n") + "\n" + newCode, map: null };
},
};
}
export default {
plugins: [inlineShadowStyles()],
// ... rest of config
};This fix works for both dev and production builds work in Chrome and Safari. UPDATE: UPDATE 2: It doesn't work in the production build in Firefox. 🤦♂️ |
| }); | ||
| }); | ||
|
|
||
| // Inject import map script into copied plain.njk, before the first script or </head> |
There was a problem hiding this comment.
This is extremely awkward, do we really need it? There must be a better solution, e.g. a separate headinclude template that this pulls in.
| "dependencies": { | ||
| "@11ty/eleventy": "^3.1.2", | ||
| "colorjs.io": "^0.5.0", | ||
| "colorjs.io": "^0.6.1", |
There was a problem hiding this comment.
We should have a very liberal version range here so that npm just uses whatever colorjs.io is installed in the project already and only installs a separate one in very rare cases.
There was a problem hiding this comment.
I don't understand
There was a problem hiding this comment.
If people install this in a package that already has its own colorjs.io, we want it to use that colorjs.io, not some other one. But we don't want to declare it as a peerDependency because then if they don't have it they now have to install two packages.
Though according to https://stackoverflow.com/questions/35207380/how-to-install-npm-peer-dependencies-automatically a peer dependency might work. I wonder if this means we need to support peer dependencies. I wonder how JSPM handles them? 🤔
DmitrySharabin
left a comment
There was a problem hiding this comment.
Production Build Investigation (Vite bundling)
Test Setup
- Test project with a single
<color-picker space="oklch" color="oklch(60% 30% 180)"> - Loaded via
<script type="module">import "color-elements"</script> - Bundled with Vite 7,
build.target: "esnext",minify: false
Browser Results
| Browser | Status |
|---|---|
| Chrome Canary | ✅ Works |
| Safari | ✅ Works |
| Zen (Firefox) | ❌ Blank page |
Zen Error
Uncaught (in promise) TypeError: can't access property "space_picker", this._el is undefined
Call stack (bottom → top):
constructed() ← NudeElement constructor calls this.constructed() during super()
$hook() ← fires a hook
run() ×4 ← hook chain
constructed() ← "constructed" hook handler (props plugin)
initializeFor ← processes observed HTML attributes
attributeChanged ← processes "space" attribute
set ← sets spaceId prop
convert ← converts value
get ← reads spaceId
get ← reads space (depends on spaceId)
update
get ← space.get() → this._el.space_picker.selectedSpace → 💥
Root Cause Analysis
The <color-picker> is in the HTML before customElements.define() runs (module script is deferred), so it gets upgraded. During upgrade: constructor → attributeChangedCallback → connectedCallback.
nude-element's constructed() method defers the "constructed" hook via microtask (Promise.resolve().then()), and connectedCallback() also fires it synchronously (with { once: true }). In both cases, the hook should fire after the full constructor chain completes, meaning this._el = named(this) in ColorPicker's constructor should have already run.
But the stack trace shows the "constructed" hook firing synchronously during super() in NudeElement's this.constructed() call — i.e., before this._el = named(this) runs in the ColorPicker constructor.
Hypotheses (unconfirmed)
- Firefox fires
connectedCallbackduring upgrade construction — If Firefox firesconnectedCallbacksynchronously as part of the constructor during element upgrade (unlike Chrome/Safari), the "constructed" hook would fire before the subclass constructor completes. - Child element creation triggers parent callback — When
this.shadowRoot.innerHTML = templateruns in ColorElement's constructor (duringsuper()), child custom elements (<space-picker>,<color-swatch>) are created. Their connection to the already-connected shadow root might somehow trigger a callback on the parent.
Build Notes
- CSS is correctly inlined as
[{ css: "..." }]objects (not fetched at runtime) static globalStyles = "./color-chart-global.css"on ColorChart is still a URL string (would 404 at runtime but doesn't cause the blank page)import.meta.urlin bundled components all resolve to the bundle URL (doesn't matter since shadow styles are inlined)
Next Steps
- Test in Zen with
vite dev(not build) to isolate bundling vs browser behavior - Inspect
$hook()implementation for a synchronous firing path - Try a guard fix:
this._el?.space_picker?.selectedSpace
e6987d4 to
4e2732d
Compare
|
@DmitrySharabin Just saw the posts about bundlers. This is fantastic research, but it should not be lost in this PR, create an issue! |
Done. #222 |
4e2732d to
f97400a
Compare
LeaVerou
left a comment
There was a problem hiding this comment.
Shouldn't we also update package.json? LGTM otherwise
In |
That's a bit weird, it means if someone cloned this repo and ran |
This is how the things were all the time. I'll fix the version in the following PR. |
Fixed. Now the package version is explicitely specified. |
Summary
NudeElement migration
toggleState()(viastatesplugin) replaces manualElementInternalsstate management.static formBehaviorreplacesstatic formAssociated.This is part 1 of 2 in a stack made with GitButler: