Skip to content

Chore: full project upgrade (mostly infra, little code) - #418

Open
ryan-roemer wants to merge 19 commits into
masterfrom
chore/uplift
Open

ryan-roemer wants to merge 19 commits into
masterfrom
chore/uplift

Conversation

@ryan-roemer

Copy link
Copy Markdown
Member

Rebuilds the repo tooling and fixes four library bugs. We'll eventually release as v5.

Tasks

  • npm trusted publisher setup
  • Vercel project reconfiguration.

Library

  • LivePreview overloads use React.JSX instead of the global JSX namespace,
    which @types/react@19 removed. This is the breaking change.
  • Adds an exports map. Published filenames are unchanged, so main/module/
    types resolve as before; deep imports into dist/ no longer resolve.
  • Build target es6es2022. engines.node >= 0.12.0>=20.19.0.
  • Forwards Editor's prism prop, which was typed but never passed through.
  • Transpiles with sucrase production, dropping the __self/__source props
    and React 19's outdated-JSX-transform warning.
  • Adds getDerivedStateFromError to the internal error boundary.

Same exports, same props, same react >=18 peer range.

Tooling

  • pnpm → npm workspaces, tsup → tsdown (publint + attw on every build),
    jest → vitest, eslint → oxlint, Prettier 3, Docusaurus 2 → 3.
  • Removes Storybook and the duplicate packages/demo. Stories are now a plain
    Vite app under packages/react-live/stories, smoke-tested in Chromium.
  • 93 tests across jsdom and Chromium; 93% statement coverage, reported not gated.
  • CI: checks, browser tests, and a React 18 job for the other end of the peer range.
  • Release: changesets v2 actions, npm trusted publishing over OIDC, approval gate.

Drops infrastructure that no longer carries its weight:

- `.sail.yml` — the last Sail vestige. Its install/lint/test/prettier/build
  flow is fully covered by GitHub Actions, and the `prettier` task it
  referenced was never defined in its own `tasks:` block.
- `packages/demo` — never deployed and not referenced by the website, whose
  `src/components/live-edit.tsx` it duplicates almost verbatim. Its Vite config
  had no alias to library source, so it resolved `react-live` to built output
  and was not even a live-source dev loop.
- Storybook 6 (EOL) and its two stories — the only consumers of `@storybook/*`,
  `webpack`, `babel-loader`, and `styled-components@4.0.0-beta`.
- `.eslintrc`, `.eslintignore`, `.babelrc`, `tsup.config.ts`, and the website's
  `babel.config.js`, all replaced in the following commits.
- `.npmignore`, obsolete since the `files` field governs the tarball.
Prettier 2 -> 3 changed the `trailingComma` default to "all". This commit is
formatting only — no logic changes — so it can be skipped with
`git blame --ignore-rev`.
Replaces the 2023-era stack. Measured on this repo: the library now builds
CJS + ESM + declarations + sourcemaps in ~76ms, and typechecks in ~0.33s.

| Concern | Before | After |
| --- | --- | --- |
| Bundling | tsup 6 (esbuild) | tsdown (rolldown + oxc) |
| Types | TypeScript 4.9 | TypeScript 7 (native compiler) |
| Tests | Jest 27 + 9 Babel packages | Vitest 5 |
| Linting | ESLint 8 + 7 plugins | oxlint |
| Formatting | Prettier 2 via eslint-plugin-prettier | Prettier 3 |
| Package manager | pnpm 7 | npm workspaces |
| Docs site | Docusaurus 2.4 (EOL) | Docusaurus 3.10 |

Roughly 30 devDependencies removed.

Workspaces are kept but reduced to two members. The website imports `react-live`
as a linked dependency, which is exactly what a workspace provides; removing
workspaces would mean replacing that link with a `file:` dep or a bundler alias.

TypeScript 7 ships no JavaScript compiler API — only an `unstable/*` RPC
surface. `tsc` and tsdown's declaration emit both work with it, but any future
tool needing the classic API would have to bring its own `typescript@6`. This
is also why the website no longer extends `@docusaurus/tsconfig`: that config
sets `baseUrl`, which TypeScript 7 removed.

Source changes, kept to the minimum:

- `LiveEditor.tsx`: rolldown cannot tell a type from a value in
  `import Editor, { Props as EditorProps }` and fails with MISSING_EXPORT, so
  the `type` modifier is now explicit.
- `LivePreview.tsx`: the global `JSX` namespace was removed in
  `@types/react@19`, and these types are emitted into the published
  declarations, so React 19 consumers could not compile against the package.
  Now `React.JSX`, verified to typecheck under both `@types/react@18` and `@19`.
- `website/.../nf-link-button.tsx`: `children` was typed `ChildNode |
  ReactNode`; `ChildNode` is a DOM node type and does not belong there. This
  only surfaced now because the website has never been typechecked in CI.

Test changes:

- The four JSX-containing `.test.js` files are renamed `.test.jsx`. Vite will
  not parse JSX inside a `.js` file.
- `jest.*` -> `vi.*`, mechanically.
- Every test in `LiveProvider.test.jsx` is now `it.skip`ed. None of them have
  ever asserted anything: `waitAsync()` returns React's `act()` thenable rather
  than a real Promise, so `return waitAsync().then(...)` was never awaited and
  its callback ran after the test had already passed. Jest swallowed the
  resulting errors entirely; Vitest reports them, which is how this was found.
  Awaiting a real `act(async () => ...)` makes them fail outright, because
  `render()` shallow-renders and therefore never runs the effect that calls
  `transformCode`. Fixing them properly needs `react-dom/client`; the full
  explanation is in a comment at the top of the file.

The published surface gains an `exports` map with separate ESM and CJS type
entries. `publint` and `@arethetypeswrong/cli` both report zero problems, and
the build now runs them on every invocation so a regression fails locally
rather than after release.

Runtime React devDependencies stay on 18, the floor of the peer range, so
tests exercise the oldest supported React; `@types/react` moves to 19 so the
emitted declarations are verified against the current major.
…yment

Replaces `code-check.yml` with `ci.yml`, which runs `npm run check` — lint,
format, typecheck, and tests — then builds. This is the first time `typecheck`
has ever run in CI, despite the old job being named "lint and typecheck".

`release.yml` adopts the split-job `changesets/action@v2` model with npm
trusted publishing: select-mode -> (version | pack -> publish). Permissions are
reset to `{}` at the top and granted per job, so `id-token: write` exists only
on the job that publishes. The `NPM_TOKEN` secret is no longer used.

The library's build script is `prepack`, not `prepublishOnly`. The release flow
packs a tarball in one job and publishes it in another, and `npm pack` does not
trigger `prepublishOnly` — left as it was, the published tarball would have
silently shipped without its README, LICENSE, and dist.

`website/vercel.json` checks the build and output settings into version
control. CONTRIBUTING.MD documents the repository layout, that the live demo
lives in the website rather than a separate app, the full toolchain, and how
the site deploys.

NOTE: the Vercel project's Build Command must be changed from `pnpm run build`
to `npm run build`. A Build Command set in the Vercel dashboard overrides
vercel.json, so this cannot be fixed from the repository alone.
The two hooks look redundant but cover different cases, and the root `prepare`
does more than convenience: because the website resolves `react-live` through
its `exports` map, `check:types` typechecks the site against the emitted
declarations, making it an integration check on the published type surface.
`prepare` rebuilt the library on every `npm install`, which is a poor trade
when you are just adding or bumping a dependency. Nothing builds implicitly
any more.

- Root `prepare` removed. `npm install` only installs.
- `website` splits its build: `build` compiles the site alone, `build:prod`
  reaches up and builds the library first. The old `prebuild` hook is gone.
- Vercel's Build Command becomes `npm run build:prod` (checked into
  `website/vercel.json`).
- CI now builds before checking. `check:types` typechecks the website against
  the library's *emitted* declarations, so `dist` has to exist first -- this
  ordering keeps that integration check without an install-time build.

The cost is one documented manual step: the library must be built once after
cloning, or `check:types` and `start:docs` fail with `Cannot find module
'react-live'`. CONTRIBUTING.MD covers it.
Ports the scenarios from the deleted Storybook stories into real tests.
Storybook was never wired into CI -- no test-runner, no storyshots -- so
those scenarios had only ever been checked by eye.

Coverage 38.46% -> 93.26% statements. Tests 15 (+4 skipped) -> 51 (0
skipped). Every React component was previously at 0%; only the transpile
utilities were covered.

The four skipped LiveProvider tests are replaced by 17 working ones. They
had never asserted anything: they chained off React's act() thenable, which
is not a real Promise, so the runner never awaited them.

New: Editor (7), LivePreview/LiveEditor/withLive (8), ErrorBoundary (4).

Two guards, both added after shipping the exact bugs they catch:

- React act() warnings now fail the run (vitest.setup.js). The warning means
  a test asserted while a state update was in flight, so it may be passing
  for the wrong reason -- one such test asserted "no error is shown" before
  anything had happened, and would have passed even if the provider always
  errored.
- CI runs with --reporter=verbose. Vitest's default reporter hides console
  output from *passing* test files, so a green run can silently print
  warnings. `CI=true npm test` reproduces it locally.

Editor interaction stays uncovered: jsdom has no contentEditable editing
model, so typing, caret-after-Enter, and tabMode need a real browser.
CONTRIBUTING documents that boundary, and why Storybook's Vitest
integration is not an option yet (addon-vitest peers vitest ^3 || ^4; this
repo is on 5).

Coverage is reported in CI, not enforced.
`stories` and `stories:test` now read as a pair -- the browser suite is the
stories environment's tests.

Adds a light dusting of the docs-site palette (stories/theme.js): a slim
navy header, a grey sidebar with a green selection marker, Inter with a
system fallback. No CSS framework, no bundled font files.

The story canvas stays neutral so components are judged on their own
rendering, and ?only=1 -- the mode the tests load -- now omits the global
stylesheet too, so nothing the harness does can influence a result.
Closes the remaining gaps against the deleted Storybook set. 27 stories vs
the original 14: StyledSubcomponents (className pass-through, without
styled-components), ControlledEditor (LiveEditor lifting state via onChange
-- the original's Sandbox, distinct from swapping in a textarea),
WithLiveHocTypeScript, CustomLanguage, and TabIndentation / TabFocus with
three browser tests for tabMode, documented in docs/api.md but covered by
neither the original stories nor any test.

The smoke test now awaits the transpile settling. LiveProvider transpiles in
an effect, so it was asserting against the first paint and passing early for
every async story; errors then surfaced after the test had finished, landing
outside any test and appearing only intermittently. Stories that throw on
purpose opt in with `expectsError: true`; only Live/RuntimeError does, and
rendering all 27 confirms nothing else is suppressed.

Not ported: Editor/PrismFromNpm. Despite the name it never passed a `prism`
prop, and could not have -- Editor declares `prism` but never reads it.
… blog

Keeps the dist filenames 4.1.x published (dist/index.js, index.mjs,
index.d.ts) via tsdown outExtensions, so main/module/types resolve exactly as
before for bundlers that ignore `exports`. attw and publint stay green, and a
packed tarball installs and resolves in both CJS and ESM.

Adds the jsdom suite's guards to the browser config, and records that React
only emits act warnings when IS_REACT_ACT_ENVIRONMENT is set -- which it is
not in a real browser, so that guard is inert there.

Folds in regression tests for error recovery. getDerivedStateFromError makes
hasError sticky, which is only safe because a new boundary class is built per
transpile.

docs/api.md claimed LiveEditor wraps react-simple-code-editor; it is not a
dependency and never was. Documents the now-working `prism` prop.

website/README.md was the untouched Docusaurus scaffold (yarn, Docusaurus 2,
gh-pages deploy).

The classic preset enables a blog by default and the config never disabled it,
so every build emitted an empty /blog page. Pre-existing, not introduced here.
Replaces two `cond && expr` statements with `if` blocks, and disables
no-array-index-key in Editor with the reasoning: for tokenised code, position
is the identity, and content-derived keys would collide on duplicate lines and
remount nodes inside a contentEditable on every edit.

The five remaining warnings are render/effect behaviour in LiveProvider and
Editor, tracked in #417. Notably the suggested fix for the effect-dependency
warnings would break re-transpilation if applied literally.
Regenerate the lockfile to undo a split React tree, replace the removed
react-test-renderer/shallow, and stop emitting JSX __self/__source debug
props. Add a React 18 CI job; revert the Tailwind 4 bump for now.
@changeset-bot

changeset-bot Bot commented Sep 15, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 8fb9d18

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
react-live Major

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel

vercel Bot commented Sep 15, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
react-live-docs-website Ready Ready Preview Sep 15, 2026 11:09pm UTC

Request Review

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The declared Node support conflicts with the toolchain, the React 18 job misses the minimum version, and the packaging script is not cross-platform.

Get a fresh assessment by requesting another Copilot review.

Pull request overview

Modernizes the project’s library, build, test, documentation, deployment, and release infrastructure in preparation for v5.

Changes:

  • Migrates tooling to npm workspaces, tsdown, Vitest, Oxlint, Vite, and Docusaurus 3.
  • Fixes React 19 typings, error handling, transpilation warnings, and custom Prism forwarding.
  • Reworks CI, trusted publishing, stories, documentation, and deployment configuration.
File summaries
File Description
website/vercel.json Adds Vercel build configuration.
website/tsconfig.json Updates site TypeScript configuration.
website/src/pages/index.tsx Changes installation command to npm.
website/src/globals.d.ts Adds static-image declarations.
website/src/components/landing/nf-link-button.tsx Simplifies children typing.
website/README.md Rewrites site development and deployment guidance.
website/package.json Upgrades Docusaurus and site tooling.
website/docusaurus.config.js Updates Docusaurus configuration.
website/babel.config.js Removes obsolete Babel configuration.
README.md Fixes the contributing-guide link.
pnpm-workspace.yaml Removes pnpm workspace configuration.
packages/react-live/vitest.setup.js Adds React warning enforcement.
packages/react-live/vitest.config.mts Configures jsdom tests and coverage.
packages/react-live/vitest.browser.mts Configures Chromium tests.
packages/react-live/vite.stories.mts Configures the story harness.
packages/react-live/tsup.config.ts Removes tsup configuration.
packages/react-live/tsdown.config.mts Adds tsdown build configuration.
packages/react-live/tsconfig.json Modernizes library TypeScript settings.
packages/react-live/stories/theme.js Adds story-harness styling.
packages/react-live/stories/stories.browser.test.jsx Smoke-tests stories in Chromium.
packages/react-live/stories/main.jsx Implements the story browser.
packages/react-live/stories/live.stories.jsx Adds live-component scenarios.
packages/react-live/stories/index.html Adds the Vite story entry page.
packages/react-live/stories/editor.stories.jsx Adds editor scenarios.
packages/react-live/src/utils/transpile/transform.ts Enables production Sucrase transforms.
packages/react-live/src/utils/transpile/index.ts Simplifies the transform pipeline.
packages/react-live/src/utils/transpile/evalCode.ts Applies formatting updates.
packages/react-live/src/utils/transpile/errorBoundary.tsx Adds error-boundary state handling.
packages/react-live/src/utils/transpile/compose.ts Applies formatting updates.
packages/react-live/src/utils/test/transpile.test.jsx Migrates mocks to Vitest.
packages/react-live/src/utils/test/renderer.js Replaces the removed shallow renderer.
packages/react-live/src/utils/test/fixtures/optional-chain.test.jsx Updates snapshot formatting.
packages/react-live/src/utils/test/errorBoundary.test.jsx Adds mounted boundary tests.
packages/react-live/src/utils/test/errorBoundary.test.js Removes the former Jest test.
packages/react-live/src/hoc/withLive.tsx Applies formatting updates.
packages/react-live/src/components/Live/LiveProvider.test.jsx Expands provider behavior coverage.
packages/react-live/src/components/Live/LiveProvider.test.js Removes obsolete provider tests.
packages/react-live/src/components/Live/LiveProvider.stories.tsx Removes Storybook stories.
packages/react-live/src/components/Live/LivePreview.tsx Uses the scoped React JSX namespace.
packages/react-live/src/components/Live/LiveEditor.tsx Marks the editor props import as type-only.
packages/react-live/src/components/Live/LiveEditor.stories.tsx Removes old editor stories.
packages/react-live/src/components/Live/live-components.test.jsx Adds live-component tests.
packages/react-live/src/components/Live/ErrorBoundary.test.jsx Adds preview boundary regression tests.
packages/react-live/src/components/Live/editor-interaction.browser.test.jsx Adds real-browser editor tests.
packages/react-live/src/components/Editor/index.tsx Forwards custom Prism instances.
packages/react-live/src/components/Editor/index.test.jsx Adds editor rendering tests.
packages/react-live/package.json Updates package metadata and tooling.
packages/react-live/.storybook/preview.js Removes Storybook preview configuration.
packages/react-live/.storybook/main.js Removes Storybook configuration.
packages/react-live/.storybook/.babelrc Removes Storybook Babel configuration.
packages/react-live/.babelrc Removes legacy Babel configuration.
packages/demo/vite.config.ts Removes the duplicate demo.
packages/demo/tsconfig.json Removes demo TypeScript configuration.
packages/demo/tailwind.config.cjs Removes demo Tailwind configuration.
packages/demo/src/vite-env.d.ts Removes demo Vite types.
packages/demo/src/main.tsx Removes the demo entry point.
packages/demo/src/index.css Removes demo styles.
packages/demo/src/app.tsx Removes the demo application.
packages/demo/postcss.config.cjs Removes demo PostCSS configuration.
packages/demo/package.json Removes the demo package.
package.json Migrates the workspace and scripts to npm.
docs/introduction.mdx Corrects JSX examples.
docs/api.md Updates editor implementation and Prism docs.
CONTRIBUTING.MD Removes the old contributor guide.
CONTRIBUTING.md Adds the new npm-based contributor guide.
CODE_OF_CONDUCT.md Extracts the code of conduct.
.sail.yml Removes obsolete Sail automation.
.prettierrc Adds Prettier 3 configuration.
.prettierignore Excludes generated files from formatting.
.oxlintrc.json Adds Oxlint configuration.
.nvmrc Selects the current Node LTS.
.npmrc Removes pnpm-era peer settings.
.npmignore Removes obsolete package exclusions.
.gitignore Updates generated-file exclusions.
.github/workflows/release.yml Rebuilds the trusted-publishing workflow.
.github/workflows/code-check.yml Removes the former checks workflow.
.github/workflows/ci.yml Adds unit, browser, and compatibility CI.
.github/actions/setup/action.yml Removes the pnpm setup action.
.eslintrc Removes ESLint configuration.
.eslintignore Removes ESLint exclusions.
.changeset/tame-moons-shake.md Records the boundary fix.
.changeset/plenty-hoops-wonder.md Records the Sucrase fix.
.changeset/olive-dolls-repeat.md Records Prism forwarding.
.changeset/nervous-donkeys-smile.md Records v5 breaking changes.
.changeset/config.json Updates Changesets configuration.
Review details
  • Files reviewed: 82/87 changed files
  • Comments generated: 5
  • Review effort level: Balanced

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread package.json
},
"engines": {
"node": ">=18.0.0"
"node": ">=20.19.0"
Comment thread .github/workflows/ci.yml
Comment on lines +87 to +96
npm pkg set \
devDependencies.react="^${{ matrix.react }}" \
devDependencies.react-dom="^${{ matrix.react }}" \
"devDependencies.@types/react=^${{ matrix.react }}" \
"devDependencies.@types/react-dom=^${{ matrix.react }}" \
-w react-live
npm pkg set \
dependencies.react="^${{ matrix.react }}" \
dependencies.react-dom="^${{ matrix.react }}" \
-w website
"lint:fix": "eslint --ext .js,.ts,.tsx src --fix"
"build": "tsdown",
"build:watch": "tsdown --watch",
"prepack": "cp ../../README.md ../../LICENSE . && npm run build",
</LiveProvider>,
);
expect(await screen.findByText("inner")).toBeDefined();
expect(container.querySelector("div")).toBeDefined();
// live editor, so sucrase emits an empty filename. React ignores both
// props, and React 19 treats `__self` as the signature of an outdated
// JSX transform and warns about it, so there is nothing to lose.
production: true,
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.

2 participants