@twobirds/microcomponents 0.9.0 → 0.10.0

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.
Files changed (45) hide show
  1. package/README.md +2 -16
  2. package/dist/cli/bundle.d.ts +106 -0
  3. package/dist/cli/bundle.d.ts.map +1 -0
  4. package/dist/cli/bundler.d.ts +44 -0
  5. package/dist/cli/bundler.d.ts.map +1 -0
  6. package/dist/cli/create.d.ts.map +1 -1
  7. package/dist/cli/dev.d.ts +87 -0
  8. package/dist/cli/dev.d.ts.map +1 -0
  9. package/dist/cli/dispatch.d.ts +6 -2
  10. package/dist/cli/dispatch.d.ts.map +1 -1
  11. package/dist/cli/index.js +1807 -1
  12. package/dist/cli/inliner.d.ts +42 -0
  13. package/dist/cli/inliner.d.ts.map +1 -0
  14. package/dist/cli/preview.d.ts +48 -0
  15. package/dist/cli/preview.d.ts.map +1 -0
  16. package/dist/core/elements.js +1294 -75
  17. package/dist/core/mcGuards.js +5 -8
  18. package/dist/mcp/api.json +2 -17
  19. package/dist/mcp/concepts/project-layout.md +9 -8
  20. package/dist/mcp/index.js +450 -230
  21. package/dist/microcomponents.js +2882 -9
  22. package/dist/state/formValues.js +2236 -281
  23. package/dist/state/observables.js +1794 -342
  24. package/dist/utils/events.js +171 -197
  25. package/package.json +3 -7
  26. package/dist/cli/create.js +0 -653
  27. package/dist/cli/dispatch.js +0 -316
  28. package/dist/cli/scaffold.js +0 -109
  29. package/dist/cli/writeScaffold.js +0 -114
  30. package/dist/core/MC.js +0 -837
  31. package/dist/core/index.js +0 -72
  32. package/dist/state/index.js +0 -5
  33. package/dist/state/placeholders.js +0 -187
  34. package/dist/types/index.js +0 -1
  35. package/dist/types/mcTypes.js +0 -9
  36. package/dist/utils/autoload.js +0 -160
  37. package/dist/utils/helpers.js +0 -532
  38. package/dist/utils/index.js +0 -12
  39. package/dist/utils/resolveComponentPath.js +0 -39
  40. package/dist/vite/index.d.ts +0 -7
  41. package/dist/vite/index.d.ts.map +0 -1
  42. package/dist/vite/index.js +0 -6
  43. package/dist/vite/loadAssets.d.ts +0 -36
  44. package/dist/vite/loadAssets.d.ts.map +0 -1
  45. package/dist/vite/loadAssets.js +0 -221
@@ -1,653 +0,0 @@
1
- /**
2
- Project scaffolding for `mc create --namespace xxx`.
3
-
4
- Pure, filesystem-free logic: given a namespace it returns the files of a complete,
5
- working Micro Components dev environment and their contents. The starter component's
6
- path is not re-derived here — it comes from {@link tagPath}, the same primitive the
7
- runtime autoload loader uses, so the scaffold and the runtime can never disagree about
8
- where a component lives.
9
-
10
- The namespace is the first segment of every component tag, so `xxx` makes `src/xxx/`
11
- the home of tags like `<xxx-app>` (tagPath `xxx-app` → `xxx/app`). The generated
12
- environment follows the three-path chain the library's own demos use: `src/` holds the
13
- authored sources, `build/` receives the per-file `tsc` output that the dev server
14
- serves through `autoload()`, and `public/` receives the bundled production site. Because
15
- the dev shell is the autoload base, `import.meta.url` + `tagPath('xxx-app')` resolves to
16
- `build/xxx/app.js` — the compiled starter.
17
-
18
- @module CLI/Project
19
- */
20
- import { tagPath } from '../utils/resolveComponentPath.js';
21
- import { kebabToPascal } from '../utils/helpers.js';
22
- /** The starter component tag for a namespace: `xxx` → `xxx-app`. */
23
- function starterTag(namespace) {
24
- return `${namespace}-app`;
25
- }
26
- /**
27
- Whether a string is a valid project namespace: 1 to 4 lowercase letters.
28
-
29
- @example
30
- ```ts
31
- console.log(isValidNamespace('mc'));
32
- console.log(isValidNamespace('mcxyz'));
33
- console.log(isValidNamespace('toolong'));
34
- ```
35
- @param ns - Candidate namespace.
36
- */
37
- export function isValidNamespace(ns) {
38
- return /^[a-z]{1,4}$/.test(ns);
39
- }
40
- /**
41
- Builds the files of a complete dev environment for a namespace.
42
-
43
- @example
44
- ```ts
45
- const { namespace, files } = scaffoldProject('mc');
46
- console.log(namespace);
47
- console.log(Object.keys(files));
48
- ```
49
- @param namespace - 1 to 4 lowercase letters; becomes the `src/<namespace>/` folder and
50
- the first segment of every component tag.
51
- @throws If `namespace` is not 1 to 4 lowercase letters.
52
- */
53
- export function scaffoldProject(namespace) {
54
- if (!isValidNamespace(namespace)) {
55
- throw new Error(`invalid namespace "${namespace}": must be 1 to 4 lowercase letters (e.g. "mc")`);
56
- }
57
- const tag = starterTag(namespace);
58
- const base = tagPath(tag);
59
- const files = {};
60
- files['package.json'] = packageJson(namespace);
61
- files['tsconfig.json'] = tsconfig();
62
- files['tsconfig.vc.json'] = tsconfigVc();
63
- files['vite.config.ts'] = viteConfig(namespace);
64
- files['.gitignore'] = gitignore();
65
- files['opencode.jsonc'] = opencodeConfig();
66
- files['.opencode/skills/components/SKILL.md'] = componentSkill(namespace, tag);
67
- files['README.md'] = readme(namespace, tag);
68
- files['src/index.html'] = prodShell(tag);
69
- files['src/index-dev.html'] = devShell(namespace, tag);
70
- files[`src/${base}.ts`] = appSource(namespace, tag);
71
- files[`src/${base}.target.html`] = appHtml(namespace);
72
- files[`src/${base}.target.css`] = appCss(tag);
73
- files[`src/${namespace}/bundle.ts`] = bundle();
74
- return { files, namespace };
75
- }
76
- function packageJson(namespace) {
77
- return JSON.stringify({
78
- name: `${namespace}-app`,
79
- private: true,
80
- type: 'module',
81
- scripts: {
82
- dev: 'vite',
83
- build: 'vite build',
84
- preview: 'vite preview --outDir public --port 5174',
85
- typecheck: 'tsc --noEmit -p tsconfig.json && tsc --noEmit -p tsconfig.vc.json',
86
- },
87
- dependencies: {
88
- '@twobirds/microcomponents': '^0.9.0',
89
- },
90
- devDependencies: {
91
- vite: '^8.1.5',
92
- typescript: '^6.0.3',
93
- '@types/node': '^26.6.5',
94
- },
95
- }, null, '\t');
96
- }
97
- function tsconfig() {
98
- return JSON.stringify({
99
- compilerOptions: {
100
- target: 'ESNext',
101
- module: 'ESNext',
102
- moduleResolution: 'Bundler',
103
- lib: ['ESNext', 'DOM', 'DOM.Iterable'],
104
- strict: true,
105
- esModuleInterop: true,
106
- skipLibCheck: true,
107
- rootDir: 'src',
108
- outDir: 'build',
109
- removeComments: true,
110
- },
111
- include: ['src/**/*.ts'],
112
- }, null, '\t');
113
- }
114
- function tsconfigVc() {
115
- return JSON.stringify({
116
- compilerOptions: {
117
- target: 'ESNext',
118
- module: 'ESNext',
119
- moduleResolution: 'Bundler',
120
- lib: ['ESNext', 'DOM'],
121
- types: ['node'],
122
- strict: true,
123
- esModuleInterop: true,
124
- skipLibCheck: true,
125
- noEmit: true,
126
- },
127
- files: ['vite.config.ts'],
128
- }, null, '\t');
129
- }
130
- function gitignore() {
131
- return `node_modules/
132
- build/
133
- public/
134
- `;
135
- }
136
- function opencodeConfig() {
137
- return JSON.stringify({
138
- $schema: 'https://opencode.ai/config.json',
139
- mcp: {
140
- microcomponents: {
141
- type: 'local',
142
- command: ['npx', '-p', '@twobirds/microcomponents', 'microcomponents-mcp'],
143
- enabled: true,
144
- },
145
- },
146
- }, null, '\t');
147
- }
148
- function componentSkill(namespace, tag) {
149
- return `---
150
- name: components
151
- description: Use when adding, editing, naming, or wiring a micro component in this project, registering a component in the bundle, choosing an event dispatch target, running dev or build, or touching build/ or public/.
152
- ---
153
-
154
- # Micro Components project
155
-
156
- Namespace \`${namespace}\`. Every tag starts with \`${namespace}-\`. Components live in \`src/${namespace}/\` and the starter is \`<${tag}>\`.
157
-
158
- ## The chain
159
-
160
- \`src/\` → \`build/\` → \`public/\`
161
-
162
- - \`src/\` is the only folder to edit.
163
- - \`build/\` is the \`tsc\` output plus mirrored companion assets, served by the dev server. Generated.
164
- - \`public/\` is the production site: \`bundle.js\` with companion assets inlined, \`bundle.css\`, \`index.html\`. Generated.
165
-
166
- Never edit, rename, or delete anything under \`build/\` or \`public/\`. Change \`src/\` and rebuild.
167
-
168
- ## Add a component
169
-
170
- \`\`\`sh
171
- npx mc new ${namespace}-name --target-html --target-css
172
- \`\`\`
173
-
174
- The tag must be kebab-case with at least one hyphen. \`ns-name\` becomes \`src/ns/name.ts\`: the first hyphen becomes the folder, which is the same rule the autoloader uses.
175
-
176
- Register the component so the production bundle picks it up:
177
-
178
- \`\`\`ts
179
- // src/${namespace}/bundle.ts
180
- import './name.js';
181
- \`\`\`
182
-
183
- A component missing from the barrel loads in dev through \`autoload()\` but is absent from \`public/bundle.js\`.
184
-
185
- ## Companion assets
186
-
187
- A camelCase descriptor maps to a dotted filename and is listed in \`static assets\`:
188
-
189
- | descriptor | file | role |
190
- | --- | --- | --- |
191
- | \`css\` | \`name.css\` | document-level tokens under \`:root\` |
192
- | \`targetCss\` | \`name.target.css\` | styles scoped to the host tag |
193
- | \`shadowCss\` | \`name.shadow.css\` | styles inside the shadow root |
194
- | \`html\` | \`name.html\` | light-DOM template |
195
- | \`targetHtml\` | \`name.target.html\` | template for \`target.innerHTML\`, \`{key}\` tokens filled by \`parse()\` |
196
- | \`shadowHtml\` | \`name.shadow.html\` | template for \`shadowRoot.innerHTML\` |
197
-
198
- The loader fetches exactly the listed descriptors, so every entry in \`static assets\` needs its file.
199
-
200
- ## Events
201
-
202
- Prefer a direct target with the default local dispatch and a typed payload:
203
-
204
- \`\`\`ts
205
- target.dispatchEvent(
206
- new CustomEvent('${namespace}:action', { detail: { data: { id: 'x' } } })
207
- );
208
- \`\`\`
209
-
210
- Bubble up or down the tree only when the receiver cannot be named.
211
-
212
- ## Verify
213
-
214
- \`\`\`sh
215
- pnpm typecheck
216
- pnpm dev
217
- pnpm build
218
- pnpm preview
219
- \`\`\`
220
-
221
- \`mc clean\` removes \`build/\`, \`public/\` and \`coverage/\`.
222
-
223
- ## API reference
224
-
225
- The \`microcomponents\` MCP server registered in \`opencode.jsonc\` documents every export, the event system, and the concept essays. Query it instead of guessing an API.
226
- `;
227
- }
228
- function readme(namespace, tag) {
229
- return `# ${namespace} App
230
-
231
- A Micro Components project scaffolded by \`mc create --namespace ${namespace}\`.
232
-
233
- ## Install
234
-
235
- \`\`\`sh
236
- pnpm install
237
- \`\`\`
238
-
239
- ## Dev
240
-
241
- \`\`\`sh
242
- pnpm dev
243
- \`\`\`
244
-
245
- The dev server runs \`tsc -w\` and serves \`build/\`, so a saved component reaches the
246
- browser through \`autoload()\` with no bundling. Companion \`.css\`, \`.html\` and \`.json\`
247
- files are mirrored into \`build/\` next to the compiled component, which is where the
248
- autoloader looks for them.
249
-
250
- ## Build
251
-
252
- \`\`\`sh
253
- pnpm build
254
- \`\`\`
255
-
256
- \`public/\` is the production site: one minified \`bundle.js\` with every companion asset
257
- inlined by \`loadAssetsPlugin\`, the concatenated \`bundle.css\`, and the page shell. A
258
- single script tag loads it, no autoloader needed.
259
-
260
- \`\`\`sh
261
- pnpm preview
262
- \`\`\`
263
-
264
- ## Type-check
265
-
266
- \`\`\`sh
267
- pnpm typecheck
268
- \`\`\`
269
-
270
- \`tsconfig.json\` compiles \`src/\` into \`build/\`; \`tsconfig.vc.json\` checks
271
- \`vite.config.ts\`, which lives outside \`src/\` and needs node types for its \`node:\`
272
- imports.
273
-
274
- ## Components
275
-
276
- Components live in \`src/${namespace}/\`. The starter is \`<${tag}>\` (\`src/${namespace}/app.ts\`).
277
-
278
- Add a component with:
279
-
280
- \`\`\`sh
281
- npx mc new ${namespace}-name --target-html --target-css
282
- \`\`\`
283
-
284
- Then add its import to \`src/${namespace}/bundle.ts\` so the production bundle picks it up.
285
-
286
- \`.opencode/skills/components/SKILL.md\` carries the same rules for opencode: the
287
- \`src/\` → \`build/\` → \`public/\` chain, the asset descriptors, the bundle registration,
288
- and the event dispatch preference.
289
- `;
290
- }
291
- function viteConfig(namespace) {
292
- return `/**
293
- * Dev and build config for a Micro Components project.
294
- *
295
- * The chain is \`src/\` → \`build/\` → \`public/\`:
296
- *
297
- * - \`build/\` is the \`tsc\` output: one \`.js\` per component at its own autoload path,
298
- * plus the copied companion assets and the dev shell. The dev server serves it, so
299
- * the page loads components on demand through \`autoload()\` and nothing is bundled.
300
- * - \`public/\` is the production output: one minified \`bundle.js\` with every companion
301
- * asset inlined by \`loadAssetsPlugin\`, the concatenated \`bundle.css\`, and the
302
- * production shell. A single script tag loads it, no autoloader needed.
303
- *
304
- * @module ViteConfig
305
- */
306
-
307
- import { defineConfig, type Plugin, type ResolvedConfig } from 'vite';
308
- import { loadAssetsPlugin } from '@twobirds/microcomponents/vite';
309
- import { spawn } from 'node:child_process';
310
- import { cp, mkdir, readdir, readFile, writeFile } from 'node:fs/promises';
311
- import path from 'node:path';
312
-
313
- const projectRoot = import.meta.dirname;
314
- const sourceRoot = path.join(projectRoot, 'src');
315
- const buildRoot = path.join(projectRoot, 'build');
316
- const publicRoot = path.join(projectRoot, 'public');
317
- const namespace = '${namespace}';
318
-
319
- const pageShells = new Set(['index.html', 'index-dev.html']);
320
-
321
- /**
322
- * Whether a served file is a companion asset that must stay raw text, rather
323
- * than one of the page shells Vite is allowed to transform.
324
- *
325
- * @example
326
- * \`\`\`ts
327
- * isCompanionAsset('app.target.html');
328
- * isCompanionAsset('index.html');
329
- * \`\`\`
330
- *
331
- * @param name Basename of the requested file.
332
- */
333
- function isCompanionAsset(name: string): boolean {
334
- if (pageShells.has(name)) return false;
335
- return name.endsWith('.css') || name.endsWith('.json') || name.endsWith('.html');
336
- }
337
-
338
- /**
339
- * Mirrors companion assets and the dev shell from \`src/\` into the served folder,
340
- * preserving the tree. Autoload resolves \`<ns-app>\` to \`<ns>/app.js\` relative to the
341
- * page shell, so the served folder needs the compiled component *and* its companion
342
- * files at the same relative path.
343
- *
344
- * @example
345
- * \`\`\`ts
346
- * await mirrorAssets(sourceRoot, buildRoot);
347
- * \`\`\`
348
- *
349
- * @param root Source root to walk.
350
- * @param out Folder to write into.
351
- * @param rel Path of the walked directory relative to \`root\`.
352
- */
353
- async function mirrorAssets(root: string, out: string, rel = ''): Promise<void> {
354
- for (const entry of await readdir(path.join(root, rel), { withFileTypes: true })) {
355
- const next = rel ? path.join(rel, entry.name) : entry.name;
356
- if (entry.isDirectory()) {
357
- await mirrorAssets(root, out, next);
358
- } else if (isCompanionAsset(entry.name) || entry.name === 'index-dev.html') {
359
- const dst = path.join(out, next);
360
- await mkdir(path.dirname(dst), { recursive: true });
361
- await cp(path.join(root, next), dst);
362
- }
363
- }
364
- }
365
-
366
- /**
367
- * Runs \`tsc -w\` for the lifetime of the dev server, so a saved component reaches
368
- * \`build/\` without a second command.
369
- *
370
- * @example
371
- * \`\`\`ts
372
- * plugins: [tscWatch()],
373
- * \`\`\`
374
- */
375
- function tscWatch(): Plugin {
376
- let child: ReturnType<typeof spawn> | undefined;
377
- return {
378
- name: 'tsc-watch',
379
- configResolved() {
380
- child = spawn('npx', ['tsc', '-w', '-p', 'tsconfig.json'], { cwd: projectRoot, stdio: 'inherit' });
381
- },
382
- closeServer() {
383
- child?.kill();
384
- },
385
- };
386
- }
387
-
388
- /**
389
- * Mirrors companion assets into \`build/\` before the dev server starts, and again on
390
- * every change, so the served folder stays autoload-ready.
391
- *
392
- * @example
393
- * \`\`\`ts
394
- * plugins: [syncBuild()],
395
- * \`\`\`
396
- */
397
- function syncBuild(): Plugin {
398
- return {
399
- name: 'sync-build',
400
- enforce: 'pre',
401
- async configureServer(server) {
402
- await mirrorAssets(sourceRoot, buildRoot);
403
- server.watcher.on('all', async (event, file) => {
404
- if (!file.startsWith(sourceRoot) || file.endsWith('.ts')) return;
405
- const rel = path.relative(sourceRoot, file);
406
- if (isCompanionAsset(path.basename(file)) || file.endsWith('index-dev.html')) {
407
- const dst = path.join(buildRoot, rel);
408
- await mkdir(path.dirname(dst), { recursive: true });
409
- await cp(file, dst);
410
- }
411
- });
412
- },
413
- };
414
- }
415
-
416
- /**
417
- * Serves companion \`.css\`, \`.html\` and \`.json\` files as raw text, ahead of the
418
- * Vite transform middleware. Without it a \`.html\` request returns the transformed
419
- * page shell and a \`.css\` request returns a JS module, so \`loadAssets()\` receives
420
- * markup it cannot use.
421
- *
422
- * @example
423
- * \`\`\`ts
424
- * plugins: [rawAssets(buildRoot)],
425
- * \`\`\`
426
- *
427
- * @param root Source root that mirrors the served tree.
428
- */
429
- function rawAssets(root: string): Plugin {
430
- return {
431
- name: 'raw-assets',
432
- enforce: 'pre',
433
- configureServer(server) {
434
- server.middlewares.use(async (req, res, next) => {
435
- const url = req.url?.split('?')[0];
436
- if (!url) return next();
437
- const file = decodeURIComponent(url);
438
- const name = file.split('/').pop() ?? '';
439
- if (!isCompanionAsset(name)) return next();
440
- try {
441
- const text = await readFile(path.join(root, file), 'utf-8');
442
- res.setHeader('Content-Type', name.endsWith('.css') ? 'text/css' : 'text/plain');
443
- res.end(text);
444
- } catch {
445
- next();
446
- }
447
- });
448
- },
449
- };
450
- }
451
-
452
- /**
453
- * Serves the autoload shell (\`index-dev.html\`) at \`/\`. Vite answers \`/\` with
454
- * \`index.html\`, which is the production shell referencing \`bundle.js\` — that file
455
- * only exists in \`public/\`, so the dev server has to redirect to the dev shell.
456
- *
457
- * @example
458
- * \`\`\`ts
459
- * plugins: [devShell()],
460
- * \`\`\`
461
- */
462
- function devShell(): Plugin {
463
- return {
464
- name: 'dev-shell',
465
- enforce: 'pre',
466
- configureServer(server) {
467
- server.middlewares.use((req, res, next) => {
468
- const url = req.url?.split('?')[0];
469
- if (url !== '/' && url !== '/index.html') return next();
470
- req.url = '/index-dev.html';
471
- next();
472
- });
473
- },
474
- };
475
- }
476
-
477
- /**
478
- * Concatenates the plain \`.css\` token presets under \`src/\` into \`bundle.css\`,
479
- * skipping the companion \`.target.css\` and \`.shadow.css\` files, which
480
- * \`loadAssetsPlugin\` inlines into the JS bundle.
481
- *
482
- * @example
483
- * \`\`\`ts
484
- * plugins: [bundleCss()],
485
- * \`\`\`
486
- */
487
- function bundleCss(): Plugin {
488
- let outDir = '';
489
- return {
490
- name: 'bundle-css',
491
- apply: 'build',
492
- configResolved(config: ResolvedConfig) {
493
- outDir = path.resolve(config.root, config.build.outDir);
494
- },
495
- async closeBundle() {
496
- async function collect(dir: string, rel: string, found: string[]): Promise<void> {
497
- for (const entry of await readdir(path.join(dir, rel), { withFileTypes: true })) {
498
- const next = rel ? path.join(rel, entry.name) : entry.name;
499
- if (entry.isDirectory()) {
500
- await collect(path.join(dir, rel), next, found);
501
- } else if (entry.name.endsWith('.css') && !/\\.(target|shadow)\\.css$/.test(entry.name)) {
502
- found.push(path.join(dir, next));
503
- }
504
- }
505
- }
506
- const files: string[] = [];
507
- await collect(sourceRoot, '', files);
508
- const parts = await Promise.all(files.map((f) => readFile(f, 'utf-8')));
509
- await mkdir(outDir, { recursive: true });
510
- await writeFile(path.join(outDir, 'bundle.css'), parts.join('\\n'), 'utf-8');
511
- },
512
- };
513
- }
514
-
515
- /**
516
- * Copies the production shell into \`public/\` next to the bundle, so the served
517
- * folder is a complete static site.
518
- *
519
- * @example
520
- * \`\`\`ts
521
- * plugins: [copyShell()],
522
- * \`\`\`
523
- */
524
- function copyShell(): Plugin {
525
- let outDir = '';
526
- return {
527
- name: 'copy-shell',
528
- apply: 'build',
529
- configResolved(config: ResolvedConfig) {
530
- outDir = path.resolve(config.root, config.build.outDir);
531
- },
532
- async closeBundle() {
533
- await cp(path.join(sourceRoot, 'index.html'), path.join(outDir, 'index.html'));
534
- },
535
- };
536
- }
537
-
538
- export default defineConfig(({ command }) => {
539
- if (command === 'serve') {
540
- return {
541
- root: buildRoot,
542
- server: { port: 5173, open: false },
543
- plugins: [syncBuild(), tscWatch(), rawAssets(buildRoot), devShell()],
544
- };
545
- }
546
- return {
547
- root: sourceRoot,
548
- build: {
549
- outDir: publicRoot,
550
- emptyOutDir: true,
551
- minify: true,
552
- target: 'esnext',
553
- sourcemap: false,
554
- lib: {
555
- entry: path.join(sourceRoot, namespace, 'bundle.ts'),
556
- formats: ['es'],
557
- fileName: () => 'bundle.js',
558
- },
559
- rollupOptions: {
560
- treeshake: false,
561
- output: { comments: false, keepNames: true },
562
- },
563
- },
564
- plugins: [loadAssetsPlugin(sourceRoot), bundleCss(), copyShell()],
565
- };
566
- });
567
- `;
568
- }
569
- function prodShell(tag) {
570
- return `<!doctype html>
571
- <html lang="en">
572
- <head>
573
- <meta charset="UTF-8" />
574
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
575
- <title>${tag.replace('-app', ' App')}</title>
576
- <script type="module" src="bundle.js"></script>
577
- <link rel="stylesheet" href="./bundle.css" />
578
- </head>
579
- <body>
580
- <${tag}></${tag}>
581
- </body>
582
- </html>
583
- `;
584
- }
585
- function devShell(namespace, tag) {
586
- return `<!doctype html>
587
- <html lang="en">
588
- <head>
589
- <meta charset="UTF-8" />
590
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
591
- <title>${namespace} App</title>
592
- <script type="module">
593
- import * as m from '@twobirds/microcomponents';
594
- m.autoload(true);
595
- Object.assign(window, m);
596
- </script>
597
- </head>
598
- <body>
599
- <${tag}></${tag}>
600
- </body>
601
- </html>
602
- `;
603
- }
604
- function bundle() {
605
- return `// Barrel entry for the production bundle: imports every component so the bundler
606
- // registers them all in one file. Add a line here when a component is created.
607
-
608
- import './app.js';
609
- `;
610
- }
611
- function appSource(namespace, tag) {
612
- const className = kebabToPascal(tag);
613
- return `import { DC, defineCE, parse, processAssets, type McEvent } from '@twobirds/microcomponents';
614
-
615
- /**
616
- \`${tag}\` — the starter micro component.
617
-
618
- @example
619
- \`\`\`html
620
- <${tag}></${tag}>
621
- \`\`\`
622
- */
623
- class ${className} extends DC {
624
- static assets = ['targetHtml', 'targetCss'];
625
-
626
- data = { title: 'Micro Components' };
627
-
628
- // Fill the template \`{key}\` tokens once, instead of binding reactive state.
629
- oneAssets(ev: McEvent<Record<string, string>>): void {
630
- processAssets(this, {
631
- targetHtml: parse(ev.detail.data.targetHtml, this.data),
632
- });
633
- }
634
- }
635
-
636
- defineCE('${tag}', ${className});
637
- `;
638
- }
639
- function appHtml(namespace) {
640
- return `<div class="app">
641
- <h1>{title}</h1>
642
- <p>Namespace scaffold ready. Edit <code>src/${namespace}/app.ts</code>.</p>
643
- </div>
644
- `;
645
- }
646
- function appCss(tag) {
647
- return `${tag} {
648
- display: block;
649
- padding: 2rem;
650
- font-family: system-ui, sans-serif;
651
- }
652
- `;
653
- }