Skip to content

Toolchain and reload audit #4

Closed

Description

@dvcolomban

Part of Design a portable contribution SDK and WebExtension runtime.

Question

Which concrete Vite/Oxc-based build and test toolchain can produce browser-safe SDK packages, a Chromium/Firefox extension, runnable host examples, development HMR, and watched production builds with a live preview backend, while enforcing strict linting and type checks?

The output is a reproducible compatibility report and recommendation. Exact reload semantics are settled in Live preview and reload contract; this audit supplies measured facts and viable mechanisms.

Context and current behavior

The repository is a single Vue extension template. Its package.json, vite.config.ts, and scripts/prepare.ts combine application rendering, manifest generation, development URL replacement, and background compilation. That is evidence of the current starting point, not an architecture to preserve.

In the inspected devframe revision a3f967755af99a0a0d8dd82b7b5d4f3dd4aec0bc, Vite adapters configure a development server and can produce static RPC/shared-state snapshots. The inspected devtools revision 121656dfc5866b2f8de5baa8e0b6166ec28599c8 also separates live hosting from static build output. Neither inspection established a configurePreviewServer integration or a watched-output notification protocol.

Primary starting references: Vite HMR API, Vite plugin API, Vite build options, Oxlint type-aware linting, and TypeScript 7 announcement. Recheck current release versions and compiler API compatibility rather than treating an announcement as proof that every integration works.

Requirements and scope

  • Use pnpm/Turbo, Vite/Oxc, strict Oxlint, and current supported dependencies. Prefer TypeScript 7 when actual emit and downstream consumption work.
  • Produce separate runtime-safe entrypoints for Node, extension background, content scripts, page-world code, extension pages, and renderer integration.
  • Chromium and Firefox builds must declare their real background and UI mechanisms. Framework-specific rendering may live inside a replaceable renderer dependency, while the SDK remains TypeScript/framework-neutral.
  • Examples are maintained workspaces using public package exports. Verify both workspace development and packed-package consumption.
  • Every public SDK API/hook/feature must have a runnable example and automated coverage in its supported environment; choose tooling capable of enforcing that requirement.
  • Local checks stay package/task-scoped. Plan complete CI coverage without authorizing a full local monorepo run.

Options and recommendation

Evaluate a maintained Vite-based extension build integration against a small custom Vite integration. Compare real control of entrypoints, browser manifests, worker reload, content-script lifecycle, source maps, CSP, and preview integration. Do not choose an extension framework based solely on its advertised HMR support.

Start with the smallest maintained integration that passes the required experiments. Keep SDK package building separate from host-specific packaging. Prefer TypeScript 7 for checking and declaration emit; document any required compatibility compiler or upstream blocker with a minimal reproduction instead of silently downgrading.

Proposed API or experiment

The following is an experiment outline, not a settled package API:

for (const candidate of extensionBuildCandidates) {
  const artifacts = await buildExampleWorkspaces(candidate, {
    browsers: ['chromium', 'firefox'],
    modes: ['development', 'build-watch-preview'],
  })

  await verifyPackageExportsAndDeclarations(artifacts)
  await verifyBrowserBundlesHaveNoNodeImports(artifacts)
  await loadExtensionAndExerciseRealRuntime(artifacts)
  await changeContributionAndObserveReload(artifacts)
  await introduceBuildErrorAndObserveRecovery(artifacts)
  await installPackedSdkIntoCleanExample(artifacts)
}

Record commands, versions, browser binary versions, output manifests, logs, and observed reload behavior. Use a tiny typed contribution with a JSON view, state, one action, and a content-script hook. This probe measures the pipeline; the full example/test inventory belongs to Examples and API coverage contract.

Scenarios and acceptance criteria

  1. A clean package-scoped build emits JavaScript and declarations consumed from a tarball without source aliases or sibling checkout links.
  2. An extension background entry imports the shared contract without bringing Node or renderer-only modules into its bundle.
  3. A renderer change in development produces the documented update/reload behavior in an actual extension page.
  4. A successful watched build becomes observable through preview; record which parts need additional integration rather than claiming source-module HMR exists in compiled assets.
  5. An invalid rebuild does not produce an unexplained blank UI or an apparently successful stale generation. Record the tool's real behavior for the reload decision.
  6. A failed public-hook example causes its test task to fail; missing matrix coverage can be detected independently from line-coverage percentages.

Dependencies

No decision blockers. Coordinate source compatibility facts with Upstream reuse audit and browser restrictions with Browser capability audit. Supply concrete build mechanisms to Live preview and reload contract and Release and conformance contract.

Definition of Ready

  • The required runtime entrypoints and example classes above are included in the probe plan.
  • Candidate versions and official documentation/source references are recorded.
  • Isolated fixture locations and narrowly scoped commands are specified.
  • Actual Chromium and Firefox verification is available or any missing environment is explicitly reported before claiming results.

Definition of Done

  • A cited report records exact tested versions, commands, artifacts, and pass/fail observations for each candidate.
  • The recommendation explains rejected alternatives and remaining implementation work.
  • TypeScript checking, declaration emit, strict Oxlint, and packed-package consumption are verified independently.
  • Development and compiled-preview behavior are distinguished with observed evidence.
  • Browser/runtime import boundaries and example test orchestration have a concrete enforcement mechanism.
  • Unresolved failures have minimal reproductions and named follow-up issues; no unperformed browser check is reported as passed.

Resolution record

Post the report and recommendation in a resolution comment, including artifact/branch links. Close only when the audit question is answered; add the resulting named decision to the parent map. No SDK implementation is implied by closing this research ticket.

Activity

  1. self-assigned this
    on Sep 22, 2026
  2. dvcolomban commented on Sep 22, 2026

    @dvcolomban
    CollaboratorAuthor

    Research checkpoint — this audit remains open.

    The isolated fixture passed TypeScript 7.0.2 declaration emit, packed-package consumption and project-reference checks; strict Oxlint 1.85.0 with tsgolint 7.0.2002; and Vitest 5.0.1. Deliberate type, promise and assertion faults failed as expected. WXT 0.21.4 and CRXJS 2.7.1 both built Chromium/Firefox MV3 artifacts using Vite 8.3.0.

    Real Chromium 153.0.8010.12 and Firefox 156.0 executed a WXT content-to-background call. A Chromium extension popup accepted a module update without changing document time origin. A Vite watched build changed compiled output, but the open preview needed an explicit reload. Invalid rebuilds preserved the previous HTML and subsequently recovered.

    TypeScript 7, Oxlint's type-aware engine, Playwright's Chromium extension runner, and Mozilla web-ext are the relevant primary references. Firefox extension execution must use its own real extension runner.

    Remaining gates: Firefox HMR; content/background reload and retained state; CRXJS runtime behavior; custom-builder comparison; full shared JSON contribution/renderer/host examples; negative package-boundary checks; selected Node LTS compatibility; complete coverage enforcement. Loading generated extension HTML directly does not establish native popup, sidepanel or inspected-tab DevTools lifecycle behavior. No SDK implementation or full-repository validation is claimed.

    The targeted Turbo task failed during registry DNS retrieval; clean orchestration remains unverified.

    Retained research context: local, unpushed branch dvcol/research/toolchain-reload, commit cf44ec6, report docs/research/toolchain-reload.md. The full research artifact remains local; no public artifact URL is claimed. This checkpoint is not a resolution or an implementation-complete claim.

  3. dvcolomban commented on Sep 22, 2026

    @dvcolomban
    CollaboratorAuthor

    Resolution

    Recommend TypeScript 7.0.2, Vite 8.3.0/Oxc, Oxlint 1.85.0 with oxlint-tsgolint 7.0.2002, pnpm 12.5.1 and Turbo 2.11.2 as the checked portable-package pipeline. Recommend WXT 0.21.4 as the extension-packaging candidate, conditional on the declaration and reload gates below. The custom Vite candidate is a working fallback. This research does not approve a blanket skipLibCheck setting or mark the later SDK implemented.

    The comparison used identical shared application modules: a JSON document typed with public Spec, stored and in-memory counts, one increment action, one deliberate failure, and a content hook. Both candidates emitted Chrome/Firefox manifests and all planned HTML/script entry classes. The fixture has a literal HTML page; it does not claim compatibility with the existing reference renderer or native browser UI.

    The second pass ran on macOS arm64/Node 26.9.0 with current stable Chrome for Testing 153.0.8010.52 and Firefox 156.0.1. It used isolated profiles. Official Chromium metadata, official Firefox metadata.

    Check Outcome
    TS7 checker/declaration emitter, strict portable compile, Oxlint controls Passed independently
    Two-workspace filtered Turbo graph Seven tasks passed; example-test failure propagated with exit 1, then restoration passed
    Standalone packed root ESM/types consumer Passed without workspace links or source aliases; full library checking enabled
    Declaration-map packaging Corrected to include mapped sources; both final map targets exist in the tarball
    Browser boundary controls Direct/transitive node:fs and one exact renderer sentinel rejected; portable input accepted
    WXT/custom production on both stable browsers RPC, stored count update, deliberate action error and extension-page execution observed
    WXT extension-page HMR Updated content and unchanged document time origin in both browsers
    Custom extension-page update Same document in Chromium; document replacement in Firefox
    WXT Chromium content update Document replacement and subsequent successful action observed
    WXT Firefox background fallback New generation, reset memory count and retained stored count observed; exact triggering edit unresolved
    WXT complete declaration check Failed minimum, described below; custom full declaration check passed
    Background/content reconnection and manifest adoption Incomplete. Firefox explicit-reload and manifest raw matches were invalidated as stale-history evidence
    Watched production build/preview First pass proved rebuild/error recovery; open preview required explicit reload

    The browser runner records failures and continues. Exit zero means the observation cases finished, not that all reload checks passed. Same-document HMR does not prove UI-local state retention or post-HMR click/listener behavior. The content result does not establish absence of duplicate listeners. A reporting timeout does not prove that the background failed to reload.

    WXT supplies browser manifest and reload machinery that the custom candidate would require us to maintain. The custom candidate passed complete declaration checking and production execution, but it deliberately contains no content/background/manifest auto-reloader. CRXJS already passed minimal Chrome/Firefox builds in the first pass and remains a reserve candidate; no unsupported runtime claims are made.

    The WXT minimum imports a type from wxt/browser. Including Chrome ambient types yields duplicate HARFormatEntry/HARFormatLog declarations from @types/chrome 0.3.0 and @wxt-dev/browser 0.3.0. Removing Chrome types yields unresolved chrome references. WXT's generated config skips library checks, but that only allowed this research adapter's authored sources to be checked; it did not resolve the incompatibility. Portable contracts, the custom candidate and packed consumer keep complete declaration checks. Published WXT package, browser declaration package.

    The following handoffs are part of the decision:

    • Release and conformance contract, before adoption in Portable contribution proof: make the retained WXT declaration minimum pass with full library checking and pinned dependencies. Keep strict portable contracts independent of packaging. If this cannot be resolved acceptably, use the custom Vite fallback.
    • Live preview and reload contract: resolve external-runner integration and select retention behavior. Require a unique marker and report cursor for each change, explicit run/browser identity and manifest version, settled restorations, post-transition clicks, atomic command IDs and a duplicate-response observation window. Observe worker generation and content reconnection separately. Define watched-preview success/failure notifications.
    • Examples and API coverage contract: implement an independent public API catalogue and example/test/host registry. A catalogue entry declares supported and unsupported hosts. Registry rows link each API to a runnable example and concrete automated assertions for those outcomes. Fail CI on unknown, missing or orphaned links. The audit's handwritten feature arrays prove only failure propagation; they do not implement this catalogue.
    • Renderer and surface contract and Portable contribution proof: exercise the actual reference renderer through valid host contexts. No fabricated extension Port/Devframe context was introduced here.
    • Release and conformance contract: expand packed checks to every public export condition and real runtime dependency graph, then establish the Node baseline and full CI host matrix.

    Retained evidence: local report docs/research/toolchain-reload.md, fixture/log directory docs/research/toolchain-reload-v2-evidence/, and its reviewed-results.json. Raw observations, negative controls, manifests, coherent lockfile and final scoped lint/type results are retained. No repository-wide validation, SDK implementation or upstream changes were performed.

    The audit's Definition of Done is met through the measured pipeline, candidate comparison, explicit limitations and named follow-ups. Its closure does not waive the packaging gates or complete the SDK. dvcol is accountable for the handoffs until each contract has an assigned implementer.

    Retained executable evidence is on local, unpushed branch dvcol/research/toolchain-reload, commit d114ad2, under docs/research/. The source-linked findings above are the public resolution; no hosted artifact URL is claimed.

    Minimal declaration failure

    The retained wxt-type-minimum/entry.ts contains:

    import type { Browser } from 'wxt/browser';
    export type BrowserManifest = Browser.runtime.Manifest;

    After wxt prepare fixtures/wxt, the TS7 minimum includes that file and the generated public-path declaration, with strict: true, skipLibCheck: false, moduleResolution: "Bundler" and types: ["node", "chrome"]. It rejects duplicate HARFormatEntry/HARFormatLog identifiers. Omitting "chrome" instead rejects unresolved references inside @wxt-dev/browser. Keep this negative case until a compatible pinned package/environment combination passes it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

wayfinder:researchEvidence gathering that unblocks a design decision

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions