Problem
After the switch to Nudeps (#218), importing components directly from the Netlify CDN (elements.colorjs.io) without an import map produces a cryptic browser error:
Uncaught TypeError: The specifier "colorjs.io" was a bare specifier, but was not remapped to anything. Relative module specifiers must start with "./", "../" or "/".
This was reported in #225 and affected apps.colorjs.io (see color-js/apps#28, fixed in color-js/apps#29).
While the immediate breakage was fixed by adding the import map in the apps repo, any user loading components from elements.colorjs.io without the import map will hit the same cryptic error:
<!-- This fails with a confusing error -->
<script type="module" src="https://elements.colorjs.io/color-picker/color-picker.js"></script>
They need to include the import map first:
<script src="https://elements.colorjs.io/importmap.js"></script>
<script type="module" src="https://elements.colorjs.io/color-picker/color-picker.js"></script>
Root cause
The base class (src/common/color-element.js) uses bare specifier imports:
import Color from "colorjs.io";
import NudeElement from "nude-element";
import { states } from "nude-element/plugins";
Static imports are resolved during the ES module "link" phase — before any module body code runs. This means we cannot wrap them in try…catch or run import.meta.resolve() first. By the time any of our code could execute, the module has already failed.
Workarounds (available today)
Users can avoid the issue entirely by not importing from the Netlify CDN:
- Use esm.sh (or another CDN that resolves specifiers server-side) — already documented in the README:
<script src="https://esm.sh/color-elements" type="module"></script>
- Use Nudeps — manages import maps and bare specifiers without a bundler
- Use a bundler (webpack, Vite, Rollup, etc.) — resolves bare specifiers at build time
These don't solve the detection problem, but they're the recommended paths for users who don't want to manage import maps manually.
Possible solutions (detection)
1. Documentation only
Add a note to the README about needing the import map when loading from elements.colorjs.io.
- Pro: No code changes, no degradation
- Con: Easy to miss; doesn't help users who hit the error
2. Convert bare specifier imports to dynamic imports with detection
Replace static imports with import.meta.resolve() check + await import():
for (const specifier of ["colorjs.io", "nude-element", "nude-element/plugins"]) {
try {
import.meta.resolve(specifier);
}
catch {
throw new Error(
`color-elements: Cannot resolve "${specifier}". `
+ `Include the import map first: `
+ `<script src="https://elements.colorjs.io/importmap.js"><\/script>`,
);
}
}
const { default: Color } = await import("colorjs.io");
const { default: NudeElement } = await import("nude-element");
const { states } = await import("nude-element/plugins");
This could live directly in color-element.js or be isolated in a separate deps.js module.
- Pro: Users get an actionable error message at runtime
- Pro:
import.meta.resolve() is synchronous — no network overhead for the check
- Con: Degrades the codebase — static imports become dynamic
- Con: Requires top-level
await (supported in all modern browsers, but changes module semantics)
- Con: Worse static analysis for bundlers (though practical impact is negligible since these deps are used in full)
3. Separate optional check script
A standalone module that users can load before component scripts. Checks import.meta.resolve() and warns.
- Pro: Core code stays untouched
- Con: Users who forget the import map are unlikely to include this either
Notes
import.meta.resolve() is supported in Chrome 105+, Firefox 106+, Safari 16.4+ — aligns with the project's modern browser target
- The recommended CDN usage via
esm.sh (in the README) resolves specifiers server-side and is unaffected
- Both
colorjs.io and nude-element are bare specifiers that need handling, not just nude-element
Problem
After the switch to Nudeps (#218), importing components directly from the Netlify CDN (
elements.colorjs.io) without an import map produces a cryptic browser error:This was reported in #225 and affected
apps.colorjs.io(see color-js/apps#28, fixed in color-js/apps#29).While the immediate breakage was fixed by adding the import map in the apps repo, any user loading components from
elements.colorjs.iowithout the import map will hit the same cryptic error:They need to include the import map first:
Root cause
The base class (
src/common/color-element.js) uses bare specifier imports:Static imports are resolved during the ES module "link" phase — before any module body code runs. This means we cannot wrap them in
try…catchor runimport.meta.resolve()first. By the time any of our code could execute, the module has already failed.Workarounds (available today)
Users can avoid the issue entirely by not importing from the Netlify CDN:
These don't solve the detection problem, but they're the recommended paths for users who don't want to manage import maps manually.
Possible solutions (detection)
1. Documentation only
Add a note to the README about needing the import map when loading from
elements.colorjs.io.2. Convert bare specifier imports to dynamic imports with detection
Replace static imports with
import.meta.resolve()check +await import():This could live directly in
color-element.jsor be isolated in a separatedeps.jsmodule.import.meta.resolve()is synchronous — no network overhead for the checkawait(supported in all modern browsers, but changes module semantics)3. Separate optional check script
A standalone module that users can load before component scripts. Checks
import.meta.resolve()and warns.Notes
import.meta.resolve()is supported in Chrome 105+, Firefox 106+, Safari 16.4+ — aligns with the project's modern browser targetesm.sh(in the README) resolves specifiers server-side and is unaffectedcolorjs.ioandnude-elementare bare specifiers that need handling, not justnude-element