@twobirds/microcomponents 0.13.0 → 0.13.2
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 +3 -3
- package/dist/cli/dispatch.d.ts +27 -1
- package/dist/cli/dispatch.d.ts.map +1 -1
- package/dist/cli/index.js +48 -11
- package/dist/docs/cli.d.ts +16 -7
- package/dist/docs/cli.d.ts.map +1 -1
- package/dist/docs/installation.d.ts +7 -2
- package/dist/docs/installation.d.ts.map +1 -1
- package/dist/mcp/api.json +1 -1
- package/dist/mcp/concepts/custom-element-registry.md +8 -0
- package/dist/mcp/concepts/mc-types.md +5 -0
- package/dist/mcp/concepts/microcomponent-architecture.md +21 -1
- package/dist/mcp/concepts/project-layout.md +7 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -138,7 +138,7 @@ Once installed from npm the same command is available as `mc` (and as `microcomp
|
|
|
138
138
|
|
|
139
139
|
```bash
|
|
140
140
|
mc new my-card [--css] [--html] [--target-html] [--shadow-html] [--target-css] [--shadow-css] [--js] [--dir <path>] [--force] [--dry-run]
|
|
141
|
-
mc create --namespace <ns> [--dir <path>] [--force] [--dry-run]
|
|
141
|
+
mc create --namespace <ns> [--dir <path>] [--force] [--yes] [--dry-run]
|
|
142
142
|
mc dev [--port <n>] [--host <addr>]
|
|
143
143
|
mc bundle
|
|
144
144
|
mc preview [--port <n>] [--host <addr>]
|
|
@@ -165,7 +165,7 @@ npx -p @twobirds/microcomponents mc new my-card --target-html --target-css
|
|
|
165
165
|
npx -p @twobirds/microcomponents microcomponents-mcp
|
|
166
166
|
```
|
|
167
167
|
|
|
168
|
-
The scaffold derives the file path from the tag using the same rule the runtime autoload loader uses (`my-card` → `my/card.ts`), names companion assets after the runtime's asset descriptors (`targetHtml` → `card.target.html`), and wires them into a `static assets` declaration. `--js` emits a plain JavaScript file for the rapid-prototyping path.
|
|
168
|
+
The scaffold derives the file path from the tag using the same rule the runtime autoload loader uses (`my-card` → `my/card.ts`), names companion assets after the runtime's asset descriptors (`targetHtml` → `card.target.html`), and wires them into a `static assets` declaration. Every hyphen becomes a folder, so the tag's segments are the DOM containment chain — `<cart-container-articles>` sits inside `<cart-container>`, so it is `src/cart/container/articles.ts` — and the last segment names what the component does, not what shape it is: `panel`, `box`, `wrapper` are superfluous, because a folder level costs a segment that has to say something. `--js` emits a plain JavaScript file for the rapid-prototyping path.
|
|
169
169
|
|
|
170
170
|
## Examples
|
|
171
171
|
|
|
@@ -414,7 +414,7 @@ Feel free to contact me for any questions or inquiries:
|
|
|
414
414
|
|
|
415
415
|
email [Frank Thürigen](mailto:fthuerigen@gmail.com?subject=Micro%20Components)
|
|
416
416
|
|
|
417
|
-
You can [fund this](https://www.
|
|
417
|
+
You can [fund this](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=fthuerigen@gmail.com&item_name=Micro+Components¤cy_code=EUR).
|
|
418
418
|
|
|
419
419
|
## Disclaimer
|
|
420
420
|
|
package/dist/cli/dispatch.d.ts
CHANGED
|
@@ -18,12 +18,38 @@ export type Runner = (script: string, options?: {
|
|
|
18
18
|
/** Asks the user to confirm a namespace long enough to be worth a second look. */
|
|
19
19
|
export type Confirmer = (namespace: string) => Promise<boolean>;
|
|
20
20
|
/**
|
|
21
|
+
Whether `mc <command>` runs the project's package.json script or the CLI's own
|
|
22
|
+
implementation.
|
|
23
|
+
|
|
24
|
+
A script that is a thin wrapper around the same command must not be delegated:
|
|
25
|
+
`mc dev` would spawn `pnpm run dev`, which runs `mc dev`, which spawns
|
|
26
|
+
`pnpm run dev`, forever. The scaffold writes exactly that shape (`"dev": "mc dev"`,
|
|
27
|
+
`"preview": "mc preview"`), so this guard is what keeps `pnpm dev` in a scaffolded
|
|
28
|
+
project from looping.
|
|
29
|
+
|
|
30
|
+
@example
|
|
31
|
+
```ts
|
|
32
|
+
scriptTarget('mc dev', 'dev'); // 'self'
|
|
33
|
+
scriptTarget('mc dev --port 5173', 'dev'); // 'self'
|
|
34
|
+
scriptTarget('npx mc preview', 'preview'); // 'self'
|
|
35
|
+
scriptTarget('vite preview --outDir build', 'dev'); // 'delegate'
|
|
36
|
+
scriptTarget('tsc -p tsconfig.json && mc bundle', 'bundle'); // 'delegate'
|
|
37
|
+
scriptTarget(undefined, 'clean'); // 'self'
|
|
38
|
+
```
|
|
39
|
+
@param body - The script body, or `undefined` when the project declares no such script.
|
|
40
|
+
@param command - The `mc` command being run.
|
|
41
|
+
@returns `'delegate'` to run the project script, `'self'` to run the CLI's implementation.
|
|
42
|
+
*/
|
|
43
|
+
export declare function scriptTarget(body: string | undefined, command: string): 'delegate' | 'self';
|
|
44
|
+
/**
|
|
21
45
|
Runs a package.json script through pnpm and resolves with its exit code.
|
|
22
46
|
|
|
23
47
|
Thin wrappers like `clean` and `check` delegate here so the script — not the
|
|
24
48
|
CLI — remains the single source of truth for what each command does. If the
|
|
25
49
|
current project defines no such script, the CLI runs its own implementation
|
|
26
|
-
against the current project, so the command works in any consumer project.
|
|
50
|
+
against the current project, so the command works in any consumer project. A script
|
|
51
|
+
that is a thin wrapper around the command itself is not delegated, because
|
|
52
|
+
delegating makes the CLI run itself forever — see {@link scriptTarget}.
|
|
27
53
|
|
|
28
54
|
@example
|
|
29
55
|
```ts
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dispatch.d.ts","sourceRoot":"","sources":["../../src/cli/dispatch.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;EAYE;AAwEF,MAAM,MAAM,MAAM,GAAG,CACpB,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,KACtC,OAAO,CAAC,MAAM,CAAC,CAAC;AAErB,kFAAkF;AAClF,MAAM,MAAM,SAAS,GAAG,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"dispatch.d.ts","sourceRoot":"","sources":["../../src/cli/dispatch.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;EAYE;AAwEF,MAAM,MAAM,MAAM,GAAG,CACpB,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,KACtC,OAAO,CAAC,MAAM,CAAC,CAAC;AAErB,kFAAkF;AAClF,MAAM,MAAM,SAAS,GAAG,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;AAgFhE;;;;;;;;;;;;;;;;;;;;;;EAsBE;AACF,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,EAAE,OAAO,EAAE,MAAM,GAAG,UAAU,GAAG,MAAM,CAQ3F;AAgED;;;;;;;;;;;;;;EAcE;AACF,wBAAsB,SAAS,CAC9B,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GACxC,OAAO,CAAC,MAAM,CAAC,CAajB;AAED;;;;;;;;;;;;;;;;;;;;EAoBE;AACF,wBAAsB,IAAI,CACzB,IAAI,EAAE,MAAM,EAAE,EACd,GAAG,GAAE,MAAkB,EACvB,OAAO,GAAE,SAA4B,GACnC,OAAO,CAAC,MAAM,CAAC,CA4IjB"}
|
package/dist/cli/index.js
CHANGED
|
@@ -267,7 +267,7 @@ function packageJson(namespace) {
|
|
|
267
267
|
typecheck: "tsc --noEmit -p tsconfig.json && tsc --noEmit -p tsconfig.test.json",
|
|
268
268
|
test: "vitest run"
|
|
269
269
|
},
|
|
270
|
-
dependencies: { "@twobirds/microcomponents": "^0.13.
|
|
270
|
+
dependencies: { "@twobirds/microcomponents": "^0.13.2" },
|
|
271
271
|
devDependencies: {
|
|
272
272
|
typescript: "^6.0.3",
|
|
273
273
|
vitest: "^4.1.10",
|
|
@@ -400,7 +400,9 @@ Never write an import map. \`autoload()\` fetches a component by its tag (\`ns-n
|
|
|
400
400
|
npx mc new ${namespace}-name --target-html --target-css
|
|
401
401
|
\`\`\`
|
|
402
402
|
|
|
403
|
-
The tag must be kebab-case with at least one hyphen. \`ns-name\` becomes \`src/ns/name.ts
|
|
403
|
+
The tag must be kebab-case with at least one hyphen. Every hyphen becomes a folder, so \`ns-parent-name\` becomes \`src/ns/parent/name.ts\` — the same rule the autoloader uses, so the tag is the file path.
|
|
404
|
+
|
|
405
|
+
The segments are the DOM containment chain: the namespace, then the components the element sits inside, then the component's own name. \`<cart-container-articles>\` sits inside \`<cart-container>\`, so it is \`src/cart/container/articles.ts\`. The last segment names what the component does, not what shape it is: \`<${namespace}-header-search>\` → \`src/${namespace}/header/search.ts\`. Generic structural words (\`panel\`, \`box\`, \`wrapper\`, \`widget\`, \`view\`) are superfluous — \`<${namespace}-header-panel>\` → \`src/${namespace}/header/panel.js\` buys a folder level that says nothing.
|
|
404
406
|
|
|
405
407
|
\`--js\` writes \`src/ns/name.js\` instead of \`.ts\`. \`tsc\` compiles only \`.ts\`, so the dev server and \`mc bundle\` copy a plain \`.js\` component to \`build/\` verbatim — it autoloads and bundles like a compiled component, with no type checking and no \`.d.ts\`.
|
|
406
408
|
|
|
@@ -1719,23 +1721,56 @@ function spawnProcess(command, args, cwd) {
|
|
|
1719
1721
|
});
|
|
1720
1722
|
}
|
|
1721
1723
|
/**
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
the CLI's own implementation when the project defines no such script.
|
|
1724
|
+
The body of a package.json script in the current directory, or `undefined` when the
|
|
1725
|
+
project declares no such script. Consumers get this check so `mc clean`/`mc check`
|
|
1726
|
+
can fall back to the CLI's own implementation when the project defines no such script.
|
|
1725
1727
|
|
|
1726
1728
|
@example
|
|
1727
1729
|
```ts
|
|
1728
|
-
|
|
1730
|
+
await scriptBody('clean'); // 'rm -rf dist'
|
|
1731
|
+
await scriptBody('nope'); // undefined
|
|
1729
1732
|
```
|
|
1730
1733
|
@param script - Script name to look up.
|
|
1731
1734
|
*/
|
|
1732
|
-
async function
|
|
1735
|
+
async function scriptBody(script) {
|
|
1733
1736
|
try {
|
|
1734
|
-
|
|
1737
|
+
const body = JSON.parse(await readFile(path.join(process.cwd(), "package.json"), "utf8"))?.scripts?.[script];
|
|
1738
|
+
return typeof body === "string" ? body : void 0;
|
|
1735
1739
|
} catch {
|
|
1736
|
-
return
|
|
1740
|
+
return;
|
|
1737
1741
|
}
|
|
1738
1742
|
}
|
|
1743
|
+
/** The CLI's own bin names, so a script that wraps this command is recognized. */
|
|
1744
|
+
var SELF_BINS = /* @__PURE__ */ new Set(["mc", "microcomponents"]);
|
|
1745
|
+
/**
|
|
1746
|
+
Whether `mc <command>` runs the project's package.json script or the CLI's own
|
|
1747
|
+
implementation.
|
|
1748
|
+
|
|
1749
|
+
A script that is a thin wrapper around the same command must not be delegated:
|
|
1750
|
+
`mc dev` would spawn `pnpm run dev`, which runs `mc dev`, which spawns
|
|
1751
|
+
`pnpm run dev`, forever. The scaffold writes exactly that shape (`"dev": "mc dev"`,
|
|
1752
|
+
`"preview": "mc preview"`), so this guard is what keeps `pnpm dev` in a scaffolded
|
|
1753
|
+
project from looping.
|
|
1754
|
+
|
|
1755
|
+
@example
|
|
1756
|
+
```ts
|
|
1757
|
+
scriptTarget('mc dev', 'dev'); // 'self'
|
|
1758
|
+
scriptTarget('mc dev --port 5173', 'dev'); // 'self'
|
|
1759
|
+
scriptTarget('npx mc preview', 'preview'); // 'self'
|
|
1760
|
+
scriptTarget('vite preview --outDir build', 'dev'); // 'delegate'
|
|
1761
|
+
scriptTarget('tsc -p tsconfig.json && mc bundle', 'bundle'); // 'delegate'
|
|
1762
|
+
scriptTarget(undefined, 'clean'); // 'self'
|
|
1763
|
+
```
|
|
1764
|
+
@param body - The script body, or `undefined` when the project declares no such script.
|
|
1765
|
+
@param command - The `mc` command being run.
|
|
1766
|
+
@returns `'delegate'` to run the project script, `'self'` to run the CLI's implementation.
|
|
1767
|
+
*/
|
|
1768
|
+
function scriptTarget(body, command) {
|
|
1769
|
+
if (!body) return "self";
|
|
1770
|
+
const tokens = body.trim().split(/\s+/);
|
|
1771
|
+
while (tokens[0] === "npx" || tokens[0] === "pnpm" && tokens[1] === "exec") tokens.splice(0, tokens[0] === "npx" ? 1 : 2);
|
|
1772
|
+
return SELF_BINS.has(tokens[0]) && tokens[1] === command ? "self" : "delegate";
|
|
1773
|
+
}
|
|
1739
1774
|
/** Build artifacts to remove, relative to the project root. */
|
|
1740
1775
|
var CLEAN_DIRS = [
|
|
1741
1776
|
"dist",
|
|
@@ -1809,7 +1844,9 @@ Runs a package.json script through pnpm and resolves with its exit code.
|
|
|
1809
1844
|
Thin wrappers like `clean` and `check` delegate here so the script — not the
|
|
1810
1845
|
CLI — remains the single source of truth for what each command does. If the
|
|
1811
1846
|
current project defines no such script, the CLI runs its own implementation
|
|
1812
|
-
against the current project, so the command works in any consumer project.
|
|
1847
|
+
against the current project, so the command works in any consumer project. A script
|
|
1848
|
+
that is a thin wrapper around the command itself is not delegated, because
|
|
1849
|
+
delegating makes the CLI run itself forever — see {@link scriptTarget}.
|
|
1813
1850
|
|
|
1814
1851
|
@example
|
|
1815
1852
|
```ts
|
|
@@ -1817,7 +1854,7 @@ const code = await runScript('check');
|
|
|
1817
1854
|
```
|
|
1818
1855
|
*/
|
|
1819
1856
|
async function runScript(script, options) {
|
|
1820
|
-
if (await
|
|
1857
|
+
if (scriptTarget(await scriptBody(script), script) === "delegate") return spawnProcess("pnpm", ["run", script]);
|
|
1821
1858
|
if (script === "clean") return cleanSelf();
|
|
1822
1859
|
if (script === "check") return checkSelf();
|
|
1823
1860
|
if (script === "dev") return runDev(process.cwd(), void 0, options?.port ?? 5173, options?.host ?? "127.0.0.1");
|
package/dist/docs/cli.d.ts
CHANGED
|
@@ -44,18 +44,25 @@ mc create --namespace shop
|
|
|
44
44
|
# project "shop" created in shop/ — run: cd shop && pnpm install && pnpm dev
|
|
45
45
|
```
|
|
46
46
|
|
|
47
|
-
Flags: `--namespace <ns>` (
|
|
48
|
-
the namespace), `--force`, `--
|
|
47
|
+
Flags: `--namespace <ns>` (lowercase letters and digits, starting with a letter, any
|
|
48
|
+
length, required), `--dir <path>` (default: the namespace), `--force`, `--yes` (`-y`,
|
|
49
|
+
skip the confirmation), `--dry-run`. The project owns its namespace, so length is the
|
|
50
|
+
user's call; past 5 characters `mc create` warns and asks the terminal to confirm before
|
|
51
|
+
anything is written, and `--dry-run` warns without prompting.
|
|
49
52
|
|
|
50
53
|
## mc dev
|
|
51
54
|
|
|
52
55
|
Runs `tsc -w` plus a `node:http` server that serves `build/`. The page loads components
|
|
53
56
|
on demand through `autoload()` — nothing is bundled, and a new component is picked up
|
|
54
|
-
without restarting.
|
|
57
|
+
without restarting. The server rewrites the `@twobirds/microcomponents` specifier in the
|
|
58
|
+
JS it serves to the `/microcomponents.min.js` it answers, so the page carries no import
|
|
59
|
+
map, and it mirrors the files `tsc` does not compile — companion assets and plain `.js`
|
|
60
|
+
components — into `build/` next to the compiled component.
|
|
55
61
|
|
|
56
62
|
```sh
|
|
57
|
-
mc dev
|
|
63
|
+
mc dev # dev server on port 5173, bound to 127.0.0.1
|
|
58
64
|
mc dev --port 3000
|
|
65
|
+
mc dev --host 0.0.0.0 # widen the bind to the LAN
|
|
59
66
|
```
|
|
60
67
|
|
|
61
68
|
## mc bundle
|
|
@@ -70,11 +77,13 @@ mc bundle
|
|
|
70
77
|
|
|
71
78
|
## mc preview
|
|
72
79
|
|
|
73
|
-
Serves the production site in `public/`.
|
|
80
|
+
Serves the production site in `public/`. Like `mc dev`, it binds to `127.0.0.1` by
|
|
81
|
+
default, so the served files are not reachable off the machine; `--host` widens the bind.
|
|
74
82
|
|
|
75
83
|
```sh
|
|
76
|
-
mc preview
|
|
84
|
+
mc preview # preview server on port 5174, bound to 127.0.0.1
|
|
77
85
|
mc preview --port 8080
|
|
86
|
+
mc preview --host 0.0.0.0 # widen the bind to the LAN
|
|
78
87
|
```
|
|
79
88
|
|
|
80
89
|
## mc clean
|
|
@@ -101,7 +110,7 @@ mc check
|
|
|
101
110
|
Reports the version of the installed library, not the version of the project it runs in.
|
|
102
111
|
|
|
103
112
|
```sh
|
|
104
|
-
mc --version # 0.13.
|
|
113
|
+
mc --version # 0.13.2
|
|
105
114
|
```
|
|
106
115
|
|
|
107
116
|
## mc --help
|
package/dist/docs/cli.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../src/docs/cli.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../src/docs/cli.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA0HE"}
|
|
@@ -14,10 +14,11 @@ the prompt).
|
|
|
14
14
|
npx @twobirds/microcomponents create --namespace shop
|
|
15
15
|
# created shop/package.json
|
|
16
16
|
# created shop/opencode.jsonc
|
|
17
|
+
# created shop/src/bundle.json
|
|
17
18
|
# created shop/src/index.html
|
|
18
19
|
# created shop/src/index-dev.html
|
|
19
20
|
# created shop/src/shop/app.ts
|
|
20
|
-
# created shop/src/shop/app.
|
|
21
|
+
# created shop/src/shop/app.html
|
|
21
22
|
# created shop/src/shop/app.target.css
|
|
22
23
|
# created shop/src/shop/bundle.ts
|
|
23
24
|
# ...
|
|
@@ -36,7 +37,11 @@ the project.
|
|
|
36
37
|
`mc new` derives the file path from the tag using the same rule the runtime autoloader
|
|
37
38
|
uses (`shop-app` → `shop/app.ts`), names companion assets after the runtime's asset
|
|
38
39
|
descriptors (`targetHtml` → `app.target.html`), and wires them into a `static assets`
|
|
39
|
-
declaration.
|
|
40
|
+
declaration. Every hyphen becomes a folder, so the segments are the DOM containment
|
|
41
|
+
chain: a child of `<cart-container>` is `<cart-container-articles>` →
|
|
42
|
+
`src/cart/container/articles.ts`. The last segment names what the component does, not
|
|
43
|
+
what shape it is — `panel`, `box`, `wrapper` are superfluous, because a folder level
|
|
44
|
+
costs a segment that has to say something.
|
|
40
45
|
|
|
41
46
|
```sh
|
|
42
47
|
mc new shop-app --target-html --target-css
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"installation.d.ts","sourceRoot":"","sources":["../../src/docs/installation.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"installation.d.ts","sourceRoot":"","sources":["../../src/docs/installation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA+BE;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAkDE"}
|
package/dist/mcp/api.json
CHANGED
|
@@ -47,6 +47,14 @@ src/
|
|
|
47
47
|
|
|
48
48
|
The DOM hierarchy is therefore immediately visible in the file system.
|
|
49
49
|
|
|
50
|
+
The name is the shortest one that says what the component does. The leaf segment is the
|
|
51
|
+
component's function, not its shape: `<shop-header-search>` → `src/shop/header/search.js`
|
|
52
|
+
says what the child of the header does. `<shop-header-panel>` → `src/shop/header/panel.js`
|
|
53
|
+
is wrong — `panel` describes a shape, so the file carries no information about the
|
|
54
|
+
component, and the folder level the tag creates is paid for by a word that says nothing.
|
|
55
|
+
Generic structural words (`panel`, `box`, `wrapper`, `widget`, `view`) are superfluous:
|
|
56
|
+
the nesting a tag encodes costs a directory, so the segment must be worth it.
|
|
57
|
+
|
|
50
58
|
---
|
|
51
59
|
|
|
52
60
|
## General-Purpose Components in Side Folders
|
|
@@ -111,6 +111,11 @@ External design of the composed unit is driven by namespaced CSS custom properti
|
|
|
111
111
|
</cart-container>
|
|
112
112
|
```
|
|
113
113
|
|
|
114
|
+
A child's tag carries its parent's prefix, so its file lands in the parent's folder
|
|
115
|
+
(`<cart-container-articles>` → `src/cart/container/articles.ts`), and the last segment
|
|
116
|
+
names what the child does — `articles`, `checkout`, `pay`. A generic structural word
|
|
117
|
+
(`panel`, `box`, `wrapper`) makes the folder level it creates say nothing.
|
|
118
|
+
|
|
114
119
|
### 1d. Leaf Components
|
|
115
120
|
|
|
116
121
|
Self-contained visual units that project no child custom elements — a button, a badge,
|
|
@@ -16,7 +16,7 @@ Every `.ts` or `.js` file contains exactly **one class**, regardless of which ty
|
|
|
16
16
|
visual, logic-only, style-only, or pure logic. This applies to `DC` subclasses, `MC`
|
|
17
17
|
subclasses, and any other class defined in `src/`.
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
For the classes the toolchain registers, three mechanisms make the rule loadable:
|
|
20
20
|
|
|
21
21
|
- **Autoload resolution** — `autoload()` maps a tag name to a single script path
|
|
22
22
|
(`<shop-header>` → `./shop/header.js`). One class per file keeps that mapping
|
|
@@ -27,5 +27,25 @@ Why:
|
|
|
27
27
|
- **Registry clarity** — `defineCE()` is called once per file, so file, class, and
|
|
28
28
|
registered tag stay in a 1:1 relationship.
|
|
29
29
|
|
|
30
|
+
The three logic types are where the rule is easiest to lose, because none of those three
|
|
31
|
+
mechanisms can catch a violation:
|
|
32
|
+
|
|
33
|
+
- **Logic-only `DC`** (`static tag = ''`) — the class is registered, so the 1:1 contract
|
|
34
|
+
holds, and the asset name is the host's tag name, which for a removed host is the
|
|
35
|
+
registered tag (`app-router` → `./app/router.css`). The class runs setup logic on a
|
|
36
|
+
host it is attached to with `DC.add(host, key, instance)`: one instance under one key,
|
|
37
|
+
so a second class in the file is a second attachable unit with no key of its own. Its
|
|
38
|
+
companions are `.css` and `.json` — the constructor removes the host after the assets
|
|
39
|
+
are applied, so a markup companion has nothing to inject into.
|
|
40
|
+
- **Style-only `DC`** (`static tag = ''`, `static assets = ['css']`) — the class is a
|
|
41
|
+
delivery vehicle for one stylesheet. File, class, registered tag, and the `.css`
|
|
42
|
+
companion are one thing under four names: `page-cockpit` in `src/page/cockpit.ts`
|
|
43
|
+
fetches `./page/cockpit.css`. A second class in the file would ship a second
|
|
44
|
+
stylesheet under a basename the first one owns.
|
|
45
|
+
- **Pure logic `MC`** — no tag, no `defineCE`, no autoload path, so nothing in the
|
|
46
|
+
toolchain sees the file at all. The rule holds for the reason a reusable unit gets its
|
|
47
|
+
own file: the import is by path, so one file is one capability and the file name is
|
|
48
|
+
the capability. A helper that is not a class is a utility function, not an `MC`.
|
|
49
|
+
|
|
30
50
|
Non-class entities that belong to the class — interfaces, types, constants, plain
|
|
31
51
|
functions — may live alongside it. A class that is reusable on its own gets its own file.
|
|
@@ -4,9 +4,11 @@ A Micro Components project keeps one folder per stage of the chain:
|
|
|
4
4
|
src/ → (tsc) → build/ → (bundle) → public/
|
|
5
5
|
```
|
|
6
6
|
|
|
7
|
-
- `src/` holds the authored sources: component `.ts` files,
|
|
8
|
-
`.target.html` / `.target.css` /
|
|
9
|
-
|
|
7
|
+
- `src/` holds the authored sources: component `.ts` files, plain `.js` components
|
|
8
|
+
written with `mc new --js`, the companion `.target.html` / `.target.css` /
|
|
9
|
+
`.shadow.css` assets, the bundle barrel `src/<namespace>/bundle.ts` with the
|
|
10
|
+
`src/bundle.json` that names it, the dev shell (`index-dev.html`) and the production
|
|
11
|
+
shell (`index.html`).
|
|
10
12
|
- `build/` is the `tsc` output: one `.js` per component at its own autoload path,
|
|
11
13
|
plus the files `tsc` does not compile — mirrored companion assets, plain `.js`
|
|
12
14
|
components, and the dev shell. `mc dev` serves it, so the page loads components
|
|
@@ -23,7 +25,8 @@ served from `public/` in production. Do not add a fourth tree for it.
|
|
|
23
25
|
|
|
24
26
|
A new component is picked up by the dev server automatically, because `autoload()`
|
|
25
27
|
resolves its tag to a file under `build/`. The bundle is not: the bundler walks a single
|
|
26
|
-
entry, so every component must be imported by the barrel
|
|
28
|
+
entry, the one `src/bundle.json` names, so every component must be imported by the barrel
|
|
29
|
+
`src/<namespace>/bundle.ts` that file points at.
|
|
27
30
|
|
|
28
31
|
## Shells
|
|
29
32
|
|