@twobirds/microcomponents 0.9.0 → 0.10.1

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 +7 -8
  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,221 +0,0 @@
1
- // oxlint-disable no-await-in-loop
2
- /**
3
- Inlines `static assets = [...]` declarations on DC classes for a bundled build: companion
4
- `.css` / `.html` files are read at build time and embedded as a `static _assets = { ... }`
5
- property inserted as the first line inside the class body, so `loadAssets()` never fetches
6
- them at runtime. The original `static assets` line is preserved for debugging purposes.
7
-
8
- The plugin ships as its own entry, `@twobirds/microcomponents/vite`, so a project that
9
- bundles to `public/` imports it without copying the source.
10
-
11
- @example
12
- ```ts
13
- import { loadAssetsPlugin } from '@twobirds/microcomponents/vite';
14
-
15
- export default defineConfig({
16
- plugins: [loadAssetsPlugin(sourceRoot)],
17
- });
18
- ```
19
-
20
- @module Vite
21
- */
22
- import { readFile } from 'node:fs/promises';
23
- import path from 'node:path';
24
- /**
25
- * Escape a raw string so it can be safely embedded inside a JS template literal.
26
- * Backticks and `${` sequences are escaped to prevent breaking the enclosing literal.
27
- *
28
- * @example Returns the input with backticks and `${` sequences escaped.
29
- * ```ts
30
- * escapeTemplateLiteral('price is ${cost}`done`');
31
- * ```
32
- */
33
- function escapeTemplateLiteral(raw) {
34
- return raw.replace(/\\/g, '\\\\').replace(/\$/g, '\\$').replace(/`/g, '\\`');
35
- }
36
- /**
37
- * Resolve the relative file path for a given asset descriptor and component tag.
38
- * Only the last segment of the tag is used as the basename since the .ts source
39
- * already lives in the correct directory (e.g. `cart-container-checkout.ts` is
40
- * inside `cart/container/`, so we just need `checkout.target.html`).
41
- *
42
- * @example Returns `./header.shadow.css`, `./card.target.html` and `./summary.css` for the calls below.
43
- * ```ts
44
- * resolveAssetPath('shop-header', 'shadowCss');
45
- * resolveAssetPath('product-card', 'targetHtml');
46
- * resolveAssetPath('cart-summary', 'css');
47
- * ```
48
- */
49
- function resolveAssetPath(tagName, asset) {
50
- const basename = tagName.split('-').pop() ?? tagName;
51
- const m = asset.match(/^([a-z]+)([A-Z][a-z]*)$/);
52
- if (m) {
53
- return `./${basename}.${m[1]}.${m[2].toLowerCase()}`;
54
- }
55
- return `./${basename}.${asset.toLowerCase()}`;
56
- }
57
- /**
58
- * Extract the tag name from a `defineCE('tag-name', ...)` call in source code.
59
- * Returns the first found tag or undefined.
60
- *
61
- * @example Returns `'shop-header'` for the code below.
62
- * ```ts
63
- * extractDefineCETag("defineCE('shop-header', ShopHeader);");
64
- * ```
65
- */
66
- function extractDefineCETag(code) {
67
- const m = code.match(/defineCE\s*\(\s*['"]([^'"]+)['"]/);
68
- return m ? m[1] : undefined;
69
- }
70
- /**
71
- * Strip all line and block comments from source code, replacing
72
- * their content with equivalent whitespace so character positions stay valid for
73
- * later string slicing.
74
- *
75
- * @example Blanks out the `// note` segment while keeping every character
76
- * position and line break, so later string slicing stays aligned.
77
- * ```ts
78
- * stripComments('const a = 1; // note\nconst b = 2;');
79
- * ```
80
- */
81
- function stripComments(code) {
82
- let result = '';
83
- let i = 0;
84
- while (i < code.length) {
85
- // Line comment
86
- if (code[i] === '/' && code[i + 1] === '/') {
87
- const end = code.indexOf('\n', i);
88
- if (end === -1) {
89
- result += ' '.repeat(code.length - i);
90
- break;
91
- }
92
- result += ' '.repeat(end - i) + '\n';
93
- i = end + 1;
94
- }
95
- else if (code[i] === '/' && code[i + 1] === '*') {
96
- const end = code.indexOf('*/', i + 2);
97
- if (end === -1) {
98
- result += ' '.repeat(code.length - i);
99
- break;
100
- }
101
- // Preserve newlines inside block comments to keep line positions valid
102
- let j = i;
103
- while (j <= end + 1) {
104
- result += code[j] === '\n' ? '\n' : ' ';
105
- j++;
106
- }
107
- i = end + 2;
108
- }
109
- else {
110
- result += code[i];
111
- i++;
112
- }
113
- }
114
- return result;
115
- }
116
- /**
117
- * Read and inline all assets for a given tag, returning an array of
118
- * `{ key, literal }` entries suitable for building object literals.
119
- * Plain `.css` files hold CSS custom-property presets and are inlined with
120
- * their real content so the component injects the default variables at
121
- * runtime; the same files are also merged into bundle.css as the override
122
- * placeholder. They are never left empty — an empty string would make the
123
- * runtime treat the asset as not-yet-loaded and re-fetch it.
124
- *
125
- * @example Returns one escaped template literal per descriptor, ready to be joined into an object.
126
- * ```ts
127
- * readAssetsInline('/src/shop/header', 'shop-header', ['shadowCss', 'targetHtml']);
128
- * ```
129
- *
130
- * @throws If a companion file for a descriptor does not exist.
131
- */
132
- async function readAssetsInline(dir, tagName, assets) {
133
- const entries = [];
134
- for (const asset of assets) {
135
- const relPath = resolveAssetPath(tagName, asset);
136
- const filePath = path.resolve(dir, relPath);
137
- try {
138
- const content = await readFile(filePath, 'utf8');
139
- entries.push({
140
- key: asset,
141
- literal: '`' +
142
- escapeTemplateLiteral(content)
143
- .replace(/[\n\t\r]/g, ' ')
144
- .replace(/\s\s/g, ' ') +
145
- '`',
146
- });
147
- }
148
- catch (_err) {
149
- throw new Error(`[load-assets] Cannot resolve asset "${asset}" for component "${tagName}": file not found at ${filePath}`, { cause: _err });
150
- }
151
- }
152
- return entries;
153
- }
154
- /**
155
- * Vite plugin factory that inlines companion `.css` / `.html` assets into the
156
- * DC classes declaring them through `static assets = [...]`.
157
- *
158
- * @example Register the plugin in a Vite config, pointing at the source root.
159
- * ```ts
160
- * import { loadAssetsPlugin } from './vite-plugin-load-assets';
161
- * export default defineConfig({ plugins: [loadAssetsPlugin(__dirname)] });
162
- * ```
163
- *
164
- * @param rootDir Directory holding the component source files.
165
- * @returns A Vite plugin option.
166
- */
167
- export function loadAssetsPlugin(rootDir) {
168
- return {
169
- name: 'load-assets-inline',
170
- async transform(code, id) {
171
- if (!code.includes('static assets'))
172
- return null;
173
- const dir = path.dirname(id);
174
- // Strip comments so JSDoc examples don't trigger false matches.
175
- // Whitespace-preserving replacement keeps character indices valid.
176
- const stripped = stripComments(code);
177
- // Resolve the component tag name from defineCE(...) call
178
- const tagName = extractDefineCETag(stripped);
179
- if (!tagName)
180
- return null;
181
- // Match: static assets = [ 'asset1' , 'asset2' ];
182
- const saRegex = /static\s+assets\s*=\s*\[(.*?)\]\s*;?/gs;
183
- const saMatches = [...stripped.matchAll(saRegex)];
184
- if (saMatches.length === 0)
185
- return null;
186
- let transformed = code;
187
- // There should be exactly one per file, but handle all in reverse
188
- for (let i = saMatches.length - 1; i >= 0; i--) {
189
- const match = saMatches[i];
190
- const assetListRaw = match[1];
191
- const assets = assetListRaw
192
- .split(',')
193
- .map((a) => a.trim().replace(/^['"]|['"]$/g, ''))
194
- .filter(Boolean);
195
- if (assets.length === 0)
196
- continue;
197
- // Read and inline all asset contents
198
- const entries = await readAssetsInline(dir, tagName, assets);
199
- // Build the static _assets property to inject as first line in class body
200
- const objEntries = entries.map((e) => `${e.key}: ${e.literal}`);
201
- const assetsObj = `{ ${objEntries.join(', ')} }`;
202
- // Find the opening brace of the class that owns this `static assets` declaration.
203
- // Search backwards from the match position to locate "class ... {" — we need the
204
- // index of the opening brace so we can insert right after it.
205
- const beforeMatch = stripped.substring(0, match.index);
206
- const classOpenMatch = /class\s+\w+[^{]*\{/.exec(beforeMatch);
207
- if (!classOpenMatch)
208
- continue;
209
- // Absolute position of the opening brace in `transformed` (= same as in `stripped`)
210
- const classBraceIndex = classOpenMatch.index + classOpenMatch[0].length - 1;
211
- // Build injection code — static _assets as first line inside the class body
212
- const injectCode = `\n static _assets = ${assetsObj};\n`;
213
- transformed =
214
- transformed.substring(0, classBraceIndex + 1) +
215
- injectCode +
216
- transformed.substring(classBraceIndex + 1);
217
- }
218
- return transformed;
219
- },
220
- };
221
- }