chocola 2.0.0-next.1 → 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.
@@ -1,6 +1,6 @@
1
1
  import { JSDOM } from "jsdom";
2
2
  import { extractContextFromElement } from "./dom-processor.js";
3
- import { genRandomId, incrementAlfabet, throwError, protectCurlyBraces } from "./utils.js";
3
+ import { genRandomId, incrementAlfabet, throwError, protectCurlyBraces, compileExpression } from "./utils.js";
4
4
  import chalk from "chalk";
5
5
  import beautify from "js-beautify";
6
6
 
@@ -101,45 +101,69 @@ 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
 
121
- function interpolateNode(node, ctxProxy) {
122
- if (node.nodeType === 3) {
123
- node.textContent = node.textContent.replace(/\{([^}]+)\}/g, (_, expr) => {
124
- try {
125
- return Function("ctx", `with(ctx) { return (${expr}); }`)(ctxProxy);
126
- } catch {
127
- return "";
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
+
125
+ function interpolateNode(root, ctxProxy) {
126
+ const stack = [root];
127
+ while (stack.length) {
128
+ const node = stack.pop();
129
+ if (node.nodeType === 3) {
130
+ node.textContent = node.textContent.replace(/\{([^}]+)\}/g, (_, expr) => {
131
+ try {
132
+ return compileExpression(expr, true)(ctxProxy);
133
+ } catch {
134
+ return "";
135
+ }
136
+ });
137
+ } else {
138
+ for (let i = node.childNodes.length - 1; i >= 0; i--) {
139
+ stack.push(node.childNodes[i]);
128
140
  }
129
- });
130
- } else {
131
- node.childNodes.forEach(child => interpolateNode(child, ctxProxy));
141
+ }
142
+ }
143
+ }
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;
132
156
  }
157
+ return null;
133
158
  }
134
159
 
135
160
  function validateChainStructure(parent, sourceFile, sourceContent, parentContent) {
136
161
  const children = [...parent.children];
137
162
  let chainActive = false;
138
- const outerCounts = {};
139
163
 
140
164
  for (const child of children) {
141
165
  const hasIf = child.hasAttribute("if");
142
- const hasDelIf = hasDelIfAttr(child, sourceFile);
166
+ const hasDelIf = hasDelIfAttr(child);
143
167
  const hasElif = child.hasAttribute("elif");
144
168
  const hasElse = child.hasAttribute("else");
145
169
 
@@ -149,36 +173,10 @@ function validateChainStructure(parent, sourceFile, sourceContent, parentContent
149
173
  const attr = hasElif ? "elif" : "else";
150
174
  let loc = sourceFile;
151
175
  if (sourceContent && parentContent) {
152
- const outer = child.outerHTML;
153
- outerCounts[outer] = (outerCounts[outer] || 0) + 1;
154
- let searchIdx = 0;
155
- let hits = 0;
156
- while ((searchIdx = parentContent.indexOf(outer, searchIdx)) !== -1) {
157
- hits++;
158
- if (hits === outerCounts[outer]) {
159
- let line = parentContent.substring(0, searchIdx).split("\n").length;
160
- if (sourceContent !== parentContent) {
161
- const anchor = parentContent.trim().slice(0, 80);
162
- const vars = [
163
- anchor,
164
- anchor.replace(/=""/g, ' '),
165
- anchor.replace(/=""/g, ''),
166
- anchor.replace(/="/g, "='").replace(/"/g, "'"),
167
- ];
168
- let anchorIdx = -1;
169
- for (const v of vars) {
170
- anchorIdx = sourceContent.indexOf(v);
171
- if (anchorIdx !== -1) break;
172
- }
173
- if (anchorIdx !== -1) {
174
- const offset = sourceContent.substring(0, anchorIdx).split("\n").length;
175
- line = offset + line - 1;
176
- }
177
- }
178
- loc = `${sourceFile}:${line}`;
179
- break;
180
- }
181
- searchIdx += outer.length;
176
+ const idx = parentContent.indexOf(child.outerHTML);
177
+ if (idx !== -1) {
178
+ const lineNum = getLineNumber(sourceContent, parentContent, idx);
179
+ if (lineNum !== null) loc = `${sourceFile}:${lineNum}`;
182
180
  }
183
181
  }
184
182
  throwError(`${loc}\n <${tag}> has ${attr} without a preceding if/del-if sibling`);
@@ -198,15 +196,6 @@ function validateChainStructure(parent, sourceFile, sourceContent, parentContent
198
196
  }
199
197
  }
200
198
 
201
- /**
202
- * Processes a single component element and inserts it into the DOM
203
- * @param {Element} element
204
- * @param {Map} loadedComponents
205
- * @param {Array} runtimeChunks
206
- * @param {Array} compIdColl
207
- * @param {object} letterState - { value: string }
208
- * @returns {boolean} - true if component was processed, false if not found
209
- */
210
199
  export function processComponentElement(
211
200
  element,
212
201
  loadedComponents,
@@ -223,12 +212,26 @@ export function processComponentElement(
223
212
  sourceContent
224
213
  ) {
225
214
  const tagName = element.tagName.toLowerCase();
226
- const compName = tagName + ".js";
227
- const instance = loadedComponents.get(compName);
215
+ const compName = tagName + ".html";
216
+ let instance = loadedComponents.get(compName);
228
217
 
229
218
  if (!instance || instance === undefined) return false;
230
219
  if (renderChain && renderChain.includes(compName)) return false;
231
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
+
232
235
  let ctx;
233
236
  if (staticCtxRegistry && staticCtxRegistry.has(element)) {
234
237
  ctx = staticCtxRegistry.get(element);
@@ -236,245 +239,286 @@ export function processComponentElement(
236
239
  ctx = extractContextFromElement(element);
237
240
  staticCtxRegistry && staticCtxRegistry.set(element, ctx);
238
241
  }
239
- 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;
240
256
 
241
257
  const ctxProxy = new Proxy(ctx, {
242
258
  has() { return true; },
243
259
  get(target, key) { return target[key]; }
244
260
  });
245
261
 
246
- if (instance.body) {
247
- let body = protectCurlyBraces(instance.body);
262
+ const fragment = JSDOM.fragment(template);
248
263
 
249
- const fragment = JSDOM.fragment(body);
250
-
251
- const slotFragment = JSDOM.fragment(srcInnerHtml);
252
- if (sourceFile) {
253
- validateChainStructure(slotFragment, sourceFile, sourceContent, srcInnerHtml);
254
- }
255
- validateChainStructure(fragment, instance.__sourceFile || compName, body, body);
256
- Array.from(fragment.querySelectorAll("slot")).forEach(slot => {
257
- slot.replaceWith(slotFragment);
258
- });
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
+ });
259
272
 
260
- const childEntries = Array.from(fragment.querySelectorAll("*")).map(el => ({
261
- el,
262
- parent: el.parentNode
263
- }));
264
- const condChains = new Map();
273
+ const childEntries = Array.from(fragment.querySelectorAll("*")).map(el => ({
274
+ el,
275
+ parent: el.parentNode
276
+ }));
277
+ const condChains = new Map();
265
278
 
266
- childEntries.forEach(({ el: child, parent }) => {
267
- if (!condChains.has(parent)) {
268
- condChains.set(parent, { active: false, rendered: false });
269
- }
270
- const condChain = condChains.get(parent);
279
+ childEntries.forEach(({ el: child, parent }) => {
280
+ if (!condChains.has(parent)) {
281
+ condChains.set(parent, { active: false, rendered: false });
282
+ }
283
+ const condChain = condChains.get(parent);
271
284
 
272
- const hasIf = child.hasAttribute("if");
273
- const hasDelIf = hasDelIfAttr(child, instance.__sourceFile || compName);
274
- const hasElif = child.hasAttribute("elif");
275
- const hasElse = child.hasAttribute("else");
285
+ const hasIf = child.hasAttribute("if");
286
+ const hasDelIf = hasDelIfAttr(child);
287
+ const hasElif = child.hasAttribute("elif");
288
+ const hasElse = child.hasAttribute("else");
276
289
 
277
- if (hasElif || hasElse) {
278
- if (!condChain.active) {
279
- throwError(`${instance.__sourceFile || compName}: <${child.tagName.toLowerCase()}> has ${hasElif ? "elif" : "else"} without a preceding if/del-if sibling`);
280
- }
281
- if (condChain.rendered) {
282
- child.remove();
283
- if (hasElse) {
284
- condChain.active = false;
285
- }
286
- return;
287
- }
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`);
288
293
  }
289
-
290
- if (child.tagName.toLowerCase() === "void") {
291
- if (hasElif || hasElse) {
292
- if (hasElif) {
293
- const expr = child.getAttribute("elif").slice(1, -1);
294
- const fn = new Function("ctx", `with(ctx) { return (${expr}); }`);
295
- if (!fn(ctxProxy)) {
296
- child.remove();
297
- return;
298
- }
299
- }
300
- child.replaceWith(...child.children);
301
- condChain.rendered = true;
302
- if (hasElse) {
303
- condChain.active = false;
304
- }
305
- } else if (hasIf || hasDelIf) {
306
- const raw = hasIf ? child.getAttribute("if") : getDelIfAttr(child);
307
- const expr = raw.slice(1, -1);
308
- const fn = new Function("ctx", `with(ctx) { return (${expr}); }`);
309
- condChain.active = true;
310
- if (fn(ctxProxy)) {
311
- child.replaceWith(...child.children);
312
- condChain.rendered = true;
313
- } else {
314
- child.remove();
315
- condChain.rendered = false;
316
- }
317
- } else {
318
- child.replaceWith(...child.children);
294
+ if (condChain.rendered) {
295
+ child.remove();
296
+ if (hasElse) {
319
297
  condChain.active = false;
320
- condChain.rendered = false;
321
298
  }
322
299
  return;
323
300
  }
301
+ }
324
302
 
325
- const reservedAttrs = ["if", "del-if", "del:if", "elif", "else"];
326
-
327
- Array.from(child.attributes).forEach(attribute => {
328
- if (!attribute || attribute === undefined) return;
329
- if (reservedAttrs.includes(attribute.name)) return;
330
- attribute.value = attribute.value.replace(
331
- /\{([^}]+)\}/g,
332
- (_, expr) => {
333
- try {
334
- return Function("ctx", `with(ctx) { return (${expr}); }`)(ctxProxy);
335
- } catch {
336
- return "";
337
- }
303
+ if (child.tagName.toLowerCase() === "void") {
304
+ if (hasElif || hasElse) {
305
+ if (hasElif) {
306
+ const expr = child.getAttribute("elif").slice(1, -1);
307
+ const fn = compileExpression(expr, true);
308
+ if (!fn(ctxProxy)) {
309
+ child.remove();
310
+ return;
338
311
  }
339
- );
340
- });
341
-
342
- processComponentElement(
343
- child,
344
- loadedComponents,
345
- runtimeChunks,
346
- compIdColl,
347
- letterState,
348
- runtimeMap,
349
- cssScopes,
350
- cssScopesMap,
351
- scopedStyles,
352
- renderChain.concat(compName),
353
- staticCtxRegistry,
354
- instance.__sourceFile || compName,
355
- body
356
- );
357
-
358
- if (hasIf) {
359
- const expr = child.getAttribute("if").slice(1, -1);
360
- const fn = new Function("ctx", `with(ctx) { return (${expr}); }`);
361
- condChain.active = true;
362
- if (fn(ctxProxy)) {
363
- condChain.rendered = true;
364
- } else {
365
- child.style.display = "none";
366
- condChain.rendered = false;
367
312
  }
368
- child.removeAttribute("if");
369
- } else if (hasDelIf) {
370
- const expr = getDelIfAttr(child).slice(1, -1);
371
- const fn = new Function("ctx", `with(ctx) { return (${expr}); }`);
313
+ child.replaceWith(...child.children);
314
+ condChain.rendered = true;
315
+ if (hasElse) {
316
+ condChain.active = false;
317
+ }
318
+ } else if (hasIf || hasDelIf) {
319
+ const raw = hasIf ? child.getAttribute("if") : getDelIfAttr(child);
320
+ const expr = raw.slice(1, -1);
321
+ const fn = compileExpression(expr, true);
372
322
  condChain.active = true;
373
323
  if (fn(ctxProxy)) {
324
+ child.replaceWith(...child.children);
374
325
  condChain.rendered = true;
375
326
  } else {
376
327
  child.remove();
377
328
  condChain.rendered = false;
378
329
  }
379
- removeDelIfAttr(child);
380
- } else if (hasElif) {
381
- const expr = child.getAttribute("elif").slice(1, -1);
382
- const fn = new Function("ctx", `with(ctx) { return (${expr}); }`);
383
- if (fn(ctxProxy)) {
384
- condChain.rendered = true;
385
- } else {
386
- child.remove();
387
- }
388
- child.removeAttribute("elif");
389
- } else if (hasElse) {
390
- condChain.rendered = true;
391
- condChain.active = false;
392
- child.removeAttribute("else");
393
330
  } else {
331
+ child.replaceWith(...child.children);
394
332
  condChain.active = false;
395
333
  condChain.rendered = false;
396
334
  }
335
+ return;
336
+ }
337
+
338
+ const reservedAttrs = ["if", "del:if", "elif", "else"];
397
339
 
398
- interpolateNode(child, ctxProxy)
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
+ );
399
353
  });
400
354
 
401
- 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
+ });
413
+
414
+ const firstChild = fragment.children[0];
415
+
416
+ if (firstChild && firstChild.nodeType === 1) {
417
+ if (script) {
418
+ const compId = "chid-" + genRandomId(compIdColl);
419
+ firstChild.setAttribute("chid", compId);
420
+
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
+ }
402
441
 
403
- if (firstChild && firstChild.nodeType === 1) {
404
- if (instance.script || instance.effects) {
405
- const compId = "chid-" + genRandomId(compIdColl);
406
- firstChild.setAttribute("chid", compId);
442
+ script = script.replace(ctxRegex, "ctx");
407
443
 
408
- let script = instance.script && instance.script.toString();
444
+ function extractRuntime(script) {
445
+ const startRegex = /(?:async\s+)?function\s+\$runtime\(([^)]*)\)\s*\{/;
446
+ const match = script.match(startRegex);
409
447
 
410
- const ctxRegex = /ctx\s*=\s*({.*?})/;
411
- const ctxMatch = script.match(ctxRegex);
412
- let runtimeCtx = {};
413
- if (ctxMatch) {
414
- try {
415
- runtimeCtx = JSON.parse(ctxMatch[1].replace(/(\w+):/g, '"$1":'));
416
- } catch (e) {
417
- runtimeCtx = {};
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
+ }
418
464
  }
419
465
  }
420
- let ctxDef = "";
421
- for (const [key, value] of Object.entries(runtimeCtx)) {
422
- 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}`);
423
481
  }
424
- script = script.replace(ctxRegex, "ctx");
425
- script = script.replace(/RUNTIME\([^)]*\)\s*{/, match => match + "\n" + ctxDef);
482
+
483
+ runtime = runtime.replace(/\$runtime\([^)]*\)\s*\{/, match => match + "\n" + ctxDef);
426
484
 
427
485
  let letterEntry = runtimeMap && runtimeMap.get(compName);
428
486
  let letter;
429
487
  if (!letterEntry) {
430
488
  letter = getNextLetter(letterState);
431
- script = script.replace(/RUNTIME/g, `${letter}RUNTIME`);
432
- runtimeChunks.push(script);
489
+ runtime = runtime.replace(`${RUNTIME_KW}()`, `${letter}r(self, ctx)`);
490
+ runtimeChunks.push(runtime);
433
491
  runtimeMap && runtimeMap.set(compName, { letter });
434
492
  } else {
435
493
  letter = letterEntry.letter;
436
494
  }
437
495
 
438
- runtimeChunks.push(`${letter}RUNTIME(document.querySelector('[chid="${compId}"]'), ${JSON.stringify(ctx)});`);
496
+ runtimeChunks.push(`${letter}r(document.querySelector('[chid="${compId}"]'), ${JSON.stringify(ctx)});`);
439
497
  }
440
498
  }
499
+ }
441
500
 
442
- let style = instance.styles && instance.styles.toString();
443
- if (style) {
444
- let cssId = cssScopesMap && cssScopesMap.get(compName);
445
- if (!cssId) {
446
- cssId = genRandomId(cssScopes, 8, true);
447
- cssScopesMap.set(compName, cssId);
448
- }
449
- if (fragment.firstChild && fragment.firstChild.nodeType === 1) {
450
- fragment.firstChild.classList.add(cssId);
451
- }
452
- style = scopeCss(style, cssId);
453
- 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);
454
506
  }
455
-
456
- if (fragment.firstChild && fragment.firstChild.nodeType === 1) {
457
- fragment.firstChild.setAttribute("data-ch-source", instance.__sourceFile || compName);
507
+ if (fragment.children.length === 1 && firstChild.nodeType === 1) {
508
+ firstChild.classList.add(cssId);
458
509
  }
510
+ styles = scopeCss(styles, cssId);
511
+ scopedStyles.push(styles);
512
+ }
459
513
 
460
- element.replaceWith(fragment);
461
- return true;
514
+ if (fragment.children.length === 1 && firstChild.nodeType === 1) {
515
+ firstChild.setAttribute("data-ch-source", compName);
462
516
  }
463
517
 
464
- console.warn(chalk.yellow(`${compName} — component could not be loaded (missing body or styles export)`));
465
- return false;
518
+ element.replaceWith(fragment);
519
+ return true;
466
520
  }
467
521
 
468
- /**
469
- * Processes all components in the app container
470
- * @param {Element[]} appElements
471
- * @param {Map} loadedComponents
472
- * @returns {{
473
- * runtimeScript: string,
474
- * hasComponents: boolean
475
- * scopesCss: CSSString
476
- * }}
477
- */
478
522
  export function processAllComponents(appElements, loadedComponents, pageSourceFile, pageSourceContent) {
479
523
  let runtimeChunks = [];
480
524
  let compIdColl = [];
@@ -486,6 +530,7 @@ export function processAllComponents(appElements, loadedComponents, pageSourceFi
486
530
  let staticCtxRegistry = new Map();
487
531
 
488
532
  appElements.forEach(el => {
533
+ if (!el.isConnected) return;
489
534
  processComponentElement(el, loadedComponents, runtimeChunks, compIdColl, letterState, runtimeMap, cssScopes, cssScopesMap, scopedStyles, [], staticCtxRegistry, pageSourceFile, pageSourceContent);
490
535
  });
491
536
  const runtimeScript = runtimeChunks.join("\n");
@@ -495,11 +540,6 @@ export function processAllComponents(appElements, loadedComponents, pageSourceFi
495
540
  return { runtimeScript, hasComponents, scopesCss };
496
541
  }
497
542
 
498
- /**
499
- * Gets the next letter in sequence or starts with 'a'
500
- * @param {object} letterState - { value: string }
501
- * @returns {string}
502
- */
503
543
  function getNextLetter(letterState) {
504
544
  if (!letterState.value) {
505
545
  letterState.value = "a";
@@ -508,3 +548,6 @@ function getNextLetter(letterState) {
508
548
  }
509
549
  return letterState.value;
510
550
  }
551
+
552
+ const RUNTIME_KW = "$runtime";
553
+ const RUNTIME_REGEX = new RegExp(RUNTIME_KW, "g");