chocola 2.0.0-next.2 → 2.0.0-next.4

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,45 +1,32 @@
1
- # chocola
2
-
3
- Chocola is a JavaScript library for creating web user interfaces.
4
-
5
- `chocola` only contains the functionality to build and serve web static builds. For production you may want to implement `chocola` to your workflow with other frameworks as Vite until official workflows are provided.
6
-
7
- ## Usage
8
-
9
- ```html
10
- <!-- counter.html -->
11
- <div>
12
- <button title={title}>{text}</button>
13
-
14
- <div class="main">
15
- <div class="number">{count}</div>
16
- </div>
17
- </div>
18
- ```
19
-
20
- ```js
21
- import body from "./html/counter.html";
22
- import styles from "./css/counter.css";
23
-
24
- function RUNTIME(self, ctx) {
25
- const btn = self.querySelector("button");
26
- const number = self.querySelector(".number");
27
-
28
- btn.addEventListener("click", () => {
29
- ctx.count++;
30
- number.textContent = ctx.count;
31
- })
32
- }
33
-
34
- export default function Counter() {
35
- return {
36
- body,
37
- styles,
38
- script: RUNTIME
39
- }
40
- }
41
- ```
42
-
43
- ## Documentation
44
-
45
- See https://github.com/sad-gabi/chocola/wiki
1
+ # chocola
2
+
3
+ Chocola is a JavaScript library for creating web user interfaces.
4
+
5
+ `chocola` only contains the functionality to build and serve web static builds. For production you may want to implement `chocola` to your workflow with other frameworks as Vite until official workflows are provided.
6
+
7
+ ## Usage
8
+
9
+ ```html
10
+ <!-- MyTitle.html -->
11
+ <script>
12
+ let self = new HTMLElement;
13
+
14
+ export let title = "Default title";
15
+
16
+ function $runtime() {
17
+ console.log("Component mounted:", self)
18
+ }
19
+ </script>
20
+
21
+ <template>
22
+ <h1>{title}</h1>
23
+ </template>
24
+
25
+ <style>
26
+ button { color: chocolate; }
27
+ </style>
28
+ ```
29
+
30
+ ## Documentation
31
+
32
+ See https://github.com/sad-gabi/chocola/wiki
@@ -101,23 +101,27 @@ function scopeCss(cssString, cssId) {
101
101
  return processBlock(cssString);
102
102
  }
103
103
 
104
- const warnedDeprecated = new Set();
105
- function hasDelIfAttr(el, sourceLoc) {
106
- if (el.hasAttribute("del-if") && !el.hasAttribute("del:if") && !warnedDeprecated.has(sourceLoc)) {
107
- warnedDeprecated.add(sourceLoc);
108
- const name = sourceLoc ? sourceLoc.split(/[\\/]/).pop() : "unknown";
109
- console.warn(chalk.yellow(`[deprecation] "del-if" is deprecated in ${chalk.cyan(name)} and will be removed in Chocola 2. Use "del:if" instead.`));
110
- }
111
- return el.hasAttribute("del-if") || el.hasAttribute("del:if");
104
+ function hasDelIfAttr(el) {
105
+ return el.hasAttribute("del:if");
112
106
  }
113
107
  function getDelIfAttr(el) {
114
- return el.getAttribute("del:if") ?? el.getAttribute("del-if");
108
+ return el.getAttribute("del:if");
115
109
  }
116
110
  function removeDelIfAttr(el) {
117
- el.removeAttribute("del-if");
118
111
  el.removeAttribute("del:if");
119
112
  }
120
113
 
114
+ function extractPropsDefaults(script) {
115
+ if (!script) return [];
116
+ const propsRegex = /export\s+let\s+([a-zA-Z_$][0-9a-zA-Z_$]*)\s*(?:=\s*([^;]+))?;/g;
117
+ let props = [];
118
+ let match;
119
+ while ((match = propsRegex.exec(script)) !== null) {
120
+ props.push({ name: match[1].trim(), defaultValue: match[2]?.trim() });
121
+ }
122
+ return props;
123
+ }
124
+
121
125
  function interpolateNode(root, ctxProxy) {
122
126
  const stack = [root];
123
127
  while (stack.length) {
@@ -138,13 +142,28 @@ function interpolateNode(root, ctxProxy) {
138
142
  }
139
143
  }
140
144
 
145
+ function getLineNumber(sourceContent, parentContent, idxInParent) {
146
+ if (sourceContent === parentContent) {
147
+ return parentContent.substring(0, idxInParent).split("\n").length;
148
+ }
149
+ const templateStartMatch = sourceContent.match(/<template[^>]*>/);
150
+ if (templateStartMatch) {
151
+ const contentStart = templateStartMatch.index + templateStartMatch[0].length;
152
+ const beforeContent = sourceContent.substring(0, contentStart);
153
+ const linesInBefore = beforeContent.split("\n").length;
154
+ const linesInTemplate = parentContent.substring(0, idxInParent).split("\n").length;
155
+ return linesInBefore + linesInTemplate - 1;
156
+ }
157
+ return null;
158
+ }
159
+
141
160
  function validateChainStructure(parent, sourceFile, sourceContent, parentContent) {
142
161
  const children = [...parent.children];
143
162
  let chainActive = false;
144
163
 
145
164
  for (const child of children) {
146
165
  const hasIf = child.hasAttribute("if");
147
- const hasDelIf = hasDelIfAttr(child, sourceFile);
166
+ const hasDelIf = hasDelIfAttr(child);
148
167
  const hasElif = child.hasAttribute("elif");
149
168
  const hasElse = child.hasAttribute("else");
150
169
 
@@ -153,10 +172,11 @@ function validateChainStructure(parent, sourceFile, sourceContent, parentContent
153
172
  const tag = child.tagName.toLowerCase();
154
173
  const attr = hasElif ? "elif" : "else";
155
174
  let loc = sourceFile;
156
- if (sourceContent && sourceContent === parentContent) {
175
+ if (sourceContent && parentContent) {
157
176
  const idx = parentContent.indexOf(child.outerHTML);
158
177
  if (idx !== -1) {
159
- loc = `${sourceFile}:${parentContent.substring(0, idx).split("\n").length}`;
178
+ const lineNum = getLineNumber(sourceContent, parentContent, idx);
179
+ if (lineNum !== null) loc = `${sourceFile}:${lineNum}`;
160
180
  }
161
181
  }
162
182
  throwError(`${loc}\n <${tag}> has ${attr} without a preceding if/del-if sibling`);
@@ -176,15 +196,6 @@ function validateChainStructure(parent, sourceFile, sourceContent, parentContent
176
196
  }
177
197
  }
178
198
 
179
- /**
180
- * Processes a single component element and inserts it into the DOM
181
- * @param {Element} element
182
- * @param {Map} loadedComponents
183
- * @param {Array} runtimeChunks
184
- * @param {Array} compIdColl
185
- * @param {object} letterState - { value: string }
186
- * @returns {boolean} - true if component was processed, false if not found
187
- */
188
199
  export function processComponentElement(
189
200
  element,
190
201
  loadedComponents,
@@ -201,12 +212,26 @@ export function processComponentElement(
201
212
  sourceContent
202
213
  ) {
203
214
  const tagName = element.tagName.toLowerCase();
204
- const compName = tagName + ".js";
205
- const instance = loadedComponents.get(compName);
215
+ const compName = tagName + ".html";
216
+ let instance = loadedComponents.get(compName);
206
217
 
207
218
  if (!instance || instance === undefined) return false;
208
219
  if (renderChain && renderChain.includes(compName)) return false;
209
220
 
221
+ instance = protectCurlyBraces(instance);
222
+ const dom = new JSDOM(instance);
223
+ const doc = dom.window.document;
224
+ let script = doc.querySelector("script")?.innerHTML;
225
+ let template = doc.querySelector("template")?.innerHTML;
226
+ let styles = doc.querySelector("style")?.innerHTML;
227
+
228
+ if (!template) {
229
+ console.warn(chalk.yellow(`${compName} — component is missing a <template>`));
230
+ return false;
231
+ }
232
+
233
+ const compProps = extractPropsDefaults(script);
234
+
210
235
  let ctx;
211
236
  if (staticCtxRegistry && staticCtxRegistry.has(element)) {
212
237
  ctx = staticCtxRegistry.get(element);
@@ -214,245 +239,286 @@ export function processComponentElement(
214
239
  ctx = extractContextFromElement(element);
215
240
  staticCtxRegistry && staticCtxRegistry.set(element, ctx);
216
241
  }
217
- const srcInnerHtml = element.innerHTML;
242
+
243
+ if (compProps.length > 0) {
244
+ compProps.forEach(({ name, defaultValue }) => {
245
+ if (defaultValue !== undefined && !(name in ctx)) {
246
+ try {
247
+ ctx[name] = compileExpression(defaultValue, false)();
248
+ } catch {
249
+ ctx[name] = defaultValue;
250
+ }
251
+ }
252
+ });
253
+ }
254
+
255
+ const elInnerHtml = element.innerHTML;
218
256
 
219
257
  const ctxProxy = new Proxy(ctx, {
220
258
  has() { return true; },
221
259
  get(target, key) { return target[key]; }
222
260
  });
223
261
 
224
- if (instance.body) {
225
- let body = protectCurlyBraces(instance.body);
262
+ const fragment = JSDOM.fragment(template);
263
+
264
+ const slotFragment = JSDOM.fragment(elInnerHtml);
265
+ if (sourceFile) {
266
+ validateChainStructure(slotFragment, sourceFile, sourceContent, elInnerHtml);
267
+ }
268
+ validateChainStructure(fragment, instance.__sourceFile || compName, instance, template);
269
+ Array.from(fragment.querySelectorAll("slot")).forEach(slot => {
270
+ slot.replaceWith(slotFragment);
271
+ });
226
272
 
227
- const fragment = JSDOM.fragment(body);
273
+ const childEntries = Array.from(fragment.querySelectorAll("*")).map(el => ({
274
+ el,
275
+ parent: el.parentNode
276
+ }));
277
+ const condChains = new Map();
228
278
 
229
- const slotFragment = JSDOM.fragment(srcInnerHtml);
230
- if (sourceFile) {
231
- validateChainStructure(slotFragment, sourceFile, sourceContent, srcInnerHtml);
279
+ childEntries.forEach(({ el: child, parent }) => {
280
+ if (!condChains.has(parent)) {
281
+ condChains.set(parent, { active: false, rendered: false });
232
282
  }
233
- validateChainStructure(fragment, instance.__sourceFile || compName, body, body);
234
- Array.from(fragment.querySelectorAll("slot")).forEach(slot => {
235
- slot.replaceWith(slotFragment);
236
- });
283
+ const condChain = condChains.get(parent);
237
284
 
238
- const childEntries = Array.from(fragment.querySelectorAll("*")).map(el => ({
239
- el,
240
- parent: el.parentNode
241
- }));
242
- const condChains = new Map();
285
+ const hasIf = child.hasAttribute("if");
286
+ const hasDelIf = hasDelIfAttr(child);
287
+ const hasElif = child.hasAttribute("elif");
288
+ const hasElse = child.hasAttribute("else");
243
289
 
244
- childEntries.forEach(({ el: child, parent }) => {
245
- if (!condChains.has(parent)) {
246
- condChains.set(parent, { active: false, rendered: false });
290
+ if (hasElif || hasElse) {
291
+ if (!condChain.active) {
292
+ throwError(`${instance.__sourceFile || compName}: <${child.tagName.toLowerCase()}> has ${hasElif ? "elif" : "else"} without a preceding if/del-if sibling`);
247
293
  }
248
- const condChain = condChains.get(parent);
249
-
250
- const hasIf = child.hasAttribute("if");
251
- const hasDelIf = hasDelIfAttr(child, instance.__sourceFile || compName);
252
- const hasElif = child.hasAttribute("elif");
253
- const hasElse = child.hasAttribute("else");
254
-
255
- if (hasElif || hasElse) {
256
- if (!condChain.active) {
257
- throwError(`${instance.__sourceFile || compName}: <${child.tagName.toLowerCase()}> has ${hasElif ? "elif" : "else"} without a preceding if/del-if sibling`);
258
- }
259
- if (condChain.rendered) {
260
- child.remove();
261
- if (hasElse) {
262
- condChain.active = false;
263
- }
264
- return;
294
+ if (condChain.rendered) {
295
+ child.remove();
296
+ if (hasElse) {
297
+ condChain.active = false;
265
298
  }
299
+ return;
266
300
  }
301
+ }
267
302
 
268
- if (child.tagName.toLowerCase() === "void") {
269
- if (hasElif || hasElse) {
270
- if (hasElif) {
271
- const expr = child.getAttribute("elif").slice(1, -1);
272
- const fn = compileExpression(expr, true);
273
- if (!fn(ctxProxy)) {
274
- child.remove();
275
- return;
276
- }
277
- }
278
- child.replaceWith(...child.children);
279
- condChain.rendered = true;
280
- if (hasElse) {
281
- condChain.active = false;
282
- }
283
- } else if (hasIf || hasDelIf) {
284
- const raw = hasIf ? child.getAttribute("if") : getDelIfAttr(child);
285
- const expr = raw.slice(1, -1);
303
+ if (child.tagName.toLowerCase() === "void") {
304
+ if (hasElif || hasElse) {
305
+ if (hasElif) {
306
+ const expr = child.getAttribute("elif").slice(1, -1);
286
307
  const fn = compileExpression(expr, true);
287
- condChain.active = true;
288
- if (fn(ctxProxy)) {
289
- child.replaceWith(...child.children);
290
- condChain.rendered = true;
291
- } else {
308
+ if (!fn(ctxProxy)) {
292
309
  child.remove();
293
- condChain.rendered = false;
310
+ return;
294
311
  }
295
- } else {
296
- child.replaceWith(...child.children);
297
- condChain.active = false;
298
- condChain.rendered = false;
299
312
  }
300
- return;
301
- }
302
-
303
- const reservedAttrs = ["if", "del-if", "del:if", "elif", "else"];
304
-
305
- Array.from(child.attributes).forEach(attribute => {
306
- if (!attribute || attribute === undefined) return;
307
- if (reservedAttrs.includes(attribute.name)) return;
308
- attribute.value = attribute.value.replace(
309
- /\{([^}]+)\}/g,
310
- (_, expr) => {
311
- try {
312
- return compileExpression(expr, true)(ctxProxy);
313
- } catch {
314
- return "";
315
- }
316
- }
317
- );
318
- });
319
-
320
- processComponentElement(
321
- child,
322
- loadedComponents,
323
- runtimeChunks,
324
- compIdColl,
325
- letterState,
326
- runtimeMap,
327
- cssScopes,
328
- cssScopesMap,
329
- scopedStyles,
330
- renderChain.concat(compName),
331
- staticCtxRegistry,
332
- instance.__sourceFile || compName,
333
- body
334
- );
335
-
336
- if (hasIf) {
337
- const expr = child.getAttribute("if").slice(1, -1);
338
- const fn = compileExpression(expr, true);
339
- condChain.active = true;
340
- if (fn(ctxProxy)) {
341
- condChain.rendered = true;
342
- } else {
343
- child.style.display = "none";
344
- condChain.rendered = false;
313
+ child.replaceWith(...child.children);
314
+ condChain.rendered = true;
315
+ if (hasElse) {
316
+ condChain.active = false;
345
317
  }
346
- child.removeAttribute("if");
347
- } else if (hasDelIf) {
348
- const expr = getDelIfAttr(child).slice(1, -1);
318
+ } else if (hasIf || hasDelIf) {
319
+ const raw = hasIf ? child.getAttribute("if") : getDelIfAttr(child);
320
+ const expr = raw.slice(1, -1);
349
321
  const fn = compileExpression(expr, true);
350
322
  condChain.active = true;
351
323
  if (fn(ctxProxy)) {
324
+ child.replaceWith(...child.children);
352
325
  condChain.rendered = true;
353
326
  } else {
354
327
  child.remove();
355
328
  condChain.rendered = false;
356
329
  }
357
- removeDelIfAttr(child);
358
- } else if (hasElif) {
359
- const expr = child.getAttribute("elif").slice(1, -1);
360
- const fn = compileExpression(expr, true);
361
- if (fn(ctxProxy)) {
362
- condChain.rendered = true;
363
- } else {
364
- child.remove();
365
- }
366
- child.removeAttribute("elif");
367
- } else if (hasElse) {
368
- condChain.rendered = true;
369
- condChain.active = false;
370
- child.removeAttribute("else");
371
330
  } else {
331
+ child.replaceWith(...child.children);
372
332
  condChain.active = false;
373
333
  condChain.rendered = false;
374
334
  }
335
+ return;
336
+ }
375
337
 
376
- interpolateNode(child, ctxProxy)
338
+ const reservedAttrs = ["if", "del:if", "elif", "else"];
339
+
340
+ Array.from(child.attributes).forEach(attribute => {
341
+ if (!attribute || attribute === undefined) return;
342
+ if (reservedAttrs.includes(attribute.name)) return;
343
+ attribute.value = attribute.value.replace(
344
+ /\{([^}]+)\}/g,
345
+ (_, expr) => {
346
+ try {
347
+ return compileExpression(expr, true)(ctxProxy);
348
+ } catch {
349
+ return "";
350
+ }
351
+ }
352
+ );
377
353
  });
378
354
 
379
- const firstChild = fragment.firstChild;
355
+ processComponentElement(
356
+ child,
357
+ loadedComponents,
358
+ runtimeChunks,
359
+ compIdColl,
360
+ letterState,
361
+ runtimeMap,
362
+ cssScopes,
363
+ cssScopesMap,
364
+ scopedStyles,
365
+ renderChain.concat(compName),
366
+ staticCtxRegistry,
367
+ compName,
368
+ template
369
+ );
370
+
371
+ if (hasIf) {
372
+ const expr = child.getAttribute("if").slice(1, -1);
373
+ const fn = compileExpression(expr, true);
374
+ condChain.active = true;
375
+ if (fn(ctxProxy)) {
376
+ condChain.rendered = true;
377
+ } else {
378
+ child.style.display = "none";
379
+ condChain.rendered = false;
380
+ }
381
+ child.removeAttribute("if");
382
+ } else if (hasDelIf) {
383
+ const expr = getDelIfAttr(child).slice(1, -1);
384
+ const fn = compileExpression(expr, true);
385
+ condChain.active = true;
386
+ if (fn(ctxProxy)) {
387
+ condChain.rendered = true;
388
+ } else {
389
+ child.remove();
390
+ condChain.rendered = false;
391
+ }
392
+ removeDelIfAttr(child);
393
+ } else if (hasElif) {
394
+ const expr = child.getAttribute("elif").slice(1, -1);
395
+ const fn = compileExpression(expr, true);
396
+ if (fn(ctxProxy)) {
397
+ condChain.rendered = true;
398
+ } else {
399
+ child.remove();
400
+ }
401
+ child.removeAttribute("elif");
402
+ } else if (hasElse) {
403
+ condChain.rendered = true;
404
+ condChain.active = false;
405
+ child.removeAttribute("else");
406
+ } else {
407
+ condChain.active = false;
408
+ condChain.rendered = false;
409
+ }
410
+
411
+ interpolateNode(child, ctxProxy)
412
+ });
380
413
 
381
- if (firstChild && firstChild.nodeType === 1) {
382
- if (instance.script || instance.effects) {
383
- const compId = "chid-" + genRandomId(compIdColl);
384
- firstChild.setAttribute("chid", compId);
414
+ const firstChild = fragment.children[0];
385
415
 
386
- let script = instance.script && instance.script.toString();
416
+ if (firstChild && firstChild.nodeType === 1) {
417
+ if (script) {
418
+ const compId = "chid-" + genRandomId(compIdColl);
419
+ firstChild.setAttribute("chid", compId);
387
420
 
388
- const ctxRegex = /ctx\s*=\s*({.*?})/;
389
- const ctxMatch = script.match(ctxRegex);
390
- let runtimeCtx = {};
391
- if (ctxMatch) {
392
- try {
393
- runtimeCtx = JSON.parse(ctxMatch[1].replace(/(\w+):/g, '"$1":'));
394
- } catch (e) {
395
- runtimeCtx = {};
421
+ const ctxRegex = /ctx\s*=\s*({.*?})/;
422
+ const ctxMatch = script.match(ctxRegex);
423
+ let runtimeCtx = {};
424
+ if (ctxMatch) {
425
+ try {
426
+ runtimeCtx = JSON.parse(ctxMatch[1].replace(/(\w+):/g, '"$1":'));
427
+ } catch (e) {
428
+ runtimeCtx = {};
429
+ }
430
+ }
431
+ let ctxDef = "";
432
+ for (const [key, value] of Object.entries(runtimeCtx)) {
433
+ ctxDef += `ctx.${key} = ctx.${key}||${JSON.stringify(value)};\n`;
434
+ }
435
+
436
+ for (const { name, defaultValue } of compProps) {
437
+ if (defaultValue !== undefined) {
438
+ ctxDef += `ctx.${name} = ctx.${name}||${defaultValue};\n`;
439
+ }
440
+ }
441
+
442
+ script = script.replace(ctxRegex, "ctx");
443
+
444
+ function extractRuntime(script) {
445
+ const startRegex = /(?:async\s+)?function\s+\$runtime\(([^)]*)\)\s*\{/;
446
+ const match = script.match(startRegex);
447
+
448
+ if (!match) return null;
449
+
450
+ const startIndexBrace = match.index + match[0].length - 1;
451
+
452
+ let bracesCount = 0;
453
+ let closingBraceIndex = -1;
454
+
455
+ for (let i = startIndexBrace; i < script.length; i++) {
456
+ if (script[i] === "{") {
457
+ bracesCount++;
458
+ } else if (script[i] === "}") {
459
+ bracesCount--;
460
+ if (bracesCount === 0) {
461
+ closingBraceIndex = i;
462
+ break;
463
+ }
396
464
  }
397
465
  }
398
- let ctxDef = "";
399
- for (const [key, value] of Object.entries(runtimeCtx)) {
400
- ctxDef += `ctx.${key} = ctx.${key}||${JSON.stringify(value)};\n`;
466
+
467
+ if (closingBraceIndex === -1) {
468
+ throw new Error(`${compName} $runtime function has unclosed curly braces.`);
469
+ }
470
+
471
+ const fullMatch = script.substring(match.index, closingBraceIndex + 1);
472
+
473
+ return fullMatch;
474
+ }
475
+
476
+ let runtime = extractRuntime(script);
477
+
478
+ if (runtime) {
479
+ for (const { name } of compProps) {
480
+ runtime = runtime.replace(name, `ctx.${name}`);
401
481
  }
402
- script = script.replace(ctxRegex, "ctx");
403
- script = script.replace(/RUNTIME\([^)]*\)\s*{/, match => match + "\n" + ctxDef);
482
+
483
+ runtime = runtime.replace(/\$runtime\([^)]*\)\s*\{/, match => match + "\n" + ctxDef);
404
484
 
405
485
  let letterEntry = runtimeMap && runtimeMap.get(compName);
406
486
  let letter;
407
487
  if (!letterEntry) {
408
488
  letter = getNextLetter(letterState);
409
- script = script.replace(/RUNTIME/g, `${letter}RUNTIME`);
410
- runtimeChunks.push(script);
489
+ runtime = runtime.replace(`${RUNTIME_KW}()`, `${letter}r(self, ctx)`);
490
+ runtimeChunks.push(runtime);
411
491
  runtimeMap && runtimeMap.set(compName, { letter });
412
492
  } else {
413
493
  letter = letterEntry.letter;
414
494
  }
415
495
 
416
- runtimeChunks.push(`${letter}RUNTIME(document.querySelector('[chid="${compId}"]'), ${JSON.stringify(ctx)});`);
496
+ runtimeChunks.push(`${letter}r(document.querySelector('[chid="${compId}"]'), ${JSON.stringify(ctx)});`);
417
497
  }
418
498
  }
499
+ }
419
500
 
420
- let style = instance.styles && instance.styles.toString();
421
- if (style) {
422
- let cssId = cssScopesMap && cssScopesMap.get(compName);
423
- if (!cssId) {
424
- cssId = genRandomId(cssScopes, 8, true);
425
- cssScopesMap.set(compName, cssId);
426
- }
427
- if (fragment.firstChild && fragment.firstChild.nodeType === 1) {
428
- fragment.firstChild.classList.add(cssId);
429
- }
430
- style = scopeCss(style, cssId);
431
- scopedStyles.push(style);
501
+ if (styles) {
502
+ let cssId = cssScopesMap && cssScopesMap.get(compName);
503
+ if (!cssId) {
504
+ cssId = genRandomId(cssScopes, 8, true);
505
+ cssScopesMap.set(compName, cssId);
432
506
  }
433
-
434
- if (fragment.firstChild && fragment.firstChild.nodeType === 1) {
435
- fragment.firstChild.setAttribute("data-ch-source", instance.__sourceFile || compName);
507
+ if (fragment.children.length === 1 && firstChild.nodeType === 1) {
508
+ firstChild.classList.add(cssId);
436
509
  }
510
+ styles = scopeCss(styles, cssId);
511
+ scopedStyles.push(styles);
512
+ }
437
513
 
438
- element.replaceWith(fragment);
439
- return true;
514
+ if (fragment.children.length === 1 && firstChild.nodeType === 1) {
515
+ firstChild.setAttribute("data-ch-source", compName);
440
516
  }
441
517
 
442
- console.warn(chalk.yellow(`${compName} — component could not be loaded (missing body or styles export)`));
443
- return false;
518
+ element.replaceWith(fragment);
519
+ return true;
444
520
  }
445
521
 
446
- /**
447
- * Processes all components in the app container
448
- * @param {Element[]} appElements
449
- * @param {Map} loadedComponents
450
- * @returns {{
451
- * runtimeScript: string,
452
- * hasComponents: boolean
453
- * scopesCss: CSSString
454
- * }}
455
- */
456
522
  export function processAllComponents(appElements, loadedComponents, pageSourceFile, pageSourceContent) {
457
523
  let runtimeChunks = [];
458
524
  let compIdColl = [];
@@ -474,11 +540,6 @@ export function processAllComponents(appElements, loadedComponents, pageSourceFi
474
540
  return { runtimeScript, hasComponents, scopesCss };
475
541
  }
476
542
 
477
- /**
478
- * Gets the next letter in sequence or starts with 'a'
479
- * @param {object} letterState - { value: string }
480
- * @returns {string}
481
- */
482
543
  function getNextLetter(letterState) {
483
544
  if (!letterState.value) {
484
545
  letterState.value = "a";
@@ -487,3 +548,6 @@ function getNextLetter(letterState) {
487
548
  }
488
549
  return letterState.value;
489
550
  }
551
+
552
+ const RUNTIME_KW = "$runtime";
553
+ const RUNTIME_REGEX = new RegExp(RUNTIME_KW, "g");