diff --git a/.changeset/compiled-plugin-exports.md b/.changeset/compiled-plugin-exports.md new file mode 100644 index 0000000..fafbaaf --- /dev/null +++ b/.changeset/compiled-plugin-exports.md @@ -0,0 +1,5 @@ +--- +"@fulldotdev/sitex": patch +--- + +Publish compiled package entrypoints for the Vite plugin and island runtime so consumers no longer load TypeScript files from node_modules. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..8cfe014 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,50 @@ +name: CI + +on: + pull_request: + branches: [main] + push: + branches: [main] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + validate: + name: Validate + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Setup PNPM + uses: pnpm/action-setup@v6 + + - name: Setup Node + uses: actions/setup-node@v6 + with: + node-version-file: .nvmrc + cache: pnpm + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Check changeset + if: github.event_name == 'pull_request' && !startsWith(github.head_ref, 'changeset-release/') + run: pnpm exec changeset status --since=origin/${{ github.event.pull_request.base.ref }} + + - name: Check formatting + run: pnpm format:check + + - name: Type check + run: pnpm typecheck + + - name: Build package + run: pnpm build:package + + - name: Build site + run: pnpm build diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..76db544 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,50 @@ +name: Release + +on: + push: + branches: [main] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false + +permissions: {} + +jobs: + release-pr: + name: Release + runs-on: ubuntu-latest + permissions: + contents: write + id-token: write + pull-requests: write + + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Setup PNPM + uses: pnpm/action-setup@v6 + + - name: Setup Node + uses: actions/setup-node@v6 + with: + node-version-file: .nvmrc + registry-url: https://registry.npmjs.org + cache: pnpm + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Create release pull request + id: changesets + uses: changesets/action@v1 + with: + version: pnpm release + publish: pnpm release:publish + commit: "[ci] release" + title: "[ci] release" + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index fde7d82..255304d 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ node_modules/ # build output dist/ +package-dist/ .vite/ .sitex/ .sitex-bench/ diff --git a/README.md b/README.md index 1678182..68b27fa 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ SiteX renders React routes to static HTML by default. When a page needs browser ## Install ```bash -pnpm add @fulldotdev/sitex react react-dom vite @vitejs/plugin-react +pnpm add --allow-build=esbuild @fulldotdev/sitex react react-dom vite @vitejs/plugin-react pnpm add -D typescript @types/react @types/react-dom ``` diff --git a/package.json b/package.json index 8102c0e..19b671f 100644 --- a/package.json +++ b/package.json @@ -2,16 +2,35 @@ "name": "@fulldotdev/sitex", "version": "0.1.0", "license": "MIT", + "repository": { + "type": "git", + "url": "git+https://github.com/fulldotdev/sitex.git" + }, + "homepage": "https://github.com/fulldotdev/sitex#readme", + "bugs": { + "url": "https://github.com/fulldotdev/sitex/issues" + }, "publishConfig": { "access": "public" }, "type": "module", "exports": { - "./island": "./sitex/hydration/server.tsx", - "./plugin": "./sitex/vite/plugin.ts", + "./island": { + "types": "./package-dist/hydration/server.d.ts", + "default": "./package-dist/hydration/server.js" + }, + "./plugin": { + "types": "./package-dist/vite/plugin.d.ts", + "default": "./package-dist/vite/plugin.js" + }, "./tsconfig": "./sitex/config/tsconfig.json", "./virtual": "./sitex/types/virtual.d.ts" }, + "files": [ + "package-dist", + "sitex/config", + "sitex/types" + ], "engines": { "node": ">=22.12.0" }, @@ -22,6 +41,11 @@ "msw" ] }, + "peerDependencies": { + "react": ">=19", + "react-dom": ">=19", + "vite": ">=7" + }, "scripts": { "dev": "vite --host 127.0.0.1 --port 4323", "start": "pnpm dev", @@ -30,26 +54,41 @@ "format:check": "prettier --check .", "typecheck": "tsc --noEmit", "check": "pnpm format:check && pnpm typecheck", + "build:package": "node scripts/build-package.mjs && tsc -p tsconfig.package.json", "build": "vite build", "preview": "vite preview --host 127.0.0.1 --port 4323", + "prepack": "pnpm build:package", "changeset": "changeset", "release": "changeset version && pnpm install --lockfile-only", - "release:publish": "pnpm build && changeset publish" + "release:publish": "pnpm build && pnpm build:package && changeset publish" }, "dependencies": { "@babel/generator": "^7.29.1", "@babel/parser": "^7.29.3", "@babel/traverse": "^7.29.0", "@babel/types": "^7.29.0", + "fast-glob": "^3.3.3", + "vite-tsconfig-paths": "^6.1.1" + }, + "devDependencies": { "@base-ui/react": "^1.5.0", + "@changesets/changelog-github": "^0.7.0", + "@changesets/cli": "^2.31.0", "@fontsource-variable/geist": "^5.2.9", + "@ianvs/prettier-plugin-sort-imports": "^4.7.1", "@tailwindcss/vite": "^4.1.13", + "@types/babel__generator": "^7.27.0", + "@types/babel__traverse": "^7.28.0", + "@types/node": "^24.3.1", + "@types/react": "^19.1.12", + "@types/react-dom": "^19.1.9", "@vitejs/plugin-react": "^5.0.2", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", - "fast-glob": "^3.3.3", "lucide-react": "^1.16.0", "next-themes": "^0.4.6", + "prettier": "^3.8.3", + "prettier-plugin-tailwindcss": "^0.8.0", "react": "^19.1.1", "react-dom": "^19.1.1", "shadcn": "^4.8.3", @@ -57,18 +96,7 @@ "tailwind-merge": "^3.6.0", "tailwindcss": "^4.1.13", "tw-animate-css": "^1.4.0", - "vite": "^7.1.5", - "vite-tsconfig-paths": "^6.1.1" - }, - "devDependencies": { - "@changesets/changelog-github": "^0.7.0", - "@changesets/cli": "^2.31.0", - "@ianvs/prettier-plugin-sort-imports": "^4.7.1", - "@types/node": "^24.3.1", - "@types/react": "^19.1.12", - "@types/react-dom": "^19.1.9", - "prettier": "^3.8.3", - "prettier-plugin-tailwindcss": "^0.8.0", - "typescript": "^5.9.2" + "typescript": "^5.9.2", + "vite": "^7.1.5" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0628e8d..c57dd87 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -20,15 +20,46 @@ importers: '@babel/types': specifier: ^7.29.0 version: 7.29.0 + fast-glob: + specifier: ^3.3.3 + version: 3.3.3 + vite-tsconfig-paths: + specifier: ^6.1.1 + version: 6.1.1(typescript@5.9.3)(vite@7.3.3(@types/node@24.12.4)(jiti@2.7.0)(lightningcss@1.32.0)) + devDependencies: '@base-ui/react': specifier: ^1.5.0 version: 1.5.0(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@changesets/changelog-github': + specifier: ^0.7.0 + version: 0.7.0 + '@changesets/cli': + specifier: ^2.31.0 + version: 2.31.0(@types/node@24.12.4) '@fontsource-variable/geist': specifier: ^5.2.9 version: 5.2.9 + '@ianvs/prettier-plugin-sort-imports': + specifier: ^4.7.1 + version: 4.7.1(prettier@3.8.3) '@tailwindcss/vite': specifier: ^4.1.13 version: 4.3.0(vite@7.3.3(@types/node@24.12.4)(jiti@2.7.0)(lightningcss@1.32.0)) + '@types/babel__generator': + specifier: ^7.27.0 + version: 7.27.0 + '@types/babel__traverse': + specifier: ^7.28.0 + version: 7.28.0 + '@types/node': + specifier: ^24.3.1 + version: 24.12.4 + '@types/react': + specifier: ^19.1.12 + version: 19.2.15 + '@types/react-dom': + specifier: ^19.1.9 + version: 19.2.3(@types/react@19.2.15) '@vitejs/plugin-react': specifier: ^5.0.2 version: 5.2.0(vite@7.3.3(@types/node@24.12.4)(jiti@2.7.0)(lightningcss@1.32.0)) @@ -38,15 +69,18 @@ importers: clsx: specifier: ^2.1.1 version: 2.1.1 - fast-glob: - specifier: ^3.3.3 - version: 3.3.3 lucide-react: specifier: ^1.16.0 version: 1.16.0(react@19.2.6) next-themes: specifier: ^0.4.6 version: 0.4.6(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + prettier: + specifier: ^3.8.3 + version: 3.8.3 + prettier-plugin-tailwindcss: + specifier: ^0.8.0 + version: 0.8.0(@ianvs/prettier-plugin-sort-imports@4.7.1(prettier@3.8.3))(prettier@3.8.3) react: specifier: ^19.1.1 version: 19.2.6 @@ -68,40 +102,12 @@ importers: tw-animate-css: specifier: ^1.4.0 version: 1.4.0 - vite: - specifier: ^7.1.5 - version: 7.3.3(@types/node@24.12.4)(jiti@2.7.0)(lightningcss@1.32.0) - vite-tsconfig-paths: - specifier: ^6.1.1 - version: 6.1.1(typescript@5.9.3)(vite@7.3.3(@types/node@24.12.4)(jiti@2.7.0)(lightningcss@1.32.0)) - devDependencies: - '@changesets/changelog-github': - specifier: ^0.7.0 - version: 0.7.0 - '@changesets/cli': - specifier: ^2.31.0 - version: 2.31.0(@types/node@24.12.4) - '@ianvs/prettier-plugin-sort-imports': - specifier: ^4.7.1 - version: 4.7.1(prettier@3.8.3) - '@types/node': - specifier: ^24.3.1 - version: 24.12.4 - '@types/react': - specifier: ^19.1.12 - version: 19.2.15 - '@types/react-dom': - specifier: ^19.1.9 - version: 19.2.3(@types/react@19.2.15) - prettier: - specifier: ^3.8.3 - version: 3.8.3 - prettier-plugin-tailwindcss: - specifier: ^0.8.0 - version: 0.8.0(@ianvs/prettier-plugin-sort-imports@4.7.1(prettier@3.8.3))(prettier@3.8.3) typescript: specifier: ^5.9.2 version: 5.9.3 + vite: + specifier: ^7.1.5 + version: 7.3.3(@types/node@24.12.4)(jiti@2.7.0)(lightningcss@1.32.0) packages: diff --git a/scripts/build-package.mjs b/scripts/build-package.mjs new file mode 100644 index 0000000..7c62753 --- /dev/null +++ b/scripts/build-package.mjs @@ -0,0 +1,34 @@ +import { rm } from "node:fs/promises" +import { builtinModules, createRequire } from "node:module" +import { build } from "esbuild" + +const require = createRequire(import.meta.url) +const packageJson = require("../package.json") + +const external = [ + ...builtinModules, + ...builtinModules.map((module) => `node:${module}`), + ...Object.keys(packageJson.dependencies ?? {}), + ...Object.keys(packageJson.peerDependencies ?? {}), + "virtual:*", +] + +await rm("package-dist", { recursive: true, force: true }) + +await build({ + entryPoints: [ + "sitex/vite/plugin.ts", + "sitex/hydration/client.tsx", + "sitex/hydration/server.tsx", + "sitex/router/index.tsx", + "sitex/router/routes.ts", + ], + outbase: "sitex", + outdir: "package-dist", + bundle: true, + external, + format: "esm", + jsx: "automatic", + platform: "neutral", + target: "es2022", +}) diff --git a/sitex/vite/plugin.ts b/sitex/vite/plugin.ts index fbf875d..5f998d3 100644 --- a/sitex/vite/plugin.ts +++ b/sitex/vite/plugin.ts @@ -28,7 +28,8 @@ import { import { findRoute, getRoutes } from "../router/routes.ts" const packageRoot = path.dirname(path.dirname(fileURLToPath(import.meta.url))) -const islandClientInput = normalizePath(packageFile("hydration/client.tsx")) +const packageSourceExtension = path.extname(fileURLToPath(import.meta.url)) +const islandClientInput = normalizePath(packageFile("hydration/client", "tsx")) const isHtmlRequest = (req: Connect.IncomingMessage) => { if (!req.url || (req.method !== "GET" && req.method !== "HEAD")) return false @@ -134,11 +135,11 @@ function sitexPlugin(): Plugin { try { const mod = await server.ssrLoadModule( - packageFile("router/index.tsx") + packageFile("router/index", "tsx") ) const html = await mod.renderRouteHtml(route, { assetTags: renderStylesheetTags(cssInputs), - islandClientSrc: devServerFileUrl("hydration/client.tsx"), + islandClientSrc: devServerFileUrl("hydration/client", "tsx"), }) const transformed = await server.transformIndexHtml(url, html) @@ -214,10 +215,10 @@ async function writeStaticHtml(root: string, cssInputs: string[]) { try { const { getRoutes } = await server.ssrLoadModule( - packageFile("router/routes.ts") + packageFile("router/routes", "ts") ) const { renderRouteHtml } = await server.ssrLoadModule( - packageFile("router/index.tsx") + packageFile("router/index", "tsx") ) const routes = await getRoutes((id: string) => server.ssrLoadModule(id)) @@ -327,12 +328,14 @@ function resolveCssImport(root: string, importer: string, source: string) { return path.resolve(path.dirname(importer), source) } -function packageFile(file: string) { - return path.join(packageRoot, file) +function packageFile(file: string, sourceExtension: "ts" | "tsx") { + const extension = packageSourceExtension === ".js" ? "js" : sourceExtension + + return path.join(packageRoot, `${file}.${extension}`) } -function devServerFileUrl(file: string) { - return `/@fs/${packageFile(file).replaceAll(path.sep, "/")}` +function devServerFileUrl(file: string, sourceExtension: "ts" | "tsx") { + return `/@fs/${packageFile(file, sourceExtension).replaceAll(path.sep, "/")}` } function normalizePath(file: string) { diff --git a/src/pages/docs/installation.tsx b/src/pages/docs/installation.tsx index f0dbe02..efb8e99 100644 --- a/src/pages/docs/installation.tsx +++ b/src/pages/docs/installation.tsx @@ -38,7 +38,7 @@ export default function InstallationPage() {

@@ -50,7 +50,7 @@ extend the SiteX TypeScript config, add the scripts, and create the first route.

diff --git a/tsconfig.package.json b/tsconfig.package.json new file mode 100644 index 0000000..4e194f0 --- /dev/null +++ b/tsconfig.package.json @@ -0,0 +1,12 @@ +{ + "extends": "./sitex/config/tsconfig.json", + "include": ["sitex/**/*"], + "exclude": ["dist"], + "compilerOptions": { + "declaration": true, + "emitDeclarationOnly": true, + "declarationMap": false, + "outDir": "package-dist", + "noEmit": false + } +} diff --git a/vite.config.ts b/vite.config.ts index 489b711..25d6f1f 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,8 +1,9 @@ import { defineConfig } from "vite" -import { sitex } from "@fulldotdev/sitex/plugin" import tailwindcss from "@tailwindcss/vite" import react from "@vitejs/plugin-react" +import { sitex } from "./sitex/vite/plugin" + export default defineConfig({ appType: "custom", plugins: [react(), sitex(), tailwindcss()],