diff --git a/.changeset/observe-option.md b/.changeset/observe-option.md new file mode 100644 index 0000000..ebcff1b --- /dev/null +++ b/.changeset/observe-option.md @@ -0,0 +1,5 @@ +--- +'@solidjs/vite-plugin': patch +--- + +New `observe` option: resolve Solid's observe builds for production observability. `observe: true` adds the `observe` export condition to every environment — client and server, inlined and externalized (`resolve.externalConditions`), inlining the core runtime and its consumers for server builds the way the dev posture already does so one build is loaded end to end — and turns on the compiler's `componentNames` option, so component owner labels (``) survive minification in diagnostics and attribution paths. `componentNames` is also enabled under the dev posture, where `lazy()` and HMR wrappers otherwise hide the tag name. Requires `@solidjs/compiler` / `@solidjs/babel-plugin` ≥ 2.0.0-rc.8 (the release that adds the option). diff --git a/README.md b/README.md index 30734d5..da0454f 100644 --- a/README.md +++ b/README.md @@ -125,6 +125,17 @@ This will inject `solid-js/dev` in place of `solid-js` in dev mode. Has no effec If set to false, it won't inject it in dev. This is useful for extra logs and debug. +#### options.observe + +- Type: Boolean +- Default: false + +Resolve Solid's observe builds in production: the production-speed runtime that keeps the +diagnostics and attribution channels (`OBSERVE`) alive for observability tooling (error +monitoring, performance tracing). Adds the `observe` export condition to every environment +and turns on the compiler's `componentNames` option, so component owner labels (``) +survive minification. Under `vite dev` the dev build still wins. + #### options.hot - Type: Boolean diff --git a/src/index.ts b/src/index.ts index 5e8933f..535231d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -236,6 +236,18 @@ export interface Options { * @default true */ dev?: boolean; + /** + * Resolve Solid's observe builds: the production-speed runtime that keeps + * the diagnostics and attribution channels (`OBSERVE`) alive for + * production observability tooling. Adds the `observe` export condition + * to every environment (client and server, inlined and externalized) and + * turns on the compiler's `componentNames` option so owner labels survive + * minification. Applies to `vite build` and preview; under `vite dev` the + * `development` condition still wins (the dev build is a superset). + * + * @default false + */ + observe?: boolean; /** * Dev-serve only: expose Solid's diagnostic and attribution channels to * out-of-process consumers (agents, tests, curl). Injects a client module @@ -498,6 +510,7 @@ function getSolidOptions( options: Partial, isSsr: boolean, dev: boolean, + observe: boolean, isTestMode = false, ): SolidOptions { let solidOptions: Pick; @@ -537,10 +550,17 @@ function getSolidOptions( // builtIns, contextToCustomElements, wrapConditionals) are baked into both // backends — @solidjs/compiler and @solidjs/babel-plugin — so only the // posture this plugin actually decides is passed. + // Component labels: the dev and observe runtimes name each component's + // owner (``) for diagnostics and attribution paths. Without the + // compiler carrying the source tag name, a minified build labels owners by + // whatever the minifier left of `Comp.name`. DOM-only by construction (the + // ssr generate ignores the flag), and the production runtime ignores the + // argument, so it is only emitted for the postures whose runtime reads it. return { ...solidOptions, ...(serverComponents && solidOptions.generate === 'ssr' ? { serverComponents: true } : {}), dev, + ...(dev || observe ? { componentNames: true } : {}), ...(options.solid || {}), }; } @@ -864,6 +884,7 @@ export default function solidPlugin(options: Partial = {}): Plugin[] { let needHmr = false; let replaceDev = false; + let observe = false; // Resolved absolute path of the start-mode document shell (normalized to // forward slashes, matching Vite ids), reported back by the start plugin's // config hook. The document is the one module whose client compile must @@ -998,7 +1019,7 @@ export default function solidPlugin(options: Partial = {}): Plugin[] { } async function compileTsrxCss(source: string, id: string): Promise { - const solidOptions = getSolidOptions(options, false, replaceDev, isTestMode); + const solidOptions = getSolidOptions(options, false, replaceDev, observe, isTestMode); if (options.compiler === 'babel') { const babelUserOptions = await getBabelUserOptions(options, source, id, false); const babelOptions = mergeAndConcat(babelUserOptions, { @@ -1036,6 +1057,7 @@ export default function solidPlugin(options: Partial = {}): Plugin[] { async config(userConfig, { command }) { // We inject the dev mode only if the user explicitly wants it or if we are in dev (serve) mode replaceDev = options.dev === true || (options.dev !== false && command === 'serve'); + observe = options.observe === true; projectRoot = userConfig.root || projectRoot; isTestMode = userConfig.mode === 'test'; // Per-vitest-project posture: the client posture (browser conditions, @@ -1077,9 +1099,12 @@ export default function solidPlugin(options: Partial = {}): Plugin[] { // Semi-framework is the right class: `ssr.noExternal` without // `optimizeDeps.exclude`, since these hold no raw Solid components. isSemiFrameworkPkgByJson(pkgJson) { - // Same gate as the core inlining in configEnvironment: dev serve - // only, never vitest (it manages inlining via test.server.deps). - if (!replaceDev || isTestMode) return false; + // Same gate as the core inlining in configEnvironment: dev serve or + // an observe build (the `observe` condition selects a server build + // the same way `development` does, and an externalized consumer + // would split it just the same), never vitest (it manages inlining + // via test.server.deps). + if (!(replaceDev || observe) || isTestMode) return false; return SOLID_RUNTIME_PKGS.some( (name) => pkgJson.dependencies?.[name] || pkgJson.peerDependencies?.[name], ); @@ -1180,7 +1205,7 @@ export default function solidPlugin(options: Partial = {}): Plugin[] { if (!isTsrxModule(id) || isTsrxCssModule(id)) return null; const compiler = await loadNativeCompiler(); const result = await compiler.transformAsync(source, { - ...getSolidOptions(options, false, replaceDev, isTestMode), + ...getSolidOptions(options, false, replaceDev, observe, isTestMode), filename: cleanModuleId(id), sourceMap: false, }); @@ -1212,6 +1237,10 @@ export default function solidPlugin(options: Partial = {}): Plugin[] { config.resolve.conditions = [ 'solid', ...(replaceDev ? ['development'] : []), + // `development` nests above `observe` in the runtime's exports, so + // both may be present: dev serve keeps the dev build, builds get the + // observe build. + ...(observe ? ['observe'] : []), // Tests resolve the browser builds even when the app is // server-rendered — the client posture applies to the whole test // pipeline, not just the codegen. Projects that explicitly opt into @@ -1231,10 +1260,13 @@ export default function solidPlugin(options: Partial = {}): Plugin[] { // runtime among them) run their PRODUCTION copy under `vite dev`: // server errors reach the client sanitized to "Internal Server Error" // instead of carrying the real message, dev-only diagnostics vanish. - // So the dev flag has to reach both lists. - if (replaceDev && config.consumer !== 'client' && name !== 'client') { + // So the dev flag has to reach both lists — and `observe` selects its + // server build the same way, with the same split if it only reaches + // one list. + if ((replaceDev || observe) && config.consumer !== 'client' && name !== 'client') { config.resolve.externalConditions = [ - 'development', + ...(replaceDev ? ['development'] : []), + ...(observe ? ['observe'] : []), ...(config.resolve.externalConditions ?? defaultExternalConditions), ]; @@ -1553,7 +1585,7 @@ export default function solidPlugin(options: Partial = {}): Plugin[] { } const inNodeModules = /node_modules/.test(id); - const solidOptions = getSolidOptions(options, !!isSsr, replaceDev, isTestMode); + const solidOptions = getSolidOptions(options, !!isSsr, replaceDev, observe, isTestMode); // We need to know if the current file extension has a typescript options tied to it const shouldBeProcessedWithTypescript =