svelte-shaker 0.9.0 → 0.9.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.
Binary file
package/dist/transform.js CHANGED
@@ -274,12 +274,17 @@ outDead) {
274
274
  * or gain a space.
275
275
  */
276
276
  function foldIfBlocks(fragment, env, setEnv, code, s, dead) {
277
- walk(fragment, { parent: null, preserve: hasPreserveWhitespaceOption(fragment) }, {
277
+ walk(fragment, { parent: null, preserve: hasPreserveWhitespaceOption(fragment), element: null }, {
278
278
  _(node, { state, next }) {
279
279
  if (node.type !== 'IfBlock') {
280
- // Descend, recording this node as the children's parent and whether it
281
- // opens a preserved-whitespace context for them.
282
- next({ parent: node, preserve: state.preserve || isPreserveElement(node) });
280
+ // Descend, recording this node as the children's parent, whether it
281
+ // opens a preserved-whitespace context, and the content-model element
282
+ // their seam would land in (for the `{" "}` validity check).
283
+ next({
284
+ parent: node,
285
+ preserve: state.preserve || isPreserveElement(node),
286
+ element: childParentElement(node, state.element),
287
+ });
283
288
  return;
284
289
  }
285
290
  // `elseif` IfBlocks are the *continuation* of a chain we already own from
@@ -294,9 +299,12 @@ function foldIfBlocks(fragment, env, setEnv, code, s, dead) {
294
299
  // node as its children's parent), so its `nodes` are the chain's siblings.
295
300
  index: state.parent?.nodes?.indexOf(node) ?? -1,
296
301
  preserve: state.preserve,
302
+ element: state.element,
297
303
  });
304
+ // kept head: descend for nested blocks. The `{#if}` is transparent to the
305
+ // content model, so children stay in the same parent element.
298
306
  if (decision.recurse)
299
- next({ parent: node, preserve: state.preserve }); // kept head: descend for nested blocks
307
+ next({ parent: node, preserve: state.preserve, element: state.element });
300
308
  // otherwise the subtree is gone or re-emitted verbatim — do not recurse.
301
309
  },
302
310
  });
@@ -366,10 +374,12 @@ function isFullRemoval(decision) {
366
374
  * edge-trimmed, so it renders exactly one space wherever it lands, matching the
367
375
  * original. Otherwise plain deletion already preserves space presence (only the
368
376
  * run LENGTH can differ, which the SSR oracle normalizes). Never compensate
369
- * under preserved whitespace — there plain deletion is byte-exact.
377
+ * under preserved whitespace — there plain deletion is byte-exact — nor inside a
378
+ * text-free parent (`<tr>`, `<tbody>`, …), where Svelte rejects the `{" "}` text
379
+ * child outright and the whitespace rendered nothing to begin with.
370
380
  */
371
381
  function removeChain(removed, span, code, s, dead, ctx) {
372
- if (!ctx.preserve && ctx.parent?.nodes && ctx.index >= 0) {
382
+ if (!ctx.preserve && !isTextFreeParent(ctx.element) && ctx.parent?.nodes && ctx.index >= 0) {
373
383
  const seam = analyzeSeam(ctx.parent.nodes, ctx.index, span, code, dead);
374
384
  if (seam) {
375
385
  s.overwrite(seam[0], seam[1], '{" "}');
@@ -431,6 +441,49 @@ function isRenderingSibling(node, code) {
431
441
  function isPreserveElement(node) {
432
442
  return node.type === 'RegularElement' && (node.name === 'pre' || node.name === 'textarea');
433
443
  }
444
+ /**
445
+ * Parent elements whose HTML content model forbids a text child: Svelte's
446
+ * `is_tag_valid_with_parent('#text', …)` rejects a `#text`/`{" "}` here with
447
+ * `node_invalid_placement` — these are exactly its `disallowed_children` entries
448
+ * that carry an `only` list (html/head/frameset/#document can't appear as
449
+ * elements inside a component, so only the table parts remain). Svelte never
450
+ * renders inter-child whitespace inside them either, so a removed chain's seam
451
+ * needs plain deletion: emitting the `{" "}` compensation would produce a
452
+ * component that fails to compile. See {@link removeChain}.
453
+ */
454
+ const TEXT_FREE_PARENTS = new Set(['table', 'thead', 'tbody', 'tfoot', 'tr', 'colgroup']);
455
+ /**
456
+ * Node types that reset the content-model parent to "unknown" (text allowed
457
+ * again), mirroring svelte's `parent_element: null` reset in the SvelteElement /
458
+ * SvelteFragment / SnippetBlock / Component visitors. A `{" "}` seam in any of
459
+ * these contexts is valid, so the seam compensation may proceed.
460
+ */
461
+ const PARENT_ELEMENT_RESET = new Set([
462
+ 'SvelteElement',
463
+ 'SvelteFragment',
464
+ 'SnippetBlock',
465
+ 'Component',
466
+ 'SvelteSelf',
467
+ 'SvelteComponent',
468
+ ]);
469
+ /**
470
+ * The content-model parent element a seam would land in for `node`'s children,
471
+ * given the element the walk is currently inside. Mirrors svelte's
472
+ * `parent_element` threading: a `RegularElement` becomes the parent, the reset
473
+ * node types clear it, and every other node (Fragment, blocks, …) is transparent
474
+ * and inherits. `null` means "text allowed" (root or a reset context).
475
+ */
476
+ function childParentElement(node, current) {
477
+ if (node.type === 'RegularElement')
478
+ return node.name ?? null;
479
+ if (PARENT_ELEMENT_RESET.has(node.type))
480
+ return null;
481
+ return current;
482
+ }
483
+ /** True when an `{" "}` seam would be an invalid text child of `element`. */
484
+ function isTextFreeParent(element) {
485
+ return element !== null && TEXT_FREE_PARENTS.has(element);
486
+ }
434
487
  /** Does the component opt into preserved whitespace via `<svelte:options>`? */
435
488
  function hasPreserveWhitespaceOption(fragment) {
436
489
  let preserve = false;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "svelte-shaker",
3
- "version": "0.9.0",
3
+ "version": "0.9.1",
4
4
  "description": "Tree shaking for Svelte components",
5
5
  "keywords": [
6
6
  "dead-code-elimination",