Skip to content

1.20.0 release - #101

Open
ECorreia45 wants to merge 139 commits into
mainfrom
next
Open

1.20.0 release#101
ECorreia45 wants to merge 139 commits into
mainfrom
next

Conversation

@ECorreia45

@ECorreia45 ECorreia45 commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

Summary

This PR prepares the Markup 1.20 release line from the next branch. It replaces the renderer internals with a compiled, persistent-part architecture while retaining the established html, state, lifecycle, ref, helper, and template-rendering model from 1.19.x.

The redesign compiles static template shapes once, keeps dynamic parts alive between updates, makes reactive ownership explicit, improves list reconciliation and teardown, removes obsolete renderer paths, and strengthens the package-owned documentation used by developers and AI tools.

Normal tagged-template usage remains source-compatible. The notable API evolutions are:

  • unsafeHTML(source) provides an explicit boundary for deliberately parsing trusted or independently sanitized markup.
  • A function returned from effect() is now lifecycle cleanup. Cleanup runs before the next execution and when the effect is disposed; non-function results continue to be cached and passed to the next execution.
  • State getters are internally identified as signal reads so a function stored in state remains a function value when bound to a template property. Ordinary functions interpolated directly into templates remain lazily evaluated computed values.

The legacy html(string[]) form remains operational but is deprecated in favor of unsafeHTML. No separate component, resource, renderer, or template-result abstraction is introduced.

Renderer architecture

Cached compilation and persistent parts

  • Compiles each tagged-template call site through a cached native <template> definition.
  • Represents dynamic children, text, attributes, properties, events, refs, and attribute spreads as persistent runtime parts.
  • Locates dynamic nodes through precomputed indexes rather than repeated selector or path discovery.
  • Reuses compiled anchors for simple primitive child bindings.
  • Updates compatible same-shape nested templates in place.
  • Initializes dynamic custom-element properties before connection.
  • Keeps compilation eager and cached so construction errors remain immediate and compilation work is not moved into the render hot path.

DOM parsing and updates

  • Preserves interpolation positions in tables, table sections, rows, cells, captions, columns, and column groups.
  • Retains the established behavior for standalone table rows containing direct text.
  • Preserves raw <script> and <style> interpolation behavior.
  • Keeps dynamic tag names escaped instead of interpreting them as elements.
  • Avoids unnecessary live childNodes traversal during insertion.
  • Removes complete owned ranges in bulk while preserving DOM-first unmount and cleanup ordering.

Lists and reconciliation

  • Retains DOM identity for keyed and automatically identified repeat rows when rebinding is valid.
  • Replaces event handlers safely while retaining row DOM.
  • Returns immediately for unchanged child sequences.
  • Mounts initial list content directly when lifecycle staging is unnecessary.
  • Lazily allocates bookkeeping used only by templates that need it.
  • Disposes nested template trees in memory and removes the owning DOM range once.
  • Keeps repeat behavior and its public API unchanged.

Reactivity, lifecycle, and refs

  • Renderer-owned effects are isolated from surrounding user effects and disposed by their owning template.
  • Conditional nested effects are reconciled by execution slot, removing effects when their branch disappears.
  • State reads inside lifecycle callbacks and custom-element connection callbacks do not leak into active render dependencies.
  • Effects created by connected custom elements remain active, including elements connected inside a live shadow root.
  • Retained nested-template effects survive parent reevaluation.
  • Parent refs reflect currently rendered dynamic descendants before dependent user effects run.
  • Function values stored in state are preserved as values instead of being mistaken for a second computed layer.
  • Directly interpolated functions continue to support recursively derived computed values.
  • effect() cleanup runs untracked before reruns and on disposal, while non-function result caching remains supported.

Undocumented HtmlTemplate.__* cooperation methods are marked internal and removed from emitted public declarations. The public class remains focused on template construction, DOM inspection, rendering, insertion, replacement, unmounting, and lifecycle callbacks.

Async rendering

The existing callable suspense() API is preserved, with more robust internal lifecycle handling:

  • resolved or rejected results are ignored after unmount;
  • results from a superseded mount cannot replace newer content;
  • synchronous throws are handled through the same rejection path as promise failures;
  • arbitrary rejection values are normalized to Error objects;
  • remounting resets the boundary to its loading state.

No public resource API is added.

HTML safety boundary

Normal html tagged-template interpolations remain outside the browser HTML parser. Runtime strings are applied as values, including standalone table-row content and attributes.

unsafeHTML(source) is the explicit boundary for parsing trusted or independently sanitized markup. User-controlled content should remain in ordinary html interpolations and must not be passed to unsafeHTML.

The older manually constructed array form, such as html([source]), remains supported for compatibility and is deprecated. The element() helper remains a small element-construction shortcut; its htmlContent option maps to unsanitized innerHTML, which is now documented explicitly.

Package and build cleanup

  • Removes the previous ReactiveNode, normalization, dynamic-raw parsing, render-content, and syncNodes paths.
  • Removes the obsolete @beforesemicolon/html-parser runtime dependency.
  • Uses @beforesemicolon/builder 2.0.1 with minification-oriented build options.
  • Produces ESM, CommonJS, TypeScript declarations, and a direct browser bundle.
  • Marks the CommonJS output boundary so Node can load the package through require().
  • Keeps the established package-root and dist publishing layout.
  • Orders the types export condition correctly.
  • Keeps renderer/effect ownership helpers out of the root public barrel and internal template methods out of declarations.
  • Prepares the package as 1.20.2-next for prerelease validation.

Current package output:

Artifact Size
Browser client 31.3 kB raw
Browser client, gzip 10.53 kB
Browser client, Brotli 9.54 kB
npm tarball 41.3 kB packed
npm tarball contents 141.8 kB unpacked / 87 files

The repository includes a separate SSR and hydration design plan. It is not part of the client API or published tarball, and any future server implementation must remain outside the browser bundle.

Documentation and AI context

  • Expands package-owned llms.txt with API contracts, behavioral rules, examples, and ecosystem boundaries.
  • Enables generation of a complete resolved llms-full.txt artifact.
  • Connects Markup documentation with the Web Component, Router, and Intl package documentation.
  • Documents signal reads, function-valued state, effect cleanup, reactive ref timing, and the unsafe HTML boundary.
  • Updates localized documentation in English, Spanish, French, Portuguese, and Chinese.
  • Clarifies that element({ htmlContent }) is unsanitized and intended only for trusted or independently sanitized HTML.
  • Updates browser bundle-size claims to match current artifacts.

Compatibility coverage

Regression coverage includes:

  • same-shape nested-template rebinding;
  • keyed and automatic repeat identity;
  • retained-row event-handler replacement;
  • adjacent dynamic cells and standalone table contexts;
  • custom-element properties and connection timing;
  • lifecycle state reads remaining untracked;
  • custom-element effects in light DOM and connected shadow roots;
  • detached-element and shadow-root movement;
  • conditional nested-effect cleanup;
  • bulk nested teardown, cleanup ordering, and remounting;
  • dynamic descendant refs and effect ordering;
  • marker-looking static text and attributes;
  • reactive interpolation inside native <template>.content;
  • union-typed TemplateStringsArray | string[] callers and emitted deprecation documentation;
  • untrusted table-row content and attributes remaining outside HTML parsing;
  • deliberate trusted markup through unsafeHTML;
  • function values stored in state remaining callable values;
  • recursively computed ordinary template functions;
  • effect cleanup before reruns and disposal, including untracked cleanup reads;
  • suspense resolution after unmount, stale remount results, failures, and arbitrary resolved values;
  • the existing callable suspense() return contract.

Performance

Renderer comparison with 1.19.1

The renderer was compared with 1.19.1 using 1,000 representative rows on an Apple M3 with Node 24.14.0. These are medians from warmed, alternating paired runs with explicit garbage collection; absolute timings vary by machine.

Scenario 1.19.1 1.20 Change
Minimal mount + unmount 86.44 ms 45.43 ms 47.4% faster
Moderate mount + unmount 177.34 ms 98.40 ms 44.5% faster
Filesystem-like mount + unmount 391.25 ms 324.36 ms 17.1% faster
Immutable list update, automatic identity 557.13 ms 5.41 ms about 103x faster
Immutable list update, keyed 548.55 ms 5.28 ms about 104x faster

js-framework-benchmark validation of the final lifecycle changes

The final runtime was also compared against the exact pre-lifecycle-change next commit with the keyed js-framework-benchmark implementation. CPU measurements use 15 iterations and the framework's default throttling. A baseline run and a reverse-order candidate run were used to control for browser and machine ordering effects.

Operation Final Baseline Change
Create 1,000 rows 27.1 ms 27.5 ms 1.5% faster
Replace 1,000 rows 31.0 ms 31.2 ms 0.6% faster
Partial update 22.1 ms 23.4 ms 5.6% faster
Select row 6.9 ms 8.8 ms 21.6% faster
Swap rows 19.2 ms 21.0 ms 8.6% faster
Remove row 14.9 ms 15.5 ms 3.9% faster
Create 10,000 rows 292.6 ms 295.2 ms 0.9% faster
Append 1,000 rows 32.4 ms 32.9 ms 1.5% faster
Clear rows 16.0 ms 16.0 ms unchanged

The weighted CPU geometric ratio is 0.967x, approximately 3.3% faster overall. Memory is effectively neutral in the single-sample memory groups. The final browser client adds 532 raw bytes, 198 gzip bytes, and 153 Brotli bytes for the signal identity, cleanup, and suspense lifecycle behavior.

These results validate that the final API and lifecycle additions do not regress the renderer hot path. Published-table placement is not claimed because the public comparison table is produced on a different machine and browser dataset.

Verification

Verification completed on the final branch:

  • npm run build
    • ESLint and Prettier passed.
    • 18 suites and 211 tests passed with no failures.
    • TypeScript declarations, ESM, CommonJS, browser bundle, and documentation built successfully.
  • npm pack --dry-run
    • Produced the expected 87-file package manifest.
    • Repository-only plans and documentation sources remain excluded from the tarball.
  • Repository microbenchmarks
    • Repeat-cache, DOM mount/update, and reactive-list suites completed.
  • js-framework-benchmark
    • All nine CPU groups, three memory groups, size, and startup completed successfully.
    • A paired baseline and reverse-order candidate pass were used for the final runtime comparison.
  • Focused safety and compatibility regressions
    • Untrusted standalone-row content is rendered as text.
    • Untrusted standalone-row attributes cannot create event attributes.
    • Explicit trusted raw HTML remains supported through unsafeHTML.
    • Public declarations omit internal HtmlTemplate.__* methods.

Review focus

The primary review areas are:

  • compiled-part rendering and template ownership in src/html.ts;
  • effect scheduling, signal identity, and cleanup in src/state.ts and src/effect-context.ts;
  • repeat reconciliation and retained-template rebinding;
  • table-context parsing and raw HTML boundaries;
  • suspense lifecycle behavior;
  • public declaration and package export boundaries;
  • compatibility regressions consolidated in the existing test suites.

ECorreia45 and others added 30 commits August 20, 2026 16:21
@codesandbox

codesandbox Bot commented Aug 23, 2026

Copy link
Copy Markdown

Review or Edit in CodeSandbox

Open the branch in Web EditorVS CodeInsiders

Open Preview

Comment thread src/html.ts
Comment thread src/html.ts
@ECorreia45 ECorreia45 changed the title Next 1.20.1 release Aug 23, 2026
@ECorreia45 ECorreia45 changed the title 1.20.1 release 1.20.0 release Aug 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants