├── .github
├── renovate.json
└── workflows
│ ├── ci.yml
│ ├── cq.yml
│ └── publish.yml
├── .gitignore
├── .husky
└── pre-commit
├── .npmrc
├── .prettierignore
├── .prettierrc
├── CONTRIBUTING.md
├── ci
├── ci.test.ts
├── package.json
└── tsconfig.json
├── examples
├── express
│ ├── .stackblitzrc
│ ├── package.json
│ ├── readme.md
│ ├── routes
│ │ ├── bar.ts
│ │ ├── foo.ts
│ │ ├── home.ts
│ │ └── nav.ts
│ ├── server.ts
│ ├── tsconfig.json
│ └── vite.config.ts
├── fastify-vike
│ ├── .gitignore
│ ├── .stackblitzrc
│ ├── package.json
│ ├── pages
│ │ ├── _error
│ │ │ └── +Page.tsx
│ │ ├── about
│ │ │ ├── +Page.tsx
│ │ │ └── code.css
│ │ ├── index
│ │ │ ├── +Page.tsx
│ │ │ └── Counter.tsx
│ │ └── star-wars
│ │ │ ├── @id
│ │ │ ├── +Page.tsx
│ │ │ └── +data.ts
│ │ │ ├── index
│ │ │ ├── +Page.tsx
│ │ │ └── +data.ts
│ │ │ └── types.ts
│ ├── readme.md
│ ├── renderer
│ │ ├── +config.ts
│ │ ├── +onPageTransitionEnd.ts
│ │ ├── +onPageTransitionStart.ts
│ │ ├── +onRenderClient.tsx
│ │ ├── +onRenderHtml.tsx
│ │ ├── Layout.css
│ │ ├── Layout.tsx
│ │ ├── Link.tsx
│ │ ├── css
│ │ │ ├── code.css
│ │ │ ├── index.css
│ │ │ ├── links.css
│ │ │ ├── page-transition-loading-animation.css
│ │ │ ├── page-transition-loading-animation
│ │ │ │ └── loading.svg
│ │ │ └── reset.css
│ │ ├── getPageTitle.ts
│ │ ├── logo.svg
│ │ ├── types.ts
│ │ ├── useData.ts
│ │ └── usePageContext.tsx
│ ├── server
│ │ └── index.ts
│ ├── tsconfig.json
│ └── vite.config.ts
├── fastify
│ ├── .stackblitzrc
│ ├── handler.ts
│ ├── package.json
│ ├── readme.md
│ ├── routes
│ │ ├── bar.ts
│ │ ├── foo.ts
│ │ ├── home.ts
│ │ └── nav.ts
│ ├── tsconfig.json
│ └── vite.config.ts
├── hapi
│ ├── .stackblitzrc
│ ├── package.json
│ ├── readme.md
│ ├── routes
│ │ ├── bar.ts
│ │ ├── foo.ts
│ │ ├── home.ts
│ │ └── nav.ts
│ ├── server.ts
│ ├── tsconfig.json
│ └── vite.config.ts
├── koa
│ ├── .stackblitzrc
│ ├── package.json
│ ├── readme.md
│ ├── routes
│ │ ├── bar.ts
│ │ ├── foo.ts
│ │ ├── home.ts
│ │ └── nav.ts
│ ├── server.ts
│ ├── tsconfig.json
│ └── vite.config.ts
├── nestjs-vike
│ ├── index.ts
│ ├── package.json
│ ├── pages
│ │ ├── _error
│ │ │ └── +Page.tsx
│ │ ├── about
│ │ │ ├── +Page.tsx
│ │ │ └── code.css
│ │ ├── index
│ │ │ ├── +Page.tsx
│ │ │ └── Counter.tsx
│ │ └── star-wars
│ │ │ ├── @id
│ │ │ ├── +Page.tsx
│ │ │ └── +data.ts
│ │ │ ├── index
│ │ │ ├── +Page.tsx
│ │ │ └── +data.ts
│ │ │ └── types.ts
│ ├── readme.md
│ ├── renderer
│ │ ├── +config.ts
│ │ ├── +onPageTransitionEnd.ts
│ │ ├── +onPageTransitionStart.ts
│ │ ├── +onRenderClient.tsx
│ │ ├── +onRenderHtml.tsx
│ │ ├── Layout.css
│ │ ├── Layout.tsx
│ │ ├── Link.tsx
│ │ ├── css
│ │ │ ├── code.css
│ │ │ ├── index.css
│ │ │ ├── links.css
│ │ │ ├── page-transition-loading-animation.css
│ │ │ ├── page-transition-loading-animation
│ │ │ │ └── loading.svg
│ │ │ └── reset.css
│ │ ├── getPageTitle.ts
│ │ ├── logo.svg
│ │ ├── types.ts
│ │ ├── useData.ts
│ │ └── usePageContext.tsx
│ ├── server
│ │ ├── app.module.ts
│ │ ├── main.ts
│ │ └── vps.module.ts
│ ├── tsconfig.json
│ └── vite.config.ts
├── nestjs
│ ├── package.json
│ ├── readme.md
│ ├── src
│ │ ├── app.controller.ts
│ │ ├── app.module.ts
│ │ └── main.ts
│ ├── tsconfig.json
│ └── vite.config.ts
├── readme.md
├── simple-standalone
│ ├── .stackblitzrc
│ ├── handler.ts
│ ├── package.json
│ ├── readme.md
│ ├── tsconfig.json
│ └── vite.config.ts
├── socket-io
│ ├── .stackblitzrc
│ ├── package.json
│ ├── readme.md
│ ├── server.ts
│ ├── tsconfig.json
│ ├── view.html
│ └── vite.config.ts
├── ssr-react-express
│ ├── .stackblitzrc
│ ├── App.tsx
│ ├── client-entry.tsx
│ ├── components
│ │ ├── Counter.tsx
│ │ └── Nav.tsx
│ ├── package.json
│ ├── pages
│ │ ├── Bar.tsx
│ │ ├── Foo.tsx
│ │ └── Home.tsx
│ ├── readme.md
│ ├── server-entry.tsx
│ ├── tsconfig.json
│ └── vite.config.ts
├── ssr-vue-express
│ ├── .stackblitzrc
│ ├── App.vue
│ ├── client-entry.ts
│ ├── components
│ │ ├── Counter.vue
│ │ └── Nav.vue
│ ├── package.json
│ ├── pages
│ │ ├── Bar.vue
│ │ ├── Foo.vue
│ │ └── Home.vue
│ ├── readme.md
│ ├── server-entry.ts
│ ├── tsconfig.json
│ ├── vite.config.ts
│ └── vue-sfc.d.ts
├── vike
│ ├── .gitignore
│ ├── .stackblitzrc
│ ├── package.json
│ ├── pages
│ │ ├── _error
│ │ │ └── +Page.tsx
│ │ ├── about
│ │ │ ├── +Page.tsx
│ │ │ └── code.css
│ │ ├── index
│ │ │ ├── +Page.tsx
│ │ │ └── Counter.tsx
│ │ └── star-wars
│ │ │ ├── @id
│ │ │ ├── +Page.tsx
│ │ │ └── +data.ts
│ │ │ ├── index
│ │ │ ├── +Page.tsx
│ │ │ └── +data.ts
│ │ │ └── types.ts
│ ├── readme.md
│ ├── renderer
│ │ ├── +config.ts
│ │ ├── +onPageTransitionEnd.ts
│ │ ├── +onPageTransitionStart.ts
│ │ ├── +onRenderClient.tsx
│ │ ├── +onRenderHtml.tsx
│ │ ├── Layout.css
│ │ ├── Layout.tsx
│ │ ├── Link.tsx
│ │ ├── css
│ │ │ ├── code.css
│ │ │ ├── index.css
│ │ │ ├── links.css
│ │ │ ├── page-transition-loading-animation.css
│ │ │ ├── page-transition-loading-animation
│ │ │ │ └── loading.svg
│ │ │ └── reset.css
│ │ ├── getPageTitle.ts
│ │ ├── logo.svg
│ │ ├── types.ts
│ │ ├── useData.ts
│ │ └── usePageContext.tsx
│ ├── server
│ │ └── index.ts
│ ├── tsconfig.json
│ └── vite.config.ts
└── ws
│ ├── .stackblitzrc
│ ├── index.html
│ ├── package.json
│ ├── readme.md
│ ├── server.ts
│ ├── tsconfig.json
│ └── vite.config.ts
├── lint-staged.config.mjs
├── package.json
├── packages
├── connect
│ ├── eslint.config.js
│ ├── lint-staged.config.mjs
│ ├── package.json
│ ├── readme.md
│ ├── src
│ │ ├── entry-standalone-with-sirv.ts
│ │ ├── entry-standalone.ts
│ │ └── index.ts
│ ├── tsconfig.json
│ └── tsup.config.ts
├── expose-vite-dev-server
│ ├── eslint.config.js
│ ├── lint-staged.config.mjs
│ ├── package.json
│ ├── readme.md
│ ├── src
│ │ ├── index.ts
│ │ └── vite-dev-server.ts
│ ├── tsconfig.json
│ └── tsup.config.ts
├── multibuild-cli
│ ├── cli.js
│ ├── eslint.config.js
│ ├── index.d.ts
│ ├── lint-staged.config.mjs
│ ├── package.json
│ ├── readme.md
│ ├── src
│ │ └── index.ts
│ ├── tsconfig.json
│ └── tsup.config.ts
├── multibuild
│ ├── eslint.config.js
│ ├── lint-staged.config.mjs
│ ├── package.json
│ ├── readme.md
│ ├── src
│ │ └── index.ts
│ ├── tsconfig.json
│ └── tsup.config.ts
├── node-loader
│ ├── cli.js
│ ├── eslint.config.js
│ ├── lint-staged.config.mjs
│ ├── package.json
│ ├── readme.md
│ ├── src
│ │ ├── cli.ts
│ │ ├── index.ts
│ │ ├── plugin.ts
│ │ └── suppress-warnings.ts
│ ├── tsconfig.json
│ └── tsup.config.ts
├── reloader
│ ├── eslint.config.js
│ ├── lint-staged.config.mjs
│ ├── package.json
│ ├── readme.md
│ ├── src
│ │ ├── http-dev-server.ts
│ │ └── index.ts
│ ├── tsconfig.json
│ └── tsup.config.ts
└── vavite
│ ├── cli.js
│ ├── eslint.config.js
│ ├── lint-staged.config.mjs
│ ├── node-loader.mjs
│ ├── package.json
│ ├── readme.md
│ ├── src
│ ├── cli.ts
│ ├── http-dev-server.ts
│ ├── index.ts
│ └── vite-dev-server.ts
│ ├── suppress-loader-warnings.cjs
│ ├── tsconfig.json
│ └── tsup.config.ts
├── pnpm-lock.yaml
├── pnpm-workspace.yaml
├── readme.md
└── version
/.github/renovate.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json",
3 | "extends": ["config:base", "schedule:weekly", "group:allNonMajor"],
4 | "ignorePresets": ["ignorePaths"],
5 | "ignorePaths": ["**/node_modules/**", "**/dist/**"],
6 | "labels": ["dependencies"],
7 | "rangeStrategy": "bump",
8 | "packageRules": [
9 | {
10 | "depTypeList": ["peerDependencies"],
11 | "enabled": false
12 | },
13 | {
14 | "matchPackagePatterns": ["^@types/node$"],
15 | "allowedVersions": "18"
16 | }
17 | ],
18 | "ignoreDeps": []
19 | }
20 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI tests
2 |
3 | on:
4 | workflow_dispatch:
5 | push:
6 | branches:
7 | - main
8 | pull_request:
9 | branches:
10 | - main
11 |
12 | concurrency:
13 | group: "${{ github.workflow }} @ ${{ github.event.pull_request.head.label || github.head_ref || github.ref || github.run_id }}"
14 | cancel-in-progress: true
15 |
16 | defaults:
17 | run:
18 | working-directory: .
19 |
20 | jobs:
21 | test:
22 | runs-on: ${{ matrix.os }}
23 | strategy:
24 | matrix:
25 | os: [ubuntu-latest, macos-latest, windows-latest]
26 | node_version: [20]
27 | include:
28 | - os: ubuntu-latest
29 | node_version: 18
30 | - os: ubuntu-latest
31 | node_version: 22
32 | fail-fast: false
33 | name: "CI tests on node-${{ matrix.node_version }}, ${{ matrix.os }}"
34 | steps:
35 | - name: Checkout
36 | uses: actions/checkout@v4
37 |
38 | - name: Install pnpm
39 | uses: pnpm/action-setup@v4
40 | with:
41 | version: 9
42 |
43 | - name: Set node version to ${{ matrix.node_version }}
44 | uses: actions/setup-node@v4
45 | with:
46 | node-version: ${{ matrix.node_version }}
47 | cache: "pnpm"
48 |
49 | - name: Install
50 | run: pnpm install
51 |
52 | - name: Build
53 | run: pnpm run build
54 |
55 | - name: Run CI tests
56 | run: pnpm run ci
57 |
--------------------------------------------------------------------------------
/.github/workflows/cq.yml:
--------------------------------------------------------------------------------
1 | name: Code quality checks
2 |
3 | on:
4 | workflow_dispatch:
5 | push:
6 | branches:
7 | - main
8 | pull_request:
9 | branches:
10 | - main
11 |
12 | concurrency:
13 | group: "${{ github.workflow }} @ ${{ github.event.pull_request.head.label || github.head_ref || github.ref || github.run_id }}"
14 | cancel-in-progress: true
15 |
16 | defaults:
17 | run:
18 | working-directory: .
19 |
20 | jobs:
21 | test:
22 | runs-on: ubuntu-latest
23 | name: "Code quality checks"
24 | steps:
25 | - name: Checkout
26 | uses: actions/checkout@v4
27 |
28 | - name: Install pnpm
29 | uses: pnpm/action-setup@v4
30 | with:
31 | version: 9
32 |
33 | - name: Set node version to 20
34 | uses: actions/setup-node@v4
35 | with:
36 | node-version: 20
37 | cache: "pnpm"
38 |
39 | - name: Install
40 | run: pnpm install
41 |
42 | - name: Build
43 | run: pnpm run build
44 |
45 | - name: Run code quality checks and tests
46 | run: pnpm run cq
47 |
--------------------------------------------------------------------------------
/.github/workflows/publish.yml:
--------------------------------------------------------------------------------
1 | name: Publish to NPM
2 |
3 | on:
4 | workflow_dispatch:
5 | inputs:
6 | version:
7 | description: "Version to publish"
8 | required: true
9 | type: "string"
10 | tag:
11 | description: "Tag to publish"
12 | required: true
13 | type: "string"
14 | default: "latest"
15 | commit:
16 | description: "Should we commit the version bump?"
17 | required: false
18 | type: "boolean"
19 | default: true
20 |
21 | defaults:
22 | run:
23 | working-directory: .
24 |
25 | jobs:
26 | publish:
27 | runs-on: ubuntu-latest
28 | name: "Publish to NPM"
29 | steps:
30 | - name: Checkout
31 | uses: actions/checkout@v4
32 |
33 | - name: Install pnpm
34 | uses: pnpm/action-setup@v4
35 | with:
36 | version: 9
37 |
38 | - name: Set node version to 20
39 | uses: actions/setup-node@v4
40 | with:
41 | node-version: 20
42 | registry-url: "https://registry.npmjs.org"
43 | cache: "pnpm"
44 |
45 | - name: Install
46 | run: pnpm install
47 |
48 | - name: Set up git user
49 | run: |
50 | git config --global user.name "GitHub Action Bot"
51 | git config --global user.email "<>"
52 |
53 | - name: Bump version
54 | run: "./version ${{ github.event.inputs.version }}"
55 |
56 | - name: "Publish to NPM"
57 | env:
58 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
59 | run: pnpm -r publish --access public --no-git-checks --tag ${{ github.event.inputs.tag }}
60 |
61 | - name: "Commit version bump"
62 | if: ${{ github.event.inputs.commit == 'true' }}
63 | run: |
64 | git status
65 | git commit -am "release: ${{ github.event.inputs.version }}"
66 | git push
67 | git tag ${{ github.event.inputs.version }}
68 | git push --tags
69 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | lerna-debug.log*
8 | .pnpm-debug.log*
9 |
10 | # Diagnostic reports (https://nodejs.org/api/report.html)
11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
12 |
13 | # Runtime data
14 | pids
15 | *.pid
16 | *.seed
17 | *.pid.lock
18 |
19 | # Directory for instrumented libs generated by jscoverage/JSCover
20 | lib-cov
21 |
22 | # Coverage directory used by tools like istanbul
23 | coverage
24 | *.lcov
25 |
26 | # nyc test coverage
27 | .nyc_output
28 |
29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
30 | .grunt
31 |
32 | # Bower dependency directory (https://bower.io/)
33 | bower_components
34 |
35 | # node-waf configuration
36 | .lock-wscript
37 |
38 | # Compiled binary addons (https://nodejs.org/api/addons.html)
39 | build/Release
40 |
41 | # Dependency directories
42 | node_modules/
43 | jspm_packages/
44 |
45 | # Snowpack dependency directory (https://snowpack.dev/)
46 | web_modules/
47 |
48 | # TypeScript cache
49 | *.tsbuildinfo
50 |
51 | # Optional npm cache directory
52 | .npm
53 |
54 | # Optional eslint cache
55 | .eslintcache
56 |
57 | # Optional stylelint cache
58 | .stylelintcache
59 |
60 | # Microbundle cache
61 | .rpt2_cache/
62 | .rts2_cache_cjs/
63 | .rts2_cache_es/
64 | .rts2_cache_umd/
65 |
66 | # Optional REPL history
67 | .node_repl_history
68 |
69 | # Output of 'npm pack'
70 | *.tgz
71 |
72 | # Yarn Integrity file
73 | .yarn-integrity
74 |
75 | # dotenv environment variable files
76 | .env
77 | .env.development.local
78 | .env.test.local
79 | .env.production.local
80 | .env.local
81 |
82 | # parcel-bundler cache (https://parceljs.org/)
83 | .cache
84 | .parcel-cache
85 |
86 | # Next.js build output
87 | .next
88 | out
89 |
90 | # Nuxt.js build / generate output
91 | .nuxt
92 | dist
93 |
94 | # Gatsby files
95 | .cache/
96 | # Comment in the public line in if your project uses Gatsby and not Next.js
97 | # https://nextjs.org/blog/next-9-1#public-directory-support
98 | # public
99 |
100 | # vuepress build output
101 | .vuepress/dist
102 |
103 | # vuepress v2.x temp and cache directory
104 | .temp
105 | .cache
106 |
107 | # Serverless directories
108 | .serverless/
109 |
110 | # FuseBox cache
111 | .fusebox/
112 |
113 | # DynamoDB Local files
114 | .dynamodb/
115 |
116 | # TernJS port file
117 | .tern-port
118 |
119 | # Stores VSCode versions used for testing VSCode extensions
120 | .vscode-test
121 | .vscode/
122 |
123 | # yarn v2
124 | .yarn/cache
125 | .yarn/unplugged
126 | .yarn/build-state.yml
127 | .yarn/install-state.gz
128 | .pnp.*
129 |
130 | # Local files
131 | *.local.*
132 |
133 | # Packed package files
134 | *.tgz
135 |
136 | .DS_Store
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | pnpm run precommit --
2 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | strict-peer-dependencies=false
2 | side-effects-cache=false
3 | link-workspace-packages=true
4 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | dist
3 | pnpm-lock.yaml
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "trailingComma": "all",
3 | "useTabs": true,
4 | "overrides": [
5 | {
6 | "files": "**/*.md",
7 | "options": {
8 | "tabWidth": 2,
9 | "useTabs": false
10 | }
11 | },
12 | {
13 | "files": "**/package.json",
14 | "options": {
15 | "tabWidth": 2,
16 | "useTabs": false
17 | }
18 | }
19 | ]
20 | }
21 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | `vavite` is a `pnpm` monorepo. To start local development:
4 |
5 | - Install `pnpm`
6 | - Fork and clone the repo
7 | - Run pnpm dev
8 | - Start hacking
9 | - Send a PR
10 |
--------------------------------------------------------------------------------
/ci/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@vavite/ci",
3 | "private": true,
4 | "type": "module",
5 | "scripts": {
6 | "ci": "vitest run --reporter=verbose"
7 | },
8 | "dependencies": {
9 | "@types/node": "^18.19.67",
10 | "kill-port": "^2.0.1",
11 | "ps-tree": "^1.2.0",
12 | "puppeteer": "^23.9.0",
13 | "typescript": "^5.7.2",
14 | "vitest": "^2.1.6"
15 | },
16 | "devDependencies": {
17 | "@types/ps-tree": "^1.1.6"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/ci/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es2020",
4 | "module": "ESNext",
5 | "esModuleInterop": true,
6 | "forceConsistentCasingInFileNames": true,
7 | "strict": true,
8 | "skipLibCheck": true,
9 | "moduleResolution": "Bundler",
10 | "customConditions": ["import"]
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/examples/express/.stackblitzrc:
--------------------------------------------------------------------------------
1 | {
2 | "installDependencies": true,
3 | "startCommand": "npm run dev"
4 | }
5 |
--------------------------------------------------------------------------------
/examples/express/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@vavite/example-reloader-express",
3 | "type": "module",
4 | "private": true,
5 | "scripts": {
6 | "start": "node dist",
7 | "dev": "vite",
8 | "build": "vite build --ssr --mode=production"
9 | },
10 | "files": [
11 | "dist"
12 | ],
13 | "devDependencies": {
14 | "@types/express": "^5.0.0",
15 | "@types/node": "^18.19.67",
16 | "typescript": "^5.7.2",
17 | "vavite": "5.1.0",
18 | "vite": "^5.4.11"
19 | },
20 | "dependencies": {
21 | "express": "^4.21.1"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/examples/express/readme.md:
--------------------------------------------------------------------------------
1 | # Vavite Express example
2 |
3 | `serverEntry` example that shows how to integrate with [Express](https://expressjs.com/).
4 |
5 | > [Try on StackBlitz](https://stackblitz.com/github/cyco130/vavite/tree/main/examples/express)
6 |
7 | Clone with:
8 |
9 | ```bash
10 | npx degit cyco130/vavite/examples/express
11 | ```
12 |
--------------------------------------------------------------------------------
/examples/express/routes/bar.ts:
--------------------------------------------------------------------------------
1 | import { RequestHandler } from "express";
2 | import viteDevServer from "vavite/vite-dev-server";
3 | import nav from "./nav";
4 |
5 | const barRoute: RequestHandler = async (req, res, next) => {
6 | let html = "
Hello from page /bar
" + nav;
7 |
8 | if (viteDevServer) {
9 | html = await viteDevServer.transformIndexHtml(req.url, html);
10 | }
11 |
12 | res.send(html);
13 | };
14 |
15 | export default barRoute;
16 |
--------------------------------------------------------------------------------
/examples/express/routes/foo.ts:
--------------------------------------------------------------------------------
1 | import { RequestHandler } from "express";
2 | import viteDevServer from "vavite/vite-dev-server";
3 | import nav from "./nav";
4 |
5 | const fooRoute: RequestHandler = async (req, res, next) => {
6 | let html = "Hello from page /foo
" + nav;
7 |
8 | if (viteDevServer) {
9 | html = await viteDevServer.transformIndexHtml(req.url, html);
10 | }
11 |
12 | res.send(html);
13 | };
14 |
15 | export default fooRoute;
16 |
--------------------------------------------------------------------------------
/examples/express/routes/home.ts:
--------------------------------------------------------------------------------
1 | import { RequestHandler } from "express";
2 | import viteDevServer from "vavite/vite-dev-server";
3 | import nav from "./nav";
4 |
5 | const homeRoute: RequestHandler = async (req, res, next) => {
6 | let html = "Hello from home page
" + nav;
7 |
8 | if (viteDevServer) {
9 | html = await viteDevServer.transformIndexHtml(req.url, html);
10 | }
11 |
12 | res.send(html);
13 | };
14 |
15 | export default homeRoute;
16 |
--------------------------------------------------------------------------------
/examples/express/routes/nav.ts:
--------------------------------------------------------------------------------
1 | export default `
2 |
9 | `;
10 |
--------------------------------------------------------------------------------
/examples/express/server.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | import express, { RequestHandler } from "express";
4 | import viteDevServer from "vavite/vite-dev-server";
5 |
6 | console.log("Running the server");
7 |
8 | const app = express();
9 |
10 | // This is an optional optimization to load routes lazily so that
11 | // when reloadOn option is set to "static-deps-change",
12 | // changes to the route handlers will not trigger a reload.
13 | // Feel free to remove this and import routes directly.
14 | function lazy(
15 | importer: () => Promise<{ default: RequestHandler }>,
16 | ): RequestHandler {
17 | return async (req, res, next) => {
18 | try {
19 | const routeHandler = (await importer()).default;
20 | routeHandler(req, res, next);
21 | } catch (err) {
22 | if (err instanceof Error) viteDevServer?.ssrFixStacktrace(err);
23 | next(err);
24 | }
25 | };
26 | }
27 |
28 | app.get(
29 | "/",
30 | lazy(() => import("./routes/home")),
31 | );
32 |
33 | app.get(
34 | "/foo",
35 | lazy(() => import("./routes/foo")),
36 | );
37 |
38 | app.get(
39 | "/bar",
40 | lazy(() => import("./routes/bar")),
41 | );
42 |
43 | export default app;
44 |
--------------------------------------------------------------------------------
/examples/express/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es2020",
4 | "module": "ESNext",
5 | "esModuleInterop": true,
6 | "forceConsistentCasingInFileNames": true,
7 | "strict": true,
8 | "skipLibCheck": true,
9 | "moduleResolution": "Bundler",
10 | "customConditions": ["import"]
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/examples/express/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "vite";
2 | import { vavite } from "vavite";
3 |
4 | export default defineConfig({
5 | plugins: [
6 | vavite({
7 | handlerEntry: "/server.ts",
8 | reloadOn: "static-deps-change",
9 | serveClientAssetsInDev: true,
10 | }),
11 | ],
12 | });
13 |
--------------------------------------------------------------------------------
/examples/fastify-vike/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | lerna-debug.log*
8 | .pnpm-debug.log*
9 |
10 | # Diagnostic reports (https://nodejs.org/api/report.html)
11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
12 |
13 | # Runtime data
14 | pids
15 | *.pid
16 | *.seed
17 | *.pid.lock
18 |
19 | # Directory for instrumented libs generated by jscoverage/JSCover
20 | lib-cov
21 |
22 | # Coverage directory used by tools like istanbul
23 | coverage
24 | *.lcov
25 |
26 | # nyc test coverage
27 | .nyc_output
28 |
29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
30 | .grunt
31 |
32 | # Bower dependency directory (https://bower.io/)
33 | bower_components
34 |
35 | # node-waf configuration
36 | .lock-wscript
37 |
38 | # Compiled binary addons (https://nodejs.org/api/addons.html)
39 | build/Release
40 |
41 | # Dependency directories
42 | node_modules/
43 | jspm_packages/
44 |
45 | # Snowpack dependency directory (https://snowpack.dev/)
46 | web_modules/
47 |
48 | # TypeScript cache
49 | *.tsbuildinfo
50 |
51 | # Optional npm cache directory
52 | .npm
53 |
54 | # Optional eslint cache
55 | .eslintcache
56 |
57 | # Microbundle cache
58 | .rpt2_cache/
59 | .rts2_cache_cjs/
60 | .rts2_cache_es/
61 | .rts2_cache_umd/
62 |
63 | # Optional REPL history
64 | .node_repl_history
65 |
66 | # Output of 'npm pack'
67 | *.tgz
68 |
69 | # Yarn Integrity file
70 | .yarn-integrity
71 |
72 | # dotenv environment variables file
73 | .env
74 | .env.test
75 | .env.production
76 |
77 | # parcel-bundler cache (https://parceljs.org/)
78 | .cache
79 | .parcel-cache
80 |
81 | # Next.js build output
82 | .next
83 | out
84 |
85 | # Nuxt.js build / generate output
86 | .nuxt
87 | dist
88 |
89 | # Gatsby files
90 | .cache/
91 | # Comment in the public line in if your project uses Gatsby and not Next.js
92 | # https://nextjs.org/blog/next-9-1#public-directory-support
93 | # public
94 |
95 | # vuepress build output
96 | .vuepress/dist
97 |
98 | # Serverless directories
99 | .serverless/
100 |
101 | # FuseBox cache
102 | .fusebox/
103 |
104 | # DynamoDB Local files
105 | .dynamodb/
106 |
107 | # TernJS port file
108 | .tern-port
109 |
110 | # Stores VSCode versions used for testing VSCode extensions
111 | .vscode-test
112 |
113 | # yarn v2
114 | .yarn/cache
115 | .yarn/unplugged
116 | .yarn/build-state.yml
117 | .yarn/install-state.gz
118 | .pnp.*
119 |
120 | # Local files
121 | *.local
122 |
--------------------------------------------------------------------------------
/examples/fastify-vike/.stackblitzrc:
--------------------------------------------------------------------------------
1 | {
2 | "installDependencies": true,
3 | "startCommand": "npm run dev"
4 | }
5 |
--------------------------------------------------------------------------------
/examples/fastify-vike/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@vavite/example-fastify-vike",
3 | "type": "module",
4 | "private": true,
5 | "scripts": {
6 | "dev": "vavite serve",
7 | "build": "vavite build",
8 | "start": "cross-env NODE_ENV=production node dist/server/index.mjs"
9 | },
10 | "dependencies": {
11 | "@fastify/static": "^8.0.3",
12 | "@types/node": "^18.19.67",
13 | "@types/react": "^18.3.12",
14 | "@types/react-dom": "^18.3.1",
15 | "@vitejs/plugin-react": "^4.3.4",
16 | "cross-env": "^7.0.3",
17 | "fastify": "^5.1.0",
18 | "react": "^18.3.1",
19 | "react-dom": "^18.3.1",
20 | "ts-node": "^10.9.2",
21 | "typescript": "^5.7.2",
22 | "vavite": "5.1.0",
23 | "vike": "^0.4.205",
24 | "vite": "^5.4.11"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/examples/fastify-vike/pages/_error/+Page.tsx:
--------------------------------------------------------------------------------
1 | export { Page };
2 |
3 | import { usePageContext } from "../../renderer/usePageContext";
4 |
5 | function Page() {
6 | const pageContext = usePageContext();
7 | let { abortReason } = pageContext;
8 | if (!abortReason) {
9 | abortReason = pageContext.is404
10 | ? "Page not found."
11 | : "Something went wrong.";
12 | }
13 | return (
14 |
15 | {abortReason}
16 |
17 | );
18 | }
19 |
20 | function Center({ children }: { children: React.ReactNode }) {
21 | return (
22 |
30 | {children}
31 |
32 | );
33 | }
34 |
--------------------------------------------------------------------------------
/examples/fastify-vike/pages/about/+Page.tsx:
--------------------------------------------------------------------------------
1 | import "./code.css";
2 |
3 | export { Page };
4 |
5 | function Page() {
6 | return (
7 | <>
8 | About
9 | Example of using Vike.
10 | >
11 | );
12 | }
13 |
--------------------------------------------------------------------------------
/examples/fastify-vike/pages/about/code.css:
--------------------------------------------------------------------------------
1 | code {
2 | font-family: monospace;
3 | background-color: #eaeaea;
4 | padding: 3px 5px;
5 | border-radius: 4px;
6 | }
7 |
--------------------------------------------------------------------------------
/examples/fastify-vike/pages/index/+Page.tsx:
--------------------------------------------------------------------------------
1 | export { Page };
2 |
3 | import { Counter } from "./Counter";
4 |
5 | function Page() {
6 | return (
7 | <>
8 | Hello from Vike on Fastify!
9 | This page is:
10 |
11 | - Rendered to HTML.
12 | -
13 | Interactive.
14 |
15 |
16 | >
17 | );
18 | }
19 |
--------------------------------------------------------------------------------
/examples/fastify-vike/pages/index/Counter.tsx:
--------------------------------------------------------------------------------
1 | export { Counter };
2 |
3 | import { useState } from "react";
4 |
5 | function Counter() {
6 | const [count, setCount] = useState(0);
7 | return (
8 |
11 | );
12 | }
13 |
--------------------------------------------------------------------------------
/examples/fastify-vike/pages/star-wars/@id/+Page.tsx:
--------------------------------------------------------------------------------
1 | export { Page };
2 |
3 | import { useData } from "../../../renderer/useData";
4 | import type { Data } from "./+data";
5 |
6 | function Page() {
7 | const { movie } = useData();
8 | return (
9 | <>
10 | {movie.title}
11 | Release Date: {movie.release_date}
12 |
13 | Director: {movie.director}
14 |
15 | Producer: {movie.producer}
16 | >
17 | );
18 | }
19 |
--------------------------------------------------------------------------------
/examples/fastify-vike/pages/star-wars/@id/+data.ts:
--------------------------------------------------------------------------------
1 | // https://vike.dev/data
2 | export { data };
3 | export type Data = Awaited>;
4 |
5 | import type { MovieDetails } from "../types";
6 | import type { PageContextServer } from "vike/types";
7 |
8 | const data = async (pageContext: PageContextServer) => {
9 | await sleep(300); // Simulate slow network
10 |
11 | const response = await fetch(
12 | `https://brillout.github.io/star-wars/api/films/${pageContext.routeParams!.id}.json`,
13 | );
14 | let movie = (await response.json()) as MovieDetails;
15 |
16 | // We remove data we don't need because the data is passed to the client; we should
17 | // minimize what is sent over the network.
18 | movie = minimize(movie);
19 |
20 | return {
21 | movie,
22 | // The page's
23 | title: movie.title,
24 | };
25 | };
26 |
27 | function minimize(movie: MovieDetails & Record): MovieDetails {
28 | const { id, title, release_date, director, producer } = movie;
29 | movie = { id, title, release_date, director, producer };
30 | return movie;
31 | }
32 |
33 | function sleep(milliseconds: number) {
34 | return new Promise((r) => setTimeout(r, milliseconds));
35 | }
36 |
--------------------------------------------------------------------------------
/examples/fastify-vike/pages/star-wars/index/+Page.tsx:
--------------------------------------------------------------------------------
1 | export default Page;
2 |
3 | import { useData } from "../../../renderer/useData";
4 | import type { Data } from "./+data";
5 |
6 | function Page() {
7 | const { movies } = useData();
8 | return (
9 | <>
10 | Star Wars Movies
11 |
12 | {movies.map(({ id, title, release_date }) => (
13 | -
14 | {title} ({release_date})
15 |
16 | ))}
17 |
18 |
19 | Source:{" "}
20 |
21 | brillout.github.io/star-wars
22 |
23 | .
24 |
25 |
26 | Data can be fetched by using the data()
hook.
27 |
28 | >
29 | );
30 | }
31 |
--------------------------------------------------------------------------------
/examples/fastify-vike/pages/star-wars/index/+data.ts:
--------------------------------------------------------------------------------
1 | // https://vike.dev/data
2 | export { data };
3 | export type Data = Awaited>;
4 |
5 | import type { MovieDetails, Movie } from "../types";
6 | import type { PageContextServer } from "vike/types";
7 |
8 | // eslint-disable-next-line @typescript-eslint/no-unused-vars
9 | const data = async (pageContext: PageContextServer) => {
10 | await sleep(700); // Simulate slow network
11 |
12 | const response = await fetch(
13 | "https://brillout.github.io/star-wars/api/films.json",
14 | );
15 | const moviesData = (await response.json()) as MovieDetails[];
16 |
17 | // We remove data we don't need because the data is passed to the client; we should
18 | // minimize what is sent over the network.
19 | const movies = minimize(moviesData);
20 |
21 | return {
22 | movies,
23 | // The page's
24 | title: `${movies.length} Star Wars Movies`,
25 | };
26 | };
27 |
28 | function minimize(movies: MovieDetails[]): Movie[] {
29 | return movies.map((movie) => {
30 | const { title, release_date, id } = movie;
31 | return { title, release_date, id };
32 | });
33 | }
34 |
35 | function sleep(milliseconds: number) {
36 | return new Promise((r) => setTimeout(r, milliseconds));
37 | }
38 |
--------------------------------------------------------------------------------
/examples/fastify-vike/pages/star-wars/types.ts:
--------------------------------------------------------------------------------
1 | export type Movie = {
2 | id: string;
3 | title: string;
4 | release_date: string;
5 | };
6 | export type MovieDetails = Movie & {
7 | director: string;
8 | producer: string;
9 | };
10 |
--------------------------------------------------------------------------------
/examples/fastify-vike/readme.md:
--------------------------------------------------------------------------------
1 | # Vavite Vike example with Fastify
2 |
3 | Simple example of using [Vike](https://vike.dev/) with Vavite and Fastify that shows how to:
4 |
5 | - Integrate Fastify with Vite's dev server
6 | - Run multiple build steps (for client and server)
7 | - Perform React SSR with Vike
8 |
9 | > [Try on StackBlitz](https://stackblitz.com/github/cyco130/vavite/tree/main/examples/fastify-vike)
10 |
11 | Clone with:
12 |
13 | ```bash
14 | npx degit cyco130/vavite/examples/fastify-vike
15 | ```
16 |
--------------------------------------------------------------------------------
/examples/fastify-vike/renderer/+config.ts:
--------------------------------------------------------------------------------
1 | import type { Config } from "vike/types";
2 |
3 | // https://vike.dev/config
4 | export default {
5 | // https://vike.dev/clientRouting
6 | clientRouting: true,
7 | // https://vike.dev/meta
8 | meta: {
9 | // Define new setting 'title'
10 | title: {
11 | env: { server: true, client: true },
12 | },
13 | // Define new setting 'description'
14 | description: {
15 | env: { server: true },
16 | },
17 | },
18 | hydrationCanBeAborted: true,
19 | } satisfies Config;
20 |
--------------------------------------------------------------------------------
/examples/fastify-vike/renderer/+onPageTransitionEnd.ts:
--------------------------------------------------------------------------------
1 | // https://vike.dev/onPageTransitionEnd
2 | export { onPageTransitionEnd };
3 |
4 | import type { OnPageTransitionEndAsync } from "vike/types";
5 |
6 | const onPageTransitionEnd: OnPageTransitionEndAsync =
7 | async (): ReturnType => {
8 | console.log("Page transition end");
9 | document.querySelector("body")!.classList.remove("page-is-transitioning");
10 | };
11 |
--------------------------------------------------------------------------------
/examples/fastify-vike/renderer/+onPageTransitionStart.ts:
--------------------------------------------------------------------------------
1 | // https://vike.dev/onPageTransitionStart
2 | export { onPageTransitionStart };
3 |
4 | import type { OnPageTransitionStartAsync } from "vike/types";
5 |
6 | const onPageTransitionStart: OnPageTransitionStartAsync =
7 | async (): ReturnType => {
8 | console.log("Page transition start");
9 | document.querySelector("body")!.classList.add("page-is-transitioning");
10 | };
11 |
--------------------------------------------------------------------------------
/examples/fastify-vike/renderer/+onRenderClient.tsx:
--------------------------------------------------------------------------------
1 | // https://vike.dev/onRenderClient
2 | export { onRenderClient };
3 |
4 | import ReactDOM from "react-dom/client";
5 | import { Layout } from "./Layout";
6 | import { getPageTitle } from "./getPageTitle";
7 | import type { OnRenderClientAsync } from "vike/types";
8 |
9 | let root: ReactDOM.Root;
10 | const onRenderClient: OnRenderClientAsync = async (
11 | pageContext,
12 | ): ReturnType => {
13 | const { Page } = pageContext;
14 |
15 | // This onRenderClient() hook only supports SSR, see https://vike.dev/render-modes for how to modify onRenderClient()
16 | // to support SPA
17 | if (!Page)
18 | throw new Error(
19 | "My onRenderClient() hook expects pageContext.Page to be defined",
20 | );
21 |
22 | const container = document.getElementById("react-root");
23 | if (!container) throw new Error("DOM element #react-root not found");
24 |
25 | const page = (
26 |
27 |
28 |
29 | );
30 | if (pageContext.isHydration) {
31 | root = ReactDOM.hydrateRoot(container, page);
32 | } else {
33 | if (!root) {
34 | root = ReactDOM.createRoot(container);
35 | }
36 | root.render(page);
37 | }
38 | document.title = getPageTitle(pageContext);
39 | };
40 |
--------------------------------------------------------------------------------
/examples/fastify-vike/renderer/+onRenderHtml.tsx:
--------------------------------------------------------------------------------
1 | // https://vike.dev/onRenderHtml
2 | export { onRenderHtml };
3 |
4 | import ReactDOMServer from "react-dom/server";
5 | import { Layout } from "./Layout";
6 | import { escapeInject, dangerouslySkipEscape } from "vike/server";
7 | import logoUrl from "./logo.svg";
8 | import type { OnRenderHtmlAsync } from "vike/types";
9 | import { getPageTitle } from "./getPageTitle";
10 |
11 | const onRenderHtml: OnRenderHtmlAsync = async (
12 | pageContext,
13 | ): ReturnType => {
14 | const { Page } = pageContext;
15 |
16 | // This onRenderHtml() hook only supports SSR, see https://vike.dev/render-modes for how to modify
17 | // onRenderHtml() to support SPA
18 | if (!Page)
19 | throw new Error(
20 | "My onRenderHtml() hook expects pageContext.Page to be defined",
21 | );
22 |
23 | // Alternativly, we can use an HTML stream, see https://vike.dev/streaming
24 | const pageHtml = ReactDOMServer.renderToString(
25 |
26 |
27 | ,
28 | );
29 |
30 | const title = getPageTitle(pageContext);
31 | const desc =
32 | pageContext.data?.description ||
33 | pageContext.config.description ||
34 | "Demo of using Vike";
35 |
36 | const documentHtml = escapeInject`
37 |
38 |
39 |
40 |
41 |
42 |
43 | ${title}
44 |
45 |
46 | ${dangerouslySkipEscape(pageHtml)}
47 |
48 | `;
49 |
50 | return {
51 | documentHtml,
52 | pageContext: {
53 | // We can add custom pageContext properties here, see https://vike.dev/pageContext#custom
54 | },
55 | };
56 | };
57 |
--------------------------------------------------------------------------------
/examples/fastify-vike/renderer/Layout.css:
--------------------------------------------------------------------------------
1 | #sidebar a {
2 | padding: 2px 10px;
3 | margin-left: -10px;
4 | }
5 | #sidebar a.is-active {
6 | background-color: #eee;
7 | }
8 |
--------------------------------------------------------------------------------
/examples/fastify-vike/renderer/Layout.tsx:
--------------------------------------------------------------------------------
1 | export { Layout };
2 |
3 | import React from "react";
4 | import logoUrl from "./logo.svg";
5 | import { PageContextProvider } from "./usePageContext";
6 | import { Link } from "./Link";
7 | import type { PageContext } from "vike/types";
8 | import "./css/index.css";
9 | import "./Layout.css";
10 |
11 | function Layout({
12 | children,
13 | pageContext,
14 | }: {
15 | children: React.ReactNode;
16 | pageContext: PageContext;
17 | }) {
18 | return (
19 |
20 |
21 |
22 |
23 |
24 | Welcome
25 | About
26 | Data Fetching
27 |
28 | {children}
29 |
30 |
31 |
32 | );
33 | }
34 |
35 | function Frame({ children }: { children: React.ReactNode }) {
36 | return (
37 |
44 | {children}
45 |
46 | );
47 | }
48 |
49 | function Sidebar({ children }: { children: React.ReactNode }) {
50 | return (
51 |
64 | );
65 | }
66 |
67 | function Content({ children }: { children: React.ReactNode }) {
68 | return (
69 |
70 |
78 | {children}
79 |
80 |
81 | );
82 | }
83 |
84 | function Logo() {
85 | return (
86 |
96 | );
97 | }
98 |
--------------------------------------------------------------------------------
/examples/fastify-vike/renderer/Link.tsx:
--------------------------------------------------------------------------------
1 | import { usePageContext } from "./usePageContext";
2 |
3 | export { Link };
4 |
5 | function Link(props: {
6 | href: string;
7 | className?: string;
8 | children: React.ReactNode;
9 | }) {
10 | const pageContext = usePageContext();
11 | const { urlPathname } = pageContext;
12 | const { href } = props;
13 | const isActive =
14 | href === "/" ? urlPathname === href : urlPathname.startsWith(href);
15 | const className = [props.className, isActive && "is-active"]
16 | .filter(Boolean)
17 | .join(" ");
18 | return ;
19 | }
20 |
--------------------------------------------------------------------------------
/examples/fastify-vike/renderer/css/code.css:
--------------------------------------------------------------------------------
1 | code {
2 | font-family: monospace;
3 | background-color: #eaeaea;
4 | padding: 3px 5px;
5 | border-radius: 4px;
6 | }
7 |
--------------------------------------------------------------------------------
/examples/fastify-vike/renderer/css/index.css:
--------------------------------------------------------------------------------
1 | @import "./reset.css";
2 | @import "./links.css";
3 | @import "./code.css";
4 | @import "./page-transition-loading-animation.css";
5 |
--------------------------------------------------------------------------------
/examples/fastify-vike/renderer/css/links.css:
--------------------------------------------------------------------------------
1 | a {
2 | text-decoration: none;
3 | }
4 |
--------------------------------------------------------------------------------
/examples/fastify-vike/renderer/css/page-transition-loading-animation.css:
--------------------------------------------------------------------------------
1 | #page-container {
2 | position: relative;
3 | width: 100%;
4 | }
5 | #page-container::before {
6 | content: "";
7 | position: absolute;
8 | width: 100%;
9 | height: 100%;
10 | z-index: 999;
11 | background: no-repeat url("./page-transition-loading-animation/loading.svg");
12 | background-size: 100px;
13 | background-position: center center;
14 | pointer-events: none;
15 | opacity: 0;
16 | }
17 | body.page-is-transitioning #page-container::before {
18 | opacity: 1;
19 | }
20 | #page-content,
21 | #page-container::before {
22 | transition: opacity 0.5s ease-in-out;
23 | }
24 | body.page-is-transitioning #page-content {
25 | opacity: 0.17;
26 | }
27 |
--------------------------------------------------------------------------------
/examples/fastify-vike/renderer/css/page-transition-loading-animation/loading.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/examples/fastify-vike/renderer/css/reset.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: sans-serif;
4 | }
5 | * {
6 | box-sizing: border-box;
7 | }
8 |
--------------------------------------------------------------------------------
/examples/fastify-vike/renderer/getPageTitle.ts:
--------------------------------------------------------------------------------
1 | export { getPageTitle };
2 |
3 | import type { PageContext } from "vike/types";
4 |
5 | function getPageTitle(pageContext: PageContext): string {
6 | const title =
7 | // Title defined dynamically by data()
8 | pageContext.data?.title ||
9 | // Title defined statically by /pages/some-page/+title.js (or by `export default { title }` in /pages/some-page/+config.js)
10 | // The setting 'pageContext.config.title' is a custom setting we defined at ./+config.ts
11 | pageContext.config.title ||
12 | "Vike Demo";
13 | return title;
14 | }
15 |
--------------------------------------------------------------------------------
/examples/fastify-vike/renderer/types.ts:
--------------------------------------------------------------------------------
1 | // https://vike.dev/pageContext#typescript
2 | declare global {
3 | namespace Vike {
4 | interface PageContext {
5 | Page: () => React.ReactElement;
6 | data?: {
7 | /** Value for defined dynmically by by /pages/some-page/+data.js */
8 | title?: string;
9 | /** Value for defined dynmically */
10 | description?: string;
11 | };
12 | config: {
13 | /** Value for defined statically by /pages/some-page/+title.js (or by `export default { title }` in /pages/some-page/+config.js) */
14 | title?: string;
15 | /** Value for defined statically */
16 | description?: string;
17 | };
18 | /** https://vike.dev/render */
19 | abortReason?: string;
20 | }
21 | }
22 | }
23 |
24 | // Tell TypeScript this file isn't an ambient module
25 | export {};
26 |
--------------------------------------------------------------------------------
/examples/fastify-vike/renderer/useData.ts:
--------------------------------------------------------------------------------
1 | // https://vike.dev/useData
2 | export { useData };
3 |
4 | import { usePageContext } from "./usePageContext";
5 |
6 | /** https://vike.dev/useData */
7 | function useData() {
8 | const { data } = usePageContext();
9 | return data as Data;
10 | }
11 |
--------------------------------------------------------------------------------
/examples/fastify-vike/renderer/usePageContext.tsx:
--------------------------------------------------------------------------------
1 | // https://vike.dev/usePageContext
2 | // eslint-disable-next-line react-refresh/only-export-components
3 | export { usePageContext };
4 | export { PageContextProvider };
5 |
6 | import React, { useContext } from "react";
7 | import type { PageContext } from "vike/types";
8 |
9 | const Context = React.createContext(
10 | undefined as unknown as PageContext,
11 | );
12 |
13 | function PageContextProvider({
14 | pageContext,
15 | children,
16 | }: {
17 | pageContext: PageContext;
18 | children: React.ReactNode;
19 | }) {
20 | return {children};
21 | }
22 |
23 | /** https://vike.dev/usePageContext */
24 | function usePageContext() {
25 | const pageContext = useContext(Context);
26 | return pageContext;
27 | }
28 |
--------------------------------------------------------------------------------
/examples/fastify-vike/server/index.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | import viteDevServer from "vavite/http-dev-server";
4 | import Fastify, { FastifyInstance } from "fastify";
5 | import FastifyStatic from "@fastify/static";
6 | import { renderPage } from "vike/server";
7 | import { fileURLToPath } from "node:url";
8 | import { IncomingMessage, ServerResponse } from "node:http";
9 |
10 | async function startServer() {
11 | const instance = Fastify({});
12 |
13 | if (!viteDevServer) {
14 | await instance.register(FastifyStatic, {
15 | root: fileURLToPath(new URL("../client/assets", import.meta.url)),
16 | prefix: "/assets/",
17 | });
18 | }
19 |
20 | instance.get("*", async (request, reply) => {
21 | const pageContext = await renderPage({ urlOriginal: request.url });
22 | const { httpResponse } = pageContext;
23 | if (!httpResponse) return;
24 |
25 | const { body, statusCode, headers } = httpResponse;
26 | headers.forEach(([name, value]) => reply.header(name, value));
27 | reply.status(statusCode);
28 | reply.send(body);
29 | });
30 |
31 | await instance.ready();
32 |
33 | fastify = instance;
34 | }
35 |
36 | let fastify: FastifyInstance | undefined;
37 | const fastifyHandlerPromise = startServer().catch((error) => {
38 | console.error(error);
39 | process.exit(1);
40 | });
41 |
42 | export default async function handler(
43 | request: IncomingMessage,
44 | reply: ServerResponse,
45 | ) {
46 | if (!fastify) {
47 | await fastifyHandlerPromise;
48 | }
49 |
50 | fastify!.server.emit("request", request, reply);
51 | }
52 |
--------------------------------------------------------------------------------
/examples/fastify-vike/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "strict": true,
4 | "module": "ES2020",
5 | "moduleResolution": "Bundler",
6 | "customConditions": ["import"],
7 | "target": "ES2017",
8 | "lib": ["DOM", "DOM.Iterable", "ESNext"],
9 | "jsx": "react-jsx",
10 | "esModuleInterop": true,
11 | "skipLibCheck": true
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/examples/fastify-vike/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "vite";
2 | import react from "@vitejs/plugin-react";
3 | import ssr from "vike/plugin";
4 | import { vavite } from "vavite";
5 |
6 | export default defineConfig({
7 | buildSteps: [
8 | { name: "client" },
9 | {
10 | name: "server",
11 | config: {
12 | build: { ssr: true },
13 | },
14 | },
15 | ],
16 |
17 | plugins: [
18 | vavite({
19 | handlerEntry: "/server/index.ts",
20 | serveClientAssetsInDev: true,
21 | }),
22 | react(),
23 | ssr({ disableAutoFullBuild: true }),
24 | ],
25 | });
26 |
--------------------------------------------------------------------------------
/examples/fastify/.stackblitzrc:
--------------------------------------------------------------------------------
1 | {
2 | "installDependencies": true,
3 | "startCommand": "npm run dev"
4 | }
5 |
--------------------------------------------------------------------------------
/examples/fastify/handler.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | import Fastify, { RouteHandlerMethod } from "fastify";
4 | import viteDevServer from "vavite/vite-dev-server";
5 | import { IncomingMessage, ServerResponse } from "node:http";
6 |
7 | const fastify = Fastify({
8 | // Your Fastify options go here.
9 | });
10 |
11 | // This is an optional optimization to load routes lazily so that
12 | // when reloadOn option is set to "static-deps-change",
13 | // changes to the route handlers will not trigger a reload.
14 | // Feel free to remove this and import routes directly.
15 | function lazy(
16 | importer: () => Promise<{ default: RouteHandlerMethod }>,
17 | ): RouteHandlerMethod {
18 | return async (req, res) => {
19 | try {
20 | const routeHandler = (await importer()).default;
21 | return routeHandler.bind(fastify)(req, res);
22 | } catch (err) {
23 | if (err instanceof Error) viteDevServer?.ssrFixStacktrace(err);
24 | throw err;
25 | }
26 | };
27 | }
28 |
29 | fastify.get(
30 | "/",
31 | lazy(() => import("./routes/home")),
32 | );
33 |
34 | fastify.get(
35 | "/foo",
36 | lazy(() => import("./routes/foo")),
37 | );
38 |
39 | fastify.get(
40 | "/bar",
41 | lazy(() => import("./routes/bar")),
42 | );
43 |
44 | let fastifyReadyPromise: PromiseLike | undefined = fastify.ready();
45 |
46 | export default async function handler(
47 | request: IncomingMessage,
48 | reply: ServerResponse,
49 | ) {
50 | if (fastifyReadyPromise) {
51 | await fastifyReadyPromise;
52 | fastifyReadyPromise = undefined;
53 | }
54 |
55 | fastify.server.emit("request", request, reply);
56 | }
57 |
--------------------------------------------------------------------------------
/examples/fastify/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@vavite/example-reloader-fastify",
3 | "type": "module",
4 | "private": true,
5 | "scripts": {
6 | "start": "node dist",
7 | "dev": "vite",
8 | "build": "vite build --ssr --mode=production"
9 | },
10 | "files": [
11 | "dist"
12 | ],
13 | "devDependencies": {
14 | "@types/node": "^18.19.67",
15 | "typescript": "^5.7.2",
16 | "vavite": "5.1.0",
17 | "vite": "^5.4.11"
18 | },
19 | "dependencies": {
20 | "fastify": "^5.1.0"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/examples/fastify/readme.md:
--------------------------------------------------------------------------------
1 | # Vavite Fastify example
2 |
3 | `handlerEntry` example that shows how to integrate with [Fastify](https://fastify.io/).
4 |
5 | > [Try on StackBlitz](https://stackblitz.com/github/cyco130/vavite/tree/main/examples/fastify)
6 |
7 | Clone with:
8 |
9 | ```bash
10 | npx degit cyco130/vavite/examples/fastify
11 | ```
12 |
--------------------------------------------------------------------------------
/examples/fastify/routes/bar.ts:
--------------------------------------------------------------------------------
1 | import { RouteHandlerMethod } from "fastify";
2 | import viteDevServer from "vavite/vite-dev-server";
3 | import nav from "./nav";
4 |
5 | const barRoute: RouteHandlerMethod = async (req, res) => {
6 | let html = "Hello from page /bar
" + nav;
7 |
8 | if (viteDevServer) {
9 | html = await viteDevServer.transformIndexHtml(req.url, html);
10 | }
11 |
12 | res.type("text/html");
13 | res.send(html);
14 | };
15 |
16 | export default barRoute;
17 |
--------------------------------------------------------------------------------
/examples/fastify/routes/foo.ts:
--------------------------------------------------------------------------------
1 | import { RouteHandlerMethod } from "fastify";
2 | import viteDevServer from "vavite/vite-dev-server";
3 | import nav from "./nav";
4 |
5 | const fooRoute: RouteHandlerMethod = async (req, res) => {
6 | let html = "Hello from page /foo
" + nav;
7 |
8 | if (viteDevServer) {
9 | html = await viteDevServer.transformIndexHtml(req.url, html);
10 | }
11 |
12 | res.type("text/html");
13 | res.send(html);
14 | };
15 |
16 | export default fooRoute;
17 |
--------------------------------------------------------------------------------
/examples/fastify/routes/home.ts:
--------------------------------------------------------------------------------
1 | import { RouteHandlerMethod } from "fastify";
2 | import viteDevServer from "vavite/vite-dev-server";
3 | import nav from "./nav";
4 |
5 | const homeRoute: RouteHandlerMethod = async (req, res) => {
6 | let html = "Hello from home page
" + nav;
7 |
8 | if (viteDevServer) {
9 | html = await viteDevServer.transformIndexHtml(req.url, html);
10 | }
11 |
12 | res.type("text/html");
13 | res.send(html);
14 | };
15 |
16 | export default homeRoute;
17 |
--------------------------------------------------------------------------------
/examples/fastify/routes/nav.ts:
--------------------------------------------------------------------------------
1 | export default `
2 |
9 | `;
10 |
--------------------------------------------------------------------------------
/examples/fastify/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es2020",
4 | "module": "ESNext",
5 | "esModuleInterop": true,
6 | "forceConsistentCasingInFileNames": true,
7 | "strict": true,
8 | "skipLibCheck": true,
9 | "moduleResolution": "Bundler",
10 | "customConditions": ["import"]
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/examples/fastify/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "vite";
2 | import { vavite } from "vavite";
3 |
4 | export default defineConfig({
5 | plugins: [vavite({ handlerEntry: "/handler.ts" })],
6 | });
7 |
--------------------------------------------------------------------------------
/examples/hapi/.stackblitzrc:
--------------------------------------------------------------------------------
1 | {
2 | "installDependencies": true,
3 | "startCommand": "npm run dev"
4 | }
5 |
--------------------------------------------------------------------------------
/examples/hapi/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@vavite/example-hapi",
3 | "type": "module",
4 | "private": true,
5 | "scripts": {
6 | "start": "node dist",
7 | "dev": "vite",
8 | "build": "vite build --ssr --mode=production"
9 | },
10 | "files": [
11 | "dist"
12 | ],
13 | "devDependencies": {
14 | "@types/node": "^18.19.67",
15 | "typescript": "^5.7.2",
16 | "vavite": "5.1.0",
17 | "vite": "^5.4.11"
18 | },
19 | "dependencies": {
20 | "@hapi/hapi": "^21.3.12"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/examples/hapi/readme.md:
--------------------------------------------------------------------------------
1 | # Vavite Hapi example
2 |
3 | `serverEntry` example that shows how to integrate with [Hapi](https://hapijs.com).
4 |
5 | > [Try on StackBlitz](https://stackblitz.com/github/cyco130/vavite/tree/main/examples/hapi)
6 |
7 | Clone with:
8 |
9 | ```bash
10 | npx degit cyco130/vavite/examples/hapi
11 | ```
12 |
--------------------------------------------------------------------------------
/examples/hapi/routes/bar.ts:
--------------------------------------------------------------------------------
1 | import { Lifecycle } from "@hapi/hapi";
2 | import viteDevServer from "vavite/vite-dev-server";
3 | import nav from "./nav";
4 |
5 | const barRoute: Lifecycle.Method = async function (req, h) {
6 | let html = "Hello from page /bar
" + nav;
7 |
8 | if (viteDevServer) {
9 | html = await viteDevServer.transformIndexHtml(req.path, html);
10 | }
11 |
12 | return html;
13 | };
14 |
15 | export default barRoute;
16 |
--------------------------------------------------------------------------------
/examples/hapi/routes/foo.ts:
--------------------------------------------------------------------------------
1 | import { Lifecycle } from "@hapi/hapi";
2 | import viteDevServer from "vavite/vite-dev-server";
3 | import nav from "./nav";
4 |
5 | const fooRoute: Lifecycle.Method = async function (req, h) {
6 | let html = "Hello from page /foo
" + nav;
7 |
8 | if (viteDevServer) {
9 | html = await viteDevServer.transformIndexHtml(req.path, html);
10 | }
11 |
12 | return html;
13 | };
14 |
15 | export default fooRoute;
16 |
--------------------------------------------------------------------------------
/examples/hapi/routes/home.ts:
--------------------------------------------------------------------------------
1 | import { Lifecycle } from "@hapi/hapi";
2 | import viteDevServer from "vavite/vite-dev-server";
3 | import nav from "./nav";
4 |
5 | const homeRoute: Lifecycle.Method = async (req, h) => {
6 | let html = "Hello from home page
" + nav;
7 |
8 | if (viteDevServer) {
9 | html = await viteDevServer.transformIndexHtml(req.path, html);
10 | }
11 |
12 | return html;
13 | };
14 |
15 | export default homeRoute;
16 |
--------------------------------------------------------------------------------
/examples/hapi/routes/nav.ts:
--------------------------------------------------------------------------------
1 | export default `
2 |
9 | `;
10 |
--------------------------------------------------------------------------------
/examples/hapi/server.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | import Hapi, { Lifecycle } from "@hapi/hapi";
4 | import viteDevServer from "vavite/vite-dev-server";
5 | import { IncomingMessage, ServerResponse } from "node:http";
6 |
7 | // This is an optional optimization to load routes lazily so that
8 | // when reloadOn option is set to "static-deps-change",
9 | // changes to the route handlers will not trigger a reload.
10 | // Feel free to remove this and import routes directly.
11 | function lazy(
12 | importer: () => Promise<{ default: Lifecycle.Method }>,
13 | ): Lifecycle.Method {
14 | return async function (req, h) {
15 | try {
16 | const routeHandler = (await importer()).default;
17 | return routeHandler.call(this, req, h);
18 | } catch (err) {
19 | if (err instanceof Error) viteDevServer?.ssrFixStacktrace(err);
20 | throw err;
21 | }
22 | };
23 | }
24 |
25 | let server: Hapi.Server;
26 |
27 | server = Hapi.server({
28 | autoListen: false,
29 | });
30 |
31 | server.route({
32 | method: "GET",
33 | path: "/",
34 | handler: lazy(() => import("./routes/home")),
35 | });
36 |
37 | server.route({
38 | method: "GET",
39 | path: "/foo",
40 | handler: lazy(() => import("./routes/foo")),
41 | });
42 |
43 | server.route({
44 | method: "GET",
45 | path: "/bar",
46 | handler: lazy(() => import("./routes/bar")),
47 | });
48 |
49 | let promise: Promise | undefined = server.start();
50 |
51 | export default async function handler(
52 | req: IncomingMessage,
53 | res: ServerResponse,
54 | ) {
55 | if (promise) {
56 | await promise;
57 | promise = undefined;
58 | }
59 |
60 | server.listener.emit("request", req, res);
61 | }
62 |
--------------------------------------------------------------------------------
/examples/hapi/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es2020",
4 | "module": "ESNext",
5 | "esModuleInterop": true,
6 | "forceConsistentCasingInFileNames": true,
7 | "strict": true,
8 | "skipLibCheck": true,
9 | "moduleResolution": "Bundler",
10 | "customConditions": ["import"]
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/examples/hapi/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "vite";
2 | import { vavite } from "vavite";
3 |
4 | export default defineConfig({
5 | plugins: [
6 | vavite({
7 | handlerEntry: "/server.ts",
8 | }),
9 | ],
10 | });
11 |
--------------------------------------------------------------------------------
/examples/koa/.stackblitzrc:
--------------------------------------------------------------------------------
1 | {
2 | "installDependencies": true,
3 | "startCommand": "npm run dev"
4 | }
5 |
--------------------------------------------------------------------------------
/examples/koa/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@vavite/example-koa",
3 | "type": "module",
4 | "private": true,
5 | "scripts": {
6 | "start": "node dist",
7 | "dev": "vite",
8 | "build": "vite build --ssr --mode=production"
9 | },
10 | "files": [
11 | "dist"
12 | ],
13 | "devDependencies": {
14 | "@types/koa": "^2.15.0",
15 | "@types/koa__router": "^12.0.4",
16 | "@types/node": "^18.19.67",
17 | "typescript": "^5.7.2",
18 | "vavite": "5.1.0",
19 | "vite": "^5.4.11"
20 | },
21 | "dependencies": {
22 | "@koa/router": "^13.1.0",
23 | "koa": "^2.15.3"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/examples/koa/readme.md:
--------------------------------------------------------------------------------
1 | # Vavite Koa example
2 |
3 | `serverEntry` example that shows how to integrate with [Koa](https://koajs.com/).
4 |
5 | > [Try on StackBlitz](https://stackblitz.com/github/cyco130/vavite/tree/main/examples/koa)
6 |
7 | Clone with:
8 |
9 | ```bash
10 | npx degit cyco130/vavite/examples/koa
11 | ```
12 |
--------------------------------------------------------------------------------
/examples/koa/routes/bar.ts:
--------------------------------------------------------------------------------
1 | import { Middleware } from "koa";
2 | import viteDevServer from "vavite/vite-dev-server";
3 | import nav from "./nav";
4 |
5 | const barRoute: Middleware = async (ctx, next) => {
6 | let html = "Hello from page /bar
" + nav;
7 |
8 | if (viteDevServer) {
9 | html = await viteDevServer.transformIndexHtml(ctx.url, html);
10 | }
11 |
12 | ctx.body = html;
13 | };
14 |
15 | export default barRoute;
16 |
--------------------------------------------------------------------------------
/examples/koa/routes/foo.ts:
--------------------------------------------------------------------------------
1 | import { Middleware } from "koa";
2 | import viteDevServer from "vavite/vite-dev-server";
3 | import nav from "./nav";
4 |
5 | const fooRoute: Middleware = async (ctx, next) => {
6 | let html = "Hello from page /foo
" + nav;
7 |
8 | if (viteDevServer) {
9 | html = await viteDevServer.transformIndexHtml(ctx.url, html);
10 | }
11 |
12 | ctx.body = html;
13 | };
14 |
15 | export default fooRoute;
16 |
--------------------------------------------------------------------------------
/examples/koa/routes/home.ts:
--------------------------------------------------------------------------------
1 | import { Middleware } from "koa";
2 | import viteDevServer from "vavite/vite-dev-server";
3 | import nav from "./nav";
4 |
5 | const homeRoute: Middleware = async (ctx, next) => {
6 | let html = "Hello from home page
" + nav;
7 |
8 | if (viteDevServer) {
9 | html = await viteDevServer.transformIndexHtml(ctx.url, html);
10 | }
11 |
12 | ctx.body = html;
13 | };
14 |
15 | export default homeRoute;
16 |
--------------------------------------------------------------------------------
/examples/koa/routes/nav.ts:
--------------------------------------------------------------------------------
1 | export default `
2 |
9 | `;
10 |
--------------------------------------------------------------------------------
/examples/koa/server.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | import Koa, { Middleware } from "koa";
4 | import Router from "@koa/router";
5 | import httpDevServer from "vavite/http-dev-server";
6 | import viteDevServer from "vavite/vite-dev-server";
7 |
8 | const app = new Koa();
9 | const router = new Router();
10 |
11 | // This is an optional optimization to load routes lazily so that
12 | // when reloadOn option is set to "static-deps-change",
13 | // changes to the route handlers will not trigger a reload.
14 | // Feel free to remove this and import routes directly.
15 | function lazy(importer: () => Promise<{ default: Middleware }>): Middleware {
16 | return async (ctx, next) => {
17 | try {
18 | const routeHandler = (await importer()).default;
19 | return routeHandler(ctx, next);
20 | } catch (err) {
21 | if (err instanceof Error) viteDevServer?.ssrFixStacktrace(err);
22 | throw err;
23 | }
24 | };
25 | }
26 |
27 | router.get(
28 | "/",
29 | lazy(() => import("./routes/home")),
30 | );
31 |
32 | router.get(
33 | "/foo",
34 | lazy(() => import("./routes/foo")),
35 | );
36 |
37 | router.get(
38 | "/bar",
39 | lazy(() => import("./routes/bar")),
40 | );
41 |
42 | app.use(router.routes());
43 |
44 | if (httpDevServer) {
45 | httpDevServer.on("request", app.callback());
46 | } else {
47 | console.log("Starting prod server");
48 | app.listen(3000);
49 | }
50 |
--------------------------------------------------------------------------------
/examples/koa/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es2020",
4 | "module": "ESNext",
5 | "esModuleInterop": true,
6 | "forceConsistentCasingInFileNames": true,
7 | "strict": true,
8 | "skipLibCheck": true,
9 | "moduleResolution": "Bundler",
10 | "customConditions": ["import"]
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/examples/koa/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "vite";
2 | import { vavite } from "vavite";
3 |
4 | export default defineConfig({
5 | plugins: [
6 | vavite({
7 | serverEntry: "/server.ts",
8 | reloadOn: "static-deps-change",
9 | serveClientAssetsInDev: true,
10 | }),
11 | ],
12 | });
13 |
--------------------------------------------------------------------------------
/examples/nestjs-vike/index.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | import viteDevServer from "vavite/http-dev-server";
4 | import Fastify, { FastifyInstance } from "fastify";
5 | import FastifyStatic from "@fastify/static";
6 | import { renderPage } from "vike/server";
7 | import { fileURLToPath } from "node:url";
8 | import { IncomingMessage, ServerResponse } from "node:http";
9 |
10 | async function startServer() {
11 | const instance = Fastify({});
12 |
13 | if (!viteDevServer) {
14 | await instance.register(FastifyStatic, {
15 | root: fileURLToPath(new URL("../client/assets", import.meta.url)),
16 | prefix: "/assets/",
17 | });
18 | }
19 |
20 | instance.get("*", async (request, reply) => {
21 | const pageContext = await renderPage({ urlOriginal: request.url });
22 | const { httpResponse } = pageContext;
23 | if (!httpResponse) return;
24 |
25 | const { body, statusCode, headers } = httpResponse;
26 | headers.forEach(([name, value]) => reply.header(name, value));
27 | reply.status(statusCode);
28 | reply.send(body);
29 | });
30 |
31 | await instance.ready();
32 |
33 | fastify = instance;
34 | }
35 |
36 | let fastify: FastifyInstance | undefined;
37 | const fastifyHandlerPromise = startServer().catch((error) => {
38 | console.error(error);
39 | process.exit(1);
40 | });
41 |
42 | export default async function handler(
43 | request: IncomingMessage,
44 | reply: ServerResponse,
45 | ) {
46 | if (!fastify) {
47 | await fastifyHandlerPromise;
48 | }
49 |
50 | fastify!.server.emit("request", request, reply);
51 | }
52 |
--------------------------------------------------------------------------------
/examples/nestjs-vike/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@vavite/example-nestjs-vike",
3 | "private": true,
4 | "type": "module",
5 | "scripts": {
6 | "dev": "vite optimize --force && vavite serve",
7 | "build": "vavite build",
8 | "start": "cross-env NODE_ENV=production node dist/server/index.mjs"
9 | },
10 | "files": [
11 | "dist"
12 | ],
13 | "devDependencies": {
14 | "@swc/core": "^1.9.3",
15 | "@types/express": "^5.0.0",
16 | "@types/node": "^18.19.67",
17 | "@types/react": "^18.3.12",
18 | "@types/react-dom": "^18.3.1",
19 | "@vitejs/plugin-react": "^4.3.4",
20 | "cross-env": "^7.0.3",
21 | "rollup-plugin-swc3": "^0.12.1",
22 | "typescript": "^5.7.2",
23 | "vavite": "5.1.0",
24 | "vite": "^5.4.11",
25 | "vite-tsconfig-paths": "^5.1.3"
26 | },
27 | "dependencies": {
28 | "@nestjs/common": "^10.4.12",
29 | "@nestjs/core": "^10.4.12",
30 | "@nestjs/platform-express": "^10.4.12",
31 | "@nestjs/serve-static": "^4.0.2",
32 | "react": "^18.3.1",
33 | "react-dom": "^18.3.1",
34 | "react-streaming": "0.3.43",
35 | "reflect-metadata": "^0.2.2",
36 | "rxjs": "^7.8.1",
37 | "vike": "^0.4.205"
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/examples/nestjs-vike/pages/_error/+Page.tsx:
--------------------------------------------------------------------------------
1 | export { Page };
2 |
3 | import { usePageContext } from "../../renderer/usePageContext";
4 |
5 | function Page() {
6 | const pageContext = usePageContext();
7 | let { abortReason } = pageContext;
8 | if (!abortReason) {
9 | abortReason = pageContext.is404
10 | ? "Page not found."
11 | : "Something went wrong.";
12 | }
13 | return (
14 |
15 | {abortReason}
16 |
17 | );
18 | }
19 |
20 | function Center({ children }: { children: React.ReactNode }) {
21 | return (
22 |
30 | {children}
31 |
32 | );
33 | }
34 |
--------------------------------------------------------------------------------
/examples/nestjs-vike/pages/about/+Page.tsx:
--------------------------------------------------------------------------------
1 | import "./code.css";
2 |
3 | export { Page };
4 |
5 | function Page() {
6 | return (
7 | <>
8 | About
9 | Example of using Vike.
10 | >
11 | );
12 | }
13 |
--------------------------------------------------------------------------------
/examples/nestjs-vike/pages/about/code.css:
--------------------------------------------------------------------------------
1 | code {
2 | font-family: monospace;
3 | background-color: #eaeaea;
4 | padding: 3px 5px;
5 | border-radius: 4px;
6 | }
7 |
--------------------------------------------------------------------------------
/examples/nestjs-vike/pages/index/+Page.tsx:
--------------------------------------------------------------------------------
1 | export { Page };
2 |
3 | import { Counter } from "./Counter";
4 |
5 | function Page() {
6 | return (
7 | <>
8 | Hello from Vike on NestJS!
9 | This page is:
10 |
11 | - Rendered to HTML.
12 | -
13 | Interactive.
14 |
15 |
16 | >
17 | );
18 | }
19 |
--------------------------------------------------------------------------------
/examples/nestjs-vike/pages/index/Counter.tsx:
--------------------------------------------------------------------------------
1 | export { Counter };
2 |
3 | import { useState } from "react";
4 |
5 | function Counter() {
6 | const [count, setCount] = useState(0);
7 | return (
8 |
11 | );
12 | }
13 |
--------------------------------------------------------------------------------
/examples/nestjs-vike/pages/star-wars/@id/+Page.tsx:
--------------------------------------------------------------------------------
1 | export { Page };
2 |
3 | import { useData } from "../../../renderer/useData";
4 | import type { Data } from "./+data";
5 |
6 | function Page() {
7 | const { movie } = useData();
8 | return (
9 | <>
10 | {movie.title}
11 | Release Date: {movie.release_date}
12 |
13 | Director: {movie.director}
14 |
15 | Producer: {movie.producer}
16 | >
17 | );
18 | }
19 |
--------------------------------------------------------------------------------
/examples/nestjs-vike/pages/star-wars/@id/+data.ts:
--------------------------------------------------------------------------------
1 | // https://vike.dev/data
2 | export { data };
3 | export type Data = Awaited>;
4 |
5 | import type { MovieDetails } from "../types";
6 | import type { PageContextServer } from "vike/types";
7 |
8 | const data = async (pageContext: PageContextServer) => {
9 | await sleep(300); // Simulate slow network
10 |
11 | const response = await fetch(
12 | `https://brillout.github.io/star-wars/api/films/${pageContext.routeParams!.id}.json`,
13 | );
14 | let movie = (await response.json()) as MovieDetails;
15 |
16 | // We remove data we don't need because the data is passed to the client; we should
17 | // minimize what is sent over the network.
18 | movie = minimize(movie);
19 |
20 | return {
21 | movie,
22 | // The page's
23 | title: movie.title,
24 | };
25 | };
26 |
27 | function minimize(movie: MovieDetails & Record): MovieDetails {
28 | const { id, title, release_date, director, producer } = movie;
29 | movie = { id, title, release_date, director, producer };
30 | return movie;
31 | }
32 |
33 | function sleep(milliseconds: number) {
34 | return new Promise((r) => setTimeout(r, milliseconds));
35 | }
36 |
--------------------------------------------------------------------------------
/examples/nestjs-vike/pages/star-wars/index/+Page.tsx:
--------------------------------------------------------------------------------
1 | export default Page;
2 |
3 | import { useData } from "../../../renderer/useData";
4 | import type { Data } from "./+data";
5 |
6 | function Page() {
7 | const { movies } = useData();
8 | return (
9 | <>
10 | Star Wars Movies
11 |
12 | {movies.map(({ id, title, release_date }) => (
13 | -
14 | {title} ({release_date})
15 |
16 | ))}
17 |
18 |
19 | Source:{" "}
20 |
21 | brillout.github.io/star-wars
22 |
23 | .
24 |
25 |
26 | Data can be fetched by using the data()
hook.
27 |
28 | >
29 | );
30 | }
31 |
--------------------------------------------------------------------------------
/examples/nestjs-vike/pages/star-wars/index/+data.ts:
--------------------------------------------------------------------------------
1 | // https://vike.dev/data
2 | export { data };
3 | export type Data = Awaited>;
4 |
5 | import type { MovieDetails, Movie } from "../types";
6 | import type { PageContextServer } from "vike/types";
7 |
8 | // eslint-disable-next-line @typescript-eslint/no-unused-vars
9 | const data = async (pageContext: PageContextServer) => {
10 | await sleep(700); // Simulate slow network
11 |
12 | const response = await fetch(
13 | "https://brillout.github.io/star-wars/api/films.json",
14 | );
15 | const moviesData = (await response.json()) as MovieDetails[];
16 |
17 | // We remove data we don't need because the data is passed to the client; we should
18 | // minimize what is sent over the network.
19 | const movies = minimize(moviesData);
20 |
21 | return {
22 | movies,
23 | // The page's
24 | title: `${movies.length} Star Wars Movies`,
25 | };
26 | };
27 |
28 | function minimize(movies: MovieDetails[]): Movie[] {
29 | return movies.map((movie) => {
30 | const { title, release_date, id } = movie;
31 | return { title, release_date, id };
32 | });
33 | }
34 |
35 | function sleep(milliseconds: number) {
36 | return new Promise((r) => setTimeout(r, milliseconds));
37 | }
38 |
--------------------------------------------------------------------------------
/examples/nestjs-vike/pages/star-wars/types.ts:
--------------------------------------------------------------------------------
1 | export type Movie = {
2 | id: string;
3 | title: string;
4 | release_date: string;
5 | };
6 | export type MovieDetails = Movie & {
7 | director: string;
8 | producer: string;
9 | };
10 |
--------------------------------------------------------------------------------
/examples/nestjs-vike/readme.md:
--------------------------------------------------------------------------------
1 | # Vavite Vike example with NestJS
2 |
3 | Simple example of using [Vike](https://vike.dev/) with Vavite and [NestJS](https://nestjs.com/) that shows how to:
4 |
5 | - Integrate NestJS with Vite's dev server
6 | - Run multiple build steps (for client and server)
7 | - Perform React SSR with Vike
8 |
9 | Clone with:
10 |
11 | ```bash
12 | npx degit cyco130/vavite/examples/nestjs-vike
13 | ```
14 |
--------------------------------------------------------------------------------
/examples/nestjs-vike/renderer/+config.ts:
--------------------------------------------------------------------------------
1 | import type { Config } from "vike/types";
2 |
3 | // https://vike.dev/config
4 | export default {
5 | // https://vike.dev/clientRouting
6 | clientRouting: true,
7 | // https://vike.dev/meta
8 | meta: {
9 | // Define new setting 'title'
10 | title: {
11 | env: { server: true, client: true },
12 | },
13 | // Define new setting 'description'
14 | description: {
15 | env: { server: true },
16 | },
17 | },
18 | hydrationCanBeAborted: true,
19 | } satisfies Config;
20 |
--------------------------------------------------------------------------------
/examples/nestjs-vike/renderer/+onPageTransitionEnd.ts:
--------------------------------------------------------------------------------
1 | // https://vike.dev/onPageTransitionEnd
2 | export { onPageTransitionEnd };
3 |
4 | import type { OnPageTransitionEndAsync } from "vike/types";
5 |
6 | const onPageTransitionEnd: OnPageTransitionEndAsync =
7 | async (): ReturnType => {
8 | console.log("Page transition end");
9 | document.querySelector("body")!.classList.remove("page-is-transitioning");
10 | };
11 |
--------------------------------------------------------------------------------
/examples/nestjs-vike/renderer/+onPageTransitionStart.ts:
--------------------------------------------------------------------------------
1 | // https://vike.dev/onPageTransitionStart
2 | export { onPageTransitionStart };
3 |
4 | import type { OnPageTransitionStartAsync } from "vike/types";
5 |
6 | const onPageTransitionStart: OnPageTransitionStartAsync =
7 | async (): ReturnType => {
8 | console.log("Page transition start");
9 | document.querySelector("body")!.classList.add("page-is-transitioning");
10 | };
11 |
--------------------------------------------------------------------------------
/examples/nestjs-vike/renderer/+onRenderClient.tsx:
--------------------------------------------------------------------------------
1 | // https://vike.dev/onRenderClient
2 | export { onRenderClient };
3 |
4 | import ReactDOM from "react-dom/client";
5 | import { Layout } from "./Layout";
6 | import { getPageTitle } from "./getPageTitle";
7 | import type { OnRenderClientAsync } from "vike/types";
8 |
9 | let root: ReactDOM.Root;
10 | const onRenderClient: OnRenderClientAsync = async (
11 | pageContext,
12 | ): ReturnType => {
13 | const { Page } = pageContext;
14 |
15 | // This onRenderClient() hook only supports SSR, see https://vike.dev/render-modes for how to modify onRenderClient()
16 | // to support SPA
17 | if (!Page)
18 | throw new Error(
19 | "My onRenderClient() hook expects pageContext.Page to be defined",
20 | );
21 |
22 | const container = document.getElementById("react-root");
23 | if (!container) throw new Error("DOM element #react-root not found");
24 |
25 | const page = (
26 |
27 |
28 |
29 | );
30 | if (pageContext.isHydration) {
31 | root = ReactDOM.hydrateRoot(container, page);
32 | } else {
33 | if (!root) {
34 | root = ReactDOM.createRoot(container);
35 | }
36 | root.render(page);
37 | }
38 | document.title = getPageTitle(pageContext);
39 | };
40 |
--------------------------------------------------------------------------------
/examples/nestjs-vike/renderer/+onRenderHtml.tsx:
--------------------------------------------------------------------------------
1 | // https://vike.dev/onRenderHtml
2 | export { onRenderHtml };
3 |
4 | import ReactDOMServer from "react-dom/server";
5 | import { Layout } from "./Layout";
6 | import { escapeInject, dangerouslySkipEscape } from "vike/server";
7 | import logoUrl from "./logo.svg";
8 | import type { OnRenderHtmlAsync } from "vike/types";
9 | import { getPageTitle } from "./getPageTitle";
10 |
11 | const onRenderHtml: OnRenderHtmlAsync = async (
12 | pageContext,
13 | ): ReturnType => {
14 | const { Page } = pageContext;
15 |
16 | // This onRenderHtml() hook only supports SSR, see https://vike.dev/render-modes for how to modify
17 | // onRenderHtml() to support SPA
18 | if (!Page)
19 | throw new Error(
20 | "My onRenderHtml() hook expects pageContext.Page to be defined",
21 | );
22 |
23 | // Alternativly, we can use an HTML stream, see https://vike.dev/streaming
24 | const pageHtml = ReactDOMServer.renderToString(
25 |
26 |
27 | ,
28 | );
29 |
30 | const title = getPageTitle(pageContext);
31 | const desc =
32 | pageContext.data?.description ||
33 | pageContext.config.description ||
34 | "Demo of using Vike";
35 |
36 | const documentHtml = escapeInject`
37 |
38 |
39 |
40 |
41 |
42 |
43 | ${title}
44 |
45 |
46 | ${dangerouslySkipEscape(pageHtml)}
47 |
48 | `;
49 |
50 | return {
51 | documentHtml,
52 | pageContext: {
53 | // We can add custom pageContext properties here, see https://vike.dev/pageContext#custom
54 | },
55 | };
56 | };
57 |
--------------------------------------------------------------------------------
/examples/nestjs-vike/renderer/Layout.css:
--------------------------------------------------------------------------------
1 | #sidebar a {
2 | padding: 2px 10px;
3 | margin-left: -10px;
4 | }
5 | #sidebar a.is-active {
6 | background-color: #eee;
7 | }
8 |
--------------------------------------------------------------------------------
/examples/nestjs-vike/renderer/Layout.tsx:
--------------------------------------------------------------------------------
1 | export { Layout };
2 |
3 | import React from "react";
4 | import logoUrl from "./logo.svg";
5 | import { PageContextProvider } from "./usePageContext";
6 | import { Link } from "./Link";
7 | import type { PageContext } from "vike/types";
8 | import "./css/index.css";
9 | import "./Layout.css";
10 |
11 | function Layout({
12 | children,
13 | pageContext,
14 | }: {
15 | children: React.ReactNode;
16 | pageContext: PageContext;
17 | }) {
18 | return (
19 |
20 |
21 |
22 |
23 |
24 | Welcome
25 | About
26 | Data Fetching
27 |
28 | {children}
29 |
30 |
31 |
32 | );
33 | }
34 |
35 | function Frame({ children }: { children: React.ReactNode }) {
36 | return (
37 |
44 | {children}
45 |
46 | );
47 | }
48 |
49 | function Sidebar({ children }: { children: React.ReactNode }) {
50 | return (
51 |
64 | );
65 | }
66 |
67 | function Content({ children }: { children: React.ReactNode }) {
68 | return (
69 |
70 |
78 | {children}
79 |
80 |
81 | );
82 | }
83 |
84 | function Logo() {
85 | return (
86 |
96 | );
97 | }
98 |
--------------------------------------------------------------------------------
/examples/nestjs-vike/renderer/Link.tsx:
--------------------------------------------------------------------------------
1 | import { usePageContext } from "./usePageContext";
2 |
3 | export { Link };
4 |
5 | function Link(props: {
6 | href: string;
7 | className?: string;
8 | children: React.ReactNode;
9 | }) {
10 | const pageContext = usePageContext();
11 | const { urlPathname } = pageContext;
12 | const { href } = props;
13 | const isActive =
14 | href === "/" ? urlPathname === href : urlPathname.startsWith(href);
15 | const className = [props.className, isActive && "is-active"]
16 | .filter(Boolean)
17 | .join(" ");
18 | return ;
19 | }
20 |
--------------------------------------------------------------------------------
/examples/nestjs-vike/renderer/css/code.css:
--------------------------------------------------------------------------------
1 | code {
2 | font-family: monospace;
3 | background-color: #eaeaea;
4 | padding: 3px 5px;
5 | border-radius: 4px;
6 | }
7 |
--------------------------------------------------------------------------------
/examples/nestjs-vike/renderer/css/index.css:
--------------------------------------------------------------------------------
1 | @import "./reset.css";
2 | @import "./links.css";
3 | @import "./code.css";
4 | @import "./page-transition-loading-animation.css";
5 |
--------------------------------------------------------------------------------
/examples/nestjs-vike/renderer/css/links.css:
--------------------------------------------------------------------------------
1 | a {
2 | text-decoration: none;
3 | }
4 |
--------------------------------------------------------------------------------
/examples/nestjs-vike/renderer/css/page-transition-loading-animation.css:
--------------------------------------------------------------------------------
1 | #page-container {
2 | position: relative;
3 | width: 100%;
4 | }
5 | #page-container::before {
6 | content: "";
7 | position: absolute;
8 | width: 100%;
9 | height: 100%;
10 | z-index: 999;
11 | background: no-repeat url("./page-transition-loading-animation/loading.svg");
12 | background-size: 100px;
13 | background-position: center center;
14 | pointer-events: none;
15 | opacity: 0;
16 | }
17 | body.page-is-transitioning #page-container::before {
18 | opacity: 1;
19 | }
20 | #page-content,
21 | #page-container::before {
22 | transition: opacity 0.5s ease-in-out;
23 | }
24 | body.page-is-transitioning #page-content {
25 | opacity: 0.17;
26 | }
27 |
--------------------------------------------------------------------------------
/examples/nestjs-vike/renderer/css/page-transition-loading-animation/loading.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/examples/nestjs-vike/renderer/css/reset.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: sans-serif;
4 | }
5 | * {
6 | box-sizing: border-box;
7 | }
8 |
--------------------------------------------------------------------------------
/examples/nestjs-vike/renderer/getPageTitle.ts:
--------------------------------------------------------------------------------
1 | export { getPageTitle };
2 |
3 | import type { PageContext } from "vike/types";
4 |
5 | function getPageTitle(pageContext: PageContext): string {
6 | const title =
7 | // Title defined dynamically by data()
8 | pageContext.data?.title ||
9 | // Title defined statically by /pages/some-page/+title.js (or by `export default { title }` in /pages/some-page/+config.js)
10 | // The setting 'pageContext.config.title' is a custom setting we defined at ./+config.ts
11 | pageContext.config.title ||
12 | "Vike Demo";
13 | return title;
14 | }
15 |
--------------------------------------------------------------------------------
/examples/nestjs-vike/renderer/types.ts:
--------------------------------------------------------------------------------
1 | // https://vike.dev/pageContext#typescript
2 | declare global {
3 | namespace Vike {
4 | interface PageContext {
5 | Page: () => React.ReactElement;
6 | data?: {
7 | /** Value for defined dynmically by by /pages/some-page/+data.js */
8 | title?: string;
9 | /** Value for defined dynmically */
10 | description?: string;
11 | };
12 | config: {
13 | /** Value for defined statically by /pages/some-page/+title.js (or by `export default { title }` in /pages/some-page/+config.js) */
14 | title?: string;
15 | /** Value for defined statically */
16 | description?: string;
17 | };
18 | /** https://vike.dev/render */
19 | abortReason?: string;
20 | }
21 | }
22 | }
23 |
24 | // Tell TypeScript this file isn't an ambient module
25 | export {};
26 |
--------------------------------------------------------------------------------
/examples/nestjs-vike/renderer/useData.ts:
--------------------------------------------------------------------------------
1 | // https://vike.dev/useData
2 | export { useData };
3 |
4 | import { usePageContext } from "./usePageContext";
5 |
6 | /** https://vike.dev/useData */
7 | function useData() {
8 | const { data } = usePageContext();
9 | return data as Data;
10 | }
11 |
--------------------------------------------------------------------------------
/examples/nestjs-vike/renderer/usePageContext.tsx:
--------------------------------------------------------------------------------
1 | // https://vike.dev/usePageContext
2 | // eslint-disable-next-line react-refresh/only-export-components
3 | export { usePageContext };
4 | export { PageContextProvider };
5 |
6 | import React, { useContext } from "react";
7 | import type { PageContext } from "vike/types";
8 |
9 | const Context = React.createContext(
10 | undefined as unknown as PageContext,
11 | );
12 |
13 | function PageContextProvider({
14 | pageContext,
15 | children,
16 | }: {
17 | pageContext: PageContext;
18 | children: React.ReactNode;
19 | }) {
20 | return {children};
21 | }
22 |
23 | /** https://vike.dev/usePageContext */
24 | function usePageContext() {
25 | const pageContext = useContext(Context);
26 | return pageContext;
27 | }
28 |
--------------------------------------------------------------------------------
/examples/nestjs-vike/server/app.module.ts:
--------------------------------------------------------------------------------
1 | import { Module } from "@nestjs/common";
2 | import { VpsModule } from "./vps.module";
3 |
4 | @Module({
5 | imports: [VpsModule.forRoot()],
6 | providers: [],
7 | })
8 | export class AppModule {}
9 |
--------------------------------------------------------------------------------
/examples/nestjs-vike/server/main.ts:
--------------------------------------------------------------------------------
1 | import { NestFactory } from "@nestjs/core";
2 | import type { Express } from "express";
3 | import { IncomingMessage, ServerResponse } from "node:http";
4 | import { AppModule } from "./app.module";
5 |
6 | bootstrap();
7 |
8 | async function bootstrap() {
9 | const app = await NestFactory.create(AppModule);
10 | await app.init();
11 | resolveHandler(await app.getHttpAdapter().getInstance());
12 | }
13 |
14 | let resolveHandler: (value: Express) => void;
15 | let expressHandler: Express | Promise = new Promise((resolve) => {
16 | resolveHandler = resolve;
17 | });
18 |
19 | export default async function handler(
20 | request: IncomingMessage,
21 | reply: ServerResponse,
22 | ) {
23 | if (expressHandler instanceof Promise) {
24 | expressHandler = await expressHandler;
25 | }
26 |
27 | expressHandler(request, reply);
28 | }
29 |
--------------------------------------------------------------------------------
/examples/nestjs-vike/server/vps.module.ts:
--------------------------------------------------------------------------------
1 | import { DynamicModule, Inject, Module, OnModuleInit } from "@nestjs/common";
2 | import { HttpAdapterHost } from "@nestjs/core";
3 | import type { NextFunction, Request, Response } from "express";
4 | import { fileURLToPath } from "node:url";
5 | import { renderPage } from "vike/server";
6 | import { ServeStaticModule } from "@nestjs/serve-static";
7 | import devServer from "vavite/http-dev-server";
8 |
9 | const OPTIONS = Symbol.for("vike.options");
10 |
11 | interface ViteSsrOptions {
12 | root?: string;
13 | }
14 |
15 | @Module({})
16 | export class VpsModule implements OnModuleInit {
17 | constructor(
18 | private readonly httpAdapterHost: HttpAdapterHost,
19 | @Inject(OPTIONS)
20 | private readonly viteSsrOptions: ViteSsrOptions,
21 | ) {}
22 |
23 | static forRoot(options?: ViteSsrOptions): DynamicModule {
24 | options ??= {
25 | root: fileURLToPath(new URL("../client", import.meta.url)),
26 | };
27 |
28 | const imports: DynamicModule[] = [];
29 | if (!devServer) {
30 | imports.push(
31 | ServeStaticModule.forRoot({
32 | rootPath: options.root,
33 | serveRoot: "/",
34 | }),
35 | );
36 | }
37 |
38 | return {
39 | module: VpsModule,
40 | imports,
41 | providers: [{ provide: OPTIONS, useValue: options }],
42 | };
43 | }
44 |
45 | async onModuleInit() {
46 | if (!this.httpAdapterHost) {
47 | throw new Error(
48 | "httpAdapterHost is undefined, no decorator metadata available",
49 | );
50 | }
51 | const httpAdapter = this.httpAdapterHost.httpAdapter;
52 | if (!httpAdapter) {
53 | return;
54 | }
55 | const app = httpAdapter.getInstance();
56 |
57 | app.get("*", async (req: Request, res: Response, next: NextFunction) => {
58 | const urlOriginal = req.originalUrl;
59 | const pageContext = await renderPage({ urlOriginal });
60 | const { httpResponse } = pageContext;
61 | if (!httpResponse) {
62 | next();
63 | return;
64 | }
65 | const { body, statusCode, headers } = httpResponse;
66 | headers.forEach(([name, value]) => res.header(name, value));
67 | res.status(statusCode);
68 | res.send(body);
69 | });
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/examples/nestjs-vike/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": ".",
4 | "target": "es2017",
5 | "module": "ESNext",
6 | "esModuleInterop": true,
7 | "lib": ["DOM", "DOM.Iterable", "ESNext"],
8 | "forceConsistentCasingInFileNames": true,
9 | "strict": true,
10 | "skipLibCheck": true,
11 | "moduleResolution": "Bundler",
12 | "customConditions": ["import"],
13 | "experimentalDecorators": true,
14 | "emitDecoratorMetadata": true,
15 | "jsx": "react-jsx",
16 | "types": ["vite/client"]
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/examples/nestjs-vike/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "vite";
2 | import { vavite } from "vavite";
3 | import { swc } from "rollup-plugin-swc3";
4 | import react from "@vitejs/plugin-react";
5 | import ssr from "vike/plugin";
6 | import { join } from "node:path";
7 |
8 | export default defineConfig({
9 | buildSteps: [
10 | {
11 | name: "client",
12 | },
13 | {
14 | name: "server",
15 | config: {
16 | build: { ssr: true },
17 | },
18 | },
19 | ],
20 | ssr: {
21 | external: ["reflect-metadata"],
22 | },
23 | esbuild: false,
24 | plugins: [
25 | {
26 | ...swc({
27 | jsc: {
28 | baseUrl: join(__dirname, "./src"),
29 | paths: {
30 | "*": ["*"],
31 | },
32 | transform: {
33 | decoratorMetadata: true,
34 | legacyDecorator: true,
35 | },
36 | target: "es2017",
37 | },
38 | }),
39 | enforce: "pre", // Make sure this is applied before anything else
40 | },
41 | vavite({
42 | handlerEntry: "/server/main.ts",
43 | serveClientAssetsInDev: true,
44 | }),
45 | react(),
46 | ssr({ disableAutoFullBuild: true }),
47 | ],
48 | });
49 |
--------------------------------------------------------------------------------
/examples/nestjs/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@vavite/example-reloader-nestjs",
3 | "private": true,
4 | "type": "module",
5 | "scripts": {
6 | "start": "node dist/index",
7 | "dev": "vite",
8 | "build": "vite build --ssr --mode=production"
9 | },
10 | "files": [
11 | "dist"
12 | ],
13 | "devDependencies": {
14 | "@swc/core": "^1.9.3",
15 | "@types/express": "^5.0.0",
16 | "@types/node": "^18.19.67",
17 | "rollup-plugin-swc3": "^0.12.1",
18 | "typescript": "^5.7.2",
19 | "vavite": "5.1.0",
20 | "vite": "^5.4.11"
21 | },
22 | "dependencies": {
23 | "@nestjs/common": "^10.4.12",
24 | "@nestjs/core": "^10.4.12",
25 | "@nestjs/platform-express": "^10.4.12",
26 | "reflect-metadata": "^0.2.2",
27 | "rxjs": "^7.8.1"
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/examples/nestjs/readme.md:
--------------------------------------------------------------------------------
1 | # Vavite NestJS example
2 |
3 | `serverEntry` example that shows how to integrate with [NestJS](https://nestjs.com/).
4 |
5 | Clone with:
6 |
7 | ```bash
8 | npx degit cyco130/vavite/examples/nestjs
9 | ```
10 |
--------------------------------------------------------------------------------
/examples/nestjs/src/app.controller.ts:
--------------------------------------------------------------------------------
1 | import { Controller, Get, Req, Request } from "@nestjs/common";
2 | import viteDevServer from "vavite/vite-dev-server";
3 |
4 | @Controller()
5 | export class AppController {
6 | @Get("/")
7 | async home(@Req() request: Request) {
8 | let html = "Hello from Nest.js
" + nav;
9 |
10 | if (viteDevServer) {
11 | html = await viteDevServer.transformIndexHtml(request.url, html);
12 | }
13 |
14 | return html;
15 | }
16 |
17 | @Get("/foo")
18 | async foo(@Req() request: Request) {
19 | let html = "Hello from page /foo
" + nav;
20 |
21 | if (viteDevServer) {
22 | html = await viteDevServer.transformIndexHtml(request.url, html);
23 | }
24 |
25 | return html;
26 | }
27 |
28 | @Get("/bar")
29 | async bar(@Req() request: Request) {
30 | let html = "Hello from page /bar
" + nav;
31 |
32 | if (viteDevServer) {
33 | html = await viteDevServer.transformIndexHtml(request.url, html);
34 | }
35 |
36 | return html;
37 | }
38 | }
39 |
40 | const nav = `
41 |
48 | `;
49 |
--------------------------------------------------------------------------------
/examples/nestjs/src/app.module.ts:
--------------------------------------------------------------------------------
1 | import { Module } from "@nestjs/common";
2 | import { AppController } from "./app.controller";
3 |
4 | @Module({
5 | controllers: [AppController],
6 | imports: [],
7 | providers: [],
8 | })
9 | export class AppModule {}
10 |
--------------------------------------------------------------------------------
/examples/nestjs/src/main.ts:
--------------------------------------------------------------------------------
1 | import { NestFactory } from "@nestjs/core";
2 | import type { Express } from "express";
3 | import { IncomingMessage, ServerResponse } from "node:http";
4 | import { AppModule } from "./app.module";
5 |
6 | bootstrap();
7 |
8 | async function bootstrap() {
9 | const app = await NestFactory.create(AppModule);
10 | await app.init();
11 | resolveHandler(await app.getHttpAdapter().getInstance());
12 | }
13 |
14 | let resolveHandler: (value: Express) => void;
15 | let expressHandler: Express | Promise = new Promise((resolve) => {
16 | resolveHandler = resolve;
17 | });
18 |
19 | export default async function handler(
20 | request: IncomingMessage,
21 | reply: ServerResponse,
22 | ) {
23 | if (expressHandler instanceof Promise) {
24 | expressHandler = await expressHandler;
25 | }
26 |
27 | expressHandler(request, reply);
28 | }
29 |
--------------------------------------------------------------------------------
/examples/nestjs/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es2020",
4 | "module": "ESNext",
5 | "esModuleInterop": true,
6 | "forceConsistentCasingInFileNames": true,
7 | "strict": true,
8 | "skipLibCheck": true,
9 | "moduleResolution": "Bundler",
10 | "customConditions": ["import"],
11 | "experimentalDecorators": true,
12 | "types": ["vite/client"]
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/examples/nestjs/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "vite";
2 | import { vavite } from "vavite";
3 | import { swc } from "rollup-plugin-swc3";
4 |
5 | export default defineConfig({
6 | ssr: {
7 | external: ["reflect-metadata"],
8 | },
9 | esbuild: false,
10 | plugins: [
11 | {
12 | ...swc({
13 | jsc: {
14 | transform: {
15 | decoratorMetadata: true,
16 | legacyDecorator: true,
17 | },
18 | target: "es2021",
19 | },
20 | }),
21 | enforce: "pre", // Make sure this is applied before anything else
22 | },
23 | vavite({
24 | handlerEntry: "/src/main.ts",
25 | serveClientAssetsInDev: true,
26 | }),
27 | ],
28 | });
29 |
--------------------------------------------------------------------------------
/examples/readme.md:
--------------------------------------------------------------------------------
1 | # Examples
2 |
3 | - [simple-standalone](simple-standalone): Simple standalone example ([Stackblitz](https://stackblitz.com/github/cyco130/vavite/tree/main/examples/simple-standalone))
4 | - [express](express): Integrating with Express ([Stackblitz](https://stackblitz.com/github/cyco130/vavite/tree/main/examples/express))
5 | - [koa](koa): Integrating with Koa ([Stackblitz](https://stackblitz.com/github/cyco130/vavite/tree/main/examples/koa))
6 | - [fastify](fastify): Integrating with Fastify ([Stackblitz](https://stackblitz.com/github/cyco130/vavite/tree/main/examples/fastify))
7 | - [hapi](hapi): Integrating with Hapi ([Stackblitz](https://stackblitz.com/github/cyco130/vavite/tree/main/examples/hapi))
8 | - [NestJS](nestjs): Integrating with [NestJS](https://nestjs.com/)
9 | - [NestJS+Vike](nestjs-vike): Vike with React and NestJS
10 | - [ssr-react-express](ssr-react-express): React SSR with Express ([Stackblitz](https://stackblitz.com/github/cyco130/vavite/tree/main/examples/ssr-react-express))
11 | - [ssr-vue-express](ssr-vue-express): Vue SSR with Express ([Stackblitz](https://stackblitz.com/github/cyco130/vavite/tree/main/examples/ssr-vue-express))
12 | - [vike](vike): Vike with React and Express ([Stackblitz](https://stackblitz.com/github/cyco130/vavite/tree/main/examples/vike))
13 | - [fastify-vike](fastify-vike): Vike with React and Fastify ([Stackblitz](https://stackblitz.com/github/cyco130/vavite/tree/main/examples/fastify-vike))
14 | - [socket-io](socket-io): [socket.io](https://socket.io/) with Express ([Stackblitz](https://stackblitz.com/github/cyco130/vavite/tree/main/examples/socket-io))
15 |
--------------------------------------------------------------------------------
/examples/simple-standalone/.stackblitzrc:
--------------------------------------------------------------------------------
1 | {
2 | "installDependencies": true,
3 | "startCommand": "npm run dev"
4 | }
5 |
--------------------------------------------------------------------------------
/examples/simple-standalone/handler.ts:
--------------------------------------------------------------------------------
1 | import type { IncomingMessage, ServerResponse } from "node:http";
2 |
3 | export default function handler(
4 | req: IncomingMessage,
5 | res: ServerResponse,
6 | next: () => void,
7 | ) {
8 | if (!res.writableEnded && req.url === "/") {
9 | res.setHeader("Content-Type", "text/html; charset=utf-8");
10 | // This is not Express, res.send is Express-specific.
11 | // So you should use res.write or res.end instead.
12 | res.end("Hello from standalone!
");
13 | }
14 |
15 | next();
16 | }
17 |
--------------------------------------------------------------------------------
/examples/simple-standalone/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@vavite/example-simple-standalone",
3 | "type": "module",
4 | "private": true,
5 | "scripts": {
6 | "start": "node dist",
7 | "dev": "vavite serve",
8 | "build": "vite build --ssr --mode=production"
9 | },
10 | "devDependencies": {
11 | "@types/node": "^18.19.67",
12 | "typescript": "^5.7.2",
13 | "vavite": "5.1.0",
14 | "vite": "^5.4.11"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/examples/simple-standalone/readme.md:
--------------------------------------------------------------------------------
1 | # Vavite Simple Standalone example
2 |
3 | Simplest example that specifies a `handlerEntry` and handles incoming requests.
4 |
5 | > [Try on StackBlitz](https://stackblitz.com/github/cyco130/vavite/tree/main/examples/simple-standalone)
6 |
7 | Clone with:
8 |
9 | ```bash
10 | npx degit cyco130/vavite/examples/simple-standalone
11 | ```
12 |
--------------------------------------------------------------------------------
/examples/simple-standalone/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es2020",
4 | "module": "ESNext",
5 | "esModuleInterop": true,
6 | "forceConsistentCasingInFileNames": true,
7 | "strict": true,
8 | "skipLibCheck": true,
9 | "moduleResolution": "Bundler",
10 | "customConditions": ["import"]
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/examples/simple-standalone/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "vite";
2 | import { vavite } from "vavite";
3 |
4 | export default defineConfig({
5 | plugins: [vavite({ handlerEntry: "/handler.ts" })],
6 | });
7 |
--------------------------------------------------------------------------------
/examples/socket-io/.stackblitzrc:
--------------------------------------------------------------------------------
1 | {
2 | "installDependencies": true,
3 | "startCommand": "npm run dev"
4 | }
5 |
--------------------------------------------------------------------------------
/examples/socket-io/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@vavite/example-socket-io",
3 | "type": "module",
4 | "private": true,
5 | "scripts": {
6 | "start": "node dist",
7 | "dev": "vite",
8 | "build": "vite build --ssr --mode=production"
9 | },
10 | "files": [
11 | "dist"
12 | ],
13 | "devDependencies": {
14 | "@types/express": "^5.0.0",
15 | "@types/node": "^18.19.67",
16 | "typescript": "^5.7.2",
17 | "vavite": "5.1.0",
18 | "vite": "^5.4.11"
19 | },
20 | "dependencies": {
21 | "express": "^4.21.1",
22 | "socket.io": "^4.8.1"
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/examples/socket-io/readme.md:
--------------------------------------------------------------------------------
1 | # Vavite socket.io example
2 |
3 | `serverEntry` example that shows how to integrate with [socket.io](http://socket.io/).
4 |
5 | The trick is to attach the socket.io server to `viteDevServer.httpServer` (from `"vavite/vite-dev-server"`) and not to `httpDevServer` from `"vavite/http-dev-server"`. The latter is merely a proxy to force server frameworks to behave in a controlled manner, the former is the actual server used by Vite.
6 |
7 | > [Try on StackBlitz](https://stackblitz.com/github/cyco130/vavite/tree/main/examples/socket-io)
8 |
9 | Clone with:
10 |
11 | ```bash
12 | npx degit cyco130/vavite/examples/socket-io
13 | ```
14 |
--------------------------------------------------------------------------------
/examples/socket-io/server.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | import express from "express";
4 | import { Server as SocketIOServer } from "socket.io";
5 | import type {} from "node:http";
6 | import type {} from "node:net";
7 |
8 | declare module "node:net" {
9 | interface Socket {
10 | server: import("node:http").Server;
11 | }
12 | }
13 |
14 | declare module "node:http" {
15 | interface Server {
16 | io?: SocketIOServer;
17 | }
18 | }
19 |
20 | const app = express();
21 |
22 | let io: SocketIOServer;
23 |
24 | app.use((req, res, next) => {
25 | if (!io || io !== req.socket.server.io) {
26 | req.socket.server.io?.close();
27 |
28 | io = req.socket.server.io = new SocketIOServer(req.socket.server);
29 |
30 | io.on("connection", (socket) => {
31 | socket.on("chat message", (msg) => {
32 | io.emit("chat message", msg);
33 | });
34 | });
35 | }
36 |
37 | next();
38 | });
39 |
40 | app.get("/", async (req, res) => {
41 | res.sendFile(process.cwd() + "/view.html");
42 | });
43 |
44 | export default app;
45 |
--------------------------------------------------------------------------------
/examples/socket-io/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es2020",
4 | "module": "ESNext",
5 | "esModuleInterop": true,
6 | "forceConsistentCasingInFileNames": true,
7 | "strict": true,
8 | "skipLibCheck": true,
9 | "moduleResolution": "Bundler",
10 | "customConditions": ["import"]
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/examples/socket-io/view.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Socket.IO chat
5 |
57 |
58 |
59 |
60 |
63 |
64 |
65 |
87 |
88 |
89 |
--------------------------------------------------------------------------------
/examples/socket-io/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "vite";
2 | import { vavite } from "vavite";
3 |
4 | export default defineConfig({
5 | plugins: [
6 | vavite({
7 | handlerEntry: "/server.ts",
8 | reloadOn: "static-deps-change",
9 | }),
10 | ],
11 | });
12 |
--------------------------------------------------------------------------------
/examples/ssr-react-express/.stackblitzrc:
--------------------------------------------------------------------------------
1 | {
2 | "installDependencies": true,
3 | "startCommand": "npm run dev"
4 | }
5 |
--------------------------------------------------------------------------------
/examples/ssr-react-express/App.tsx:
--------------------------------------------------------------------------------
1 | import { ReactNode } from "react";
2 | import { Nav } from "./components/Nav";
3 |
4 | export function App(props: { children: ReactNode }) {
5 | return (
6 |
7 |
10 | {props.children}
11 |
12 | );
13 | }
14 |
--------------------------------------------------------------------------------
/examples/ssr-react-express/client-entry.tsx:
--------------------------------------------------------------------------------
1 | import { hydrate } from "react-dom";
2 | import { App } from "./App";
3 |
4 | async function render() {
5 | // Tiny, crappy router
6 | const importer = {
7 | "/": () => import("./pages/Home"),
8 | "/foo": () => import("./pages/Foo"),
9 | "/bar": () => import("./pages/Bar"),
10 | }[window.location.pathname];
11 |
12 | if (!importer) {
13 | throw new Error(`No page found for ${window.location.pathname}`);
14 | }
15 |
16 | const Page = (await importer()).default;
17 |
18 | const root = document.getElementById("root");
19 | hydrate(
20 |
21 |
22 | ,
23 | root,
24 | );
25 | }
26 |
27 | render();
28 |
--------------------------------------------------------------------------------
/examples/ssr-react-express/components/Counter.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from "react";
2 |
3 | export function Counter() {
4 | const [count, setCount] = useState(0);
5 |
6 | return (
7 |
8 | );
9 | }
10 |
--------------------------------------------------------------------------------
/examples/ssr-react-express/components/Nav.tsx:
--------------------------------------------------------------------------------
1 | export function Nav() {
2 | return (
3 |
16 | );
17 | }
18 |
--------------------------------------------------------------------------------
/examples/ssr-react-express/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@vavite/example-ssr-react-express",
3 | "type": "module",
4 | "private": true,
5 | "scripts": {
6 | "dev": "vavite serve",
7 | "build": "vavite build",
8 | "start": "node dist/server"
9 | },
10 | "devDependencies": {
11 | "@types/express": "^5.0.0",
12 | "@types/node": "^18.19.67",
13 | "@types/react": "^18.3.12",
14 | "@types/react-dom": "^18.3.1",
15 | "@vitejs/plugin-react": "^4.3.4",
16 | "typescript": "^5.7.2",
17 | "vavite": "5.1.0",
18 | "vite": "^5.4.11"
19 | },
20 | "dependencies": {
21 | "express": "^4.21.1",
22 | "react": "^18.3.1",
23 | "react-dom": "^18.3.1"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/examples/ssr-react-express/pages/Bar.tsx:
--------------------------------------------------------------------------------
1 | export default function Bar() {
2 | return (
3 |
4 | Bar
5 |
6 | );
7 | }
8 |
--------------------------------------------------------------------------------
/examples/ssr-react-express/pages/Foo.tsx:
--------------------------------------------------------------------------------
1 | export default function Foo() {
2 | return (
3 |
4 | Foo
5 |
6 | );
7 | }
8 |
--------------------------------------------------------------------------------
/examples/ssr-react-express/pages/Home.tsx:
--------------------------------------------------------------------------------
1 | import { Counter } from "../components/Counter";
2 |
3 | export default function Home() {
4 | return (
5 |
6 | Hello from home page!
7 |
8 |
9 | );
10 | }
11 |
--------------------------------------------------------------------------------
/examples/ssr-react-express/readme.md:
--------------------------------------------------------------------------------
1 | # Vavite React SSR example with Express
2 |
3 | Simple example for React SSR with Vavite that shows how to:
4 |
5 | - Integrate Express with Vite's dev server
6 | - Run multiple build steps (for client and server)
7 | - Perform SSR in React
8 |
9 | > [Try on StackBlitz](https://stackblitz.com/github/cyco130/vavite/tree/main/examples/ssr-react-express)
10 |
11 | Clone with:
12 |
13 | ```bash
14 | npx degit cyco130/vavite/examples/ssr-react-express
15 | ```
16 |
--------------------------------------------------------------------------------
/examples/ssr-react-express/server-entry.tsx:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | import express, { Request, Response } from "express";
4 | import httpDevServer from "vavite/http-dev-server";
5 | import viteDevServer from "vavite/vite-dev-server";
6 | import { ComponentType } from "react";
7 | import { renderToString } from "react-dom/server";
8 | import { App } from "./App";
9 |
10 | const app = express();
11 |
12 | if (import.meta.env.PROD) {
13 | // Serve client assets in production
14 | app.use(express.static("dist/client"));
15 | }
16 |
17 | // Page routes
18 | app.get("/", (req, res) => render(req, res, () => import("./pages/Home")));
19 | app.get("/foo", (req, res) => render(req, res, () => import("./pages/Foo")));
20 | app.get("/bar", (req, res) => render(req, res, () => import("./pages/Bar")));
21 |
22 | type PageImporter = () => Promise<{ default: ComponentType }>;
23 |
24 | async function render(req: Request, res: Response, importer: PageImporter) {
25 | const Page = (await importer()).default;
26 |
27 | let clientEntryPath: string;
28 | if (viteDevServer) {
29 | // In development, we can simply refer to the source file name
30 | clientEntryPath = "/client-entry.tsx";
31 | } else {
32 | // In production we'll figure out the path to the client entry file using the manifest
33 | // @ts-ignore: This only exists after the client build is complete
34 | const manifest = (await import("./dist/client/.vite/manifest.json"))
35 | .default;
36 | clientEntryPath = manifest["client-entry.tsx"].file;
37 |
38 | // In a real application we would also use the manifest to generate
39 | // preload links for assets needed for the rendered page
40 | }
41 |
42 | let html = `
43 |
44 |
45 | SSR React Express
46 |
47 |
48 | ${renderToString(
49 |
50 |
51 | ,
52 | )}
53 |
54 |
55 | `;
56 |
57 | if (viteDevServer) {
58 | // This will inject the Vite client and React fast refresh in development
59 | html = await viteDevServer.transformIndexHtml(req.url, html);
60 | }
61 |
62 | res.send(html);
63 | }
64 |
65 | if (viteDevServer) {
66 | httpDevServer!.on("request", app);
67 | } else {
68 | console.log("Starting production server");
69 | app.listen(3000);
70 | }
71 |
--------------------------------------------------------------------------------
/examples/ssr-react-express/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es2020",
4 | "module": "ESNext",
5 | "esModuleInterop": true,
6 | "forceConsistentCasingInFileNames": true,
7 | "strict": true,
8 | "skipLibCheck": true,
9 | "moduleResolution": "Bundler",
10 | "customConditions": ["import"],
11 | "jsx": "react-jsx"
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/examples/ssr-react-express/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "vite";
2 | import { vavite } from "vavite";
3 | import react from "@vitejs/plugin-react";
4 |
5 | export default defineConfig({
6 | buildSteps: [
7 | {
8 | name: "client",
9 | config: {
10 | build: {
11 | outDir: "dist/client",
12 | manifest: true,
13 | rollupOptions: { input: "client-entry.tsx" },
14 | },
15 | },
16 | },
17 | {
18 | name: "server",
19 | config: {
20 | build: {
21 | ssr: true,
22 | outDir: "dist/server",
23 | },
24 | },
25 | },
26 | ],
27 |
28 | plugins: [
29 | react(),
30 | vavite({
31 | serverEntry: "/server-entry.tsx",
32 | serveClientAssetsInDev: true,
33 | // Don't reload when dynamically imported dependencies change
34 | reloadOn: "static-deps-change",
35 | }),
36 | ],
37 | });
38 |
--------------------------------------------------------------------------------
/examples/ssr-vue-express/.stackblitzrc:
--------------------------------------------------------------------------------
1 | {
2 | "installDependencies": true,
3 | "startCommand": "npm run dev"
4 | }
5 |
--------------------------------------------------------------------------------
/examples/ssr-vue-express/App.vue:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
12 |
13 |
--------------------------------------------------------------------------------
/examples/ssr-vue-express/client-entry.ts:
--------------------------------------------------------------------------------
1 | import { createSSRApp, h } from "vue";
2 | import App from "./App.vue";
3 |
4 | async function render() {
5 | // Tiny, crappy router
6 | const importer = {
7 | "/": () => import("./pages/Home.vue"),
8 | "/foo": () => import("./pages/Foo.vue"),
9 | "/bar": () => import("./pages/Bar.vue"),
10 | }[window.location.pathname];
11 |
12 | if (!importer) {
13 | throw new Error(`No page found for ${window.location.pathname}`);
14 | }
15 |
16 | const Page = (await importer()).default;
17 |
18 | const app = createSSRApp({
19 | render() {
20 | return h(
21 | App,
22 | {},
23 | {
24 | default() {
25 | return h(Page, {});
26 | },
27 | },
28 | );
29 | },
30 | });
31 |
32 | app.mount("#root");
33 | }
34 |
35 | render();
36 |
--------------------------------------------------------------------------------
/examples/ssr-vue-express/components/Counter.vue:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/examples/ssr-vue-express/components/Nav.vue:
--------------------------------------------------------------------------------
1 |
2 |
15 |
16 |
--------------------------------------------------------------------------------
/examples/ssr-vue-express/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@vavite/example-ssr-vue-express",
3 | "type": "module",
4 | "private": true,
5 | "scripts": {
6 | "dev": "vavite serve",
7 | "build": "vavite build",
8 | "start": "node dist/server"
9 | },
10 | "devDependencies": {
11 | "@types/express": "^5.0.0",
12 | "@types/node": "^18.19.67",
13 | "@vitejs/plugin-vue": "^5.2.1",
14 | "typescript": "^5.7.2",
15 | "vavite": "5.1.0",
16 | "vite": "^5.4.11"
17 | },
18 | "dependencies": {
19 | "express": "^4.21.1",
20 | "vue": "^3.5.13"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/examples/ssr-vue-express/pages/Bar.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | Bar
4 |
5 |
6 |
--------------------------------------------------------------------------------
/examples/ssr-vue-express/pages/Foo.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | Foo
4 |
5 |
6 |
--------------------------------------------------------------------------------
/examples/ssr-vue-express/pages/Home.vue:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 | Hello from home page!
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/examples/ssr-vue-express/readme.md:
--------------------------------------------------------------------------------
1 | # Vavite Vue SSR example with Express
2 |
3 | Simple example for Vue SSR with Vavite that shows how to:
4 |
5 | - Integrate Express with Vite's dev server
6 | - Run multiple build steps (for client and server)
7 | - Perform SSR in Vue
8 |
9 | > [Try on StackBlitz](https://stackblitz.com/github/cyco130/vavite/tree/main/examples/ssr-vue-express)
10 |
11 | Clone with:
12 |
13 | ```bash
14 | npx degit cyco130/vavite/examples/ssr-vue-express
15 | ```
16 |
--------------------------------------------------------------------------------
/examples/ssr-vue-express/server-entry.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | import express, { Request, Response } from "express";
4 | import httpDevServer from "vavite/http-dev-server";
5 | import viteDevServer from "vavite/vite-dev-server";
6 | import Vue, { createSSRApp, h, defineComponent } from "vue";
7 | import { renderToString } from "vue/server-renderer";
8 | import App from "./App.vue";
9 |
10 | const app = express();
11 |
12 | if (import.meta.env.PROD) {
13 | // Serve client assets in production
14 | app.use(express.static("dist/client"));
15 | }
16 |
17 | // Page routes
18 | app.get("/", (req, res) => render(req, res, () => import("./pages/Home.vue")));
19 | app.get("/foo", (req, res) =>
20 | render(req, res, () => import("./pages/Foo.vue")),
21 | );
22 | app.get("/bar", (req, res) =>
23 | render(req, res, () => import("./pages/Bar.vue")),
24 | );
25 |
26 | type PageImporter = () => Promise<{ default: any }>;
27 |
28 | async function render(req: Request, res: Response, importer: PageImporter) {
29 | const Page = (await importer()).default;
30 |
31 | let clientEntryPath: string;
32 | if (viteDevServer) {
33 | // In development, we can simply refer to the source file name
34 | clientEntryPath = "/client-entry.ts";
35 | } else {
36 | // In production we'll figure out the path to the client entry file using the manifest
37 | // @ts-ignore: This only exists after the client build is complete
38 | const manifest = (await import("./dist/client/.vite/manifest.json"))
39 | .default;
40 | clientEntryPath = manifest["client-entry.ts"].file;
41 |
42 | // In a real application we would also use the manifest to generate
43 | // preload links for assets needed for the rendered page
44 | }
45 |
46 | const Content = defineComponent({
47 | render() {
48 | return h(
49 | App,
50 | {},
51 | {
52 | default() {
53 | return h(Page, {});
54 | },
55 | },
56 | );
57 | },
58 | });
59 |
60 | const content = await renderToString(createSSRApp(Content));
61 |
62 | let html = `
63 |
64 |
65 | SSR Vue Express
66 |
67 |
68 | ${content}
69 |
70 |
71 | `;
72 |
73 | if (viteDevServer) {
74 | // This will inject the Vite client and React fast refresh in development
75 | html = await viteDevServer.transformIndexHtml(req.url, html);
76 | }
77 |
78 | res.send(html);
79 | }
80 |
81 | if (viteDevServer) {
82 | httpDevServer!.on("request", app);
83 | } else {
84 | console.log("Starting production server");
85 | app.listen(3000);
86 | }
87 |
--------------------------------------------------------------------------------
/examples/ssr-vue-express/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es2020",
4 | "module": "ESNext",
5 | "esModuleInterop": true,
6 | "forceConsistentCasingInFileNames": true,
7 | "strict": true,
8 | "skipLibCheck": true,
9 | "moduleResolution": "Bundler",
10 | "customConditions": ["import"]
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/examples/ssr-vue-express/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "vite";
2 | import { vavite } from "vavite";
3 | import vue from "@vitejs/plugin-vue";
4 |
5 | export default defineConfig({
6 | buildSteps: [
7 | {
8 | name: "client",
9 | config: {
10 | build: {
11 | outDir: "dist/client",
12 | manifest: true,
13 | rollupOptions: { input: "client-entry.ts" },
14 | },
15 | },
16 | },
17 | {
18 | name: "server",
19 | config: {
20 | build: {
21 | ssr: true,
22 | outDir: "dist/server",
23 | },
24 | },
25 | },
26 | ],
27 |
28 | plugins: [
29 | vue(),
30 | vavite({
31 | serverEntry: "/server-entry.ts",
32 | serveClientAssetsInDev: true,
33 | // Don't reload when dynamically imported dependencies change
34 | reloadOn: "static-deps-change",
35 | }),
36 | ],
37 | });
38 |
--------------------------------------------------------------------------------
/examples/ssr-vue-express/vue-sfc.d.ts:
--------------------------------------------------------------------------------
1 | declare module "*.vue" {
2 | const Component: any;
3 | export default Component;
4 | }
5 |
--------------------------------------------------------------------------------
/examples/vike/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | lerna-debug.log*
8 | .pnpm-debug.log*
9 |
10 | # Diagnostic reports (https://nodejs.org/api/report.html)
11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
12 |
13 | # Runtime data
14 | pids
15 | *.pid
16 | *.seed
17 | *.pid.lock
18 |
19 | # Directory for instrumented libs generated by jscoverage/JSCover
20 | lib-cov
21 |
22 | # Coverage directory used by tools like istanbul
23 | coverage
24 | *.lcov
25 |
26 | # nyc test coverage
27 | .nyc_output
28 |
29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
30 | .grunt
31 |
32 | # Bower dependency directory (https://bower.io/)
33 | bower_components
34 |
35 | # node-waf configuration
36 | .lock-wscript
37 |
38 | # Compiled binary addons (https://nodejs.org/api/addons.html)
39 | build/Release
40 |
41 | # Dependency directories
42 | node_modules/
43 | jspm_packages/
44 |
45 | # Snowpack dependency directory (https://snowpack.dev/)
46 | web_modules/
47 |
48 | # TypeScript cache
49 | *.tsbuildinfo
50 |
51 | # Optional npm cache directory
52 | .npm
53 |
54 | # Optional eslint cache
55 | .eslintcache
56 |
57 | # Microbundle cache
58 | .rpt2_cache/
59 | .rts2_cache_cjs/
60 | .rts2_cache_es/
61 | .rts2_cache_umd/
62 |
63 | # Optional REPL history
64 | .node_repl_history
65 |
66 | # Output of 'npm pack'
67 | *.tgz
68 |
69 | # Yarn Integrity file
70 | .yarn-integrity
71 |
72 | # dotenv environment variables file
73 | .env
74 | .env.test
75 | .env.production
76 |
77 | # parcel-bundler cache (https://parceljs.org/)
78 | .cache
79 | .parcel-cache
80 |
81 | # Next.js build output
82 | .next
83 | out
84 |
85 | # Nuxt.js build / generate output
86 | .nuxt
87 | dist
88 |
89 | # Gatsby files
90 | .cache/
91 | # Comment in the public line in if your project uses Gatsby and not Next.js
92 | # https://nextjs.org/blog/next-9-1#public-directory-support
93 | # public
94 |
95 | # vuepress build output
96 | .vuepress/dist
97 |
98 | # Serverless directories
99 | .serverless/
100 |
101 | # FuseBox cache
102 | .fusebox/
103 |
104 | # DynamoDB Local files
105 | .dynamodb/
106 |
107 | # TernJS port file
108 | .tern-port
109 |
110 | # Stores VSCode versions used for testing VSCode extensions
111 | .vscode-test
112 |
113 | # yarn v2
114 | .yarn/cache
115 | .yarn/unplugged
116 | .yarn/build-state.yml
117 | .yarn/install-state.gz
118 | .pnp.*
119 |
120 | # Local files
121 | *.local
122 |
--------------------------------------------------------------------------------
/examples/vike/.stackblitzrc:
--------------------------------------------------------------------------------
1 | {
2 | "installDependencies": true,
3 | "startCommand": "npm run dev"
4 | }
5 |
--------------------------------------------------------------------------------
/examples/vike/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@vavite/example-vike",
3 | "type": "module",
4 | "private": true,
5 | "scripts": {
6 | "dev": "vavite serve",
7 | "build": "vavite build",
8 | "start": "cross-env NODE_ENV=production node dist/server/index.mjs"
9 | },
10 | "dependencies": {
11 | "@types/express": "^5.0.0",
12 | "@types/node": "^18.19.67",
13 | "@types/react": "^18.3.12",
14 | "@types/react-dom": "^18.3.1",
15 | "@vitejs/plugin-react": "^4.3.4",
16 | "cross-env": "^7.0.3",
17 | "express": "^4.21.1",
18 | "react": "^18.3.1",
19 | "react-dom": "^18.3.1",
20 | "ts-node": "^10.9.2",
21 | "typescript": "^5.7.2",
22 | "vavite": "5.1.0",
23 | "vike": "^0.4.205",
24 | "vite": "^5.4.11"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/examples/vike/pages/_error/+Page.tsx:
--------------------------------------------------------------------------------
1 | export { Page };
2 |
3 | import { usePageContext } from "../../renderer/usePageContext";
4 |
5 | function Page() {
6 | const pageContext = usePageContext();
7 | let { abortReason } = pageContext;
8 | if (!abortReason) {
9 | abortReason = pageContext.is404
10 | ? "Page not found."
11 | : "Something went wrong.";
12 | }
13 | return (
14 |
15 | {abortReason}
16 |
17 | );
18 | }
19 |
20 | function Center({ children }: { children: React.ReactNode }) {
21 | return (
22 |
30 | {children}
31 |
32 | );
33 | }
34 |
--------------------------------------------------------------------------------
/examples/vike/pages/about/+Page.tsx:
--------------------------------------------------------------------------------
1 | import "./code.css";
2 |
3 | export { Page };
4 |
5 | function Page() {
6 | return (
7 | <>
8 | About
9 | Example of using Vike.
10 | >
11 | );
12 | }
13 |
--------------------------------------------------------------------------------
/examples/vike/pages/about/code.css:
--------------------------------------------------------------------------------
1 | code {
2 | font-family: monospace;
3 | background-color: #eaeaea;
4 | padding: 3px 5px;
5 | border-radius: 4px;
6 | }
7 |
--------------------------------------------------------------------------------
/examples/vike/pages/index/+Page.tsx:
--------------------------------------------------------------------------------
1 | export { Page };
2 |
3 | import { Counter } from "./Counter";
4 |
5 | function Page() {
6 | return (
7 | <>
8 | Hello from Vike on Express!
9 | This page is:
10 |
11 | - Rendered to HTML.
12 | -
13 | Interactive.
14 |
15 |
16 | >
17 | );
18 | }
19 |
--------------------------------------------------------------------------------
/examples/vike/pages/index/Counter.tsx:
--------------------------------------------------------------------------------
1 | export { Counter };
2 |
3 | import { useState } from "react";
4 |
5 | function Counter() {
6 | const [count, setCount] = useState(0);
7 | return (
8 |
11 | );
12 | }
13 |
--------------------------------------------------------------------------------
/examples/vike/pages/star-wars/@id/+Page.tsx:
--------------------------------------------------------------------------------
1 | export { Page };
2 |
3 | import { useData } from "../../../renderer/useData";
4 | import type { Data } from "./+data";
5 |
6 | function Page() {
7 | const { movie } = useData();
8 | return (
9 | <>
10 | {movie.title}
11 | Release Date: {movie.release_date}
12 |
13 | Director: {movie.director}
14 |
15 | Producer: {movie.producer}
16 | >
17 | );
18 | }
19 |
--------------------------------------------------------------------------------
/examples/vike/pages/star-wars/@id/+data.ts:
--------------------------------------------------------------------------------
1 | // https://vike.dev/data
2 | export { data };
3 | export type Data = Awaited>;
4 |
5 | import type { MovieDetails } from "../types";
6 | import type { PageContextServer } from "vike/types";
7 |
8 | const data = async (pageContext: PageContextServer) => {
9 | await sleep(300); // Simulate slow network
10 |
11 | const response = await fetch(
12 | `https://brillout.github.io/star-wars/api/films/${pageContext.routeParams!.id}.json`,
13 | );
14 | let movie = (await response.json()) as MovieDetails;
15 |
16 | // We remove data we don't need because the data is passed to the client; we should
17 | // minimize what is sent over the network.
18 | movie = minimize(movie);
19 |
20 | return {
21 | movie,
22 | // The page's
23 | title: movie.title,
24 | };
25 | };
26 |
27 | function minimize(movie: MovieDetails & Record): MovieDetails {
28 | const { id, title, release_date, director, producer } = movie;
29 | movie = { id, title, release_date, director, producer };
30 | return movie;
31 | }
32 |
33 | function sleep(milliseconds: number) {
34 | return new Promise((r) => setTimeout(r, milliseconds));
35 | }
36 |
--------------------------------------------------------------------------------
/examples/vike/pages/star-wars/index/+Page.tsx:
--------------------------------------------------------------------------------
1 | export default Page;
2 |
3 | import { useData } from "../../../renderer/useData";
4 | import type { Data } from "./+data";
5 |
6 | function Page() {
7 | const { movies } = useData();
8 | return (
9 | <>
10 | Star Wars Movies
11 |
12 | {movies.map(({ id, title, release_date }) => (
13 | -
14 | {title} ({release_date})
15 |
16 | ))}
17 |
18 |
19 | Source:{" "}
20 |
21 | brillout.github.io/star-wars
22 |
23 | .
24 |
25 |
26 | Data can be fetched by using the data()
hook.
27 |
28 | >
29 | );
30 | }
31 |
--------------------------------------------------------------------------------
/examples/vike/pages/star-wars/index/+data.ts:
--------------------------------------------------------------------------------
1 | // https://vike.dev/data
2 | export { data };
3 | export type Data = Awaited>;
4 |
5 | import type { MovieDetails, Movie } from "../types";
6 | import type { PageContextServer } from "vike/types";
7 |
8 | // eslint-disable-next-line @typescript-eslint/no-unused-vars
9 | const data = async (pageContext: PageContextServer) => {
10 | await sleep(700); // Simulate slow network
11 |
12 | const response = await fetch(
13 | "https://brillout.github.io/star-wars/api/films.json",
14 | );
15 | const moviesData = (await response.json()) as MovieDetails[];
16 |
17 | // We remove data we don't need because the data is passed to the client; we should
18 | // minimize what is sent over the network.
19 | const movies = minimize(moviesData);
20 |
21 | return {
22 | movies,
23 | // The page's
24 | title: `${movies.length} Star Wars Movies`,
25 | };
26 | };
27 |
28 | function minimize(movies: MovieDetails[]): Movie[] {
29 | return movies.map((movie) => {
30 | const { title, release_date, id } = movie;
31 | return { title, release_date, id };
32 | });
33 | }
34 |
35 | function sleep(milliseconds: number) {
36 | return new Promise((r) => setTimeout(r, milliseconds));
37 | }
38 |
--------------------------------------------------------------------------------
/examples/vike/pages/star-wars/types.ts:
--------------------------------------------------------------------------------
1 | export type Movie = {
2 | id: string;
3 | title: string;
4 | release_date: string;
5 | };
6 | export type MovieDetails = Movie & {
7 | director: string;
8 | producer: string;
9 | };
10 |
--------------------------------------------------------------------------------
/examples/vike/readme.md:
--------------------------------------------------------------------------------
1 | # Vavite Vike example with Express
2 |
3 | Simple example of using [Vike](https://vike.dev/) with Vavite and Express that shows how to:
4 |
5 | - Integrate Express with Vite's dev server
6 | - Run multiple build steps (for client and server)
7 | - Perform React SSR with Vike
8 |
9 | > [Try on StackBlitz](https://stackblitz.com/github/cyco130/vavite/tree/main/examples/vike)
10 |
11 | Clone with:
12 |
13 | ```bash
14 | npx degit cyco130/vavite/examples/vike
15 | ```
16 |
--------------------------------------------------------------------------------
/examples/vike/renderer/+config.ts:
--------------------------------------------------------------------------------
1 | import type { Config } from "vike/types";
2 |
3 | // https://vike.dev/config
4 | export default {
5 | // https://vike.dev/clientRouting
6 | clientRouting: true,
7 | // https://vike.dev/meta
8 | meta: {
9 | // Define new setting 'title'
10 | title: {
11 | env: { server: true, client: true },
12 | },
13 | // Define new setting 'description'
14 | description: {
15 | env: { server: true },
16 | },
17 | },
18 | hydrationCanBeAborted: true,
19 | } satisfies Config;
20 |
--------------------------------------------------------------------------------
/examples/vike/renderer/+onPageTransitionEnd.ts:
--------------------------------------------------------------------------------
1 | // https://vike.dev/onPageTransitionEnd
2 | export { onPageTransitionEnd };
3 |
4 | import type { OnPageTransitionEndAsync } from "vike/types";
5 |
6 | const onPageTransitionEnd: OnPageTransitionEndAsync =
7 | async (): ReturnType => {
8 | console.log("Page transition end");
9 | document.querySelector("body")!.classList.remove("page-is-transitioning");
10 | };
11 |
--------------------------------------------------------------------------------
/examples/vike/renderer/+onPageTransitionStart.ts:
--------------------------------------------------------------------------------
1 | // https://vike.dev/onPageTransitionStart
2 | export { onPageTransitionStart };
3 |
4 | import type { OnPageTransitionStartAsync } from "vike/types";
5 |
6 | const onPageTransitionStart: OnPageTransitionStartAsync =
7 | async (): ReturnType => {
8 | console.log("Page transition start");
9 | document.querySelector("body")!.classList.add("page-is-transitioning");
10 | };
11 |
--------------------------------------------------------------------------------
/examples/vike/renderer/+onRenderClient.tsx:
--------------------------------------------------------------------------------
1 | // https://vike.dev/onRenderClient
2 | export { onRenderClient };
3 |
4 | import ReactDOM from "react-dom/client";
5 | import { Layout } from "./Layout";
6 | import { getPageTitle } from "./getPageTitle";
7 | import type { OnRenderClientAsync } from "vike/types";
8 |
9 | let root: ReactDOM.Root;
10 | const onRenderClient: OnRenderClientAsync = async (
11 | pageContext,
12 | ): ReturnType => {
13 | const { Page } = pageContext;
14 |
15 | // This onRenderClient() hook only supports SSR, see https://vike.dev/render-modes for how to modify onRenderClient()
16 | // to support SPA
17 | if (!Page)
18 | throw new Error(
19 | "My onRenderClient() hook expects pageContext.Page to be defined",
20 | );
21 |
22 | const container = document.getElementById("react-root");
23 | if (!container) throw new Error("DOM element #react-root not found");
24 |
25 | const page = (
26 |
27 |
28 |
29 | );
30 | if (pageContext.isHydration) {
31 | root = ReactDOM.hydrateRoot(container, page);
32 | } else {
33 | if (!root) {
34 | root = ReactDOM.createRoot(container);
35 | }
36 | root.render(page);
37 | }
38 | document.title = getPageTitle(pageContext);
39 | };
40 |
--------------------------------------------------------------------------------
/examples/vike/renderer/+onRenderHtml.tsx:
--------------------------------------------------------------------------------
1 | // https://vike.dev/onRenderHtml
2 | export { onRenderHtml };
3 |
4 | import ReactDOMServer from "react-dom/server";
5 | import { Layout } from "./Layout";
6 | import { escapeInject, dangerouslySkipEscape } from "vike/server";
7 | import logoUrl from "./logo.svg";
8 | import type { OnRenderHtmlAsync } from "vike/types";
9 | import { getPageTitle } from "./getPageTitle";
10 |
11 | const onRenderHtml: OnRenderHtmlAsync = async (
12 | pageContext,
13 | ): ReturnType => {
14 | const { Page } = pageContext;
15 |
16 | // This onRenderHtml() hook only supports SSR, see https://vike.dev/render-modes for how to modify
17 | // onRenderHtml() to support SPA
18 | if (!Page)
19 | throw new Error(
20 | "My onRenderHtml() hook expects pageContext.Page to be defined",
21 | );
22 |
23 | // Alternativly, we can use an HTML stream, see https://vike.dev/streaming
24 | const pageHtml = ReactDOMServer.renderToString(
25 |
26 |
27 | ,
28 | );
29 |
30 | const title = getPageTitle(pageContext);
31 | const desc =
32 | pageContext.data?.description ||
33 | pageContext.config.description ||
34 | "Demo of using Vike";
35 |
36 | const documentHtml = escapeInject`
37 |
38 |
39 |
40 |
41 |
42 |
43 | ${title}
44 |
45 |
46 | ${dangerouslySkipEscape(pageHtml)}
47 |
48 | `;
49 |
50 | return {
51 | documentHtml,
52 | pageContext: {
53 | // We can add custom pageContext properties here, see https://vike.dev/pageContext#custom
54 | },
55 | };
56 | };
57 |
--------------------------------------------------------------------------------
/examples/vike/renderer/Layout.css:
--------------------------------------------------------------------------------
1 | #sidebar a {
2 | padding: 2px 10px;
3 | margin-left: -10px;
4 | }
5 | #sidebar a.is-active {
6 | background-color: #eee;
7 | }
8 |
--------------------------------------------------------------------------------
/examples/vike/renderer/Layout.tsx:
--------------------------------------------------------------------------------
1 | export { Layout };
2 |
3 | import React from "react";
4 | import logoUrl from "./logo.svg";
5 | import { PageContextProvider } from "./usePageContext";
6 | import { Link } from "./Link";
7 | import type { PageContext } from "vike/types";
8 | import "./css/index.css";
9 | import "./Layout.css";
10 |
11 | function Layout({
12 | children,
13 | pageContext,
14 | }: {
15 | children: React.ReactNode;
16 | pageContext: PageContext;
17 | }) {
18 | return (
19 |
20 |
21 |
22 |
23 |
24 | Welcome
25 | About
26 | Data Fetching
27 |
28 | {children}
29 |
30 |
31 |
32 | );
33 | }
34 |
35 | function Frame({ children }: { children: React.ReactNode }) {
36 | return (
37 |
44 | {children}
45 |
46 | );
47 | }
48 |
49 | function Sidebar({ children }: { children: React.ReactNode }) {
50 | return (
51 |
64 | );
65 | }
66 |
67 | function Content({ children }: { children: React.ReactNode }) {
68 | return (
69 |
70 |
78 | {children}
79 |
80 |
81 | );
82 | }
83 |
84 | function Logo() {
85 | return (
86 |
96 | );
97 | }
98 |
--------------------------------------------------------------------------------
/examples/vike/renderer/Link.tsx:
--------------------------------------------------------------------------------
1 | import { usePageContext } from "./usePageContext";
2 |
3 | export { Link };
4 |
5 | function Link(props: {
6 | href: string;
7 | className?: string;
8 | children: React.ReactNode;
9 | }) {
10 | const pageContext = usePageContext();
11 | const { urlPathname } = pageContext;
12 | const { href } = props;
13 | const isActive =
14 | href === "/" ? urlPathname === href : urlPathname.startsWith(href);
15 | const className = [props.className, isActive && "is-active"]
16 | .filter(Boolean)
17 | .join(" ");
18 | return ;
19 | }
20 |
--------------------------------------------------------------------------------
/examples/vike/renderer/css/code.css:
--------------------------------------------------------------------------------
1 | code {
2 | font-family: monospace;
3 | background-color: #eaeaea;
4 | padding: 3px 5px;
5 | border-radius: 4px;
6 | }
7 |
--------------------------------------------------------------------------------
/examples/vike/renderer/css/index.css:
--------------------------------------------------------------------------------
1 | @import "./reset.css";
2 | @import "./links.css";
3 | @import "./code.css";
4 | @import "./page-transition-loading-animation.css";
5 |
--------------------------------------------------------------------------------
/examples/vike/renderer/css/links.css:
--------------------------------------------------------------------------------
1 | a {
2 | text-decoration: none;
3 | }
4 |
--------------------------------------------------------------------------------
/examples/vike/renderer/css/page-transition-loading-animation.css:
--------------------------------------------------------------------------------
1 | #page-container {
2 | position: relative;
3 | width: 100%;
4 | }
5 | #page-container::before {
6 | content: "";
7 | position: absolute;
8 | width: 100%;
9 | height: 100%;
10 | z-index: 999;
11 | background: no-repeat url("./page-transition-loading-animation/loading.svg");
12 | background-size: 100px;
13 | background-position: center center;
14 | pointer-events: none;
15 | opacity: 0;
16 | }
17 | body.page-is-transitioning #page-container::before {
18 | opacity: 1;
19 | }
20 | #page-content,
21 | #page-container::before {
22 | transition: opacity 0.5s ease-in-out;
23 | }
24 | body.page-is-transitioning #page-content {
25 | opacity: 0.17;
26 | }
27 |
--------------------------------------------------------------------------------
/examples/vike/renderer/css/page-transition-loading-animation/loading.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/examples/vike/renderer/css/reset.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: sans-serif;
4 | }
5 | * {
6 | box-sizing: border-box;
7 | }
8 |
--------------------------------------------------------------------------------
/examples/vike/renderer/getPageTitle.ts:
--------------------------------------------------------------------------------
1 | export { getPageTitle };
2 |
3 | import type { PageContext } from "vike/types";
4 |
5 | function getPageTitle(pageContext: PageContext): string {
6 | const title =
7 | // Title defined dynamically by data()
8 | pageContext.data?.title ||
9 | // Title defined statically by /pages/some-page/+title.js (or by `export default { title }` in /pages/some-page/+config.js)
10 | // The setting 'pageContext.config.title' is a custom setting we defined at ./+config.ts
11 | pageContext.config.title ||
12 | "Vike Demo";
13 | return title;
14 | }
15 |
--------------------------------------------------------------------------------
/examples/vike/renderer/types.ts:
--------------------------------------------------------------------------------
1 | // https://vike.dev/pageContext#typescript
2 | declare global {
3 | namespace Vike {
4 | interface PageContext {
5 | Page: () => React.ReactElement;
6 | data?: {
7 | /** Value for defined dynmically by by /pages/some-page/+data.js */
8 | title?: string;
9 | /** Value for defined dynmically */
10 | description?: string;
11 | };
12 | config: {
13 | /** Value for defined statically by /pages/some-page/+title.js (or by `export default { title }` in /pages/some-page/+config.js) */
14 | title?: string;
15 | /** Value for defined statically */
16 | description?: string;
17 | };
18 | /** https://vike.dev/render */
19 | abortReason?: string;
20 | }
21 | }
22 | }
23 |
24 | // Tell TypeScript this file isn't an ambient module
25 | export {};
26 |
--------------------------------------------------------------------------------
/examples/vike/renderer/useData.ts:
--------------------------------------------------------------------------------
1 | // https://vike.dev/useData
2 | export { useData };
3 |
4 | import { usePageContext } from "./usePageContext";
5 |
6 | /** https://vike.dev/useData */
7 | function useData() {
8 | const { data } = usePageContext();
9 | return data as Data;
10 | }
11 |
--------------------------------------------------------------------------------
/examples/vike/renderer/usePageContext.tsx:
--------------------------------------------------------------------------------
1 | // https://vike.dev/usePageContext
2 | // eslint-disable-next-line react-refresh/only-export-components
3 | export { usePageContext };
4 | export { PageContextProvider };
5 |
6 | import React, { useContext } from "react";
7 | import type { PageContext } from "vike/types";
8 |
9 | const Context = React.createContext(
10 | undefined as unknown as PageContext,
11 | );
12 |
13 | function PageContextProvider({
14 | pageContext,
15 | children,
16 | }: {
17 | pageContext: PageContext;
18 | children: React.ReactNode;
19 | }) {
20 | return {children};
21 | }
22 |
23 | /** https://vike.dev/usePageContext */
24 | function usePageContext() {
25 | const pageContext = useContext(Context);
26 | return pageContext;
27 | }
28 |
--------------------------------------------------------------------------------
/examples/vike/server/index.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | import express from "express";
4 | import { renderPage } from "vike/server";
5 | import viteDevServer from "vavite/vite-dev-server";
6 |
7 | const app = express();
8 |
9 | if (!viteDevServer) {
10 | // Serve static files in production
11 | app.use(express.static("dist/client"));
12 | }
13 |
14 | // Vike middleware. It should always be our last middleware (because it's a
15 | // catch-all middleware superseding any middleware placed after it).
16 | app.get("*", async (req, res, next) => {
17 | const pageContextInit = {
18 | urlOriginal: req.originalUrl,
19 | headersOriginal: req.headers,
20 | };
21 | const pageContext = await renderPage(pageContextInit);
22 | if (pageContext.errorWhileRendering) {
23 | // Install error tracking here, see https://vike.dev/errors
24 | }
25 | const { httpResponse } = pageContext;
26 | if (!httpResponse) {
27 | return next();
28 | } else {
29 | const { body, statusCode, headers, earlyHints } = httpResponse;
30 | if (res.writeEarlyHints)
31 | res.writeEarlyHints({ link: earlyHints.map((e) => e.earlyHintLink) });
32 | headers.forEach(([name, value]) => res.setHeader(name, value));
33 | res.status(statusCode);
34 | // For HTTP streams use httpResponse.pipe() instead, see https://vike.dev/streaming
35 | res.send(body);
36 | }
37 | });
38 |
39 | export default app;
40 |
--------------------------------------------------------------------------------
/examples/vike/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "strict": true,
4 | "module": "ES2020",
5 | "moduleResolution": "Bundler",
6 | "customConditions": ["import"],
7 | "target": "ES2017",
8 | "lib": ["DOM", "DOM.Iterable", "ESNext"],
9 | "jsx": "react-jsx",
10 | "esModuleInterop": true,
11 | "skipLibCheck": true
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/examples/vike/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "vite";
2 | import react from "@vitejs/plugin-react";
3 | import ssr from "vike/plugin";
4 | import { vavite } from "vavite";
5 |
6 | export default defineConfig({
7 | buildSteps: [
8 | {
9 | name: "client",
10 | },
11 | {
12 | name: "server",
13 | config: {
14 | build: { ssr: true },
15 | },
16 | },
17 | ],
18 |
19 | plugins: [
20 | vavite({
21 | handlerEntry: "/server/index.ts",
22 | serveClientAssetsInDev: true,
23 | }),
24 | react(),
25 | ssr({ disableAutoFullBuild: true }),
26 | ],
27 | });
28 |
--------------------------------------------------------------------------------
/examples/ws/.stackblitzrc:
--------------------------------------------------------------------------------
1 | {
2 | "installDependencies": true,
3 | "startCommand": "npm run dev"
4 | }
5 |
--------------------------------------------------------------------------------
/examples/ws/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Websocket example
7 |
8 |
9 | Hello
10 |
11 | Test route
12 |
13 |
14 |
15 |
16 |
17 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/examples/ws/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@vavite/example-reloader-ws",
3 | "type": "module",
4 | "private": true,
5 | "scripts": {
6 | "start": "node dist",
7 | "dev": "vite",
8 | "build": "vite build --ssr --mode=production"
9 | },
10 | "files": [
11 | "dist"
12 | ],
13 | "devDependencies": {
14 | "@types/express": "^5.0.0",
15 | "@types/express-ws": "^3.0.5",
16 | "@types/node": "^18.19.67",
17 | "@types/ws": "^8.5.13",
18 | "typescript": "^5.7.2",
19 | "vavite": "5.1.0",
20 | "vite": "^5.4.11"
21 | },
22 | "dependencies": {
23 | "express": "^4.21.1",
24 | "express-ws": "^5.0.2",
25 | "ws": "^8.18.0"
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/examples/ws/readme.md:
--------------------------------------------------------------------------------
1 | # Vavite WebSocket example
2 |
3 | WebSocket example.
4 |
5 | > [Try on StackBlitz](https://stackblitz.com/github/cyco130/vavite/tree/main/examples/ws)
6 |
7 | Clone with:
8 |
9 | ```bash
10 | npx degit cyco130/vavite/examples/ws
11 | ```
12 |
--------------------------------------------------------------------------------
/examples/ws/server.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | import express from "express";
4 | import { WebSocketServer } from "ws";
5 | import type { Server, IncomingMessage } from "node:http";
6 | import type { Duplex } from "node:stream";
7 | import html from "./index.html?raw";
8 |
9 | const app = express();
10 |
11 | // Since we don't have access to the server instance, we will lazily add the
12 | // upgrade listener on the first request in the following middleware.
13 | // This is fine for dev since our server code won't run until the first request.
14 | // If it is unacceptable for prod, you can provide a custom server entry and
15 | // add the upgrade listener there (and guard this code with an environment check).
16 | let isFirstRequest = true;
17 | app.use((req, _res, next) => {
18 | if (!isFirstRequest) {
19 | next();
20 | return;
21 | }
22 | isFirstRequest = false;
23 |
24 | // Types are missing but the server instance can be accessed via the socket.
25 | const server: Server = (req.socket as any).server;
26 |
27 | // Vite can reload the server file on changes, so we need to remove the
28 | // previous upgrade listener before adding a new one to avoid multiple
29 | // listeners. We save the previous listener on the global object so that
30 | // it survives reloads.
31 | if ((global as any).__previousUpgradeListener) {
32 | app.off("upgrade", (global as any).__previousUpgradeListener);
33 | }
34 |
35 | // Reuse the WebSocket server instance after reload if it already exists.
36 | const wss: WebSocketServer =
37 | (global as any).__previousWss || new WebSocketServer({ noServer: true });
38 | (global as any).__previousWss = wss;
39 |
40 | function upgradeListener(req: IncomingMessage, socket: Duplex, head: Buffer) {
41 | // Vite uses the / path for HMR so we need to use some other path for our
42 | // WebSocket server.
43 | if (req.url !== "/ws") return;
44 |
45 | wss.handleUpgrade(req, socket, head, (ws) => {
46 | ws.emit("connection", ws, req);
47 |
48 | ws.on("message", (message) => {
49 | console.log(`Received message: ${message}`);
50 | // Echo the message back to the client.
51 | ws.send(`Hello, you sent: ${message}`);
52 | });
53 | });
54 | }
55 |
56 | // Add the upgrade listener to the server.
57 | server.on("upgrade", upgradeListener);
58 |
59 | // Save the upgrade listener so we can remove it later.
60 | (global as any).__previousUpgradeListener = upgradeListener;
61 | (global as any).__previousWss = wss;
62 |
63 | next();
64 | });
65 |
66 | app.get("/", (_req, res) => {
67 | res.send(html);
68 | });
69 |
70 | export default app;
71 |
--------------------------------------------------------------------------------
/examples/ws/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es2020",
4 | "module": "ESNext",
5 | "esModuleInterop": true,
6 | "forceConsistentCasingInFileNames": true,
7 | "strict": true,
8 | "skipLibCheck": true,
9 | "moduleResolution": "Bundler",
10 | "customConditions": ["import"]
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/examples/ws/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "vite";
2 | import { vavite } from "vavite";
3 |
4 | export default defineConfig({
5 | appType: "custom",
6 | plugins: [
7 | vavite({
8 | handlerEntry: "/server.ts",
9 | serveClientAssetsInDev: true,
10 | }),
11 | ],
12 | });
13 |
--------------------------------------------------------------------------------
/lint-staged.config.mjs:
--------------------------------------------------------------------------------
1 | export default {
2 | "*": "prettier --ignore-unknown --write",
3 | };
4 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vavite-workspace-root",
3 | "private": "true",
4 | "scripts": {
5 | "dev": "pnpm -r --parallel --filter \"./packages/*\" run dev",
6 | "build": "pnpm -r --filter \"./packages/*\" run build",
7 | "prepare": "husky",
8 | "precommit": "lint-staged",
9 | "test": "pnpm run \"/^(cq|ci)$/\"",
10 | "cq": "pnpm run /^test:/",
11 | "ci": "pnpm -r --stream --workspace-concurrency=1 run ci",
12 | "test:packages": "pnpm -r --stream run test",
13 | "test:prettier": "prettier --check --ignore-path .gitignore --ignore-unknown . '!pnpm-lock.yaml'",
14 | "format": "prettier --ignore-path .gitignore --ignore-unknown . '!pnpm-lock.yaml' --write"
15 | },
16 | "devDependencies": {
17 | "husky": "^9.1.7",
18 | "lint-staged": "^15.2.10",
19 | "prettier": "^3.4.1",
20 | "publint": "^0.2.12",
21 | "typescript": "^5.7.2"
22 | },
23 | "pnpm": {
24 | "peerDependencyRules": {
25 | "ignoreMissing": [
26 | "rollup"
27 | ]
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/packages/connect/eslint.config.js:
--------------------------------------------------------------------------------
1 | import config from "@cyco130/eslint-config/node";
2 | import path from "node:path";
3 | import { fileURLToPath } from "node:url";
4 |
5 | const tsconfigRootDir =
6 | // @ts-expect-error: import.meta.dirname requires v20.11.0 or v21.2.0
7 | import.meta.dirname ?? path.dirname(fileURLToPath(import.meta.url));
8 |
9 | /** @type {typeof config} */
10 | export default [
11 | ...config,
12 | {
13 | ignores: ["dist/", "node_modules/"],
14 | },
15 | {
16 | languageOptions: {
17 | parserOptions: {
18 | projectService: true,
19 | tsconfigRootDir,
20 | },
21 | },
22 | },
23 | ];
24 |
--------------------------------------------------------------------------------
/packages/connect/lint-staged.config.mjs:
--------------------------------------------------------------------------------
1 | export default {
2 | "**/*.ts?(x)": [
3 | () => "tsc -p tsconfig.json --noEmit",
4 | "eslint --max-warnings 0 --ignore-pattern dist",
5 | ],
6 | "*": "prettier --ignore-unknown --write",
7 | };
8 |
--------------------------------------------------------------------------------
/packages/connect/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@vavite/connect",
3 | "version": "5.1.0",
4 | "type": "module",
5 | "exports": {
6 | ".": {
7 | "import": "./dist/index.js"
8 | },
9 | "./entry-standalone": {
10 | "import": "./dist/entry-standalone.js"
11 | },
12 | "./entry-standalone-bundled-sirv": {
13 | "import": "./dist/entry-standalone-bundled-sirv.js"
14 | },
15 | "./entry-standalone-imported-sirv": {
16 | "import": "./dist/entry-standalone-imported-sirv.js"
17 | }
18 | },
19 | "typesVersions": {
20 | "*": {
21 | "*": [
22 | "dist/*.d.ts"
23 | ]
24 | }
25 | },
26 | "files": [
27 | "dist",
28 | "*.d.ts"
29 | ],
30 | "description": "Vite plugin for developing Node.js server applications",
31 | "author": "Fatih Aygün ",
32 | "repository": "github:cyco130/vavite",
33 | "license": "MIT",
34 | "scripts": {
35 | "build": "tsup",
36 | "dev": "tsup --watch",
37 | "prepack": "rm -rf dist && pnpm build",
38 | "test": "pnpm run test:typecheck && pnpm run test:lint && pnpm run test:package",
39 | "test:typecheck": "tsc -p tsconfig.json --noEmit",
40 | "test:lint": "eslint . --max-warnings 0 --ignore-pattern dist",
41 | "test:package": "publint --strict"
42 | },
43 | "peerDependencies": {
44 | "vite": "^2.8.1 || 3 || 4 || 5 || 6"
45 | },
46 | "devDependencies": {
47 | "@cyco130/eslint-config": "^5.0.1",
48 | "eslint": "^9.16.0",
49 | "sirv": "^3.0.0",
50 | "tsup": "^8.3.5",
51 | "typescript": "^5.7.2",
52 | "vite": "^5.4.11"
53 | },
54 | "dependencies": {
55 | "@types/node": "^18.19.67"
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/packages/connect/readme.md:
--------------------------------------------------------------------------------
1 | # @vavite/connect
2 |
3 | `@vavite/connect` is a [Vite](https://vitejs.dev) plugin for developing and building server-side Node.js applications in the form of a [Connect](https://github.com/senchalabs/connect)-compatible middleware function `(req, res, next)`. For development, the middleware is plugged into Vite's development server. For production, `@vavite/connect` can build a middleware, or a standalone server. You can also provide a custom server entry to be processed with Vite so that you can use Vite-specific features such as `import.meta.env`.
4 |
5 | `@vavite/connect` does _not_ support custom servers during development, check out the [`@vavite/reloader`](../reloader) package if that's your requirement.
6 |
7 | ## Installation and usage
8 |
9 | Install `vite` and `@vavite/connect` as development dependencies (`npm install --save-dev vite @vavite/connect`) and add `@vavite/connect` to your Vite config:
10 |
11 | ```ts
12 | import { defineConfig } from "vite";
13 | import { vaviteConnect } from "@vavite/connect";
14 |
15 | export default defineConfig({
16 | plugins: [
17 | vaviteConnect({
18 | // Options, see below
19 | }),
20 | ],
21 | });
22 | ```
23 |
24 | Then create an `handler.ts` (or `.js`) file in the root of your project that default exports a function that takes `(req, res, next)` and handles the request. For example:
25 |
26 | ```ts
27 | import type { IncomingMessage, ServerResponse } from "node:http";
28 | import type { SirvOptions } from "@vavite/connect";
29 |
30 | export default function handler(
31 | req: IncomingMessage,
32 | res: ServerResponse,
33 | next: () => void,
34 | ) {
35 | if (req.url === "/") {
36 | res.setHeader("Content-Type", "text/html; charset=utf-8");
37 | // This is plain http.Server, not Express, so res.send() is
38 | // not available, use res.write() and res.end()
39 | res.end("Hello, world!
");
40 | } else {
41 | next();
42 | }
43 | }
44 |
45 | export const sirvOptions: SirvOptions = {
46 | // sirv options, optional, see below.
47 | };
48 | ```
49 |
50 | Now you can start the development server with `npx vite` and visit `http://localhost:3000/` to see the result.
51 |
52 | You can build a standalone application with the entry point in `dist/server.js` with `npx vite build --ssr`. The application will listen to the host and port specified in the `HOST` and `PORT` environment variables, or `0.0.0.0:3000` by default.
53 |
54 | ## Options
55 |
56 | ### `handlerEntry: string = "/handler"`
57 |
58 | Handler entry module. The default value `"/handler"` will resolve to `handler.js`, `handler.ts` etc. in your project root.
59 |
60 | ### `serveClientAssetsInDev: boolean = false`
61 |
62 | Whether to serve client assets in development mode. Enable when developing full-stack or server-side rendering applications.
63 |
64 | > TODO: Currently HTML files are _not_ served regardless of this setting. An option to enable it is being considered.
65 |
66 | ### `standalone: boolean = true`
67 |
68 | Whether to build a standalone application instead of a Connect-compatible middleware function to be used with a custom server.
69 |
70 | ### `clientAssetsDir: string | null = null`
71 |
72 | Directory that contains client assets to be served using the [`sirv`](https://github.com/lukeed/sirv) package. By default it's set to `null` to exclude `sirv` from the build.
73 |
74 | If you do provide a client assets directory, you can export `sirvOptions` from your handler entry to customize the options. The types for the options are exported from `@vavite/connect` as `SirvOptions` so you don't have to install `sirv` yourself.
75 |
76 | ### `bundleSirv: boolean = true`
77 |
78 | Whether `sirv` should be bundled with the application or simply be imported. You must install `sirv` as a production dependency if you set this to `false`.
79 |
80 | ## Custom server entry
81 |
82 | > Reminder: This section applies to production build only. `@vavite/connect` does _not_ support custom servers during development, check out the [`@vavite/reloader`](../reloader) package for that use case.
83 |
84 | You can provide a custom server for production with or without processing it with Vite. In this case, you will import the handler in the appropriate way (see below) and add it to your application as a middleware function. For example, in Express it would be something like `app.use(handler)`.
85 |
86 | For a custom server entry processed with Vite, build your application with `npx vite build --ssr server.js` where `server.js` is your custom server entry. The `standalone` option has no effect when using a custom server entry like this and `sirv` middleware will not be injected, you will have to handle serving client assets yourself.
87 |
88 | If you don't want to process your custom server with Vite -maybe because you have an existing application with separate tooling- you should set `standalone` to `false` and import the handler from `./dist/handler.js` (or whatever your build output path is).
89 |
--------------------------------------------------------------------------------
/packages/connect/src/entry-standalone-with-sirv.ts:
--------------------------------------------------------------------------------
1 | import { createServer } from "node:http";
2 | import sirv, { RequestHandler, Options } from "sirv";
3 |
4 | let handleExports: {
5 | default: RequestHandler;
6 | sirvOptions?: Options;
7 | };
8 |
9 | let sirvHandler: RequestHandler;
10 |
11 | async function init() {
12 | // @ts-expect-error: This is a virtual module
13 | handleExports = await import("/virtual:vavite-connect-handler");
14 |
15 | sirvHandler = sirv(
16 | // @ts-expect-error: This will be defined by the plugin
17 | __VAVITE_CLIENT_BUILD_OUTPUT_DIR,
18 | handleExports.sirvOptions,
19 | );
20 |
21 | const PORT = Number(process.env.PORT) || 3000;
22 | const HOST = process.env.HOST || "localhost";
23 |
24 | createServer((req, res) =>
25 | sirvHandler(req, res, () => {
26 | handleExports.default(req, res, () => {
27 | if (!res.writableEnded) {
28 | res.statusCode = 404;
29 | res.end();
30 | }
31 | });
32 | }),
33 | ).listen(PORT, HOST, () => {
34 | // eslint-disable-next-line no-console
35 | console.log(`Server listening on http://${HOST}:${PORT}`);
36 | });
37 | }
38 |
39 | init().catch((error) => {
40 | console.error(error);
41 | process.exit(1);
42 | });
43 |
--------------------------------------------------------------------------------
/packages/connect/src/entry-standalone.ts:
--------------------------------------------------------------------------------
1 | import { createServer } from "node:http";
2 | // @ts-expect-error: This is a virtual module
3 | import handler from "/virtual:vavite-connect-handler";
4 |
5 | const PORT = Number(process.env.PORT) || 3000;
6 | const HOST = process.env.HOST || "localhost";
7 |
8 | createServer((req, res) =>
9 | handler(req, res, () => {
10 | if (!res.writableEnded) {
11 | res.statusCode = 404;
12 | res.end();
13 | }
14 | }),
15 | ).listen(PORT, HOST, () => {
16 | // eslint-disable-next-line no-console
17 | console.log(`Server listening on http://${HOST}:${PORT}`);
18 | });
19 |
--------------------------------------------------------------------------------
/packages/connect/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "noEmit": true,
4 | "target": "es2020",
5 | "module": "ESNext",
6 | "esModuleInterop": true,
7 | "forceConsistentCasingInFileNames": true,
8 | "strict": true,
9 | "skipLibCheck": true,
10 | "moduleResolution": "Bundler",
11 | "customConditions": ["import"],
12 | "checkJs": true
13 | },
14 | "exclude": ["node_modules", "dist"]
15 | }
16 |
--------------------------------------------------------------------------------
/packages/connect/tsup.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "tsup";
2 |
3 | export default defineConfig([
4 | // Plugin
5 | {
6 | entry: ["./src/index.ts"],
7 | format: ["esm"],
8 | platform: "node",
9 | target: "node18",
10 | dts: true,
11 | },
12 | // Standalone entry
13 | {
14 | entry: {
15 | "entry-standalone": "./src/entry-standalone.ts",
16 | "entry-standalone-imported-sirv": "./src/entry-standalone-with-sirv.ts",
17 | },
18 | format: ["esm"],
19 | platform: "node",
20 | target: "esnext",
21 | shims: false,
22 | external: ["sirv", "/virtual:vavite-connect-handler"],
23 | },
24 | {
25 | entry: {
26 | "entry-standalone-bundled-sirv": "./src/entry-standalone-with-sirv.ts",
27 | },
28 | format: ["esm"],
29 | platform: "node",
30 | target: "esnext",
31 | shims: false,
32 | external: ["/virtual:vavite-connect-handler"],
33 | noExternal: ["sirv"],
34 | },
35 | ]);
36 |
--------------------------------------------------------------------------------
/packages/expose-vite-dev-server/eslint.config.js:
--------------------------------------------------------------------------------
1 | import config from "@cyco130/eslint-config/node";
2 | import path from "node:path";
3 | import { fileURLToPath } from "node:url";
4 |
5 | const tsconfigRootDir =
6 | // @ts-expect-error: import.meta.dirname requires v20.11.0 or v21.2.0
7 | import.meta.dirname ?? path.dirname(fileURLToPath(import.meta.url));
8 |
9 | /** @type {typeof config} */
10 | export default [
11 | ...config,
12 | {
13 | ignores: ["dist/", "node_modules/"],
14 | },
15 | {
16 | languageOptions: {
17 | parserOptions: {
18 | projectService: true,
19 | tsconfigRootDir,
20 | },
21 | },
22 | },
23 | ];
24 |
--------------------------------------------------------------------------------
/packages/expose-vite-dev-server/lint-staged.config.mjs:
--------------------------------------------------------------------------------
1 | export default {
2 | "**/*.ts?(x)": [
3 | () => "tsc -p tsconfig.json --noEmit",
4 | "eslint --max-warnings 0 --ignore-pattern dist",
5 | ],
6 | "*": "prettier --ignore-unknown --write",
7 | };
8 |
--------------------------------------------------------------------------------
/packages/expose-vite-dev-server/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@vavite/expose-vite-dev-server",
3 | "version": "5.1.0",
4 | "type": "module",
5 | "files": [
6 | "dist",
7 | "*.d.ts"
8 | ],
9 | "exports": {
10 | ".": {
11 | "import": "./dist/index.js"
12 | },
13 | "./vite-dev-server": {
14 | "import": "./dist/vite-dev-server.js"
15 | }
16 | },
17 | "typesVersions": {
18 | "*": {
19 | "*": [
20 | "dist/*.d.ts"
21 | ]
22 | }
23 | },
24 | "description": "Vite plugin for exposing Vite's development server to user code",
25 | "author": "Fatih Aygün ",
26 | "repository": "github:cyco130/vavite",
27 | "license": "MIT",
28 | "scripts": {
29 | "build": "tsup",
30 | "dev": "tsup --watch",
31 | "prepack": "rm -rf dist && pnpm build",
32 | "test": "pnpm run test:typecheck && pnpm run test:lint && pnpm run test:package",
33 | "test:typecheck": "tsc -p tsconfig.json --noEmit",
34 | "test:lint": "eslint . --max-warnings 0 --ignore-pattern dist",
35 | "test:package": "publint --strict"
36 | },
37 | "peerDependencies": {
38 | "vite": "^2.8.1 || 3 || 4 || 5 || 6"
39 | },
40 | "devDependencies": {
41 | "@cyco130/eslint-config": "^5.0.1",
42 | "@types/node": "^18.19.67",
43 | "eslint": "^9.16.0",
44 | "tsup": "^8.3.5",
45 | "typescript": "^5.7.2",
46 | "vite": "^5.4.11"
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/packages/expose-vite-dev-server/readme.md:
--------------------------------------------------------------------------------
1 | # @vavite/expose-vite-dev-server
2 |
3 | `@vavite/expose-vite-dev-server` is a [Vite](https://vitejs.dev) plugin for exposing the Vite development server to user code during development. It can be used for accessing Vite development server methods such as `ssrFixStacktrace` and `transformIndexHtml` in [`vavite`](../vavite), [`@vavite/connect`](../connect), and [`@vavite/reloader`](../reloader) applications.
4 |
5 | ## Installation and usage
6 |
7 | Install `vite` and `@vavite/expose-vite-dev-server` as development dependencies (`npm install --save-dev vite @vavite/expose-vite-dev-server`) and add `@vavite/expose-vite-dev-server` plugin to your Vite config:
8 |
9 | ```ts
10 | import { defineConfig } from "vite";
11 | import exposeViteDevServer from "@vavite/expose-vite-dev-server";
12 |
13 | export default defineConfig({
14 | plugins: [exposeViteDevServer()],
15 | });
16 | ```
17 |
18 | Now you can import the dev server in your application with `import viteDevServer from @vavite/expose-vite-dev-server/vite-dev-server` during development. In production, it will be available but its value will be `undefined`.
19 |
--------------------------------------------------------------------------------
/packages/expose-vite-dev-server/src/index.ts:
--------------------------------------------------------------------------------
1 | import type { Plugin, SSROptions, UserConfig, ViteDevServer } from "vite";
2 | import crypto from "node:crypto";
3 |
4 | export default function vaviteDevServerPlugin(): Plugin {
5 | let dev: boolean;
6 | let viteDevServer: ViteDevServer | undefined;
7 | let globalSymbol: string;
8 |
9 | function getModuleContents() {
10 | return `export default ${globalSymbol}`;
11 | }
12 |
13 | return {
14 | name: "@vavite/expose-vite-dev-server",
15 |
16 | enforce: "pre",
17 |
18 | buildStart() {
19 | globalSymbol =
20 | "VAVITE_VITE_DEV_SERVER_" + crypto.randomBytes(20).toString("hex");
21 | (global as any)[globalSymbol] = viteDevServer;
22 | },
23 |
24 | closeBundle() {
25 | delete (global as any)[globalSymbol];
26 | },
27 |
28 | resolveId(source, _importer, options) {
29 | if (
30 | (source === "@vavite/expose-vite-dev-server/vite-dev-server" ||
31 | source === "vavite/vite-dev-server" ||
32 | source === "virtual:vavite-vite-dev-server") &&
33 | dev &&
34 | options.ssr
35 | ) {
36 | return "virtual:vavite-vite-dev-server";
37 | }
38 | },
39 |
40 | load(id, options) {
41 | if (id === "virtual:vavite-vite-dev-server" && dev && options?.ssr) {
42 | return getModuleContents();
43 | }
44 | },
45 |
46 | config(_config, env) {
47 | dev = env.command === "serve";
48 |
49 | const out: UserConfig & { ssr: SSROptions } = {
50 | ssr: {
51 | noExternal: ["vavite"],
52 | optimizeDeps: {
53 | exclude: [
54 | "@vavite/expose-vite-dev-server",
55 | "virtual:vavite-vite-dev-server",
56 | ],
57 | },
58 | },
59 | optimizeDeps: {
60 | exclude: [
61 | "@vavite/expose-vite-dev-server",
62 | "virtual:vavite-vite-dev-server",
63 | ],
64 | },
65 | };
66 |
67 | return out;
68 | },
69 |
70 | configureServer(server) {
71 | viteDevServer = server;
72 | },
73 | };
74 | }
75 |
--------------------------------------------------------------------------------
/packages/expose-vite-dev-server/src/vite-dev-server.ts:
--------------------------------------------------------------------------------
1 | import type { ViteDevServer } from "vite";
2 |
3 | const viteDevServer: ViteDevServer | undefined = undefined;
4 |
5 | export default viteDevServer;
6 |
--------------------------------------------------------------------------------
/packages/expose-vite-dev-server/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "noEmit": true,
4 | "target": "es2020",
5 | "module": "ESNext",
6 | "esModuleInterop": true,
7 | "forceConsistentCasingInFileNames": true,
8 | "strict": true,
9 | "skipLibCheck": true,
10 | "moduleResolution": "Bundler",
11 | "customConditions": ["import"],
12 | "checkJs": true
13 | },
14 | "exclude": ["node_modules", "dist"]
15 | }
16 |
--------------------------------------------------------------------------------
/packages/expose-vite-dev-server/tsup.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "tsup";
2 |
3 | export default defineConfig([
4 | {
5 | entry: ["./src/index.ts"],
6 | format: ["esm"],
7 | platform: "node",
8 | target: "node18",
9 | dts: true,
10 | },
11 | {
12 | entry: ["./src/vite-dev-server.ts"],
13 | format: ["esm"],
14 | platform: "node",
15 | target: "node18",
16 | dts: true,
17 | },
18 | ]);
19 |
--------------------------------------------------------------------------------
/packages/multibuild-cli/cli.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | import "./dist/index.js";
3 |
--------------------------------------------------------------------------------
/packages/multibuild-cli/eslint.config.js:
--------------------------------------------------------------------------------
1 | import config from "@cyco130/eslint-config/node";
2 | import path from "node:path";
3 | import { fileURLToPath } from "node:url";
4 |
5 | const tsconfigRootDir =
6 | // @ts-expect-error: import.meta.dirname requires v20.11.0 or v21.2.0
7 | import.meta.dirname ?? path.dirname(fileURLToPath(import.meta.url));
8 |
9 | /** @type {typeof config} */
10 | export default [
11 | ...config,
12 | {
13 | ignores: ["dist/", "node_modules/", "cli.js"],
14 | },
15 | {
16 | languageOptions: {
17 | parserOptions: {
18 | projectService: true,
19 | tsconfigRootDir,
20 | },
21 | },
22 | },
23 | ];
24 |
--------------------------------------------------------------------------------
/packages/multibuild-cli/index.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | export {};
4 |
--------------------------------------------------------------------------------
/packages/multibuild-cli/lint-staged.config.mjs:
--------------------------------------------------------------------------------
1 | export default {
2 | "**/*.ts?(x)": [
3 | () => "tsc -p tsconfig.json --noEmit",
4 | "eslint --max-warnings 0 --ignore-pattern dist",
5 | ],
6 | "*": "prettier --ignore-unknown --write",
7 | };
8 |
--------------------------------------------------------------------------------
/packages/multibuild-cli/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@vavite/multibuild-cli",
3 | "version": "5.1.0",
4 | "type": "module",
5 | "bin": {
6 | "vavite-multibuild": "./cli.js"
7 | },
8 | "files": [
9 | "dist",
10 | "cli.js",
11 | "index.d.ts"
12 | ],
13 | "description": "CLI command for orchestrating multiple Vite builds",
14 | "author": "Fatih Aygün ",
15 | "repository": "github:cyco130/vavite",
16 | "license": "MIT",
17 | "scripts": {
18 | "build": "tsup",
19 | "dev": "tsup --watch",
20 | "prepack": "rm -rf dist && pnpm build",
21 | "test": "pnpm run test:typecheck && pnpm run test:lint && pnpm run test:package",
22 | "test:typecheck": "tsc -p tsconfig.json --noEmit",
23 | "test:lint": "eslint . --max-warnings 0 --ignore-pattern dist",
24 | "test:package": "publint --strict"
25 | },
26 | "peerDependencies": {
27 | "vite": "^2.8.1 || 3 || 4 || 5 || 6"
28 | },
29 | "devDependencies": {
30 | "@cyco130/eslint-config": "^5.0.1",
31 | "eslint": "^9.16.0",
32 | "tsup": "^8.3.5",
33 | "typescript": "^5.7.2",
34 | "vite": "^5.4.11"
35 | },
36 | "dependencies": {
37 | "@types/node": "^18.19.67",
38 | "@vavite/multibuild": "workspace:*",
39 | "cac": "^6.7.14",
40 | "picocolors": "^1.1.1"
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/packages/multibuild-cli/readme.md:
--------------------------------------------------------------------------------
1 | # @vavite/multibuild-cli
2 |
3 | `@vavite/multibuild-cli` is a tool for orchestrating multiple [Vite](https://vitejs.dev) builds. This package is the CLI binary, check out [`@vavite/multibuild`](../multibuild) for the JavaScript API.
4 |
5 | Developing applications that perform server-side rendering (SSR) with Vite requires two separate build steps: one for the client and one for the server. This library allows you to run both builds with a single command and customize the configuration for each build.
6 |
7 | We're hoping that, eventually, [this feature will be implemented in Vite itself](https://github.com/vitejs/vite/issues/5936). This package exists to provide a workaround until then.
8 |
9 | ## Usage
10 |
11 | `@vavite/multibuild` extends the Vite configuration with a `buildSteps` property, which is an array of build step definitions. A build step definition is an object with a `name` property (which is simply a string naming the build step), and an optional `config` property which will be merged into the Vite configuration for the build step. For example, a client build followed by a server build can be defined like this:
12 |
13 | ```ts
14 | import { defineConfig } from "vite";
15 |
16 | export default defineConfig({
17 | buildSteps: [
18 | {
19 | name: "client",
20 | config: {
21 | build: {
22 | outDir: "dist/client",
23 | rollupOptions: {
24 | // Client entry
25 | input: "/client",
26 | },
27 | },
28 | },
29 | },
30 | {
31 | name: "server",
32 | config: {
33 | build: {
34 | // Server entry
35 | ssr: "/server",
36 | outDir: "dist/server",
37 | },
38 | },
39 | },
40 | ],
41 | });
42 | ```
43 |
44 | You can then run the `vavite-multibuild` command as a drop-in replacement for `vite build`. This will call `resolveConfig` with the `mode` parameter set to `"multibuild"` to extract the build steps. Setting `buildSteps` in subsequent steps has no effect.
45 |
46 | ## Sharing information between builds
47 |
48 | `@vavite/multibuild-cli` will call the `buildStepStart` hook on each plugin when a build step starts and pass it information about the current step and data forwarded from the previous step. The `buildStepEnd` hook will be called when the build step ends and its return value will be forwarded to the next step. If a promise is returned, it will be awaited first.
49 |
50 | If no build steps are defined, `buildStepStart` and `buildStepEnd` will not be called.
51 |
--------------------------------------------------------------------------------
/packages/multibuild-cli/src/index.ts:
--------------------------------------------------------------------------------
1 | import { BuildOptions, LogLevel, ResolvedConfig } from "vite";
2 | import { cac } from "cac";
3 | import { multibuild } from "@vavite/multibuild";
4 | import { version } from "../package.json";
5 | import colors from "picocolors";
6 |
7 | interface GlobalCLIOptions {
8 | "--"?: string[];
9 | c?: boolean | string;
10 | config?: string;
11 | base?: string;
12 | l?: LogLevel;
13 | logLevel?: LogLevel;
14 | clearScreen?: boolean;
15 | d?: boolean | string;
16 | debug?: boolean | string;
17 | f?: string;
18 | filter?: string;
19 | m?: string;
20 | mode?: string;
21 | }
22 |
23 | const cli = cac("vavite");
24 |
25 | /**
26 | * removing global flags before passing as command specific sub-configs
27 | */
28 | function cleanOptions(
29 | options: Options,
30 | ): Omit {
31 | const ret = { ...options };
32 | delete ret["--"];
33 | delete ret.c;
34 | delete ret.config;
35 | delete ret.base;
36 | delete ret.l;
37 | delete ret.logLevel;
38 | delete ret.clearScreen;
39 | delete ret.d;
40 | delete ret.debug;
41 | delete ret.f;
42 | delete ret.filter;
43 | delete ret.m;
44 | delete ret.mode;
45 | return ret;
46 | }
47 |
48 | cli
49 | .command("[root]")
50 | .option("-c, --config ", `[string] use specified config file`)
51 | .option("--base ", `[string] public base path (default: /)`)
52 | .option("-l, --logLevel ", `[string] info | warn | error | silent`)
53 | .option("--clearScreen", `[boolean] allow/disable clear screen when logging`)
54 | .option("-d, --debug [feat]", `[string | boolean] show debug logs`)
55 | .option("-f, --filter ", `[string] filter debug logs`)
56 | .option("-m, --mode ", `[string] set env mode`)
57 | .option("--target ", `[string] transpile target (default: 'modules')`)
58 | .option("--outDir ", `[string] output directory (default: dist)`)
59 | .option(
60 | "--assetsDir ",
61 | `[string] directory under outDir to place assets in (default: _assets)`,
62 | )
63 | .option(
64 | "--assetsInlineLimit ",
65 | `[number] static asset base64 inline threshold in bytes (default: 4096)`,
66 | )
67 | .option(
68 | "--ssr [entry]",
69 | `[string] build specified entry for server-side rendering`,
70 | )
71 | .option(
72 | "--sourcemap",
73 | `[boolean] output source maps for build (default: false)`,
74 | )
75 | .option(
76 | "--minify [minifier]",
77 | `[boolean | "terser" | "esbuild"] enable/disable minification, ` +
78 | `or specify minifier to use (default: esbuild)`,
79 | )
80 | .option("--manifest", `[boolean] emit build manifest json`)
81 | .option("--ssrManifest", `[boolean] emit ssr manifest json`)
82 | .option(
83 | "--emptyOutDir",
84 | `[boolean] force empty outDir when it's outside of root`,
85 | )
86 | .option("-w, --watch", `[boolean] rebuilds when modules have changed on disk`)
87 | .action(async (root: string, options: BuildOptions & GlobalCLIOptions) => {
88 | const buildOptions: BuildOptions = cleanOptions(options);
89 |
90 | let initialConfig: ResolvedConfig;
91 |
92 | process.env.NODE_ENV = options.mode || "production";
93 |
94 | await multibuild(
95 | {
96 | root,
97 | base: options.base,
98 | mode: options.mode,
99 | configFile: options.config,
100 | logLevel: options.logLevel,
101 | clearScreen: options.clearScreen,
102 | build: buildOptions,
103 | },
104 | {
105 | onInitialConfigResolved(config) {
106 | initialConfig = config;
107 | // eslint-disable-next-line no-console
108 | console.log(initialConfig.buildSteps);
109 | },
110 |
111 | onStartBuildStep(info) {
112 | initialConfig.logger.info(
113 | (info.currentStepIndex ? "\n" : "") +
114 | colors.cyan("vavite: ") +
115 | (info.currentStep.description ||
116 | colors.white("running build step") +
117 | " " +
118 | colors.blue(info.currentStep.name)) +
119 | " (" +
120 | colors.green(
121 | info.currentStepIndex + 1 + "/" + info.buildSteps.length,
122 | ) +
123 | ")",
124 | );
125 | },
126 | },
127 | );
128 | });
129 |
130 | cli.help();
131 | cli.version(version);
132 |
133 | cli.parse();
134 |
--------------------------------------------------------------------------------
/packages/multibuild-cli/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "noEmit": true,
4 | "target": "es2020",
5 | "module": "ESNext",
6 | "esModuleInterop": true,
7 | "forceConsistentCasingInFileNames": true,
8 | "strict": true,
9 | "skipLibCheck": true,
10 | "moduleResolution": "Bundler",
11 | "customConditions": ["import"],
12 | "checkJs": true
13 | },
14 | "exclude": ["node_modules", "dist", "cli.js"]
15 | }
16 |
--------------------------------------------------------------------------------
/packages/multibuild-cli/tsup.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "tsup";
2 |
3 | export default defineConfig([
4 | {
5 | entry: ["./src/index.ts"],
6 | format: ["esm"],
7 | platform: "node",
8 | target: "node18",
9 | },
10 | ]);
11 |
--------------------------------------------------------------------------------
/packages/multibuild/eslint.config.js:
--------------------------------------------------------------------------------
1 | import config from "@cyco130/eslint-config/node";
2 | import path from "node:path";
3 | import { fileURLToPath } from "node:url";
4 |
5 | const tsconfigRootDir =
6 | // @ts-expect-error: import.meta.dirname requires v20.11.0 or v21.2.0
7 | import.meta.dirname ?? path.dirname(fileURLToPath(import.meta.url));
8 |
9 | /** @type {typeof config} */
10 | export default [
11 | ...config,
12 | {
13 | ignores: ["dist/", "node_modules/"],
14 | },
15 | {
16 | languageOptions: {
17 | parserOptions: {
18 | projectService: true,
19 | tsconfigRootDir,
20 | },
21 | },
22 | },
23 | ];
24 |
--------------------------------------------------------------------------------
/packages/multibuild/lint-staged.config.mjs:
--------------------------------------------------------------------------------
1 | export default {
2 | "**/*.ts?(x)": [
3 | () => "tsc -p tsconfig.json --noEmit",
4 | "eslint --max-warnings 0 --ignore-pattern dist",
5 | ],
6 | "*": "prettier --ignore-unknown --write",
7 | };
8 |
--------------------------------------------------------------------------------
/packages/multibuild/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@vavite/multibuild",
3 | "version": "5.1.0",
4 | "type": "module",
5 | "exports": {
6 | ".": {
7 | "import": "./dist/index.js"
8 | }
9 | },
10 | "typesVersions": {
11 | "*": {
12 | "*": [
13 | "dist/*.d.ts"
14 | ]
15 | }
16 | },
17 | "files": [
18 | "dist",
19 | "*.d.ts"
20 | ],
21 | "description": "Tool for orchestrating multiple Vite builds",
22 | "author": "Fatih Aygün ",
23 | "repository": "github:cyco130/vavite",
24 | "license": "MIT",
25 | "scripts": {
26 | "build": "tsup",
27 | "dev": "tsup --watch",
28 | "prepack": "rm -rf dist && pnpm build",
29 | "test": "pnpm run test:typecheck && pnpm run test:lint && pnpm run test:package",
30 | "test:typecheck": "tsc -p tsconfig.json --noEmit",
31 | "test:lint": "eslint . --max-warnings 0 --ignore-pattern dist",
32 | "test:package": "publint --strict"
33 | },
34 | "peerDependencies": {
35 | "vite": "^2.8.1 || 3 || 4 || 5 || 6"
36 | },
37 | "devDependencies": {
38 | "@cyco130/eslint-config": "^5.0.1",
39 | "eslint": "^9.16.0",
40 | "tsup": "^8.3.5",
41 | "typescript": "^5.7.2",
42 | "vite": "^5.4.11"
43 | },
44 | "dependencies": {
45 | "@types/node": "^18.19.67",
46 | "cac": "^6.7.14",
47 | "picocolors": "^1.1.1"
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/packages/multibuild/readme.md:
--------------------------------------------------------------------------------
1 | # @vavite/multibuild
2 |
3 | `@vavite/multibuild` is a tool for orchestrating multiple [Vite](https://vitejs.dev) builds. This package is the JavaScript API, check out [`@vavite/multibuild-cli`](../multibuild-cli) for the command line interface.
4 |
5 | Developing applications that perform server-side rendering (SSR) with Vite requires two separate build steps: one for the client and one for the server. This library allows you to run both builds in a and customize the configuration for each build.
6 |
7 | We're hoping that, eventually, [this feature will be implemented in Vite itself](https://github.com/vitejs/vite/issues/5936). This package exists to provide workaround until then.
8 |
9 | ## Usage
10 |
11 | `@vavite/multibuild` extends the Vite configuration with a `buildSteps` property, which is an array of build step definitions. A build step definition is an object with a `name` property (which is simply a string naming the build step), and an optional `config` property which will be merged into the Vite configuration for the build step. For example, a client build followed by a server build can be defined like this:
12 |
13 | ```ts
14 | import { defineConfig } from "vite";
15 |
16 | export default defineConfig({
17 | buildSteps: [
18 | {
19 | name: "client",
20 | config: {
21 | build: {
22 | outDir: "dist/client",
23 | rollupOptions: {
24 | // Client entry
25 | input: "/client",
26 | },
27 | },
28 | },
29 | },
30 | {
31 | name: "server",
32 | config: {
33 | build: {
34 | // Server entry
35 | ssr: "/server",
36 | outDir: "dist/server",
37 | },
38 | },
39 | },
40 | ],
41 | });
42 | ```
43 |
44 | You can then `import { multibuild } from "@vavite/multibuild"` and use it instead of Vite's `build` function to run a multi-step build.
45 |
46 | `@vavite/multibuild` will call `resolveConfig` with the `mode` parameter set to `"multibuild"` to extract the build steps. Setting `buildSteps` in subsequent steps has no effect.
47 |
48 | ## Sharing information between builds
49 |
50 | `@vavite/multibuild` will call the `buildStepStart` hook on each plugin when a build step starts and pass it information about the current step and data forwarded from the previous step. The `buildStepEnd` hook will be called when the build step ends and its return value will be forwarded to the next step. If a promise is returned, it will be awaited first.
51 |
52 | If no build steps are defined, `buildStepStart` and `buildStepEnd` will not be called.
53 |
--------------------------------------------------------------------------------
/packages/multibuild/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "noEmit": true,
4 | "target": "es2020",
5 | "module": "ESNext",
6 | "esModuleInterop": true,
7 | "forceConsistentCasingInFileNames": true,
8 | "strict": true,
9 | "skipLibCheck": true,
10 | "moduleResolution": "Bundler",
11 | "customConditions": ["import"],
12 | "checkJs": true
13 | },
14 | "exclude": ["node_modules", "dist"]
15 | }
16 |
--------------------------------------------------------------------------------
/packages/multibuild/tsup.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "tsup";
2 |
3 | export default defineConfig([
4 | {
5 | entry: ["./src/index.ts"],
6 | format: ["esm"],
7 | platform: "node",
8 | target: "node18",
9 | dts: true,
10 | },
11 | ]);
12 |
--------------------------------------------------------------------------------
/packages/node-loader/cli.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | import "./dist/cli.js";
3 |
--------------------------------------------------------------------------------
/packages/node-loader/eslint.config.js:
--------------------------------------------------------------------------------
1 | import config from "@cyco130/eslint-config/node";
2 | import path from "node:path";
3 | import { fileURLToPath } from "node:url";
4 |
5 | const tsconfigRootDir =
6 | // @ts-expect-error: import.meta.dirname requires v20.11.0 or v21.2.0
7 | import.meta.dirname ?? path.dirname(fileURLToPath(import.meta.url));
8 |
9 | /** @type {typeof config} */
10 | export default [
11 | ...config,
12 | {
13 | ignores: ["dist/", "node_modules/", "cli.js"],
14 | },
15 | {
16 | languageOptions: {
17 | parserOptions: {
18 | projectService: true,
19 | tsconfigRootDir,
20 | },
21 | },
22 | },
23 | ];
24 |
--------------------------------------------------------------------------------
/packages/node-loader/lint-staged.config.mjs:
--------------------------------------------------------------------------------
1 | export default {
2 | "**/*.ts?(x)": [
3 | () => "tsc -p tsconfig.json --noEmit",
4 | "eslint --max-warnings 0 --ignore-pattern dist",
5 | ],
6 | "*": "prettier --ignore-unknown --write",
7 | };
8 |
--------------------------------------------------------------------------------
/packages/node-loader/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@vavite/node-loader",
3 | "version": "5.1.0",
4 | "type": "module",
5 | "exports": {
6 | ".": "./dist/index.js",
7 | "./plugin": {
8 | "import": "./dist/plugin.js"
9 | },
10 | "./suppress-warnings": "./dist/suppress-warnings.cjs"
11 | },
12 | "typesVersions": {
13 | "*": {
14 | "*": [
15 | "dist/*.d.ts"
16 | ]
17 | }
18 | },
19 | "bin": {
20 | "vavite-loader": "cli.js"
21 | },
22 | "files": [
23 | "dist",
24 | "cli.js"
25 | ],
26 | "description": "ESM loader for transpiling modules with Vite",
27 | "author": "Fatih Aygün ",
28 | "repository": "github:cyco130/vavite",
29 | "license": "MIT",
30 | "scripts": {
31 | "build": "tsup",
32 | "dev": "tsup --watch",
33 | "prepack": "rm -rf dist && pnpm build",
34 | "test": "pnpm run test:typecheck && pnpm run test:lint && pnpm run test:package",
35 | "test:typecheck": "tsc -p tsconfig.json --noEmit",
36 | "test:lint": "eslint . --max-warnings 0 --ignore-pattern dist",
37 | "test:package": "publint --strict"
38 | },
39 | "peerDependencies": {
40 | "vite": "^2.8.1 || 3 || 4 || 5 || 6"
41 | },
42 | "devDependencies": {
43 | "@cyco130/eslint-config": "^5.0.1",
44 | "@types/node": "^18.19.67",
45 | "eslint": "^9.16.0",
46 | "sirv": "^3.0.0",
47 | "tsup": "^8.3.5",
48 | "typescript": "^5.7.2",
49 | "vite": "^5.4.11"
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/packages/node-loader/readme.md:
--------------------------------------------------------------------------------
1 | # @vavite/node-loader
2 |
3 | `@vavite/node-loader` is [Node ESM loader](https://nodejs.org/api/esm.html#loaders) that uses [Vite](https://vitejs.dev) to transpile modules. It is part of the `vavite` project but it can be used in any Vite SSR project to enable sourcemap and breakpoints support.
4 |
5 | ## Installation
6 |
7 | ```sh
8 | npm install --save-dev @vavite/node-loader
9 | ```
10 |
11 | ## Usage
12 |
13 | Add the following to your Vite config:
14 |
15 | ```ts
16 | import { defineConfig } from "vite";
17 | import { nodeLoaderPlugin } from "@vavite/node-loader/plugin";
18 |
19 | export default defineConfig({
20 | plugins: [
21 | nodeLoaderPlugin(),
22 | // ...
23 | ],
24 | });
25 | ```
26 |
27 | And run your project with `vavite-loader vite dev`.
28 |
--------------------------------------------------------------------------------
/packages/node-loader/src/cli.ts:
--------------------------------------------------------------------------------
1 | import { spawn } from "node:child_process";
2 | import { fileURLToPath } from "node:url";
3 |
4 | const suppressPath = fileURLToPath(
5 | new URL("./suppress-warnings.cjs", import.meta.url).href,
6 | );
7 | const loaderPath = new URL("./index.js", import.meta.url).href;
8 |
9 | const options =
10 | (process.env.NODE_OPTIONS ? process.env.NODE_OPTIONS + " " : "") +
11 | `-r ${JSON.stringify(suppressPath)} --loader ${loaderPath}`;
12 |
13 | const command = process.argv[2];
14 | const args = process.argv.slice(3);
15 |
16 | // Run the command with the options
17 | const cp = spawn(command, args, {
18 | shell: true,
19 | stdio: "inherit",
20 | env: {
21 | ...process.env,
22 | NODE_OPTIONS: options,
23 | },
24 | });
25 |
26 | cp.on("error", (err) => {
27 | console.error(err);
28 | process.exit(1);
29 | });
30 |
31 | cp.on("exit", (code) => {
32 | process.exit(code ?? 0);
33 | });
34 |
--------------------------------------------------------------------------------
/packages/node-loader/src/plugin.ts:
--------------------------------------------------------------------------------
1 | import type { Plugin, ViteDevServer } from "vite";
2 |
3 | declare global {
4 | // eslint-disable-next-line no-var
5 | var __vite_dev_server__: ViteDevServer | undefined;
6 | // eslint-disable-next-line no-var
7 | var __vavite_loader__: boolean;
8 | }
9 |
10 | const hasLoader = global.__vavite_loader__;
11 |
12 | export function nodeLoaderPlugin(): Plugin {
13 | return {
14 | name: "@vavite/node-loader",
15 | enforce: "pre",
16 | apply: "serve",
17 | config() {
18 | if (hasLoader) {
19 | return {
20 | experimental: {
21 | skipSsrTransform: true,
22 | },
23 | };
24 | }
25 | },
26 | configResolved(config) {
27 | if (!hasLoader) {
28 | config.logger.warn(
29 | "@vavite/node-loader/plugin: @vavite/node-loader is not enabled. " +
30 | "Please run with `node --experimental-loader=@vavite/node-loader`.",
31 | );
32 | }
33 | },
34 | configureServer(server) {
35 | if (hasLoader) {
36 | global.__vite_dev_server__ = server;
37 | server.ssrLoadModule = (id) => import(id + "?ssrLoadModuleEntry");
38 | server.ssrFixStacktrace = () => {
39 | /* noop */
40 | };
41 | server.ssrRewriteStacktrace = (s) => s;
42 | }
43 | },
44 | buildEnd() {
45 | global.__vite_dev_server__ = undefined;
46 | },
47 | };
48 | }
49 |
--------------------------------------------------------------------------------
/packages/node-loader/src/suppress-warnings.ts:
--------------------------------------------------------------------------------
1 | // Based on following code:
2 | // https://github.com/esbuild-kit/tsx/blob/bd3ec4a5ed67545c044d6780fcd3187f0ff2f8a8/src/suppress-warnings.cts
3 | // Copyright (c) Hiroki Osame
4 | // Under MIT License
5 |
6 | const ignoreWarnings = new Set([
7 | "--experimental-loader is an experimental feature. This feature could change at any time",
8 | "Custom ESM Loaders is an experimental feature. This feature could change at any time",
9 | "Custom ESM Loaders is an experimental feature and might change at any time",
10 | ]);
11 |
12 | const { emit } = process;
13 |
14 | process.emit = function (this: any, event: string, warning: Error) {
15 | if (event === "warning" && ignoreWarnings.has(warning.message)) {
16 | return;
17 | }
18 |
19 | // eslint-disable-next-line prefer-rest-params
20 | return Reflect.apply(emit, this, arguments);
21 | } as any;
22 |
--------------------------------------------------------------------------------
/packages/node-loader/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "noEmit": true,
4 | "target": "es2020",
5 | "module": "ESNext",
6 | "esModuleInterop": true,
7 | "forceConsistentCasingInFileNames": true,
8 | "strict": true,
9 | "skipLibCheck": true,
10 | "moduleResolution": "Bundler",
11 | "customConditions": ["import"],
12 | "checkJs": true
13 | },
14 | "exclude": ["node_modules", "dist", "cli.js"]
15 | }
16 |
--------------------------------------------------------------------------------
/packages/node-loader/tsup.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "tsup";
2 |
3 | export default defineConfig([
4 | // Plugin
5 | {
6 | entry: ["./src/index.ts", "./src/cli.ts"],
7 | format: ["esm"],
8 | platform: "node",
9 | target: "node18",
10 | },
11 | {
12 | entry: ["./src/plugin.ts"],
13 | format: ["esm"],
14 | platform: "node",
15 | target: "node18",
16 | dts: true,
17 | },
18 | {
19 | entry: ["./src/suppress-warnings.ts"],
20 | format: ["cjs"],
21 | platform: "node",
22 | target: "node18",
23 | },
24 | ]);
25 |
--------------------------------------------------------------------------------
/packages/reloader/eslint.config.js:
--------------------------------------------------------------------------------
1 | import config from "@cyco130/eslint-config/node";
2 | import path from "node:path";
3 | import { fileURLToPath } from "node:url";
4 |
5 | const tsconfigRootDir =
6 | // @ts-expect-error: import.meta.dirname requires v20.11.0 or v21.2.0
7 | import.meta.dirname ?? path.dirname(fileURLToPath(import.meta.url));
8 |
9 | /** @type {typeof config} */
10 | export default [
11 | ...config,
12 | {
13 | ignores: ["dist/", "node_modules/"],
14 | },
15 | {
16 | languageOptions: {
17 | parserOptions: {
18 | projectService: true,
19 | tsconfigRootDir,
20 | },
21 | },
22 | },
23 | ];
24 |
--------------------------------------------------------------------------------
/packages/reloader/lint-staged.config.mjs:
--------------------------------------------------------------------------------
1 | export default {
2 | "**/*.ts?(x)": [
3 | () => "tsc -p tsconfig.json --noEmit",
4 | "eslint --max-warnings 0 --ignore-pattern dist",
5 | ],
6 | "*": "prettier --ignore-unknown --write",
7 | };
8 |
--------------------------------------------------------------------------------
/packages/reloader/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@vavite/reloader",
3 | "version": "5.1.0",
4 | "type": "module",
5 | "files": [
6 | "dist",
7 | "*.d.ts"
8 | ],
9 | "exports": {
10 | ".": {
11 | "import": "./dist/index.js"
12 | },
13 | "./http-dev-server": {
14 | "import": "./dist/http-dev-server.js"
15 | }
16 | },
17 | "typesVersions": {
18 | "*": {
19 | "*": [
20 | "dist/*.d.ts"
21 | ]
22 | }
23 | },
24 | "description": "Vite plugin for hot hot reloading server applications",
25 | "author": "Fatih Aygün ",
26 | "repository": "github:cyco130/vavite",
27 | "license": "MIT",
28 | "scripts": {
29 | "build": "tsup",
30 | "dev": "tsup --watch",
31 | "prepack": "rm -rf dist && pnpm build",
32 | "test": "pnpm run test:typecheck && pnpm run test:lint && pnpm run test:package",
33 | "test:typecheck": "tsc -p tsconfig.json --noEmit",
34 | "test:lint": "eslint . --max-warnings 0 --ignore-pattern dist",
35 | "test:package": "publint --strict"
36 | },
37 | "peerDependencies": {
38 | "vite": "^2.8.1 || 3 || 4 || 5 || 6"
39 | },
40 | "devDependencies": {
41 | "@cyco130/eslint-config": "^5.0.1",
42 | "@types/node": "^18.19.67",
43 | "eslint": "^9.16.0",
44 | "tsup": "^8.3.5",
45 | "typescript": "^5.7.2",
46 | "vite": "^5.4.11"
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/packages/reloader/src/http-dev-server.ts:
--------------------------------------------------------------------------------
1 | import type { Server } from "node:http";
2 |
3 | const httpDevServer: Server | undefined = undefined;
4 |
5 | export default httpDevServer;
6 |
--------------------------------------------------------------------------------
/packages/reloader/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "noEmit": true,
4 | "target": "es2020",
5 | "module": "ESNext",
6 | "esModuleInterop": true,
7 | "forceConsistentCasingInFileNames": true,
8 | "strict": true,
9 | "skipLibCheck": true,
10 | "moduleResolution": "Bundler",
11 | "customConditions": ["import"],
12 | "checkJs": true
13 | },
14 | "exclude": ["node_modules", "dist"]
15 | }
16 |
--------------------------------------------------------------------------------
/packages/reloader/tsup.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "tsup";
2 |
3 | export default defineConfig([
4 | {
5 | entry: ["./src/index.ts", "./src/http-dev-server.ts"],
6 | format: ["esm"],
7 | platform: "node",
8 | target: "node18",
9 | dts: true,
10 | },
11 | ]);
12 |
--------------------------------------------------------------------------------
/packages/vavite/cli.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | import "./dist/cli.js";
3 |
--------------------------------------------------------------------------------
/packages/vavite/eslint.config.js:
--------------------------------------------------------------------------------
1 | import config from "@cyco130/eslint-config/node";
2 | import path from "node:path";
3 | import { fileURLToPath } from "node:url";
4 |
5 | const tsconfigRootDir =
6 | // @ts-expect-error: import.meta.dirname requires v20.11.0 or v21.2.0
7 | import.meta.dirname ?? path.dirname(fileURLToPath(import.meta.url));
8 |
9 | /** @type {typeof config} */
10 | export default [
11 | ...config,
12 | {
13 | ignores: [
14 | "dist/",
15 | "node_modules/",
16 | "cli.js",
17 | "node-loader.mjs",
18 | "suppress-loader-warnings.cjs",
19 | ],
20 | },
21 | {
22 | languageOptions: {
23 | parserOptions: {
24 | projectService: true,
25 | tsconfigRootDir,
26 | },
27 | },
28 | },
29 | ];
30 |
--------------------------------------------------------------------------------
/packages/vavite/lint-staged.config.mjs:
--------------------------------------------------------------------------------
1 | export default {
2 | "**/*.ts?(x)": [
3 | () => "tsc -p tsconfig.json --noEmit",
4 | "eslint --max-warnings 0 --ignore-pattern dist",
5 | ],
6 | "*": "prettier --ignore-unknown --write",
7 | };
8 |
--------------------------------------------------------------------------------
/packages/vavite/node-loader.mjs:
--------------------------------------------------------------------------------
1 | export * from "@vavite/node-loader";
2 |
--------------------------------------------------------------------------------
/packages/vavite/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vavite",
3 | "version": "5.1.0",
4 | "description": "A Vite plugin for develoing server-side applications",
5 | "type": "module",
6 | "files": [
7 | "dist",
8 | "*.d.ts",
9 | "node-loader.mjs",
10 | "suppress-loader-warnings.cjs"
11 | ],
12 | "exports": {
13 | ".": {
14 | "import": "./dist/index.js"
15 | },
16 | "./vite-dev-server": {
17 | "import": "./dist/vite-dev-server.js"
18 | },
19 | "./http-dev-server": {
20 | "import": "./dist/http-dev-server.js"
21 | },
22 | "./node-loader": "./node-loader.mjs",
23 | "./suppress-loader-warnings": "./suppress-loader-warnings.cjs",
24 | "./package.json": "./package.json"
25 | },
26 | "typesVersions": {
27 | "*": {
28 | "*": [
29 | "dist/*.d.ts"
30 | ]
31 | }
32 | },
33 | "author": "Fatih Aygün ",
34 | "repository": "github:cyco130/vavite",
35 | "license": "MIT",
36 | "scripts": {
37 | "build": "tsup",
38 | "dev": "tsup --watch",
39 | "prepack": "rm -rf dist && pnpm build",
40 | "test": "pnpm run test:typecheck && pnpm run test:lint && pnpm run test:package",
41 | "test:typecheck": "tsc -p tsconfig.json --noEmit",
42 | "test:lint": "eslint . --max-warnings 0 --ignore-pattern dist",
43 | "test:package": "publint --strict"
44 | },
45 | "bin": {
46 | "vavite": "./cli.js"
47 | },
48 | "peerDependencies": {
49 | "vite": "^2.8.1 || 3 || 4 || 5 || 6"
50 | },
51 | "devDependencies": {
52 | "@cyco130/eslint-config": "^5.0.1",
53 | "@types/node": "^18.19.67",
54 | "eslint": "^9.16.0",
55 | "sirv": "^3.0.0",
56 | "tsup": "^8.3.5",
57 | "typescript": "^5.7.2",
58 | "vite": "^5.4.11"
59 | },
60 | "dependencies": {
61 | "@vavite/connect": "workspace:*",
62 | "@vavite/expose-vite-dev-server": "workspace:*",
63 | "@vavite/multibuild": "workspace:*",
64 | "@vavite/multibuild-cli": "workspace:*",
65 | "@vavite/node-loader": "workspace:*",
66 | "@vavite/reloader": "workspace:*",
67 | "cac": "^6.7.14",
68 | "picocolors": "^1.1.1"
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/packages/vavite/src/http-dev-server.ts:
--------------------------------------------------------------------------------
1 | import type { Server } from "node:http";
2 |
3 | const httpDevServer: Server | undefined = undefined;
4 |
5 | export default httpDevServer;
6 |
--------------------------------------------------------------------------------
/packages/vavite/src/index.ts:
--------------------------------------------------------------------------------
1 | import { Plugin, UserConfig } from "vite";
2 | import { vaviteConnect } from "@vavite/connect";
3 | import { reloader } from "@vavite/reloader";
4 | import vaviteExposeViteDevServer from "@vavite/expose-vite-dev-server";
5 | import { nodeLoaderPlugin } from "@vavite/node-loader/plugin";
6 |
7 | declare global {
8 | // eslint-disable-next-line no-var
9 | var __vavite_loader__: boolean;
10 | }
11 |
12 | const hasLoader = global.__vavite_loader__;
13 |
14 | export interface VaviteOptions {
15 | /** Entry module that default exports a middleware function.
16 | * You have to provide either a handler entry or a server entry.
17 | * If you provide both, the server entry will only be used in the
18 | * production build.
19 | */
20 | handlerEntry?: string;
21 | /** Server entry point. You have to provide either a handler entry
22 | * or a server entry. If you provide both, the server entry will only
23 | * be used in the production build.
24 | */
25 | serverEntry?: string;
26 | /** Whether to serve client-side assets in development.
27 | * @default false
28 | */
29 | serveClientAssetsInDev?: boolean;
30 | /** If you only provide a handler entry, this option controls whether
31 | * to build a standalone server application or a middleware function.
32 | * @default true
33 | */
34 | standalone?: boolean;
35 | /** Directory where the client-side assets are located. Set to null to disable
36 | * static file serving in production.
37 | * @default null
38 | */
39 | clientAssetsDir?: string | null;
40 | /** Whether to bundle the sirv package or to import it when building in standalone
41 | * mode. You have to install it as a production dependency if this is set to false.
42 | * @default true
43 | */
44 | bundleSirv?: boolean;
45 | /**
46 | * When to reload the server. "any-change" reloads every time any of the dependencies of the
47 | * server entry changes. "static-deps-change" only reloads when statically imported dependencies
48 | * change, dynamically imported dependencies are not tracked.
49 | * @default "any-change"
50 | */
51 | reloadOn?: "any-change" | "static-deps-change";
52 | /** Whether to use the vite runtime to execute the server entry.
53 | * @experimental
54 | * @default false (can be overriden by the USE_VITE_RUNTIME environment variable to "true")
55 | */
56 | useViteRuntime?: boolean;
57 | }
58 |
59 | export function vavite(options: VaviteOptions): Plugin[] {
60 | const {
61 | serverEntry,
62 | handlerEntry,
63 | serveClientAssetsInDev,
64 | standalone,
65 | clientAssetsDir,
66 | bundleSirv,
67 | reloadOn,
68 | useViteRuntime,
69 | } = options;
70 |
71 | if (!serverEntry && !handlerEntry) {
72 | throw new Error(
73 | "vavite: either serverEntry or handlerEntry must be specified",
74 | );
75 | }
76 |
77 | let buildStepStartCalled = false;
78 |
79 | const plugins: (Plugin | false)[] = [
80 | {
81 | name: "vavite:check-multibuild",
82 |
83 | buildStepStart() {
84 | buildStepStartCalled = true;
85 | },
86 |
87 | config() {
88 | return {
89 | ssr: {
90 | optimizeDeps: {
91 | exclude: ["vavite"],
92 | },
93 | },
94 | optimizeDeps: {
95 | exclude: ["vavite"],
96 | },
97 | } as UserConfig;
98 | },
99 |
100 | configResolved(config) {
101 | if (
102 | config.buildSteps &&
103 | config.command === "build" &&
104 | config.mode !== "multibuild" &&
105 | !buildStepStartCalled
106 | ) {
107 | throw new Error(
108 | "vavite: You have multiple build steps defined in your Vite config, please use the 'vavite' command instead of 'vite build' to build.",
109 | );
110 | }
111 | },
112 | },
113 | hasLoader && nodeLoaderPlugin(),
114 | ];
115 |
116 | if (handlerEntry) {
117 | plugins.push(
118 | ...vaviteConnect({
119 | handlerEntry,
120 | customServerEntry: serverEntry,
121 | serveClientAssetsInDev,
122 | standalone,
123 | clientAssetsDir,
124 | bundleSirv,
125 | useViteRuntime,
126 | }),
127 | );
128 | } else {
129 | plugins.push(
130 | reloader({
131 | entry: serverEntry,
132 | serveClientAssetsInDev,
133 | reloadOn,
134 | useViteRuntime,
135 | }),
136 | );
137 | }
138 |
139 | plugins.push(vaviteExposeViteDevServer());
140 |
141 | return plugins.filter(Boolean) as Plugin[];
142 | }
143 |
144 | export type {
145 | BuildStep,
146 | CustomBuildStep,
147 | VaviteMultiBuildInfo,
148 | ViteBuildStep,
149 | } from "@vavite/multibuild";
150 |
--------------------------------------------------------------------------------
/packages/vavite/src/vite-dev-server.ts:
--------------------------------------------------------------------------------
1 | import type { ViteDevServer } from "vite";
2 |
3 | const viteDevServer: ViteDevServer | undefined = undefined;
4 |
5 | export default viteDevServer;
6 |
--------------------------------------------------------------------------------
/packages/vavite/suppress-loader-warnings.cjs:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line @typescript-eslint/no-require-imports
2 | require("@vavite/node-loader/suppress-warnings");
3 |
--------------------------------------------------------------------------------
/packages/vavite/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "noEmit": true,
4 | "target": "es2020",
5 | "module": "ESNext",
6 | "esModuleInterop": true,
7 | "forceConsistentCasingInFileNames": true,
8 | "strict": true,
9 | "skipLibCheck": true,
10 | "moduleResolution": "Bundler",
11 | "customConditions": ["import"],
12 | "checkJs": true
13 | },
14 | "exclude": [
15 | "node_modules",
16 | "dist",
17 | "cli.js",
18 | "node-loader.mjs",
19 | "suppress-loader-warnings.cjs"
20 | ]
21 | }
22 |
--------------------------------------------------------------------------------
/packages/vavite/tsup.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "tsup";
2 |
3 | export default defineConfig([
4 | {
5 | entry: ["./src/index.ts"],
6 | format: ["esm"],
7 | platform: "node",
8 | target: "node18",
9 | dts: true,
10 | },
11 | {
12 | entry: ["./src/vite-dev-server.ts", "./src/http-dev-server.ts"],
13 | format: ["esm"],
14 | platform: "node",
15 | dts: true,
16 | },
17 | {
18 | entry: ["./src/cli.ts"],
19 | format: ["esm"],
20 | platform: "node",
21 | },
22 | ]);
23 |
--------------------------------------------------------------------------------
/pnpm-workspace.yaml:
--------------------------------------------------------------------------------
1 | packages:
2 | - "packages/*"
3 | - "ci"
4 | - "examples/*"
5 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | # Va vite!
2 |
3 | `vavite` is a set of tools for developing and building server-side applications with [Vite](https://vitejs.dev).
4 |
5 | Vite, despite being a frontend tool, has support for transpiling server-side code. The feature is intended for building [server-side rendering (SSR)](https://vitejs.dev/guide/ssr.html) applications. But there's no reason why it can't be leveraged for building server-side applications that are not necessarily related to SSR. `vavite` lets you do that, but also vastly simplifies the SSR workflow.
6 |
7 | Vite's official SSR guide describes a workflow where Vite's development server is used as a middleware function in a server application made with a [Connect](https://github.com/senchalabs/connect) compatible Node.js framework (like [Express](https://expressjs.com)). If your server-side code needs transpilation (e.g. for TypeScript), you're required to use another set of tools (say [`ts-node`](https://typestrong.org/ts-node/) and [`nodemon`](https://nodemon.io/)) for development and building. `vavite` enables you to use Vite itself to transpile your server-side code.
8 |
9 | ## Examples
10 |
11 | - [simple-standalone](examples/simple-standalone): Simple standalone example ([Stackblitz](https://stackblitz.com/github/cyco130/vavite/tree/main/examples/simple-standalone))
12 | - [express](examples/express): Integrating with Express ([Stackblitz](https://stackblitz.com/github/cyco130/vavite/tree/main/examples/express))
13 | - [koa](examples/koa): Integrating with Koa ([Stackblitz](https://stackblitz.com/github/cyco130/vavite/tree/main/examples/koa))
14 | - [fastify](examples/fastify): Integrating with Fastify ([Stackblitz](https://stackblitz.com/github/cyco130/vavite/tree/main/examples/fastify))
15 | - [hapi](examples/hapi): Integrating with Hapi ([Stackblitz](https://stackblitz.com/github/cyco130/vavite/tree/main/examples/hapi))
16 | - [ssr-react-express](examples/ssr-react-express): React SSR with Express ([Stackblitz](https://stackblitz.com/github/cyco130/vavite/tree/main/examples/ssr-react-express))
17 | - [ssr-vue-express](examples/ssr-vue-express): Vue SSR with Express ([Stackblitz](https://stackblitz.com/github/cyco130/vavite/tree/main/examples/ssr-vue-express))
18 | - [vike](examples/vike): Vike with React and Express ([Stackblitz](https://stackblitz.com/github/cyco130/vavite/tree/main/examples/vike))
19 | - [socket-io](examples/socket-io): [socket.io](https://socket.io/) with Express ([Stackblitz](https://stackblitz.com/github/cyco130/vavite/tree/main/examples/socket-io))
20 | - [Nest.js](examples/nestjs): [Nest.js](https://nestjs.com/) with Express ([Stackblitz](https://stackblitz.com/github/cyco130/vavite/tree/main/examples/nestjs))
21 | - [Nest.js with vike](examples/nestjs-vike): [Nest.js](https://nestjs.com/) with Vike ([Stackblitz](https://stackblitz.com/github/cyco130/vavite/tree/main/examples/nestjs-vike))
22 |
23 | ## Packages
24 |
25 | [`vavite`](packages/vavite) is the main package that should work for most workflows but it is built on a set of lower level tools that you can use independently:
26 |
27 | - [`@vavite/connect`](packages/connect) is a Vite plugin that turns the official SSR workflow around: Instead of mounting Vite's dev server into your application as a middleware function, you write your application in the form of a middleware function (with the `(req, res, next)` signature) and mount it into Vite's dev server. For production, you can provide a custom server entry or it can build a standalone Node.js server application for you. This workflow is best if you're only interested in handling requests and you don't need control over the server entry during development.
28 |
29 | - If you do need control over your server entry even during development, [`@vavite/reloader`](packages/reloader) is a Vite plugin that provides live reloading capabilities for applications written with _any_ Node.js server framework. It should be usable with any framework that allows you to provide your own `http.Server` instance. **Note that this is a less reliable method and some things don't work on some operating systems.**
30 |
31 | - [`@vavite/expose-vite-dev-server`](packages/expose-vite-dev-server) is a plugin that provides access to Vite's dev server by simply importing it. It's useful for accessing server methods like `ssrFixStacktrace` and `transformIndexHtml` during development using either `@vavite/connect` or `@vavite/reloader`.
32 |
33 | - Building an SSR application with Vite involves at least two invocations of Vite's build command: once for the client and once for the server. [`@vavite/multibuild`](packages/multibuild) provides a JavaScript API for orchestrating multiple Vite builds and [`@vavite/multibuild-cli`](packages/multibuild-cli) is a drop-in replacement for the `vite build` CLI command for invoking multiple builds.
34 |
35 | - [`@vavite/node-loader`](packages/node-loader) is a Vite plugin that makes it possible to debug SSR code with full support for sourcemaps and breakpoints. It uses a Node ESM loader behind the scenes.
36 |
--------------------------------------------------------------------------------
/version:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | WHITE='\033[1;37m'
3 | GREEN='\033[0;32m'
4 | BLUE='\033[1;34m'
5 | NC='\033[0m'
6 |
7 | echo -e ${WHITE}Updating package versions${NC}
8 | pnpm -r --filter=./packages/* exec -- bash -c "echo -e ${BLUE@Q}\$PNPM_PACKAGE_NAME${NC@Q}@${GREEN@Q}\`npm version --allow-same-version --no-git-tag-version $@\`${NC@Q}"
9 | echo -e ${WHITE}Updating dependency versions in examples${NC}
10 | pnpm -r --filter=./examples/* update --workspace --save-workspace-protocol=false 'vavite' '@vavite/*'
11 |
--------------------------------------------------------------------------------