@wcstack/autoloader 1.9.1 → 1.10.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,276 +1,276 @@
1
- # @wcstack/autoloader
2
-
3
- **What if custom elements loaded themselves?**
4
-
5
- Imagine a future where you just write a custom element tag and the browser figures out where to find it. No `import`, no `customElements.define()`, no registration boilerplate. You write the tag, it loads.
6
-
7
- That's what `<wcs-autoloader>` explores. One CDN import, zero dependencies, powered by Import Maps.
8
-
9
- ## Features
10
-
11
- ### Basic Features
12
- - **Auto Detection & Loading**: Detects undefined custom element tags and automatically `import()`s them.
13
- - **Dynamic Content Support**: Instantly detects elements added later via `innerHTML` or `appendChild`.
14
- - **Zero Config / Buildless**: Works with browser standard features only; no bundler configuration required.
15
- - **Zero Dependencies**: Lightweight with no external dependencies.
16
-
17
- ### Unique Features
18
- - **Import Map Extension**: A standards-compliant approach that defines `@components/` rules within standard Import Maps.
19
- - **Namespace Prefix Auto-Resolution**: No need to register components one by one. Just define a prefix like `@components/ui/`, and it auto-resolves `<ui-button>` to `button.js`.
20
- - **Inline Loader Specification**: Specify loaders in Import Map keys like `@components/ui|lit/`. Easily mix multiple frameworks.
21
- - **Advanced `is` Attribute Support**: Automatically loads extended built-in elements. Infers `extends` from class definitions and calls `define` appropriately.
22
- - **Abstracted Loaders**: The file loading logic itself is pluggable, allowing customization of extensions and processing systems.
23
-
24
- ## Usage
25
-
26
- ### 1. Setup Import Map
27
-
28
- Define the autoloader path using the `@wcstack/autoloader` key.
29
- Define your component paths in an import map using the `@components/` prefix.
30
-
31
- ```html
32
- <script type="importmap">
33
- {
34
- "imports": {
35
- "@wcstack/autoloader": "/path/to/autoloader",
36
- "@components/ui/": "./components/ui/",
37
- "@components/app/": "./components/app/"
38
- }
39
- }
40
- </script>
41
- ```
42
-
43
- ### 2. Load the Autoloader
44
-
45
- Load the autoloader script via `<script>` tag, or import and call `bootstrapAutoloader` manually.
46
-
47
- ```html
48
- <!-- Option A: Zero-config script (recommended) -->
49
- <script type="module" src="https://esm.run/@wcstack/autoloader/auto"></script>
50
-
51
- <!-- Option B: Manual initialization -->
52
- <script type="module">
53
- import { bootstrapAutoloader } from "@wcstack/autoloader";
54
- bootstrapAutoloader();
55
- </script>
56
- ```
57
-
58
- ### 3. Place the `<wcs-autoloader>` Element
59
-
60
- Add `<wcs-autoloader>` to your HTML. This element triggers the loading lifecycle — eager loading starts on element creation, and lazy loading starts when the element is connected to the DOM.
61
-
62
- ```html
63
- <body>
64
- <wcs-autoloader></wcs-autoloader>
65
- <!-- your app components -->
66
- </body>
67
- ```
68
-
69
- ### 4. Use Components
70
-
71
- Just use your custom elements in HTML. `@wcstack/autoloader` will automatically import the matching file.
72
-
73
- ```html
74
- <!-- Loads ./components/ui/button.js -->
75
- <ui-button></ui-button>
76
-
77
- <!-- Loads ./components/app/header.js -->
78
- <app-header></app-header>
79
- ```
80
-
81
- ## Import Map Syntax
82
-
83
- `@wcstack/autoloader` parses keys in the import map starting with `@components/`.
84
-
85
- ### Lazy Loading (Namespaces)
86
-
87
- To enable lazy loading for a group of components, use a key ending with `/`.
88
-
89
- Format: `"@components/<prefix>[|<loader>]/": "<path>"`
90
-
91
- - **Prefix**: The tag prefix. Slashes are converted to dashes.
92
- - **Loader** (Optional): The loader to use (e.g., `vanilla`, `lit`). Defaults to `vanilla`.
93
-
94
- **Examples:**
95
-
96
- ```json
97
- {
98
- "imports": {
99
- // Maps <my-component> to ./components/component.js
100
- "@components/my/": "./components/",
101
-
102
- // Maps <ui-button> to ./ui/button.js (using 'lit' loader if configured)
103
- "@components/ui|lit/": "./ui/"
104
- }
105
- }
106
- ```
107
-
108
- ### Eager Loading
109
-
110
- To load a specific component immediately, use a key that does NOT end with `/`.
111
-
112
- Format: `"@components/<tagName>[|<loader>[,<extends>]]": "<path>"`
113
-
114
- - **Loader** (Optional): If omitted, it is automatically resolved based on the file extension (e.g., `.js` -> default loader, `.lit.js` -> lit-loader).
115
- - **Extends** (Optional): If omitted, it is automatically detected if the component class extends a built-in HTML element (e.g., `HTMLButtonElement` -> `extends: 'button'`).
116
-
117
- **Examples:**
118
-
119
- ```json
120
- {
121
- "imports": {
122
- // Eager loads <my-button> from ./my-button.js
123
- // Loader: Auto-detected (.js)
124
- // Extends: Auto-detected (e.g. if class extends HTMLButtonElement)
125
- "@components/my-button": "./my-button.js",
126
-
127
- // Explicitly specifying loader and extends
128
- "@components/fancy-input|vanilla,input": "./fancy-input.js",
129
-
130
- // Auto-detect loader for Lit element (if lit-loader is configured)
131
- "@components/my-lit-button": "./my-button.lit.js"
132
- }
133
- }
134
- ```
135
-
136
- ## Component Requirements
137
-
138
- By default (using the `vanilla` loader), your component files should:
139
-
140
- 1. Have a `.js` extension (configurable).
141
- 2. Export the custom element class as `default`.
142
-
143
- ```javascript
144
- // components/ui/button.js
145
- export default class UiButton extends HTMLElement {
146
- constructor() {
147
- super();
148
- this.attachShadow({ mode: 'open' }).innerHTML = '<button><slot></slot></button>';
149
- }
150
- }
151
- ```
152
-
153
- ## Customized Built-in Elements (`is` attribute)
154
-
155
- The autoloader detects customized built-in elements using the `is` attribute:
156
-
157
- ```html
158
- <!-- Autoloader detects and loads "my-button" -->
159
- <button is="my-button">Click me</button>
160
- ```
161
-
162
- **Lazy loading**: The `extends` value is automatically inferred from the host element tag (e.g., `<button>` → `extends: "button"`).
163
-
164
- **Eager loading**: The `extends` value is inferred from the component class prototype (e.g., `HTMLButtonElement` → `extends: "button"`), or can be specified explicitly in the import map:
165
-
166
- ```json
167
- {
168
- "imports": {
169
- "@components/my-button|vanilla,button": "./my-button.js"
170
- }
171
- }
172
- ```
173
-
174
- ```javascript
175
- // my-button.js
176
- export default class MyButton extends HTMLButtonElement {
177
- connectedCallback() {
178
- this.style.color = 'red';
179
- }
180
- }
181
- // Autoloader calls: customElements.define('my-button', MyButton, { extends: 'button' })
182
- ```
183
-
184
- ## Configuration
185
-
186
- Initialize the autoloader with optional configuration via `bootstrapAutoloader()`:
187
-
188
- ```typescript
189
- interface ILoader {
190
- postfix: string;
191
- loader: (path: string) => Promise<CustomElementConstructor | null>;
192
- }
193
-
194
- interface IWritableTagNames {
195
- autoloader?: string;
196
- }
197
-
198
- interface IWritableConfig {
199
- loaders?: Record<string, ILoader | string>;
200
- observable?: boolean;
201
- tagNames?: IWritableTagNames;
202
- }
203
- ```
204
-
205
- | Option | Type | Default | Description |
206
- |--------|------|---------|-------------|
207
- | `loaders` | `Record<string, ILoader \| string>` | See below | Loader definitions. Values can be `ILoader` objects or string aliases pointing to other loader keys. |
208
- | `observable` | `boolean` | `true` | Enables MutationObserver to detect dynamically added elements. Set to `false` to disable. |
209
- | `tagNames` | `IWritableTagNames` | `{ autoloader: "wcs-autoloader" }` | Custom element tag name. Can be changed to avoid naming conflicts. |
210
-
211
- ### Default Configuration
212
-
213
- ```javascript
214
- {
215
- loaders: {
216
- // Built-in vanilla loader: imports module and returns default export
217
- vanilla: { postfix: ".js", loader: vanillaLoader },
218
- // Default key: used as fallback when no loader matches
219
- "*": "vanilla"
220
- },
221
- observable: true
222
- }
223
- ```
224
-
225
- - **`vanilla`**: The built-in loader that dynamically imports a module and returns its `default` export as the custom element constructor.
226
- - **`"*"` (default key)**: Fallback loader. Its value is a string alias `"vanilla"`, meaning unmatched components use the vanilla loader.
227
-
228
- ### Loader Resolution
229
-
230
- When a component has no explicit loader key (e.g., lazy-loaded namespaces without `|loader`), the autoloader resolves the loader as follows:
231
-
232
- 1. **Postfix matching**: Checks the file path against all registered loaders' `postfix` values (longest match first).
233
- 2. **Default key fallback**: If no postfix matches, uses the loader referenced by the `"*"` key.
234
-
235
- ### Example
236
-
237
- ```javascript
238
- import { bootstrapAutoloader } from "@wcstack/autoloader";
239
-
240
- bootstrapAutoloader({
241
- loaders: {
242
- // Override vanilla loader's file extension
243
- vanilla: { postfix: ".vanilla.js" },
244
- // Add a custom loader for .lit.js files
245
- lit: {
246
- postfix: ".lit.js",
247
- loader: async (path) => {
248
- const module = await import(path);
249
- return module.default;
250
- }
251
- }
252
- },
253
- // Disable MutationObserver (no dynamic content detection)
254
- observable: false
255
- });
256
- ```
257
-
258
- ## How it Works
259
-
260
- ### Loading Lifecycle
261
-
262
- 1. **Registration**: `bootstrapAutoloader()` registers the `<wcs-autoloader>` custom element via `customElements.define()`.
263
- 2. **Constructor** (on element creation): All `<script type="importmap">` elements are parsed for `@components/` entries. Eager loading starts immediately for non-namespaced keys (not ending with `/`).
264
- 3. **connectedCallback** (on DOM attachment): Waits for `DOMContentLoaded` if the document is still loading, then scans the DOM using TreeWalker for undefined custom elements matching registered namespaces.
265
- 4. **Nested Loading**: After each custom element is defined and upgraded, its Shadow DOM (if present) is also scanned for nested custom elements.
266
- 5. **Observation** (if `observable: true`): A MutationObserver watches for new elements added to the DOM and triggers lazy loading.
267
- 6. **disconnectedCallback** (on removal): Disconnects the MutationObserver and releases the singleton instance.
268
-
269
- ### Error Handling
270
-
271
- - Components that fail to load are tracked internally and will not be retried on subsequent scans.
272
- - Duplicate loading is prevented: if a component is already being loaded, subsequent requests wait for the existing load to complete.
273
-
274
- ## License
275
-
276
- MIT
1
+ # @wcstack/autoloader
2
+
3
+ **What if custom elements loaded themselves?**
4
+
5
+ Imagine a future where you just write a custom element tag and the browser figures out where to find it. No `import`, no `customElements.define()`, no registration boilerplate. You write the tag, it loads.
6
+
7
+ That's what `<wcs-autoloader>` explores. One CDN import, zero dependencies, powered by Import Maps.
8
+
9
+ ## Features
10
+
11
+ ### Basic Features
12
+ - **Auto Detection & Loading**: Detects undefined custom element tags and automatically `import()`s them.
13
+ - **Dynamic Content Support**: Instantly detects elements added later via `innerHTML` or `appendChild`.
14
+ - **Zero Config / Buildless**: Works with browser standard features only; no bundler configuration required.
15
+ - **Zero Dependencies**: Lightweight with no external dependencies.
16
+
17
+ ### Unique Features
18
+ - **Import Map Extension**: A standards-compliant approach that defines `@components/` rules within standard Import Maps.
19
+ - **Namespace Prefix Auto-Resolution**: No need to register components one by one. Just define a prefix like `@components/ui/`, and it auto-resolves `<ui-button>` to `button.js`.
20
+ - **Inline Loader Specification**: Specify loaders in Import Map keys like `@components/ui|lit/`. Easily mix multiple frameworks.
21
+ - **Advanced `is` Attribute Support**: Automatically loads extended built-in elements. Infers `extends` from class definitions and calls `define` appropriately.
22
+ - **Abstracted Loaders**: The file loading logic itself is pluggable, allowing customization of extensions and processing systems.
23
+
24
+ ## Usage
25
+
26
+ ### 1. Setup Import Map
27
+
28
+ Define the autoloader path using the `@wcstack/autoloader` key.
29
+ Define your component paths in an import map using the `@components/` prefix.
30
+
31
+ ```html
32
+ <script type="importmap">
33
+ {
34
+ "imports": {
35
+ "@wcstack/autoloader": "/path/to/autoloader",
36
+ "@components/ui/": "./components/ui/",
37
+ "@components/app/": "./components/app/"
38
+ }
39
+ }
40
+ </script>
41
+ ```
42
+
43
+ ### 2. Load the Autoloader
44
+
45
+ Load the autoloader script via `<script>` tag, or import and call `bootstrapAutoloader` manually.
46
+
47
+ ```html
48
+ <!-- Option A: Zero-config script (recommended) -->
49
+ <script type="module" src="https://esm.run/@wcstack/autoloader/auto"></script>
50
+
51
+ <!-- Option B: Manual initialization -->
52
+ <script type="module">
53
+ import { bootstrapAutoloader } from "@wcstack/autoloader";
54
+ bootstrapAutoloader();
55
+ </script>
56
+ ```
57
+
58
+ ### 3. Place the `<wcs-autoloader>` Element
59
+
60
+ Add `<wcs-autoloader>` to your HTML. This element triggers the loading lifecycle — eager loading starts on element creation, and lazy loading starts when the element is connected to the DOM.
61
+
62
+ ```html
63
+ <body>
64
+ <wcs-autoloader></wcs-autoloader>
65
+ <!-- your app components -->
66
+ </body>
67
+ ```
68
+
69
+ ### 4. Use Components
70
+
71
+ Just use your custom elements in HTML. `@wcstack/autoloader` will automatically import the matching file.
72
+
73
+ ```html
74
+ <!-- Loads ./components/ui/button.js -->
75
+ <ui-button></ui-button>
76
+
77
+ <!-- Loads ./components/app/header.js -->
78
+ <app-header></app-header>
79
+ ```
80
+
81
+ ## Import Map Syntax
82
+
83
+ `@wcstack/autoloader` parses keys in the import map starting with `@components/`.
84
+
85
+ ### Lazy Loading (Namespaces)
86
+
87
+ To enable lazy loading for a group of components, use a key ending with `/`.
88
+
89
+ Format: `"@components/<prefix>[|<loader>]/": "<path>"`
90
+
91
+ - **Prefix**: The tag prefix. Slashes are converted to dashes.
92
+ - **Loader** (Optional): The loader to use (e.g., `vanilla`, `lit`). Defaults to `vanilla`.
93
+
94
+ **Examples:**
95
+
96
+ ```json
97
+ {
98
+ "imports": {
99
+ // Maps <my-component> to ./components/component.js
100
+ "@components/my/": "./components/",
101
+
102
+ // Maps <ui-button> to ./ui/button.js (using 'lit' loader if configured)
103
+ "@components/ui|lit/": "./ui/"
104
+ }
105
+ }
106
+ ```
107
+
108
+ ### Eager Loading
109
+
110
+ To load a specific component immediately, use a key that does NOT end with `/`.
111
+
112
+ Format: `"@components/<tagName>[|<loader>[,<extends>]]": "<path>"`
113
+
114
+ - **Loader** (Optional): If omitted, it is automatically resolved based on the file extension (e.g., `.js` -> default loader, `.lit.js` -> lit-loader).
115
+ - **Extends** (Optional): If omitted, it is automatically detected if the component class extends a built-in HTML element (e.g., `HTMLButtonElement` -> `extends: 'button'`).
116
+
117
+ **Examples:**
118
+
119
+ ```json
120
+ {
121
+ "imports": {
122
+ // Eager loads <my-button> from ./my-button.js
123
+ // Loader: Auto-detected (.js)
124
+ // Extends: Auto-detected (e.g. if class extends HTMLButtonElement)
125
+ "@components/my-button": "./my-button.js",
126
+
127
+ // Explicitly specifying loader and extends
128
+ "@components/fancy-input|vanilla,input": "./fancy-input.js",
129
+
130
+ // Auto-detect loader for Lit element (if lit-loader is configured)
131
+ "@components/my-lit-button": "./my-button.lit.js"
132
+ }
133
+ }
134
+ ```
135
+
136
+ ## Component Requirements
137
+
138
+ By default (using the `vanilla` loader), your component files should:
139
+
140
+ 1. Have a `.js` extension (configurable).
141
+ 2. Export the custom element class as `default`.
142
+
143
+ ```javascript
144
+ // components/ui/button.js
145
+ export default class UiButton extends HTMLElement {
146
+ constructor() {
147
+ super();
148
+ this.attachShadow({ mode: 'open' }).innerHTML = '<button><slot></slot></button>';
149
+ }
150
+ }
151
+ ```
152
+
153
+ ## Customized Built-in Elements (`is` attribute)
154
+
155
+ The autoloader detects customized built-in elements using the `is` attribute:
156
+
157
+ ```html
158
+ <!-- Autoloader detects and loads "my-button" -->
159
+ <button is="my-button">Click me</button>
160
+ ```
161
+
162
+ **Lazy loading**: The `extends` value is automatically inferred from the host element tag (e.g., `<button>` → `extends: "button"`).
163
+
164
+ **Eager loading**: The `extends` value is inferred from the component class prototype (e.g., `HTMLButtonElement` → `extends: "button"`), or can be specified explicitly in the import map:
165
+
166
+ ```json
167
+ {
168
+ "imports": {
169
+ "@components/my-button|vanilla,button": "./my-button.js"
170
+ }
171
+ }
172
+ ```
173
+
174
+ ```javascript
175
+ // my-button.js
176
+ export default class MyButton extends HTMLButtonElement {
177
+ connectedCallback() {
178
+ this.style.color = 'red';
179
+ }
180
+ }
181
+ // Autoloader calls: customElements.define('my-button', MyButton, { extends: 'button' })
182
+ ```
183
+
184
+ ## Configuration
185
+
186
+ Initialize the autoloader with optional configuration via `bootstrapAutoloader()`:
187
+
188
+ ```typescript
189
+ interface ILoader {
190
+ postfix: string;
191
+ loader: (path: string) => Promise<CustomElementConstructor | null>;
192
+ }
193
+
194
+ interface IWritableTagNames {
195
+ autoloader?: string;
196
+ }
197
+
198
+ interface IWritableConfig {
199
+ loaders?: Record<string, ILoader | string>;
200
+ observable?: boolean;
201
+ tagNames?: IWritableTagNames;
202
+ }
203
+ ```
204
+
205
+ | Option | Type | Default | Description |
206
+ |--------|------|---------|-------------|
207
+ | `loaders` | `Record<string, ILoader \| string>` | See below | Loader definitions. Values can be `ILoader` objects or string aliases pointing to other loader keys. |
208
+ | `observable` | `boolean` | `true` | Enables MutationObserver to detect dynamically added elements. Set to `false` to disable. |
209
+ | `tagNames` | `IWritableTagNames` | `{ autoloader: "wcs-autoloader" }` | Custom element tag name. Can be changed to avoid naming conflicts. |
210
+
211
+ ### Default Configuration
212
+
213
+ ```javascript
214
+ {
215
+ loaders: {
216
+ // Built-in vanilla loader: imports module and returns default export
217
+ vanilla: { postfix: ".js", loader: vanillaLoader },
218
+ // Default key: used as fallback when no loader matches
219
+ "*": "vanilla"
220
+ },
221
+ observable: true
222
+ }
223
+ ```
224
+
225
+ - **`vanilla`**: The built-in loader that dynamically imports a module and returns its `default` export as the custom element constructor.
226
+ - **`"*"` (default key)**: Fallback loader. Its value is a string alias `"vanilla"`, meaning unmatched components use the vanilla loader.
227
+
228
+ ### Loader Resolution
229
+
230
+ When a component has no explicit loader key (e.g., lazy-loaded namespaces without `|loader`), the autoloader resolves the loader as follows:
231
+
232
+ 1. **Postfix matching**: Checks the file path against all registered loaders' `postfix` values (longest match first).
233
+ 2. **Default key fallback**: If no postfix matches, uses the loader referenced by the `"*"` key.
234
+
235
+ ### Example
236
+
237
+ ```javascript
238
+ import { bootstrapAutoloader } from "@wcstack/autoloader";
239
+
240
+ bootstrapAutoloader({
241
+ loaders: {
242
+ // Override vanilla loader's file extension
243
+ vanilla: { postfix: ".vanilla.js" },
244
+ // Add a custom loader for .lit.js files
245
+ lit: {
246
+ postfix: ".lit.js",
247
+ loader: async (path) => {
248
+ const module = await import(path);
249
+ return module.default;
250
+ }
251
+ }
252
+ },
253
+ // Disable MutationObserver (no dynamic content detection)
254
+ observable: false
255
+ });
256
+ ```
257
+
258
+ ## How it Works
259
+
260
+ ### Loading Lifecycle
261
+
262
+ 1. **Registration**: `bootstrapAutoloader()` registers the `<wcs-autoloader>` custom element via `customElements.define()`.
263
+ 2. **Constructor** (on element creation): All `<script type="importmap">` elements are parsed for `@components/` entries. Eager loading starts immediately for non-namespaced keys (not ending with `/`).
264
+ 3. **connectedCallback** (on DOM attachment): Waits for `DOMContentLoaded` if the document is still loading, then scans the DOM using TreeWalker for undefined custom elements matching registered namespaces.
265
+ 4. **Nested Loading**: After each custom element is defined and upgraded, its Shadow DOM (if present) is also scanned for nested custom elements.
266
+ 5. **Observation** (if `observable: true`): A MutationObserver watches for new elements added to the DOM and triggers lazy loading.
267
+ 6. **disconnectedCallback** (on removal): Disconnects the MutationObserver and releases the singleton instance.
268
+
269
+ ### Error Handling
270
+
271
+ - Components that fail to load are tracked internally and will not be retried on subsequent scans.
272
+ - Duplicate loading is prevented: if a component is already being loaded, subsequent requests wait for the existing load to complete.
273
+
274
+ ## License
275
+
276
+ MIT
package/dist/auto.js CHANGED
@@ -1,3 +1,3 @@
1
- import { bootstrapAutoloader } from "./index.esm.js";
2
-
3
- bootstrapAutoloader();
1
+ import { bootstrapAutoloader } from "./index.esm.js";
2
+
3
+ bootstrapAutoloader();
package/dist/auto.min.js CHANGED
@@ -1,3 +1,3 @@
1
- import { bootstrapAutoloader } from "./index.esm.min.js";
2
-
3
- bootstrapAutoloader();
1
+ import { bootstrapAutoloader } from "./index.esm.min.js";
2
+
3
+ bootstrapAutoloader();