Skip to content

Commit 6039884

Browse files
kevinccbsgclaude
andauthored
feat(plugin): twd() Vite plugin for plug-and-play dev setup (#238)
* docs: add design spec for TWD Vite plugin auto-init POC Captures the design for moving the dev-only initTWD(...) block out of user entry files into a single Vite plugin call. POC scope, validation criteria, backward-compat plan, and deferred follow-ups documented. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(plugin): scaffold twd Vite plugin (name + apply: serve) Initial plugin shell with name 'twd' and apply: 'serve' so it only runs at dev time. Hooks added in subsequent tasks. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(plugin): add virtual module for twd init resolveId / load hooks emit a virtual:twd/init module containing import { initTWD } + import.meta.glob + initTWD call with default options inlined via JSON.stringify. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * test(plugin): cover custom options + testFilePattern round-trip Locks the contract that custom init options are JSON-serialized into the virtual module and that testFilePattern is consumed by the plugin without leaking into initTWD's options object. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(plugin): inject virtual init script via transformIndexHtml Adds a <script type="module" src="/@id/virtual:twd/init"> tag to the served HTML so the virtual module executes in the browser at dev time. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(plugin): export twd from twd-js/vite-plugin Adds twd() and TwdPluginOptions to the public vite-plugin entry alongside the existing twdHmr and removeMockServiceWorker exports. Includes JSDoc + @example matching the twdHmr documentation style. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore(examples): switch twd-test-app to twd() Vite plugin Removes the entry-file initTests/initRequestMocking block; the new twd() Vite plugin handles both via the injected virtual init module. The twd-relay browser-client init stays in main.tsx (separate concern). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(examples): alias twd-js/bundled to dist (not source) in test app The previous alias pointed to src/bundled.tsx, exposing the source file to the test app's React JSX transform. The resulting React vnodes are frozen by React.createElement in dev, so Preact's render() crashed trying to attach __ properties. Switching the alias to dist/bundled.es.js uses the artifact built with @preact/preset-vite (Preact JSX baked in), eliminating the mismatch. Documents the same caveat in the design spec: in-repo / local-dist consumers must alias to the built file, not source. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore(examples): adopt twd() plugin in tutorial and vue examples tutorial-example: drop the dev-only initTWD block from main.tsx and register twd() in vite.config.ts; alias twd-js/bundled to the local src/dist copy since the tutorial does not install twd-js as a package. vue-twd-example: same migration, plus relocate the dark theme literal into vite.config.ts so initTWD options live in one place. main.ts is now a 5-line Vue bootstrap. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore: switch tsconfig moduleResolution to Bundler Vite 8 ships its types only through conditional package.json exports, which the legacy 'Node' moduleResolution cannot read. The result was TS2307 errors on every 'import type { Plugin } from "vite"' across the existing twdHmr / removeMockServiceWorker plugins (and the new twd plugin inherited the same issue). 'Bundler' is the recommended setting for Vite-based projects and is already consistent with the existing module: ESNext / target: ESNext. 402/402 tests pass and npm run build succeeds after the change. The remaining tsc --noEmit complaints (fs/path in removeMockServiceWorker.ts, two unrelated url.spec.ts findings) are pre-existing and out of scope for this branch. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore(deps): bump lint-staged to 17 and typescript-eslint patch The typescript-eslint 8.59.2 patch tightens no-floating-promises and flagged two pre-existing call sites in twdHmr.spec.ts where the handleHotUpdate hook (typed as void | Promise<void>) was invoked without awaiting. Prefix the calls with the void operator so the fire-and-forget intent is explicit. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs: add better tests for pagination example * chore(release): 1.8.0-beta.0 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs: lead with twd() Vite plugin across docs site and README Migrates the docs to recommend the new twd() Vite plugin as the primary setup for Vite-based projects (React, Vue, Solid.js, Astro). The manual initTWD bundled approach is now positioned as the fallback for non-Vite projects (Angular, Webpack/CRA) and as a secondary option for Vite users who need full control. Pages updated: - README.md — installation snippet - docs/.vitepress/theme/components/HomePage.vue — quick start - docs/getting-started.md — primary setup + manual non-Vite section - docs/tutorial/installation.md — tutorial first install step - docs/frameworks.md — React/Vue/Solid lead with plugin; Angular and CRA stay manual; Astro updated to use plugin via vite block - docs/theming.md — theme passed via plugin in vite.config.ts - docs/api/index.md — twd() plugin documented first; initTWD as manual API - docs/testing-library.md — rootSelector example shows both plugin and manual - docs/api-mocking.md — service worker registration via plugin (default) - docs/tutorial/api-mocking.md — same pattern in tutorial flavor - docs/coverage.md + tutorial/coverage.md — twd() plus istanbul example - docs/tutorial/production-builds.md — removeMockServiceWorker import path corrected - docs/claude-plugin.md — auto-setup uses plugin - docs/ai-remote-testing.md — twd() and twdRemote() shown together npx twd-js init public is still mentioned in getting-started and api-mocking — auto-install hasn't shipped yet. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore(release): 1.8.0 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(plugin): add @example for serviceWorker option in twd() JSDoc Surfaces the request-mocking toggles (serviceWorker, serviceWorkerUrl) in the function-level JSDoc so they're discoverable via IntelliSense without expanding the options interface. Caught during real-project migration where a reviewer concluded the options had been dropped because they weren't shown in the function's example block. No runtime change — JSDoc only. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(plugin): respect Vite base path in script injection and SW URL The plugin was emitting a hardcoded "/@id/virtual:twd/init" script tag in transformIndexHtml(), which 404s on dev servers configured with a non-root base (e.g. base: "/platform-admin/") because Vite serves all dev resources under that prefix. Capture the resolved base via configResolved() and prefix: - script src in transformIndexHtml — always - default serviceWorkerUrl — only when the user did NOT explicitly set it (so user-supplied paths like "/platform-admin/mock-sw.js" are not double-prefixed) Default base "/" preserves existing behavior. Tests cover all four combinations (base x serviceWorkerUrl override). Reported by a real-project consumer with: base: "/platform-admin/" → GET http://localhost:5173/@id/virtual:twd/init 404 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore(release): 1.8.0-beta.1 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent d22b144 commit 6039884

38 files changed

Lines changed: 1460 additions & 676 deletions

CHANGELOG.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,24 @@
1+
## <small>1.8.0 (2026-05-01)</small>
2+
3+
* feat(plugin): export twd from twd-js/vite-plugin ([9c90803](https://github.com/BRIKEV/twd/commit/9c90803))
4+
* feat(plugin): inject virtual init script via transformIndexHtml ([4cdc7e5](https://github.com/BRIKEV/twd/commit/4cdc7e5))
5+
* feat(plugin): add virtual module for twd init ([4674cdb](https://github.com/BRIKEV/twd/commit/4674cdb))
6+
* feat(plugin): scaffold twd Vite plugin (name + apply: serve) ([7da3ccd](https://github.com/BRIKEV/twd/commit/7da3ccd))
7+
* feat: add Contacts page with pagination and loader functionality (#239) ([d22b144](https://github.com/BRIKEV/twd/commit/d22b144)), closes [#239](https://github.com/BRIKEV/twd/issues/239)
8+
* fix(examples): alias twd-js/bundled to dist (not source) in test app ([75f2e0d](https://github.com/BRIKEV/twd/commit/75f2e0d))
9+
* docs: lead with twd() Vite plugin across docs site and README ([55e1d72](https://github.com/BRIKEV/twd/commit/55e1d72))
10+
* docs: add better tests for pagination example ([0276d05](https://github.com/BRIKEV/twd/commit/0276d05))
11+
* docs: add design spec for TWD Vite plugin auto-init POC ([f89ace4](https://github.com/BRIKEV/twd/commit/f89ace4))
12+
* docs: use absolute GitHub URLs for README images and examples link ([b4278d4](https://github.com/BRIKEV/twd/commit/b4278d4))
13+
* chore(deps): bump lint-staged to 17 and typescript-eslint patch ([b9a6daa](https://github.com/BRIKEV/twd/commit/b9a6daa))
14+
* chore: switch tsconfig moduleResolution to Bundler ([69b2861](https://github.com/BRIKEV/twd/commit/69b2861))
15+
* chore(examples): adopt twd() plugin in tutorial and vue examples ([4cd58f3](https://github.com/BRIKEV/twd/commit/4cd58f3))
16+
* chore(examples): switch twd-test-app to twd() Vite plugin ([5377325](https://github.com/BRIKEV/twd/commit/5377325))
17+
* chore(deps-dev): bump @eslint/js from 9.39.4 to 10.0.1 (#236) ([4147382](https://github.com/BRIKEV/twd/commit/4147382)), closes [#236](https://github.com/BRIKEV/twd/issues/236)
18+
* chore(deps-dev): bump eslint from 9.39.4 to 10.3.0 (#237) ([14348da](https://github.com/BRIKEV/twd/commit/14348da)), closes [#237](https://github.com/BRIKEV/twd/issues/237)
19+
* test(plugin): cover custom options + testFilePattern round-trip ([6adc100](https://github.com/BRIKEV/twd/commit/6adc100))
20+
21+
122
## <small>1.7.3 (2026-05-01)</small>
223

324
* feat: add hidden assertion and improve test coverage (#232) ([425dd66](https://github.com/BRIKEV/twd/commit/425dd66), closes [#232](https://github.com/BRIKEV/twd/issues/232)

README.md

Lines changed: 36 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -36,25 +36,26 @@ pnpm add twd-js
3636

3737
## Quick Start
3838

39-
### React / Vue / Angular / Other Frameworks (Bundled / recommended)
39+
### Vite-based projects (React, Vue, Solid, and more)
4040

41-
TWD now supports any framework via its bundled version.
41+
Add the `twd()` plugin to your `vite.config.ts`. The plugin auto-loads the sidebar and discovers test files in dev — no entry-file changes required.
4242

4343
```ts
44-
// Only load the test sidebar and tests in development mode
45-
if (import.meta.env.DEV) {
46-
const { initTWD } = await import('twd-js/bundled');
47-
const tests = import.meta.glob("./**/*.twd.test.ts");
48-
49-
// Initialize TWD with tests and optional configuration
50-
// Request mocking is automatically initialized by default
51-
initTWD(tests, {
52-
open: true,
53-
position: 'left',
54-
serviceWorker: true, // Enable request mocking (default: true)
55-
serviceWorkerUrl: '/mock-sw.js' // Custom service worker path (default: '/mock-sw.js')
56-
});
57-
}
44+
// vite.config.ts
45+
import { defineConfig } from 'vite';
46+
import react from '@vitejs/plugin-react'; // or vue, solid, etc.
47+
import { twd } from 'twd-js/vite-plugin';
48+
49+
export default defineConfig({
50+
plugins: [
51+
react(),
52+
twd({
53+
testFilePattern: '/**/*.twd.test.{ts,tsx}',
54+
open: true,
55+
position: 'left',
56+
}),
57+
],
58+
});
5859
```
5960

6061
### Set Up Mock Service Worker
@@ -65,6 +66,25 @@ If you plan to use API mocking, set up the mock service worker:
6566
npx twd-js init public
6667
```
6768

69+
### Non-Vite projects (Angular, Webpack, etc.)
70+
71+
If your project doesn't use Vite, initialize TWD manually in your dev entry point:
72+
73+
```ts
74+
// Only load the test sidebar and tests in development mode
75+
if (import.meta.env.DEV) {
76+
const { initTWD } = await import('twd-js/bundled');
77+
const tests = import.meta.glob('./**/*.twd.test.ts');
78+
79+
initTWD(tests, {
80+
open: true,
81+
position: 'left',
82+
serviceWorker: true, // Enable request mocking (default: true)
83+
serviceWorkerUrl: '/mock-sw.js', // Custom service worker path (default: '/mock-sw.js')
84+
});
85+
}
86+
```
87+
6888
Check the [Framework Integration Guide](https://brikev.github.io/twd/frameworks) for more details.
6989

7090
## Writing Tests

docs/.vitepress/theme/components/HomePage.vue

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -249,19 +249,19 @@ const faqs = [
249249
<span class="step-line"></span>
250250
</div>
251251
<div class="step-content">
252-
<h3 class="step-title">Install and add to your entry point</h3>
252+
<h3 class="step-title">Install and add the Vite plugin</h3>
253253
<div class="code-block">
254254
<div class="code-header"><span class="code-dot"></span><span class="code-dot"></span><span class="code-dot"></span><span class="code-filename">terminal</span></div>
255255
<pre><code>npm install twd-js</code></pre>
256256
</div>
257257
<div class="code-block">
258-
<div class="code-header"><span class="code-dot"></span><span class="code-dot"></span><span class="code-dot"></span><span class="code-filename">src/main.tsx</span></div>
259-
<pre><code><span class="hl-comment">// Only load TWD in development</span>
260-
<span class="hl-keyword">if</span> (<span class="hl-meta">import.meta.env.DEV</span>) {
261-
<span class="hl-keyword">const</span> { initTWD } = <span class="hl-keyword">await</span> <span class="hl-keyword">import</span>(<span class="hl-string">'twd-js/bundled'</span>);
262-
<span class="hl-keyword">const</span> tests = <span class="hl-meta">import.meta.glob</span>(<span class="hl-string">"./**/*.twd.test.ts"</span>);
263-
<span class="hl-func">initTWD</span>(tests, { <span class="hl-prop">open</span>: <span class="hl-keyword">true</span> });
264-
}</code></pre>
258+
<div class="code-header"><span class="code-dot"></span><span class="code-dot"></span><span class="code-dot"></span><span class="code-filename">vite.config.ts</span></div>
259+
<pre><code><span class="hl-keyword">import</span> { defineConfig } <span class="hl-keyword">from</span> <span class="hl-string">'vite'</span>;
260+
<span class="hl-keyword">import</span> { twd } <span class="hl-keyword">from</span> <span class="hl-string">'twd-js/vite-plugin'</span>;
261+
262+
<span class="hl-keyword">export default</span> <span class="hl-func">defineConfig</span>({
263+
<span class="hl-prop">plugins</span>: [<span class="hl-func">twd</span>({ <span class="hl-prop">open</span>: <span class="hl-keyword">true</span> })],
264+
});</code></pre>
265265
</div>
266266
</div>
267267
</div>

docs/ai-remote-testing.md

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,16 +56,20 @@ npm install --save-dev twd-relay
5656

5757
### Option A: Vite Plugin (recommended)
5858

59-
If you already use Vite, attach the relay to your dev server — the WebSocket runs on the same host/port:
59+
If you already use Vite, attach the relay to your dev server alongside the `twd()` plugin — the WebSocket runs on the same host/port:
6060

6161
```ts
6262
// vite.config.ts
63+
import { defineConfig } from 'vite';
64+
import react from '@vitejs/plugin-react';
65+
import { twd } from 'twd-js/vite-plugin';
6366
import { twdRemote } from 'twd-relay/vite';
6467

6568
export default defineConfig({
6669
plugins: [
6770
react(),
68-
twdRemote(), // adds /__twd/ws to your dev server
71+
twd(), // sidebar + test discovery
72+
twdRemote(), // adds /__twd/ws to your dev server
6973
],
7074
});
7175
```
@@ -76,8 +80,10 @@ Then connect the browser client in your app entry:
7680
// main.ts
7781
import { createBrowserClient } from 'twd-relay/browser';
7882

79-
const client = createBrowserClient();
80-
client.connect();
83+
if (import.meta.env.DEV) {
84+
const client = createBrowserClient();
85+
client.connect();
86+
}
8187
```
8288

8389
When using the Vite plugin the URL is auto-detected — no configuration needed.

docs/api-mocking.md

Lines changed: 35 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,13 @@ The service worker file (`mock-sw.js`) is only needed during development for API
3030
```ts
3131
// vite.config.ts
3232
import { defineConfig } from 'vite';
33-
import { removeMockServiceWorker } from 'twd-js';
33+
import { twd, removeMockServiceWorker } from 'twd-js/vite-plugin';
3434

3535
export default defineConfig({
3636
plugins: [
37-
// ... other plugins
38-
removeMockServiceWorker()
39-
]
37+
twd(), // dev: discovery + sidebar + request mocking
38+
removeMockServiceWorker(), // build: strip mock-sw.js from prod dist
39+
],
4040
});
4141
```
4242

@@ -48,26 +48,46 @@ The plugin only runs during build time (`apply: 'build'`) and will not affect yo
4848

4949
### 3. Initialize Mocking
5050

51-
Initialize request mocking in your main entry file using the standard TWD setup:
51+
The `twd()` Vite plugin (recommended) initializes request mocking automatically — no entry-file changes required. Mocking is enabled by default (`serviceWorker: true`).
5252

5353
```ts
54-
// src/main.tsx (or your main entry file)
54+
// vite.config.ts
55+
import { defineConfig } from 'vite';
56+
import { twd } from 'twd-js/vite-plugin';
57+
58+
export default defineConfig({
59+
plugins: [
60+
twd({
61+
serviceWorker: true, // default — request mocking enabled
62+
serviceWorkerUrl: '/mock-sw.js', // default
63+
}),
64+
],
65+
});
66+
```
67+
68+
For non-Vite projects, initialize manually in your entry file. Both `initTWD` and the standard setup register the service worker for you:
69+
70+
```ts
71+
// Bundled (recommended for non-Vite projects)
72+
if (import.meta.env.DEV) {
73+
const { initTWD } = await import('twd-js/bundled');
74+
const tests = import.meta.glob('./**/*.twd.test.ts');
75+
initTWD(tests, { serviceWorker: true });
76+
}
77+
```
78+
79+
```ts
80+
// Standard (React-only, full control)
5581
if (import.meta.env.DEV) {
56-
const testModules = import.meta.glob("./**/*.twd.test.ts");
82+
const testModules = import.meta.glob('./**/*.twd.test.ts');
5783
const { initTests, twd, TWDSidebar } = await import('twd-js');
5884
initTests(testModules, <TWDSidebar open={true} position="left" />, createRoot);
59-
twd.initRequestMocking()
60-
.then(() => {
61-
console.log("Request mocking initialized");
62-
})
63-
.catch((err) => {
64-
console.error("Error initializing request mocking:", err);
65-
});
85+
twd.initRequestMocking().catch(console.error);
6686
}
6787
```
6888

6989
::: tip
70-
You only need to call `initRequestMocking()` once in your main entry file, not in individual tests.
90+
You only need to register request mocking once (via the plugin or `initTWD`/`initRequestMocking`), not per test.
7191
:::
7292

7393
## Basic Mocking

0 commit comments

Comments
 (0)