From 1e8be4145ede81e2ed8af4913550e79a7a73378e Mon Sep 17 00:00:00 2001 From: Birk Skyum Date: Thu, 20 Aug 2026 01:44:32 +0200 Subject: [PATCH] feat: make direct imports production-safe --- .changeset/safe-production-imports.md | 5 +++++ README.md | 9 +++++---- package.json | 13 ++++++++----- rollup.config.js | 7 +++++++ src/exports.test.ts | 25 +++++++++++++++++++++++++ src/index.tsx | 13 ++++++++++--- src/noop.ts | 14 ++++++++++++++ src/server.tsx | 9 ++++++--- 8 files changed, 80 insertions(+), 15 deletions(-) create mode 100644 .changeset/safe-production-imports.md create mode 100644 src/exports.test.ts create mode 100644 src/noop.ts diff --git a/.changeset/safe-production-imports.md b/.changeset/safe-production-imports.md new file mode 100644 index 0000000..23c4b78 --- /dev/null +++ b/.changeset/safe-production-imports.md @@ -0,0 +1,5 @@ +--- +"@solidjs/start-devtools": patch +--- + +Make direct imports production-safe with a no-op default export and development-only browser and server implementations. Register server-function observers from the browser entry. diff --git a/README.md b/README.md index 2f9dbb7..129122c 100644 --- a/README.md +++ b/README.md @@ -18,10 +18,11 @@ import { DevToolbar } from "@solidjs/start-devtools"; ; ``` -For authored server and client entries, render `DevToolbar` around the app in a shared -document or root component. The package selects an SSR-safe build on the server, so the -same component provides the development error boundary on both sides. +For custom server and client entries, render `DevToolbar` around the app in a shared +document or root component. In development, the package selects the browser or SSR build +for the current environment. In production, it becomes a children-only passthrough and +does not include the toolbar. -The package is intended for development and should not be imported into production entries. +The same import is safe in development and production entries. For component and reactivity inspection, see [Solid Devtools](https://github.com/thetarnav/solid-devtools). diff --git a/package.json b/package.json index d7675b6..a5f3057 100644 --- a/package.json +++ b/package.json @@ -11,11 +11,14 @@ "exports": { ".": { "types": "./dist/types/index.d.ts", - "worker": "./dist/server.js", - "browser": "./dist/index.js", - "deno": "./dist/server.js", - "node": "./dist/server.js", - "import": "./dist/index.js" + "development": { + "worker": "./dist/server.js", + "browser": "./dist/index.js", + "deno": "./dist/server.js", + "node": "./dist/server.js", + "import": "./dist/index.js" + }, + "default": "./dist/noop.js" } }, "scripts": { diff --git a/rollup.config.js b/rollup.config.js index c606449..20b75bd 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -105,4 +105,11 @@ export default [ generate: 'ssr', server: true, }), + config({ + input: 'src/noop.ts', + entryFileNames: 'noop.js', + chunkFileNames: 'noop-chunks/[name]-[hash].js', + generate: 'ssr', + server: true, + }), ]; diff --git a/src/exports.test.ts b/src/exports.test.ts new file mode 100644 index 0000000..c71b0d2 --- /dev/null +++ b/src/exports.test.ts @@ -0,0 +1,25 @@ +import { execFileSync } from 'node:child_process'; +import { describe, expect, it } from 'vitest'; + +function resolvePackage(...conditions: string[]): string { + return execFileSync( + process.execPath, + [ + ...conditions.map((condition) => `--conditions=${condition}`), + '--input-type=module', + '--eval', + `console.log(import.meta.resolve('@solidjs/start-devtools'))`, + ], + { encoding: 'utf-8' }, + ).trim(); +} + +describe('package exports', () => { + it('uses the no-op build by default', () => { + expect(resolvePackage()).toMatch(/\/dist\/noop\.js$/); + }); + + it('uses the server build for development in Node', () => { + expect(resolvePackage('development')).toMatch(/\/dist\/server\.js$/); + }); +}); diff --git a/src/index.tsx b/src/index.tsx index 46ef092..040c0a3 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,11 +1,18 @@ import { render } from '@solidjs/web'; -import { pushServerFunctionCall } from './dev-toolbar/functions/tracker.js'; -import { DevToolbar } from './dev-toolbar/index.js'; +import * as serverFunctions from '@solidjs/web/server-functions'; +import { + pushServerFunctionCall, + type ServerFunctionCall, +} from './dev-toolbar/functions/tracker.js'; +import { DevToolbar, type DevToolbarProps } from './dev-toolbar/index.js'; let dispose: (() => void) | undefined; let frame: number | undefined; -export { DevToolbar, pushServerFunctionCall }; +export { DevToolbar, type DevToolbarProps, pushServerFunctionCall, type ServerFunctionCall }; + +const observe = Reflect.get(serverFunctions, 'observeServerFunctionCalls'); +if (typeof observe === 'function') observe(pushServerFunctionCall); export function mountDevToolbar(): () => void { if (dispose) return dispose; diff --git a/src/noop.ts b/src/noop.ts new file mode 100644 index 0000000..b5bfad0 --- /dev/null +++ b/src/noop.ts @@ -0,0 +1,14 @@ +import type { ServerFunctionCall } from './dev-toolbar/functions/tracker.js'; +import type { DevToolbarProps } from './dev-toolbar/index.js'; + +export type { DevToolbarProps, ServerFunctionCall }; + +export function DevToolbar(props: DevToolbarProps) { + return props.children; +} + +export function mountDevToolbar(): () => void { + return () => {}; +} + +export function pushServerFunctionCall(_event: ServerFunctionCall): void {} diff --git a/src/server.tsx b/src/server.tsx index 847a0a9..cf72986 100644 --- a/src/server.tsx +++ b/src/server.tsx @@ -1,7 +1,10 @@ -import { pushServerFunctionCall } from './dev-toolbar/functions/tracker.js'; -import { DevToolbar } from './dev-toolbar/index.js'; +import { + pushServerFunctionCall, + type ServerFunctionCall, +} from './dev-toolbar/functions/tracker.js'; +import { DevToolbar, type DevToolbarProps } from './dev-toolbar/index.js'; -export { DevToolbar, pushServerFunctionCall }; +export { DevToolbar, type DevToolbarProps, pushServerFunctionCall, type ServerFunctionCall }; export function mountDevToolbar(): () => void { return () => {};