diff --git a/.changeset/honest-needles-grow.md b/.changeset/honest-needles-grow.md new file mode 100644 index 000000000..5f7a8e1bd --- /dev/null +++ b/.changeset/honest-needles-grow.md @@ -0,0 +1,5 @@ +--- +"@solidjs/vite-plugin-nitro-2": minor +--- + +Publishing as NPM package diff --git a/.github/workflows/cr.yml b/.github/workflows/cr.yml index 8b3fa8323..8fc64abcc 100644 --- a/.github/workflows/cr.yml +++ b/.github/workflows/cr.yml @@ -32,12 +32,14 @@ jobs: - name: Install dependencies run: pnpm install --frozen-lockfile - - name: Build start + - name: Build Packages run: | pnpm run build - # rewrite .ts exports to .js - pnpm --filter='./packages/*' -c exec "echo \$(cat package.json | jq '.exports = .publishConfig.exports') > package.json" + pnpm rewrite-exports - - name: Release + - name: Release Packages + # remove the compat flag until all packages are available on npm + # run: | + # pnpm dlx pkg-pr-new@0.0 publish './packages/*' --template './examples/*' --compact run: | - pnpm dlx pkg-pr-new@0.0 publish './packages/start' --template './examples/*' --compact + pnpm dlx pkg-pr-new@0.0 publish './packages/*' diff --git a/.github/workflows/dist-typecheck.yml b/.github/workflows/dist-typecheck.yml index f491235f0..75108e7c2 100644 --- a/.github/workflows/dist-typecheck.yml +++ b/.github/workflows/dist-typecheck.yml @@ -34,5 +34,7 @@ jobs: # build the package and all its dependencies run: pnpm --filter ${{ matrix.package }}... build + - run: pnpm rewrite-exports + - name: Check types with @arethetypeswrong/cli - run: pnpx @arethetypeswrong/cli --pack packages/${{ matrix.package }} --profile esm-only + run: pnpm --filter ${{ matrix.package }} typecheck:dist diff --git a/package.json b/package.json index d432c488c..4acdfe848 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,8 @@ "packages:build": "pnpm --filter=./packages/* build", "packages:clean": "pnpx rimraf ./packages/*/node_modules/ ./packages/*/dist/", "clean:test": "pnpx rimraf .tmp", - "release": "pnpm build && changeset publish" + "release": "pnpm build && changeset publish", + "rewrite-exports": "pnpm --filter='./packages/*' -c exec \"echo \\$(cat package.json | jq '.exports = .publishConfig.exports') > package.json\"" }, "devDependencies": { "@changesets/cli": "^2.27.12", diff --git a/packages/start-nitro-v2-vite-plugin/README.md b/packages/start-nitro-v2-vite-plugin/README.md new file mode 100644 index 000000000..2f690e318 --- /dev/null +++ b/packages/start-nitro-v2-vite-plugin/README.md @@ -0,0 +1,38 @@ +# Vite-Plugin-Nitro-2 + +This package moves Nitro into a Vite-Plugin to consolidate the API surface between Nitro v2 and v3. + +## Usage + +This plugin will provide SolidStart with the needed Node.js runtime to run in the backend. + +```ts +import { defineConfig } from "vite"; +import { nitroV2Plugin } from "@solidjs/vite-plugin-nitro-2"; +import { solidStart } from "@solidjs/start/config"; + +export default defineConfig({ + plugins: [solidStart(), nitroV2Plugin()] +}); +``` + +Some features that previously were re-exported by SolidStart are available directly through this plugin now. + +### Example: Prerendering + +```ts +import { defineConfig } from "vite"; +import { nitroV2Plugin } from "@solidjs/vite-plugin-nitro-2"; +import { solidStart } from "@solidjs/start/config"; + +export default defineConfig({ + plugins: [ + solidStart(), + nitroV2Plugin({ + prerender: { + crawlLinks: true + } + }) + ] +}); +``` diff --git a/packages/start-nitro-v2-vite-plugin/package.json b/packages/start-nitro-v2-vite-plugin/package.json index ba1e3f9ce..138ad80f6 100644 --- a/packages/start-nitro-v2-vite-plugin/package.json +++ b/packages/start-nitro-v2-vite-plugin/package.json @@ -1,10 +1,18 @@ { - "name": "@solidjs/start-nitro-v2-vite-plugin", + "name": "@solidjs/vite-plugin-nitro-2", + "description": "Nitro v2 plugin for development with SolidStart 2.0", "version": "0.0.1", "type": "module", "scripts": { - "build": "tsc" + "build": "tsc", + "typecheck": "tsc --noEmit", + "typecheck:dist": "pnpm build && pnpx @arethetypeswrong/cli --pack . --profile esm-only" }, + "files": [ + "dist", + "package.json", + "README.md" + ], "exports": { ".": "./src/index.ts" }, @@ -15,7 +23,12 @@ } }, "dependencies": { - "nitropack": "^2.11.10", + "nitropack": "^2.11.10" + }, + "devDependencies": { "vite": "^7.1.10" + }, + "peerDependencies": { + "vite": "^7" } } diff --git a/packages/start-nitro-v2-vite-plugin/src/index.ts b/packages/start-nitro-v2-vite-plugin/src/index.ts index 222d4122f..131182a69 100644 --- a/packages/start-nitro-v2-vite-plugin/src/index.ts +++ b/packages/start-nitro-v2-vite-plugin/src/index.ts @@ -1,206 +1,198 @@ -import { promises as fsp } from "node:fs"; -import path, { dirname, resolve } from "node:path"; import { - build, - copyPublicAssets, - createNitro, - type Nitro, - type NitroConfig, - prepare, - prerender, + build, + copyPublicAssets, + createNitro, + type Nitro, + type NitroConfig, + prepare, + prerender } from "nitropack"; +import { promises as fsp } from "node:fs"; +import path, { dirname, resolve } from "node:path"; import type { PluginOption, Rollup } from "vite"; let ssrBundle: Rollup.OutputBundle; let ssrEntryFile: string; -export type UserNitroConfig = Omit< - NitroConfig, - "dev" | "publicAssets" | "renderer" ->; +export type UserNitroConfig = Omit; export function nitroV2Plugin(nitroConfig?: UserNitroConfig): PluginOption { - return [ - { - name: "solid-start-vite-plugin-nitro", - generateBundle: { - handler(_options, bundle) { - if (this.environment.name !== "ssr") { - return; - } - - // find entry point of ssr bundle - let entryFile: string | undefined; - for (const [_name, file] of Object.entries(bundle)) { - if (file.type === "chunk") { - if (file.isEntry) { - if (entryFile !== undefined) { - this.error( - `Multiple entry points found for service "${this.environment.name}". Only one entry point is allowed.`, - ); - } - entryFile = file.fileName; - } - } - } - if (entryFile === undefined) { - this.error( - `No entry point found for service "${this.environment.name}".`, - ); - } - ssrEntryFile = entryFile!; - ssrBundle = bundle; - }, - }, - config() { - return { - environments: { - ssr: { - consumer: "server", - build: { - commonjsOptions: { - include: [], - }, - ssr: true, - sourcemap: true, - }, - }, - }, - builder: { - sharedPlugins: true, - async buildApp(builder) { - const client = builder.environments.client; - const server = builder.environments.ssr; - - if (!client) throw new Error("Client environment not found"); - if (!server) throw new Error("SSR environment not found"); - - await builder.build(client); - await builder.build(server); - - const virtualEntry = "#solid-start/entry"; - const resolvedNitroConfig: NitroConfig = { - compatibilityDate: "2024-11-19", - logLevel: 3, - preset: "node-server", - typescript: { - generateTsConfig: false, - generateRuntimeConfigTypes: false, - }, - ...nitroConfig, - dev: false, - publicAssets: [ - { - dir: client.config.build.outDir, - maxAge: 31536000, // 1 year - baseURL: "/", - }, - ], - renderer: virtualEntry, - rollupConfig: { - ...nitroConfig?.rollupConfig, - plugins: [virtualBundlePlugin(ssrBundle) as any], - }, - experimental: { - ...nitroConfig?.experimental, - asyncContext: true, - }, - virtual: { - ...nitroConfig?.virtual, - [virtualEntry]: `import { fromWebHandler } from 'h3' + return [ + { + name: "solid-start-vite-plugin-nitro", + generateBundle: { + handler(_options, bundle) { + if (this.environment.name !== "ssr") { + return; + } + + // find entry point of ssr bundle + let entryFile: string | undefined; + for (const [_name, file] of Object.entries(bundle)) { + if (file.type === "chunk") { + if (file.isEntry) { + if (entryFile !== undefined) { + this.error( + `Multiple entry points found for service "${this.environment.name}". Only one entry point is allowed.` + ); + } + entryFile = file.fileName; + } + } + } + if (entryFile === undefined) { + this.error(`No entry point found for service "${this.environment.name}".`); + } + ssrEntryFile = entryFile!; + ssrBundle = bundle; + } + }, + config() { + return { + environments: { + ssr: { + consumer: "server", + build: { + commonjsOptions: { + include: [] + }, + ssr: true, + sourcemap: true + } + } + }, + builder: { + sharedPlugins: true, + async buildApp(builder) { + const client = builder.environments.client; + const server = builder.environments.ssr; + + if (!client) throw new Error("Client environment not found"); + if (!server) throw new Error("SSR environment not found"); + + await builder.build(client); + await builder.build(server); + + const virtualEntry = "#solid-start/entry"; + const resolvedNitroConfig: NitroConfig = { + compatibilityDate: "2024-11-19", + logLevel: 3, + preset: "node-server", + typescript: { + generateTsConfig: false, + generateRuntimeConfigTypes: false + }, + ...nitroConfig, + dev: false, + publicAssets: [ + { + dir: client.config.build.outDir, + maxAge: 31536000, // 1 year + baseURL: "/" + } + ], + renderer: virtualEntry, + rollupConfig: { + ...nitroConfig?.rollupConfig, + plugins: [virtualBundlePlugin(ssrBundle) as any] + }, + experimental: { + ...nitroConfig?.experimental, + asyncContext: true + }, + virtual: { + ...nitroConfig?.virtual, + [virtualEntry]: `import { fromWebHandler } from 'h3' import handler from '${ssrEntryFile}' - export default fromWebHandler(handler.fetch)`, - }, - }; - - const nitro = await createNitro(resolvedNitroConfig); - - await buildNitroEnvironment(nitro, () => build(nitro)); - }, - }, - }; - }, - }, - nitroConfig?.preset === "netlify" && { - name: "solid-start-nitro-netlify-fix", + export default fromWebHandler(handler.fetch)` + } + }; + + const nitro = await createNitro(resolvedNitroConfig); + + await buildNitroEnvironment(nitro, () => build(nitro)); + } + } + }; + } + }, + nitroConfig?.preset === "netlify" && { + name: "solid-start-nitro-netlify-fix", enforce: "post", - config() { + config() { return { environments: { client: { build: { outDir: ".solid-start/client" } }, ssr: { build: { outDir: ".solid-start/server" } } } - } - } - } - ]; + }; + } + } + ]; } -export async function buildNitroEnvironment( - nitro: Nitro, - build: () => Promise, -) { - await prepare(nitro); - await copyPublicAssets(nitro); - await prerender(nitro); - await build(); - - const publicDir = nitro.options.output.publicDir; - - // As a part of the build process, the `.vite/` directory - // is copied over from `node_modules/.tanstack-start/client-dist/` - // to the `publicDir` (e.g. `.output/public/`). - // This directory (containing the vite manifest) should not be - // included in the final build, so we remove it here. - const viteDir = path.resolve(publicDir, ".vite"); - if (await fsp.stat(viteDir).catch(() => false)) { - await fsp.rm(viteDir, { recursive: true, force: true }); - } - - await nitro.close(); +export async function buildNitroEnvironment(nitro: Nitro, build: () => Promise) { + await prepare(nitro); + await copyPublicAssets(nitro); + await prerender(nitro); + await build(); + + const publicDir = nitro.options.output.publicDir; + + // As a part of the build process, the `.vite/` directory + // is copied over from `node_modules/.tanstack-start/client-dist/` + // to the `publicDir` (e.g. `.output/public/`). + // This directory (containing the vite manifest) should not be + // included in the final build, so we remove it here. + const viteDir = path.resolve(publicDir, ".vite"); + if (await fsp.stat(viteDir).catch(() => false)) { + await fsp.rm(viteDir, { recursive: true, force: true }); + } + + await nitro.close(); } function virtualBundlePlugin(ssrBundle: Rollup.OutputBundle): PluginOption { - type VirtualModule = { code: string; map: string | null }; - const _modules = new Map(); - - // group chunks and source maps - for (const [fileName, content] of Object.entries(ssrBundle)) { - if (content.type === "chunk") { - const virtualModule: VirtualModule = { - code: content.code, - map: null, - }; - const maybeMap = ssrBundle[`${fileName}.map`]; - if (maybeMap && maybeMap.type === "asset") { - virtualModule.map = maybeMap.source as string; - } - _modules.set(fileName, virtualModule); - _modules.set(resolve(fileName), virtualModule); - } - } - - return { - name: "virtual-bundle", - resolveId(id, importer) { - if (_modules.has(id)) { - return resolve(id); - } - - if (importer) { - const resolved = resolve(dirname(importer), id); - if (_modules.has(resolved)) { - return resolved; - } - } - return null; - }, - load(id) { - const m = _modules.get(id); - if (!m) { - return null; - } - return m; - }, - }; + type VirtualModule = { code: string; map: string | null }; + const _modules = new Map(); + + // group chunks and source maps + for (const [fileName, content] of Object.entries(ssrBundle)) { + if (content.type === "chunk") { + const virtualModule: VirtualModule = { + code: content.code, + map: null + }; + const maybeMap = ssrBundle[`${fileName}.map`]; + if (maybeMap && maybeMap.type === "asset") { + virtualModule.map = maybeMap.source as string; + } + _modules.set(fileName, virtualModule); + _modules.set(resolve(fileName), virtualModule); + } + } + + return { + name: "virtual-bundle", + resolveId(id, importer) { + if (_modules.has(id)) { + return resolve(id); + } + + if (importer) { + const resolved = resolve(dirname(importer), id); + if (_modules.has(resolved)) { + return resolved; + } + } + return null; + }, + load(id) { + const m = _modules.get(id); + if (!m) { + return null; + } + return m; + } + }; } diff --git a/packages/start-nitro-v2-vite-plugin/tsconfig.json b/packages/start-nitro-v2-vite-plugin/tsconfig.json index 6eb9ea069..93ff01e43 100644 --- a/packages/start-nitro-v2-vite-plugin/tsconfig.json +++ b/packages/start-nitro-v2-vite-plugin/tsconfig.json @@ -1,8 +1,8 @@ { "compilerOptions": { "target": "ESNext", - "module": "ESNext", - "moduleResolution": "bundler", + "module": "NodeNext", + "moduleResolution": "NodeNext", "strict": true, "noUncheckedIndexedAccess": true, "allowSyntheticDefaultImports": true, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7dede6b22..67df5ce9b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -404,6 +404,7 @@ importers: nitropack: specifier: ^2.11.10 version: 2.11.11(@netlify/blobs@8.2.0)(better-sqlite3@11.8.1)(drizzle-orm@0.31.4(@opentelemetry/api@1.9.0)(@prisma/client@5.22.0(prisma@5.22.0))(@types/better-sqlite3@7.6.12)(better-sqlite3@11.8.1)(prisma@5.22.0)) + devDependencies: vite: specifier: ^7.1.10 version: 7.1.10(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.19.2)(yaml@2.8.1) @@ -4561,8 +4562,8 @@ packages: resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==} engines: {node: '>=0.6'} - quansync@0.2.10: - resolution: {integrity: sha512-t41VRkMYbkHyCYmOvx/6URnN80H7k4X0lLdBMGsz+maAwrJQYB1djpV6vHrQIBE0WBSGqhtEHrK9U3DWWH8v7A==} + quansync@0.2.11: + resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==} querystringify@2.2.0: resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==} @@ -9269,7 +9270,7 @@ snapshots: dependencies: mlly: 1.7.4 pkg-types: 2.1.0 - quansync: 0.2.10 + quansync: 0.2.11 locate-path@5.0.0: dependencies: @@ -10129,7 +10130,7 @@ snapshots: dependencies: side-channel: 1.1.0 - quansync@0.2.10: {} + quansync@0.2.11: {} querystringify@2.2.0: optional: true