designertool-cdn-loader
v0.0.3
Published
A thin, dependency-free loader for the [Postcard Designer Tool](https://www.npmjs.com/package/designertool). It exposes the **same public API** as the `designertool` package (`register` and `PostcardDesignerElement`), but instead of bundling the Vue app i
Readme
designertool-cdn-loader
A thin, dependency-free loader for the Postcard Designer Tool. It exposes the same public API as the designertool package (register and PostcardDesignerElement), but instead of bundling the Vue app it loads the published bundle from a CDN at runtime.
This means your app ships a tiny package and always picks up the current Designer bundle, without re-installing or re-bundling on every Designer release.
Why use it
- Drop-in API parity.
registerandPostcardDesignerElementmatch thedesignertoolexports. - Tiny footprint. No Vue/Pinia in your dependency tree — the loaded bundle brings its own runtime.
- Always current. Defaults to the
latestpublisheddesignertoolon jsDelivr; pin a version when you need stability. - Safe by default. HTTPS-only for remote hosts, credential-omitting CORS, optional Subresource Integrity, request timeouts, and an optional fallback CDN.
- SSR-safe. Importing the module never touches
window,document, orcustomElements.
Installation
npm install designertool-cdn-loaderQuick start
import { register } from 'designertool-cdn-loader';
// Defines the <postcard-designer> custom element (loads the bundle on first use).
await register();<postcard-designer></postcard-designer>Because the bundle loads asynchronously, register() and PostcardDesignerElement.get() return Promises — designertool's own register() is synchronous, so if you migrate between the two, await the loader's version before relying on the element being defined.
Configuring the loader
setLoaderConfig() is an addition over the designertool surface — it controls where and how the bundle is fetched. Call it before the first API call that triggers a load; once loading begins, cdnUrl and version are locked.
import { setLoaderConfig, register } from 'designertool-cdn-loader';
setLoaderConfig({
version: '0.73.0', // omit or leave empty for "latest"
timeoutMs: 15000, // default 10000; valid range [1000, 120000]
});
await register();LoaderConfig options
| Option | Type | Default | Notes |
| --- | --- | --- | --- |
| cdnUrl | string | jsDelivr (see below) | Base URL for a self-hosted bundle. Empty/whitespace uses the default. Trailing slash is normalized. Must be HTTPS (except localhost/loopback). |
| version | string | latest | Pinned semver version, or latest when absent/empty. |
| fallbackCdnUrl | string | — | Secondary base, attempted exactly once if the primary fails. |
| timeoutMs | number | 10000 | Per-attempt timeout. Must be within [1000, 120000]. |
| integrity | { js? } | — | SRI hash applied to the module request. Only useful with a pinned version. |
How the module URL is composed
With no cdnUrl set, the loader fetches the designertool npm package straight from jsDelivr using jsDelivr's npm URL scheme:
https://cdn.jsdelivr.net/npm/designertool@{version}/dist/designertool.jsWith a custom cdnUrl (self-hosted), a plain directory layout is used instead:
{cdnUrl}/{version}/designertool.jsThere is no separate CSS asset — the Designer bundle inlines its styles into the custom element's shadow DOM.
Note on
latest: jsDelivr caches version resolution, so a newly publisheddesignertoolrelease can take a while to show up under@latest. Pin aversionif you need a release immediately.
Configuring the designer
The published designertool package is configured through the custom element itself (see the designertool README). The loader adds nothing here — whatever works against designertool works against the loaded bundle, because it is the same bundle.
API reference
| Export | Signature | Description |
| --- | --- | --- |
| register | () => Promise<void> | Defines <postcard-designer>. Idempotent; browser-only. |
| PostcardDesignerElement | { get(): Promise<CustomElementConstructor> } | Lazily resolves the custom element constructor from the loaded bundle. |
| setLoaderConfig | (config: LoaderConfig) => void | Configure the loader (see above). Loader-only. |
| getLoadingState | () => 'loading' \| 'loaded' \| 'error' | Synchronous read of the current load state, for loading indicators. Loader-only. |
Tracking load state
import { getLoadingState, register } from 'designertool-cdn-loader';
register().catch((err) => {
// err.category names the failure: 'js-load', 'timeout', 'integrity',
// 'version-not-found', 'both-cdn-failed', 'insecure-url', ...
// err.attemptedUrls lists the CDN URL(s) that were tried.
console.error('Designer failed to load:', err.category, err.attemptedUrls);
});
console.log(getLoadingState()); // 'loading' | 'loaded' | 'error'Behavior notes
- Single load. Exactly one network load is ever in flight; all callers share one promise. Once loaded, subsequent calls reuse it with no new request.
- Retry on failure. After a failed load, the next bundle-requiring call starts exactly one new attempt.
processshim. The published bundle is built for consumption through a bundler and referencesprocess.env.NODE_ENV; the loader installs a minimalglobalThis.process = { env: { NODE_ENV: 'production' } }shim before importing it — only when noprocessglobal exists.- Non-browser environments. Bundle-requiring calls surface a browser-only error and issue no network request when
window/documentare undefined.
Local end-to-end testing
The automated test suite mocks all network I/O. To exercise the loader against a real, locally served bundle, see example/README.md.
Development
npm ci
npm run type-check
npm test
npm run build:npmLicense
See the repository root.
