├── .eslintignore ├── src ├── index.ts ├── server │ ├── index.ts │ ├── express.ts │ └── common.ts ├── node │ ├── utils.ts │ ├── cli.ts │ ├── build.ts │ ├── server.ts │ └── render.ts ├── utils │ ├── document-ready.ts │ └── state.ts ├── components.ts ├── config.ts ├── entry.ts └── types.ts ├── pnpm-workspace.yaml ├── .npmrc ├── examples ├── vitesse-lite-naive-ui │ ├── src │ │ ├── composables │ │ │ ├── index.ts │ │ │ └── dark.ts │ │ ├── pages │ │ │ ├── [...all].vue │ │ │ ├── README.md │ │ │ ├── index.vue │ │ │ └── form.vue │ │ ├── server.ts │ │ ├── styles │ │ │ └── main.css │ │ ├── components │ │ │ ├── Counter.vue │ │ │ ├── README.md │ │ │ └── Footer.vue │ │ ├── App.vue │ │ ├── main.ts │ │ └── express.js │ ├── .gitignore │ ├── test │ │ ├── basic.test.ts │ │ ├── __snapshots__ │ │ │ └── component.test.ts.snap │ │ └── component.test.ts │ ├── vue-shims.d.ts │ ├── bin │ │ └── www.js │ ├── tsconfig.json │ ├── tsconfig copy.json │ ├── vite.config.noexternal.ts │ ├── public │ │ ├── favicon.svg │ │ └── naive-ui.svg │ ├── index.html │ ├── unocss.config.ts │ ├── vite.config.ts │ ├── package.json │ ├── components.d.ts │ └── auto-imports.d.ts ├── basic │ ├── public │ │ └── favicon.ico │ ├── src │ │ ├── assets │ │ │ └── logo.png │ │ ├── pages │ │ │ ├── fetch.vue │ │ │ └── index.vue │ │ ├── main.ts │ │ ├── server.ts │ │ ├── App.vue │ │ ├── components │ │ │ └── HelloWorld.vue │ │ └── express.js │ ├── vue-shim.d.ts │ ├── bin │ │ └── www.js │ ├── tsconfig.json │ ├── vite.config.ts │ ├── vite.config.noexternal.ts │ ├── .gitignore │ ├── index.html │ └── package.json └── README.md ├── .gitignore ├── .eslintrc.json ├── bin └── vite-ssr.js ├── tsconfig.json ├── tsup.config.ts ├── .github └── workflows │ └── release.yml ├── LICENSE ├── package.json └── README.md /.eslintignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './entry' 2 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - examples/* 3 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | shamefully-hoist = true 2 | ignore-workspace-root-check = true -------------------------------------------------------------------------------- /examples/vitesse-lite-naive-ui/src/composables/index.ts: -------------------------------------------------------------------------------- 1 | export * from './dark' 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | .history 3 | node_modules 4 | *.cache 5 | dist 6 | *.log 7 | .idea 8 | -------------------------------------------------------------------------------- /examples/basic/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aliuq/vite-ssr-vue3/master/examples/basic/public/favicon.ico -------------------------------------------------------------------------------- /examples/basic/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aliuq/vite-ssr-vue3/master/examples/basic/src/assets/logo.png -------------------------------------------------------------------------------- /examples/vitesse-lite-naive-ui/src/pages/[...all].vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /examples/vitesse-lite-naive-ui/src/composables/dark.ts: -------------------------------------------------------------------------------- 1 | export const isDark = useDark() 2 | export const toggleDark = useToggle(isDark) 3 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@antfu/eslint-config", 3 | "rules": { 4 | "@typescript-eslint/no-unused-vars": "off" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /examples/basic/src/pages/fetch.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 8 | -------------------------------------------------------------------------------- /examples/basic/src/pages/index.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 8 | -------------------------------------------------------------------------------- /src/server/index.ts: -------------------------------------------------------------------------------- 1 | export { createRender } from '../node/render' 2 | export { createServer as createExpressServer, startServer as startExpressServer } from './express' 3 | -------------------------------------------------------------------------------- /bin/vite-ssr.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict' 3 | if (typeof __dirname !== 'undefined') 4 | require('../dist/node/cli.cjs') 5 | else import('../dist/node/cli.mjs') 6 | -------------------------------------------------------------------------------- /examples/vitesse-lite-naive-ui/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .vite-ssg-dist 3 | .vite-ssg-temp 4 | *.local 5 | dist 6 | dist-ssr 7 | node_modules 8 | .idea/ 9 | *.log 10 | -------------------------------------------------------------------------------- /examples/basic/vue-shim.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.vue' { 2 | import type { DefineComponent } from 'vue' 3 | const component: DefineComponent<{}, {}, any> 4 | export default component 5 | } 6 | -------------------------------------------------------------------------------- /examples/basic/bin/www.js: -------------------------------------------------------------------------------- 1 | const { startExpressServer: startServer } = require('vite-ssr-vue3/server') 2 | const { createApp } = require('../dist/server/main.cjs') 3 | 4 | startServer({ createApp }) 5 | -------------------------------------------------------------------------------- /examples/basic/src/main.ts: -------------------------------------------------------------------------------- 1 | import { ViteSSR } from 'vite-ssr-vue3' 2 | import routes from 'virtual:generated-pages' 3 | import App from './App.vue' 4 | 5 | export const createApp = ViteSSR(App, { routes }) 6 | -------------------------------------------------------------------------------- /examples/vitesse-lite-naive-ui/test/basic.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | 3 | describe('Hi', () => { 4 | it('should works', () => { 5 | expect(1 + 1).toEqual(2) 6 | }) 7 | }) 8 | -------------------------------------------------------------------------------- /examples/vitesse-lite-naive-ui/vue-shims.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.vue' { 2 | import type { DefineComponent } from 'vue' 3 | const component: DefineComponent<{}, {}, any> 4 | export default component 5 | } 6 | -------------------------------------------------------------------------------- /examples/basic/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "types": [ 5 | "vite/client", 6 | "vite-plugin-pages/client" 7 | ] 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /examples/vitesse-lite-naive-ui/bin/www.js: -------------------------------------------------------------------------------- 1 | const { startExpressServer: startServer } = require('vite-ssr-vue3/server') 2 | const { createApp } = require('../dist/server/main.cjs') 3 | 4 | startServer({ createApp }) 5 | -------------------------------------------------------------------------------- /examples/basic/src/server.ts: -------------------------------------------------------------------------------- 1 | // Start server 2 | import { startExpressServer as startServer } from 'vite-ssr-vue3/server' 3 | import { createApp } from './main' 4 | 5 | startServer({ createApp, root: __dirname, outDir: '..' }) 6 | -------------------------------------------------------------------------------- /examples/vitesse-lite-naive-ui/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "types": [ 5 | "vite/client", 6 | "vite-plugin-pages/client" 7 | ] 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /examples/vitesse-lite-naive-ui/tsconfig copy.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "types": [ 5 | "vite/client", 6 | "vite-plugin-pages/client" 7 | ] 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /examples/vitesse-lite-naive-ui/test/__snapshots__/component.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1 2 | 3 | exports[`Counter.vue > should render 1`] = `"
10
"`; 4 | -------------------------------------------------------------------------------- /examples/vitesse-lite-naive-ui/src/server.ts: -------------------------------------------------------------------------------- 1 | // Start server 2 | import { startExpressServer as startServer } from 'vite-ssr-vue3/server' 3 | import { createApp } from './main' 4 | 5 | startServer({ createApp, root: __dirname, outDir: '..' }) 6 | -------------------------------------------------------------------------------- /src/node/utils.ts: -------------------------------------------------------------------------------- 1 | import { blue, gray, yellow } from 'kolorist' 2 | 3 | export function buildLog(text: string, count?: number) { 4 | // eslint-disable-next-line no-console 5 | console.log(`\n${gray('[vite-ssr]')} ${yellow(text)}${count ? blue(` (${count})`) : ''}`) 6 | } 7 | -------------------------------------------------------------------------------- /examples/basic/vite.config.ts: -------------------------------------------------------------------------------- 1 | import type { UserConfig } from 'vite' 2 | import { defineConfig } from 'vite' 3 | import vue from '@vitejs/plugin-vue' 4 | import Pages from 'vite-plugin-pages' 5 | 6 | // https://vitejs.dev/config/ 7 | 8 | export default defineConfig({ 9 | plugins: [vue(), Pages()], 10 | } as UserConfig) 11 | -------------------------------------------------------------------------------- /src/utils/document-ready.ts: -------------------------------------------------------------------------------- 1 | export function documentReady(_passThrough?: any) { 2 | if (document.readyState === 'loading') { 3 | return new Promise((resolve) => { 4 | document.addEventListener('DOMContentLoaded', () => resolve(_passThrough)) 5 | }) 6 | } 7 | 8 | return Promise.resolve(_passThrough) 9 | } 10 | -------------------------------------------------------------------------------- /examples/basic/vite.config.noexternal.ts: -------------------------------------------------------------------------------- 1 | import config from './vite.config' 2 | /** 3 | * @type {import('vite').UserConfig} 4 | */ 5 | module.exports = Object.assign(config, { 6 | ssrOptions: { 7 | serverConfig: { 8 | build: { 9 | ssr: './src/server', 10 | }, 11 | ssr: { 12 | noExternal: /./, 13 | }, 14 | }, 15 | }, 16 | }) 17 | -------------------------------------------------------------------------------- /examples/basic/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /examples/vitesse-lite-naive-ui/vite.config.noexternal.ts: -------------------------------------------------------------------------------- 1 | import config from './vite.config' 2 | /** 3 | * @type {import('vite').UserConfig} 4 | */ 5 | module.exports = Object.assign(config, { 6 | ssrOptions: { 7 | serverConfig: { 8 | build: { 9 | ssr: './src/server', 10 | }, 11 | ssr: { 12 | noExternal: /./, 13 | }, 14 | }, 15 | }, 16 | }) 17 | -------------------------------------------------------------------------------- /examples/vitesse-lite-naive-ui/src/styles/main.css: -------------------------------------------------------------------------------- 1 | html, 2 | body, 3 | #app { 4 | height: 100%; 5 | margin: 0; 6 | padding: 0; 7 | } 8 | 9 | html.dark { 10 | background: #121212; 11 | } 12 | 13 | 14 | button.n-button { 15 | color: var(--n-text-color); 16 | background-color: var(--n-color); 17 | border-radius: var(--n-border-radius); 18 | font-weight: var(--n-font-weight); 19 | } 20 | -------------------------------------------------------------------------------- /examples/basic/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite App 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /examples/vitesse-lite-naive-ui/public/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /examples/vitesse-lite-naive-ui/src/components/Counter.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 20 | -------------------------------------------------------------------------------- /examples/vitesse-lite-naive-ui/src/components/README.md: -------------------------------------------------------------------------------- 1 | ## Components 2 | 3 | Components in this dir will be auto-registered and on-demand, powered by [`unplugin-vue-components`](https://github.com/antfu/unplugin-vue-components). 4 | 5 | ### Icons 6 | 7 | You can use icons from almost any icon sets by the power of [Iconify](https://iconify.design/). 8 | 9 | It will only bundle the icons you use. Check out [`unplugin-icons`](https://github.com/antfu/unplugin-icons) for more details. 10 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "ESNext", 4 | "target": "es2020", 5 | "lib": [ 6 | "ESNext", 7 | "DOM" 8 | ], 9 | "esModuleInterop": true, 10 | "strict": true, 11 | "strictNullChecks": true, 12 | "moduleResolution": "Node", 13 | "resolveJsonModule": true, 14 | "skipLibCheck": true, 15 | "jsx": "preserve" 16 | }, 17 | "exclude": [ 18 | "**/dist", 19 | "**/node_modules", 20 | "**/test" 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /examples/vitesse-lite-naive-ui/src/pages/README.md: -------------------------------------------------------------------------------- 1 | ## File-based Routing 2 | 3 | Routes will be auto-generated for Vue files in this dir with the same file structure. 4 | Check out [`vite-plugin-pages`](https://github.com/hannoeru/vite-plugin-pages) for more details. 5 | 6 | ### Path Aliasing 7 | 8 | `~/` is aliased to `./src/` folder. 9 | 10 | For example, instead of having 11 | 12 | ```ts 13 | import { isDark } from '../../../../composables' 14 | ``` 15 | 16 | now, you can use 17 | 18 | ```ts 19 | import { isDark } from '~/composables' 20 | ``` 21 | -------------------------------------------------------------------------------- /examples/vitesse-lite-naive-ui/src/components/Footer.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 22 | -------------------------------------------------------------------------------- /examples/vitesse-lite-naive-ui/src/App.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 22 | -------------------------------------------------------------------------------- /tsup.config.ts: -------------------------------------------------------------------------------- 1 | import type { Options } from 'tsup' 2 | 3 | export default { 4 | entryPoints: { 5 | 'index': 'src/index.ts', 6 | 'server': 'src/server/index.ts', 7 | 'node/cli': 'src/node/cli.ts', 8 | }, 9 | dts: true, 10 | target: 'node14', 11 | format: [ 12 | 'esm', 13 | 'cjs', 14 | ], 15 | external: [ 16 | 'vue', 17 | 'vite', 18 | 'vue/server-renderer', 19 | 'vue/compiler-sfc', 20 | ], 21 | clean: true, 22 | esbuildOptions: (options: any, { format }: any) => { 23 | options.outExtension = { '.js': format === 'cjs' ? '.cjs' : format === 'esm' ? '.mjs' : '.js' } 24 | }, 25 | } 26 | -------------------------------------------------------------------------------- /examples/vitesse-lite-naive-ui/src/main.ts: -------------------------------------------------------------------------------- 1 | import routes from 'virtual:generated-pages' 2 | import { ViteSSR } from 'vite-ssr-vue3' 3 | import { setup } from '@css-render/vue3-ssr' 4 | import App from './App.vue' 5 | 6 | import '@unocss/reset/tailwind.css' 7 | import './styles/main.css' 8 | import 'uno.css' 9 | 10 | export const createApp = ViteSSR(App, { routes }, (ctx) => { 11 | if (!ctx.isClient) { 12 | let collect: any 13 | ctx.onBeforePageRender = () => { 14 | collect = setup(ctx.app).collect 15 | } 16 | ctx.onPageRender = ({ preloadLinks }) => { 17 | preloadLinks += collect() 18 | return { preloadLinks } 19 | } 20 | } 21 | }) 22 | -------------------------------------------------------------------------------- /examples/basic/src/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | 13 | 23 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # How to run/build the examples 2 | 3 | ```bash 4 | # Clone repo 5 | git clone https://github.com/aliuq/vite-ssr-vue3.git 6 | cd vite-ssr-vue3 7 | 8 | # Install all project dependencies and build project 9 | pnpm install 10 | pnpm build 11 | 12 | # Basic example 13 | pnpm basic:dev 14 | pnpm basic:build 15 | pnpm basic:serve 16 | pnpm basic:serve:bin 17 | pnpm basic:serve:custom 18 | 19 | pnpm basic:build:noexternal 20 | pnpm basic:preview 21 | 22 | # Vitesse lite and naive-ui 23 | pnpm vl-naive:dev 24 | pnpm vl-naive:build 25 | pnpm vl-naive:serve 26 | pnpm vl-naive:serve:bin 27 | pnpm vl-naive:serve:custom 28 | 29 | pnpm vl-naive:build:noexternal 30 | pnpm vl-naive:preview 31 | ``` 32 | -------------------------------------------------------------------------------- /examples/basic/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "basic", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "vite-ssr --port 3333", 7 | "build": "vite-ssr build", 8 | "serve": "vite-ssr --mode production", 9 | "serve:bin": "node ./bin/www", 10 | "serve:custom": "node ./src/express", 11 | "build:noexternal": "vite-ssr build --config ./vite.config.noexternal.ts", 12 | "preview": "node ./dist/server/server.cjs" 13 | }, 14 | "dependencies": { 15 | "vue": "^3.2.25" 16 | }, 17 | "devDependencies": { 18 | "@vitejs/plugin-vue": "^2.3.0", 19 | "typescript": "^4.5.4", 20 | "vite": "^2.9.1", 21 | "vite-plugin-pages": "^0.22.0", 22 | "vite-ssr-vue3": "workspace:*" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*' 7 | 8 | jobs: 9 | release: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v2 13 | with: 14 | fetch-depth: 0 15 | - uses: actions/setup-node@v2 16 | with: 17 | node-version: '14' 18 | registry-url: https://registry.npmjs.org/ 19 | - run: npm i -g pnpm 20 | - run: pnpm install -F vite-ssr-vue3 --frozen-lockfile 21 | - run: npm publish --access public 22 | env: 23 | NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} 24 | - run: npx conventional-github-releaser -p angular 25 | env: 26 | CONVENTIONAL_GITHUB_RELEASER_TOKEN: ${{secrets.GITHUB_TOKEN}} 27 | -------------------------------------------------------------------------------- /examples/vitesse-lite-naive-ui/test/component.test.ts: -------------------------------------------------------------------------------- 1 | import { mount } from '@vue/test-utils' 2 | import { describe, expect, it } from 'vitest' 3 | import Counter from '../src/components/Counter.vue' 4 | 5 | describe('Counter.vue', () => { 6 | it('should render', () => { 7 | const wrapper = mount(Counter, { props: { initial: 10 } }) 8 | expect(wrapper.text()).toContain('10') 9 | expect(wrapper.html()).toMatchSnapshot() 10 | }) 11 | 12 | it('should be interactive', async() => { 13 | const wrapper = mount(Counter, { props: { initial: 0 } }) 14 | expect(wrapper.text()).toContain('0') 15 | 16 | expect(wrapper.find('.inc').exists()).toBe(true) 17 | 18 | await wrapper.get('button').trigger('click') 19 | 20 | expect(wrapper.text()).toContain('1') 21 | }) 22 | }) 23 | -------------------------------------------------------------------------------- /examples/vitesse-lite-naive-ui/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vitesse Lite 8 | 9 | 10 | 11 |
12 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /examples/vitesse-lite-naive-ui/src/pages/index.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 30 | -------------------------------------------------------------------------------- /src/node/cli.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-expressions */ 2 | import { bold, gray, red, reset, underline } from 'kolorist' 3 | import yargs from 'yargs' 4 | import { hideBin } from 'yargs/helpers' 5 | import { bugs } from '../../package.json' 6 | import { startServer } from './server' 7 | import { build } from './build' 8 | 9 | yargs(hideBin(process.argv)) 10 | .scriptName('vite-ssr') 11 | .usage('$0 [args]') 12 | .command( 13 | 'build', 14 | 'Build SSR', 15 | args => args, 16 | args => build(args as any), 17 | ) 18 | .command( 19 | '*', 20 | 'development and production environment', 21 | args => args, 22 | async args => (await startServer)(args), 23 | ) 24 | .fail((msg, err, yargs) => { 25 | console.error(`\n${gray('[vite-ssr]')} ${bold(red('An internal error occurred.'))}`) 26 | console.error(`${gray('[vite-ssr]')} ${reset(`Please report an issue, if none already exists: ${underline(bugs)}`)}`) 27 | yargs.exit(1, err) 28 | }) 29 | .showHelpOnFail(false) 30 | .help() 31 | .argv 32 | -------------------------------------------------------------------------------- /src/utils/state.ts: -------------------------------------------------------------------------------- 1 | // https://github.com/yahoo/serialize-javascript 2 | const UNSAFE_CHARS_REGEXP = /[<>\/\u2028\u2029]/g 3 | const ESCAPED_CHARS = { 4 | '<': '\\u003C', 5 | '>': '\\u003E', 6 | '/': '\\u002F', 7 | '\u2028': '\\u2028', 8 | '\u2029': '\\u2029', 9 | } 10 | 11 | function escapeUnsafeChars(unsafeChar: string) { 12 | return ESCAPED_CHARS[unsafeChar as keyof typeof ESCAPED_CHARS] 13 | } 14 | 15 | export function serializeState(state: any) { 16 | if (state == null || Object.keys(state).length === 0) 17 | return null 18 | try { 19 | return JSON.stringify(JSON.stringify(state || {})).replace( 20 | UNSAFE_CHARS_REGEXP, 21 | escapeUnsafeChars, 22 | ) 23 | } 24 | catch (error) { 25 | console.error('[SSR] On state serialization -', error, state) 26 | return null 27 | } 28 | } 29 | 30 | export function deserializeState(state: string) { 31 | try { 32 | return JSON.parse(state || '{}') 33 | } 34 | catch (error) { 35 | console.error('[SSR] On state deserialization -', error, state) 36 | return {} 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /examples/vitesse-lite-naive-ui/unocss.config.ts: -------------------------------------------------------------------------------- 1 | import { 2 | defineConfig, 3 | presetAttributify, 4 | presetIcons, 5 | presetUno, 6 | presetWebFonts, 7 | // transformerDirectives, 8 | // transformerVariantGroup, 9 | } from 'unocss' 10 | 11 | export default defineConfig({ 12 | shortcuts: [ 13 | ['btn', 'px-4 py-1 rounded inline-block bg-teal-600 text-white cursor-pointer hover:bg-teal-700 disabled:cursor-default disabled:bg-gray-600 disabled:opacity-50'], 14 | ['icon-btn', 'text-[0.9em] inline-block cursor-pointer select-none opacity-75 transition duration-200 ease-in-out hover:opacity-100 hover:text-teal-600'], 15 | ], 16 | presets: [ 17 | presetUno(), 18 | presetAttributify(), 19 | presetIcons({ 20 | scale: 1.2, 21 | warn: true, 22 | }), 23 | presetWebFonts({ 24 | fonts: { 25 | sans: 'DM Sans', 26 | serif: 'DM Serif Display', 27 | mono: 'DM Mono', 28 | }, 29 | }), 30 | ], 31 | // transformers: [ 32 | // transformerDirectives(), 33 | // transformerVariantGroup(), 34 | // ], 35 | }) 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 liuq 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /examples/basic/src/components/HelloWorld.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 37 | 38 | 55 | -------------------------------------------------------------------------------- /examples/vitesse-lite-naive-ui/vite.config.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | import path from 'path' 4 | import { defineConfig } from 'vite' 5 | import Vue from '@vitejs/plugin-vue' 6 | import Pages from 'vite-plugin-pages' 7 | import Components from 'unplugin-vue-components/vite' 8 | import AutoImport from 'unplugin-auto-import/vite' 9 | import Unocss from 'unocss/vite' 10 | import { NaiveUiResolver } from 'unplugin-vue-components/resolvers' 11 | 12 | export default defineConfig({ 13 | resolve: { 14 | alias: { 15 | '~/': `${path.resolve(__dirname, 'src')}/`, 16 | }, 17 | }, 18 | plugins: [ 19 | Vue({ 20 | reactivityTransform: true, 21 | }), 22 | 23 | // https://github.com/hannoeru/vite-plugin-pages 24 | Pages(), 25 | 26 | // https://github.com/antfu/unplugin-auto-import 27 | AutoImport({ 28 | imports: [ 29 | 'vue', 30 | 'vue/macros', 31 | 'vue-router', 32 | '@vueuse/core', 33 | ], 34 | dts: true, 35 | }), 36 | 37 | // https://github.com/antfu/vite-plugin-components 38 | Components({ 39 | resolvers: [NaiveUiResolver()], 40 | }), 41 | 42 | // https://github.com/antfu/unocss 43 | // see unocss.config.ts for config 44 | Unocss(), 45 | ], 46 | 47 | // https://github.com/vitest-dev/vitest 48 | test: { 49 | environment: 'jsdom', 50 | }, 51 | }) 52 | -------------------------------------------------------------------------------- /src/components.ts: -------------------------------------------------------------------------------- 1 | import type { App } from 'vue' 2 | import { defineComponent, inject, onMounted, ref } from 'vue' 3 | import { useRoute } from 'vue-router' 4 | import type { ViteSSRContext } from './types' 5 | 6 | export const ClientOnly = defineComponent({ 7 | name: 'ClientOnly', 8 | setup(_: any, { slots }: any) { 9 | const show = ref(false) 10 | onMounted(() => { 11 | show.value = true 12 | }) 13 | 14 | return () => (show.value && slots.default ? slots.default() : null) 15 | }, 16 | }) 17 | 18 | const CONTEXT_SYMBOL = Symbol('unique-context-symbol') 19 | export function provideContext(app: App, context: ViteSSRContext) { 20 | app.provide(CONTEXT_SYMBOL, context) 21 | } 22 | 23 | export function useContext() { 24 | return inject(CONTEXT_SYMBOL) as ViteSSRContext 25 | } 26 | 27 | export async function useFetch(key: string, fn: () => Promise) { 28 | const { initialState } = useContext() 29 | const { name } = useRoute() 30 | key = `${name as string}_${key}` 31 | const state = ref(initialState[key] || null) 32 | // @ts-expect-error Support client side hmr, need to update state 33 | if (import.meta.hot) 34 | state.value = await fn() 35 | 36 | if (!state.value) { 37 | state.value = await fn() 38 | // @ts-expect-error global variable 39 | if (import.meta.env.SSR) 40 | initialState[key] = state.value 41 | } 42 | return state 43 | } 44 | -------------------------------------------------------------------------------- /examples/basic/src/express.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | const path = require('path') 3 | const express = require('express') 4 | const { createRender } = require('vite-ssr-vue3/server') 5 | 6 | async function createServer() { 7 | const out = path.join(__dirname, '../dist') 8 | const resolve = p => path.resolve(out, p) 9 | const app = express() 10 | app.use(express.static(resolve('./client'), { 11 | index: false, 12 | maxAge: '1y', 13 | })) 14 | 15 | const render = await createRender({ isProd: true, root: __dirname, outDir: '../dist' }) 16 | const { createApp } = require(resolve('./server/main.cjs')) 17 | 18 | app.use('*', async(req, res) => { 19 | try { 20 | const url = req.originalUrl 21 | 22 | if (req.method !== 'GET' || url === '/sw.js' || url === '/favicon.ico') 23 | return 24 | 25 | const context = await createApp(false) 26 | let html = await render(url, { context }) 27 | 28 | html += '\n' 29 | 30 | res.status(200).set({ 'Content-Type': 'text/html' }).end(html) 31 | } 32 | catch (error) { 33 | console.error(error) 34 | res.status(500).end(error.stack) 35 | } 36 | }) 37 | 38 | return { server: app } 39 | } 40 | 41 | createServer().then(({ server }) => 42 | server.listen(3000, () => { 43 | // eslint-disable-next-line no-console 44 | console.info('http://localhost:3000') 45 | }), 46 | ) 47 | -------------------------------------------------------------------------------- /examples/vitesse-lite-naive-ui/src/express.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | const path = require('path') 3 | const express = require('express') 4 | const { createRender } = require('vite-ssr-vue3/server') 5 | 6 | async function createServer() { 7 | const out = path.join(__dirname, '../dist') 8 | const resolve = p => path.resolve(out, p) 9 | const app = express() 10 | app.use(express.static(resolve('./client'), { 11 | index: false, 12 | maxAge: '1y', 13 | })) 14 | 15 | const render = await createRender({ isProd: true, root: __dirname, outDir: '../dist' }) 16 | const { createApp } = require(resolve('./server/main.cjs')) 17 | 18 | app.use('*', async(req, res) => { 19 | try { 20 | const url = req.originalUrl 21 | 22 | if (req.method !== 'GET' || url === '/sw.js' || url === '/favicon.ico') 23 | return 24 | 25 | const context = await createApp(false) 26 | let html = await render(url, { context }) 27 | 28 | html += '\n' 29 | 30 | res.status(200).set({ 'Content-Type': 'text/html' }).end(html) 31 | } 32 | catch (error) { 33 | console.error(error) 34 | res.status(500).end(error.stack) 35 | } 36 | }) 37 | 38 | return { server: app } 39 | } 40 | 41 | createServer().then(({ server }) => 42 | server.listen(3000, () => { 43 | // eslint-disable-next-line no-console 44 | console.info('http://localhost:3000') 45 | }), 46 | ) 47 | -------------------------------------------------------------------------------- /examples/vitesse-lite-naive-ui/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vitesse-lite-naive-ui", 3 | "private": true, 4 | "packageManager": "pnpm@6.32.3", 5 | "files": [ 6 | "dist", 7 | "bin", 8 | "*.d.ts" 9 | ], 10 | "bin": "./bin/www.js", 11 | "scripts": { 12 | "dev": "vite-ssr --port 3333", 13 | "build": "vite-ssr build", 14 | "serve": "vite-ssr --mode production", 15 | "serve:bin": "node ./bin/www", 16 | "serve:custom": "node ./src/express", 17 | "build:noexternal": "vite-ssr build --config ./vite.config.noexternal.ts", 18 | "preview": "node ./dist/server/server.cjs", 19 | "lint": "eslint .", 20 | "typecheck": "vue-tsc --noEmit", 21 | "test": "vitest" 22 | }, 23 | "dependencies": { 24 | "@vueuse/core": "^8.2.4", 25 | "vue": "^3.2.31", 26 | "vue-router": "^4.0.14" 27 | }, 28 | "devDependencies": { 29 | "@antfu/eslint-config": "^0.19.4", 30 | "@css-render/vue3-ssr": "^0.15.9", 31 | "@iconify-json/carbon": "^1.1.2", 32 | "@types/node": "^17.0.23", 33 | "@unocss/reset": "^0.30.11", 34 | "@vitejs/plugin-vue": "^2.3.1", 35 | "@vue/test-utils": "^2.0.0-rc.18", 36 | "eslint": "^8.12.0", 37 | "jsdom": "^19.0.0", 38 | "naive-ui": "^2.26.4", 39 | "pnpm": "^6.32.4", 40 | "typescript": "^4.6.3", 41 | "unocss": "^0.30.11", 42 | "unplugin-auto-import": "^0.6.9", 43 | "unplugin-vue-components": "^0.18.5", 44 | "vite": "^2.9.1", 45 | "vite-plugin-pages": "^0.22.0", 46 | "vite-ssr-vue3": "workspace:*", 47 | "vitest": "^0.8.4", 48 | "vue-tsc": "^0.33.9" 49 | }, 50 | "eslintConfig": { 51 | "extends": "@antfu" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/server/express.ts: -------------------------------------------------------------------------------- 1 | import path from 'path' 2 | import { performance } from 'perf_hooks' 3 | import express from 'express' 4 | import type { CreateServerOptions, ViteSSRContext } from '../types' 5 | import { createRender } from '../node/render' 6 | import { createStartServer } from './common' 7 | 8 | export async function createServer({ 9 | createApp, 10 | routePath, 11 | root = process.cwd(), 12 | outDir = 'dist', 13 | }: CreateServerOptions) { 14 | const out = path.isAbsolute(outDir) ? outDir : path.join(root, outDir) 15 | const resolve = (dir: string) => path.resolve(out, dir) 16 | 17 | const app = express() 18 | app.use(express.static(resolve('./client'), { 19 | index: false, 20 | maxAge: '1y', 21 | })) 22 | 23 | const render = await createRender({ isProd: true, root, outDir }) 24 | 25 | app.use('*', async(req, res, next) => { 26 | try { 27 | const url = req.originalUrl || req.url 28 | if (req.method !== 'GET' || url === '/favicon.ico') 29 | return next() 30 | 31 | globalThis.__ssr_start_time = performance.now() 32 | const context: ViteSSRContext = await createApp(false, routePath) 33 | let html = await render(url, { context }) 34 | 35 | const time = Math.round(performance.now() - globalThis.__ssr_start_time) 36 | html += `\n\n` 37 | 38 | return res.status(200).set({ 'Content-Type': 'text/html' }).end(html) 39 | } 40 | catch (error: any) { 41 | console.error(error) 42 | res.status(500).end(error.stack) 43 | } 44 | }) 45 | 46 | return { server: app } 47 | } 48 | 49 | export const startServer = createStartServer(createServer) 50 | -------------------------------------------------------------------------------- /examples/vitesse-lite-naive-ui/components.d.ts: -------------------------------------------------------------------------------- 1 | // generated by unplugin-vue-components 2 | // We suggest you to commit this file into source control 3 | // Read more: https://github.com/vuejs/vue-next/pull/3399 4 | 5 | declare module 'vue' { 6 | export interface GlobalComponents { 7 | Counter: typeof import('./src/components/Counter.vue')['default'] 8 | Footer: typeof import('./src/components/Footer.vue')['default'] 9 | NButton: typeof import('naive-ui')['NButton'] 10 | NCheckbox: typeof import('naive-ui')['NCheckbox'] 11 | NCheckboxGroup: typeof import('naive-ui')['NCheckboxGroup'] 12 | NConfigProvider: typeof import('naive-ui')['NConfigProvider'] 13 | NDatePicker: typeof import('naive-ui')['NDatePicker'] 14 | NForm: typeof import('naive-ui')['NForm'] 15 | NFormItem: typeof import('naive-ui')['NFormItem'] 16 | NFormItemGi: typeof import('naive-ui')['NFormItemGi'] 17 | NGrid: typeof import('naive-ui')['NGrid'] 18 | NH1: typeof import('naive-ui')['NH1'] 19 | NInput: typeof import('naive-ui')['NInput'] 20 | NInputNumber: typeof import('naive-ui')['NInputNumber'] 21 | NMessageProvider: typeof import('naive-ui')['NMessageProvider'] 22 | NRadio: typeof import('naive-ui')['NRadio'] 23 | NRadioButton: typeof import('naive-ui')['NRadioButton'] 24 | NRadioGroup: typeof import('naive-ui')['NRadioGroup'] 25 | NSelect: typeof import('naive-ui')['NSelect'] 26 | NSlider: typeof import('naive-ui')['NSlider'] 27 | NSpace: typeof import('naive-ui')['NSpace'] 28 | NSwitch: typeof import('naive-ui')['NSwitch'] 29 | NTimePicker: typeof import('naive-ui')['NTimePicker'] 30 | NTransfer: typeof import('naive-ui')['NTransfer'] 31 | } 32 | } 33 | 34 | export { } 35 | -------------------------------------------------------------------------------- /src/node/build.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | import { isAbsolute, join } from 'path' 3 | import type { ResolvedConfig, UserConfig } from 'vite' 4 | import { mergeConfig, build as viteBuild } from 'vite' 5 | import { getEntryPoint, resolveViteConfig } from '../config' 6 | import { buildLog } from './utils' 7 | 8 | export interface CliOptions { 9 | mode?: string 10 | format?: string 11 | config?: string 12 | } 13 | 14 | export async function build(cliOptions: CliOptions = {}) { 15 | const mode = process.env.MODE || process.env.NODE_ENV || cliOptions.mode || 'production' 16 | const config: ResolvedConfig = await resolveViteConfig(mode, { configFile: cliOptions.config }) 17 | 18 | const cwd = process.cwd() 19 | const root = config.root || cwd 20 | const outDir = config.build.outDir || 'dist' 21 | const out = isAbsolute(outDir) ? outDir : join(root, outDir) 22 | 23 | const { input, clientConfig = {}, serverConfig = {} } = Object.assign({}, config.ssrOptions || {}, cliOptions) 24 | 25 | buildLog('Build for client...') 26 | await viteBuild(mergeConfig({ 27 | build: { 28 | ssrManifest: true, 29 | outDir: join(out, 'client'), 30 | rollupOptions: { 31 | input: { 32 | app: join(root, input || 'index.html'), 33 | }, 34 | }, 35 | }, 36 | mode: config.mode, 37 | }, clientConfig) as UserConfig) 38 | 39 | buildLog('Build for server...') 40 | await viteBuild(mergeConfig({ 41 | build: { 42 | ssr: await getEntryPoint(config.ssrOptions || {}, config), 43 | outDir: join(out, 'server'), 44 | minify: false, 45 | cssCodeSplit: false, 46 | rollupOptions: { 47 | output: { 48 | entryFileNames: '[name].cjs', 49 | format: 'cjs', 50 | }, 51 | }, 52 | }, 53 | mode: config.mode, 54 | }, serverConfig) as UserConfig) 55 | } 56 | -------------------------------------------------------------------------------- /src/server/common.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-var-requires */ 2 | import type { AddressInfo, Server } from 'net' 3 | import { performance } from 'perf_hooks' 4 | import type { ViteDevServer } from 'vite' 5 | import { bold, cyan } from 'kolorist' 6 | import type { CreateServerOptions } from '../types' 7 | 8 | function printInfo(server: Server, vite?: ViteDevServer | null) { 9 | // eslint-disable-next-line no-console 10 | const info = vite?.config ? vite.config.logger.info : console.info 11 | 12 | const address = server.address() 13 | const isAddressInfo = (x: any): x is AddressInfo => x?.address 14 | if (isAddressInfo(address)) { 15 | const ad = cyan(`http://localhost:${bold(address.port)}`) 16 | try { 17 | let msg = `\n express v${require('express/package.json').version}` 18 | if (vite?.config) 19 | msg += ` + vite v${require('vite/package.json').version}` 20 | 21 | info( 22 | cyan(msg), 23 | vite?.config ? { clear: !vite.config.logger.hasWarned } : '', 24 | ) 25 | } 26 | catch (err) { 27 | // TODO: Avoid error 28 | } 29 | finally { 30 | info('\n -- SSR mode \n') 31 | 32 | info(` > Running at: ${ad}`) 33 | 34 | if (vite?.config && vite.config.plugins.find(p => p.name.includes('unocss:inspector'))) 35 | info(` > Unocss: ${ad}${cyan('/__unocss')}`) 36 | 37 | if (vite?.config && vite.config.plugins.find(p => p.name.includes('vite-plugin-inspect'))) 38 | info(` > Inspect: ${ad}${cyan('/__inspect')}`) 39 | 40 | const time = Math.round(performance.now() - globalThis.__ssr_ready_time) 41 | info(cyan(`\n ready in ${time}ms.\n`)) 42 | } 43 | } 44 | } 45 | 46 | export function createStartServer(createServer: any) { 47 | return async(args: CreateServerOptions | any) => { 48 | globalThis.__ssr_ready_time = performance.now() 49 | const { server, vite = null } = await createServer(args) 50 | const ser = server.listen(args.port || 3000, () => { 51 | printInfo(ser, vite) 52 | }) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /examples/vitesse-lite-naive-ui/public/naive-ui.svg: -------------------------------------------------------------------------------- 1 | Naive UI - LOGO -------------------------------------------------------------------------------- /src/config.ts: -------------------------------------------------------------------------------- 1 | import path, { join as _join, resolve as _resolve } from 'path' 2 | import { resolveConfig } from 'vite' 3 | import type { InlineConfig, ResolvedConfig } from 'vite' 4 | import fs from 'fs-extra' 5 | 6 | import type { ViteSSROptions } from './types' 7 | 8 | let __CONFIG__: ResolvedConfig 9 | 10 | export async function getConfig(mode: string | undefined = process.env.NODE_ENV) { 11 | if (!__CONFIG__) 12 | __CONFIG__ = await resolveConfig({}, 'build', mode) 13 | 14 | const ssrOptions: ViteSSROptions = Object.assign({ 15 | input: 'index.html', 16 | entry: 'src/main', 17 | rootContainerId: 'app', 18 | useViteMiddleware: process.env.NODE_ENV !== 'production', 19 | mode: __CONFIG__.mode, 20 | }, __CONFIG__?.ssrOptions || {}) 21 | 22 | const join = (dir: string) => _join(__CONFIG__.root, dir) 23 | 24 | const resolve = (dir: string) => _resolve(__CONFIG__.root, dir) 25 | 26 | return { 27 | config: __CONFIG__, 28 | ssrOptions, 29 | join, 30 | resolve, 31 | } 32 | } 33 | 34 | export async function getSsrOptions(mode?: string | undefined) { 35 | const { ssrOptions } = await getConfig(mode) 36 | return ssrOptions 37 | } 38 | 39 | export async function getEntry(mode?: string | undefined) { 40 | const { join } = await getConfig(mode) 41 | const html = await getIndexTemplate(mode) 42 | // 43 | const matches = html.match(/ 64 | const matches = indexHtml.match(/` 86 | : '' 87 | 88 | const html = template 89 | .replace('', `\n${preloadLinks}`) 90 | .replace( 91 | '
', 92 | `
${appHtml}
${stateScript}`, 93 | ) 94 | 95 | return html 96 | } 97 | 98 | return render 99 | } 100 | 101 | async function renderPreloadLinks(modules: any, manifest: any) { 102 | let links = '' 103 | const seen = new Set() 104 | const { basename } = await import('path') 105 | 106 | modules && modules.forEach((id: any) => { 107 | const files = manifest[id] 108 | if (files) { 109 | files.forEach((file: any) => { 110 | if (!seen.has(file)) { 111 | seen.add(file) 112 | const filename = basename(file) 113 | if (manifest[filename]) { 114 | for (const depFile of manifest[filename]) { 115 | links += renderPreloadLink(depFile) 116 | seen.add(depFile) 117 | } 118 | } 119 | links += renderPreloadLink(file) 120 | } 121 | }) 122 | } 123 | }) 124 | return links 125 | } 126 | 127 | function renderPreloadLink(file: string) { 128 | if (file.endsWith('.js')) { 129 | return `` 130 | } 131 | else if (file.endsWith('.css')) { 132 | return `` 133 | } 134 | else if (file.endsWith('.woff')) { 135 | return ` ` 136 | } 137 | else if (file.endsWith('.woff2')) { 138 | return ` ` 139 | } 140 | else if (file.endsWith('.gif')) { 141 | return ` ` 142 | } 143 | else if (file.endsWith('.jpg') || file.endsWith('.jpeg')) { 144 | return ` ` 145 | } 146 | else if (file.endsWith('.png')) { 147 | return ` ` 148 | } 149 | else { 150 | // TODO 151 | return '' 152 | } 153 | } 154 | 155 | function getDefaultTemplate(ssrOptions: ViteSSROptions) { 156 | const { rootContainerId = 'app', entry = 'src/main.ts' } = ssrOptions 157 | const template = ` 158 | 159 | 160 | 161 | 162 | 163 | 164 | Vite SSR 165 | 171 | 172 | 173 |
174 | 175 | 176 | 177 | ` 178 | return template 179 | } 180 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Vite-SSR-Vue3 2 | 3 | ![version](https://img.shields.io/npm/v/vite-ssr-vue3) 4 | 5 | Vite plugin for vue Server-Side Rendering, Used express to create a server. 6 | 7 | ## Usage 8 | 9 | ### Install 10 | 11 | ```bash 12 | # npm 13 | npm install -D vite-ssr-vue3 vue-router 14 | 15 | # yarn 16 | yarn add -D vite-ssr-vue3 vue-router 17 | 18 | # pnpm 19 | pnpm add -D vite-ssr-vue3 vue-router 20 | 21 | # pnpm workspaces 22 | pnpm add -D vite-ssr-vue3 vue-router -F 23 | ``` 24 | 25 | Modify `package.json` scripts to: 26 | 27 | ```diff 28 | { 29 | "scripts": { 30 | - "dev": "vite --port 3333", 31 | + "dev": "vite-ssr --port 3333", 32 | - "build": "vite build", 33 | + "build": "vite-ssr build", 34 | + "serve": "vite-ssr --mode production" 35 | }, 36 | } 37 | ``` 38 | 39 | ```ts 40 | // src/main.ts 41 | import { ViteSSR } from 'vite-ssr-vue3' 42 | import routes from './routes' 43 | import App from './App.vue' 44 | 45 | // Export `createApp` function is required by vite-ssr 46 | export const createApp = ViteSSR(App, { routes }, (context) => { 47 | // if (import.meta.env.SSR) { 48 | // // Running in server 49 | // } else { 50 | // // Running in browser 51 | // } 52 | }) 53 | ``` 54 | 55 | ### Initial State 56 | 57 | ```ts 58 | // src/main.ts 59 | import { ViteSSR } from 'vite-ssr-vue3' 60 | import routes from './routes' 61 | import App from './App.vue' 62 | 63 | export const createApp = ViteSSR(App, { routes }, (context) => { 64 | const { app, initialState } = context 65 | 66 | // Use pinia to store your data 67 | const pinia = createPinia() 68 | 69 | if (import.meta.env.SSR) 70 | initialState.pinia = pinia.state.value 71 | else 72 | pinia.state.value = initialState.pinia 73 | 74 | }) 75 | ``` 76 | 77 | ### Data Fetching 78 | 79 | ```ts 80 | import { useFetch } from 'vite-ssr-vue3' 81 | 82 | const counts = await useFetch('counts', () => Promise.resolve([1, 2, 3])) 83 | ``` 84 | 85 | ### Full Control Build 86 | 87 | `vite-ssr build` command default equals build client and server, so you can customize your build process. 88 | 89 | ```diff 90 | { 91 | "scripts": { 92 | + "build": "vite-ssr build", 93 | - "build:client": "vite build --ssrManifest --outDir dist/client", 94 | - "build:server": "vite build --ssr src/main.ts --outDir dist/server", 95 | }, 96 | } 97 | ``` 98 | 99 | vite-ssr support separate options for client and server building, Configure them for separate builds 100 | 101 | ```ts 102 | import { defineConfig } from 'vite' 103 | 104 | export default defineConfig({ 105 | ssrOptions: { 106 | // Client build options 107 | clientConfig: {}, 108 | // Server build options 109 | serverConfig: {}, 110 | }, 111 | }) 112 | ``` 113 | 114 | Compiled with `vite-ssr build`, there are three ways to start the server: 115 | 116 | 1. Run `vite-ssr --mode production` 117 | 2. Use vite-ssr built-in express server, then run `node ./bin/www` 118 | 119 | ```js 120 | // ./bin/www.js 121 | const { startExpressServer: startServer } = require('vite-ssr-vue3/server') 122 | const { createApp } = require('../dist/server/main.cjs') 123 | 124 | startServer({ createApp }) 125 | ``` 126 | 127 | 3. Customize server 128 | 129 | `vite-ssr-vue3/server` export a function `createRender` to allows you create a render function. 130 |
131 | Express example 132 | 133 | ```js 134 | // ./src/express.js 135 | // @ts-check 136 | const path = require('path') 137 | const express = require('express') 138 | const { createRender } = require('vite-ssr-vue3/server') 139 | 140 | async function createServer() { 141 | const out = path.join(__dirname, '../dist') 142 | const resolve = p => path.resolve(out, p) 143 | const app = express() 144 | app.use(express.static(resolve('./client'), { 145 | index: false, 146 | maxAge: '1y', 147 | })) 148 | 149 | const render = await createRender({ isProd: true, root: __dirname, outDir: '../dist' }) 150 | const { createApp } = require(resolve('./server/main.cjs')) 151 | 152 | app.use('*', async(req, res) => { 153 | try { 154 | const url = req.originalUrl 155 | 156 | if (req.method !== 'GET' || url === '/sw.js' || url === '/favicon.ico') 157 | return 158 | 159 | const context = await createApp(false) 160 | let html = await render(url, { context }) 161 | 162 | html += '\n' 163 | 164 | res.status(200).set({ 'Content-Type': 'text/html' }).end(html) 165 | } 166 | catch (error) { 167 | -console.error(error) 168 | res.status(500).end(error.stack) 169 | } 170 | }) 171 | 172 | return { server: app } 173 | } 174 | 175 | createServer().then(({ server }) => 176 | server.listen(3000, () => { 177 | // eslint-disable-next-line no-console 178 | console.info('http://localhost:3000') 179 | }), 180 | ) 181 | ``` 182 | 183 |
184 | 185 | In the above three ways, we need to strongly depend on the server, although we can use `ssr.noExternal` to package dependencies, but the server cannot be packaged in, you must install server package `npm install express` 186 | 187 | we can modify the server building entry file to avoid this, create a new file to wrapper your server instance(*`vite-ssr` will not works*), configure `ssr.noExternal: /./` to package all dependencies. if you want to runing anywhere, this is recommended. 188 | 189 | ```ts 190 | // src/server.ts 191 | import { startExpressServer as startServer } from 'vite-ssr-vue3/server' 192 | import { createApp } from './main' 193 | 194 | startServer({ createApp, root: __dirname, outDir: '..' }) 195 | ``` 196 | 197 | ```ts 198 | // vite.config.noexternal.ts 199 | import type { UserConfig } from 'vite' 200 | import { defineConfig } from 'vite' 201 | 202 | export default defineConfig({ 203 | ssrOptions: { 204 | serverConfig: { 205 | build: { 206 | ssr: './src/server', 207 | }, 208 | ssr: { 209 | noExternal: /./, 210 | }, 211 | }, 212 | }, 213 | } as UserConfig) 214 | ``` 215 | 216 | then run `vite-ssr build:noexternal`, wait for a while, now you can run `node dist/server/server.cjs` (Required right path) to start the server anywhere. 217 | 218 | ## Thanks 219 | 220 | Lots of references to the following repos. 221 | 222 | + [vite playground ssr-vue](https://github.com/vitejs/vite/tree/main/packages/playground/ssr-vue) 223 | + [vite-ssg](https://github.com/antfu/vite-ssg) 224 | + [vite-ssr](https://github.com/frandiox/vite-ssr) 225 | 226 | ## License 227 | 228 | [MIT](https://github.com/aliuq/vite-ssr-vue3/blob/master/LICENSE) 229 | -------------------------------------------------------------------------------- /examples/vitesse-lite-naive-ui/src/pages/form.vue: -------------------------------------------------------------------------------- 1 | 134 | 135 | 287 | 288 | 293 | -------------------------------------------------------------------------------- /examples/vitesse-lite-naive-ui/auto-imports.d.ts: -------------------------------------------------------------------------------- 1 | // Generated by 'unplugin-auto-import' 2 | // We suggest you to commit this file into source control 3 | declare global { 4 | const $: typeof import('vue/macros')['$'] 5 | const $$: typeof import('vue/macros')['$$'] 6 | const $computed: typeof import('vue/macros')['$computed'] 7 | const $customRef: typeof import('vue/macros')['$customRef'] 8 | const $ref: typeof import('vue/macros')['$ref'] 9 | const $shallowRef: typeof import('vue/macros')['$shallowRef'] 10 | const $toRef: typeof import('vue/macros')['$toRef'] 11 | const asyncComputed: typeof import('@vueuse/core')['asyncComputed'] 12 | const autoResetRef: typeof import('@vueuse/core')['autoResetRef'] 13 | const computed: typeof import('vue')['computed'] 14 | const computedAsync: typeof import('@vueuse/core')['computedAsync'] 15 | const computedEager: typeof import('@vueuse/core')['computedEager'] 16 | const computedInject: typeof import('@vueuse/core')['computedInject'] 17 | const computedWithControl: typeof import('@vueuse/core')['computedWithControl'] 18 | const controlledComputed: typeof import('@vueuse/core')['controlledComputed'] 19 | const controlledRef: typeof import('@vueuse/core')['controlledRef'] 20 | const createApp: typeof import('vue')['createApp'] 21 | const createEventHook: typeof import('@vueuse/core')['createEventHook'] 22 | const createGlobalState: typeof import('@vueuse/core')['createGlobalState'] 23 | const createInjectionState: typeof import('@vueuse/core')['createInjectionState'] 24 | const createReactiveFn: typeof import('@vueuse/core')['createReactiveFn'] 25 | const createSharedComposable: typeof import('@vueuse/core')['createSharedComposable'] 26 | const createUnrefFn: typeof import('@vueuse/core')['createUnrefFn'] 27 | const customRef: typeof import('vue')['customRef'] 28 | const debouncedRef: typeof import('@vueuse/core')['debouncedRef'] 29 | const debouncedWatch: typeof import('@vueuse/core')['debouncedWatch'] 30 | const defineAsyncComponent: typeof import('vue')['defineAsyncComponent'] 31 | const defineComponent: typeof import('vue')['defineComponent'] 32 | const eagerComputed: typeof import('@vueuse/core')['eagerComputed'] 33 | const effectScope: typeof import('vue')['effectScope'] 34 | const EffectScope: typeof import('vue')['EffectScope'] 35 | const extendRef: typeof import('@vueuse/core')['extendRef'] 36 | const getCurrentInstance: typeof import('vue')['getCurrentInstance'] 37 | const getCurrentScope: typeof import('vue')['getCurrentScope'] 38 | const h: typeof import('vue')['h'] 39 | const ignorableWatch: typeof import('@vueuse/core')['ignorableWatch'] 40 | const inject: typeof import('vue')['inject'] 41 | const isDefined: typeof import('@vueuse/core')['isDefined'] 42 | const isReadonly: typeof import('vue')['isReadonly'] 43 | const isRef: typeof import('vue')['isRef'] 44 | const logicAnd: typeof import('@vueuse/core')['logicAnd'] 45 | const logicNot: typeof import('@vueuse/core')['logicNot'] 46 | const logicOr: typeof import('@vueuse/core')['logicOr'] 47 | const makeDestructurable: typeof import('@vueuse/core')['makeDestructurable'] 48 | const markRaw: typeof import('vue')['markRaw'] 49 | const nextTick: typeof import('vue')['nextTick'] 50 | const onActivated: typeof import('vue')['onActivated'] 51 | const onBeforeMount: typeof import('vue')['onBeforeMount'] 52 | const onBeforeUnmount: typeof import('vue')['onBeforeUnmount'] 53 | const onBeforeUpdate: typeof import('vue')['onBeforeUpdate'] 54 | const onClickOutside: typeof import('@vueuse/core')['onClickOutside'] 55 | const onDeactivated: typeof import('vue')['onDeactivated'] 56 | const onErrorCaptured: typeof import('vue')['onErrorCaptured'] 57 | const onKeyStroke: typeof import('@vueuse/core')['onKeyStroke'] 58 | const onLongPress: typeof import('@vueuse/core')['onLongPress'] 59 | const onMounted: typeof import('vue')['onMounted'] 60 | const onRenderTracked: typeof import('vue')['onRenderTracked'] 61 | const onRenderTriggered: typeof import('vue')['onRenderTriggered'] 62 | const onScopeDispose: typeof import('vue')['onScopeDispose'] 63 | const onServerPrefetch: typeof import('vue')['onServerPrefetch'] 64 | const onStartTyping: typeof import('@vueuse/core')['onStartTyping'] 65 | const onUnmounted: typeof import('vue')['onUnmounted'] 66 | const onUpdated: typeof import('vue')['onUpdated'] 67 | const pausableWatch: typeof import('@vueuse/core')['pausableWatch'] 68 | const provide: typeof import('vue')['provide'] 69 | const reactify: typeof import('@vueuse/core')['reactify'] 70 | const reactifyObject: typeof import('@vueuse/core')['reactifyObject'] 71 | const reactive: typeof import('vue')['reactive'] 72 | const reactiveComputed: typeof import('@vueuse/core')['reactiveComputed'] 73 | const reactiveOmit: typeof import('@vueuse/core')['reactiveOmit'] 74 | const reactivePick: typeof import('@vueuse/core')['reactivePick'] 75 | const readonly: typeof import('vue')['readonly'] 76 | const ref: typeof import('vue')['ref'] 77 | const refAutoReset: typeof import('@vueuse/core')['refAutoReset'] 78 | const refDebounced: typeof import('@vueuse/core')['refDebounced'] 79 | const refDefault: typeof import('@vueuse/core')['refDefault'] 80 | const refThrottled: typeof import('@vueuse/core')['refThrottled'] 81 | const refWithControl: typeof import('@vueuse/core')['refWithControl'] 82 | const resolveComponent: typeof import('vue')['resolveComponent'] 83 | const shallowReactive: typeof import('vue')['shallowReactive'] 84 | const shallowReadonly: typeof import('vue')['shallowReadonly'] 85 | const shallowRef: typeof import('vue')['shallowRef'] 86 | const syncRef: typeof import('@vueuse/core')['syncRef'] 87 | const syncRefs: typeof import('@vueuse/core')['syncRefs'] 88 | const templateRef: typeof import('@vueuse/core')['templateRef'] 89 | const throttledRef: typeof import('@vueuse/core')['throttledRef'] 90 | const throttledWatch: typeof import('@vueuse/core')['throttledWatch'] 91 | const toRaw: typeof import('vue')['toRaw'] 92 | const toReactive: typeof import('@vueuse/core')['toReactive'] 93 | const toRef: typeof import('vue')['toRef'] 94 | const toRefs: typeof import('vue')['toRefs'] 95 | const triggerRef: typeof import('vue')['triggerRef'] 96 | const tryOnBeforeMount: typeof import('@vueuse/core')['tryOnBeforeMount'] 97 | const tryOnBeforeUnmount: typeof import('@vueuse/core')['tryOnBeforeUnmount'] 98 | const tryOnMounted: typeof import('@vueuse/core')['tryOnMounted'] 99 | const tryOnScopeDispose: typeof import('@vueuse/core')['tryOnScopeDispose'] 100 | const tryOnUnmounted: typeof import('@vueuse/core')['tryOnUnmounted'] 101 | const unref: typeof import('vue')['unref'] 102 | const unrefElement: typeof import('@vueuse/core')['unrefElement'] 103 | const until: typeof import('@vueuse/core')['until'] 104 | const useActiveElement: typeof import('@vueuse/core')['useActiveElement'] 105 | const useAsyncQueue: typeof import('@vueuse/core')['useAsyncQueue'] 106 | const useAsyncState: typeof import('@vueuse/core')['useAsyncState'] 107 | const useAttrs: typeof import('vue')['useAttrs'] 108 | const useBase64: typeof import('@vueuse/core')['useBase64'] 109 | const useBattery: typeof import('@vueuse/core')['useBattery'] 110 | const useBreakpoints: typeof import('@vueuse/core')['useBreakpoints'] 111 | const useBroadcastChannel: typeof import('@vueuse/core')['useBroadcastChannel'] 112 | const useBrowserLocation: typeof import('@vueuse/core')['useBrowserLocation'] 113 | const useCached: typeof import('@vueuse/core')['useCached'] 114 | const useClamp: typeof import('@vueuse/core')['useClamp'] 115 | const useClipboard: typeof import('@vueuse/core')['useClipboard'] 116 | const useColorMode: typeof import('@vueuse/core')['useColorMode'] 117 | const useConfirmDialog: typeof import('@vueuse/core')['useConfirmDialog'] 118 | const useCounter: typeof import('@vueuse/core')['useCounter'] 119 | const useCssModule: typeof import('vue')['useCssModule'] 120 | const useCssVar: typeof import('@vueuse/core')['useCssVar'] 121 | const useCssVars: typeof import('vue')['useCssVars'] 122 | const useCycleList: typeof import('@vueuse/core')['useCycleList'] 123 | const useDark: typeof import('@vueuse/core')['useDark'] 124 | const useDateFormat: typeof import('@vueuse/core')['useDateFormat'] 125 | const useDebounce: typeof import('@vueuse/core')['useDebounce'] 126 | const useDebouncedRefHistory: typeof import('@vueuse/core')['useDebouncedRefHistory'] 127 | const useDebounceFn: typeof import('@vueuse/core')['useDebounceFn'] 128 | const useDeviceMotion: typeof import('@vueuse/core')['useDeviceMotion'] 129 | const useDeviceOrientation: typeof import('@vueuse/core')['useDeviceOrientation'] 130 | const useDevicePixelRatio: typeof import('@vueuse/core')['useDevicePixelRatio'] 131 | const useDevicesList: typeof import('@vueuse/core')['useDevicesList'] 132 | const useDisplayMedia: typeof import('@vueuse/core')['useDisplayMedia'] 133 | const useDocumentVisibility: typeof import('@vueuse/core')['useDocumentVisibility'] 134 | const useDraggable: typeof import('@vueuse/core')['useDraggable'] 135 | const useElementBounding: typeof import('@vueuse/core')['useElementBounding'] 136 | const useElementByPoint: typeof import('@vueuse/core')['useElementByPoint'] 137 | const useElementHover: typeof import('@vueuse/core')['useElementHover'] 138 | const useElementSize: typeof import('@vueuse/core')['useElementSize'] 139 | const useElementVisibility: typeof import('@vueuse/core')['useElementVisibility'] 140 | const useEventBus: typeof import('@vueuse/core')['useEventBus'] 141 | const useEventListener: typeof import('@vueuse/core')['useEventListener'] 142 | const useEventSource: typeof import('@vueuse/core')['useEventSource'] 143 | const useEyeDropper: typeof import('@vueuse/core')['useEyeDropper'] 144 | const useFavicon: typeof import('@vueuse/core')['useFavicon'] 145 | const useFetch: typeof import('@vueuse/core')['useFetch'] 146 | const useFileSystemAccess: typeof import('@vueuse/core')['useFileSystemAccess'] 147 | const useFocus: typeof import('@vueuse/core')['useFocus'] 148 | const useFocusWithin: typeof import('@vueuse/core')['useFocusWithin'] 149 | const useFps: typeof import('@vueuse/core')['useFps'] 150 | const useFullscreen: typeof import('@vueuse/core')['useFullscreen'] 151 | const useGamepad: typeof import('@vueuse/core')['useGamepad'] 152 | const useGeolocation: typeof import('@vueuse/core')['useGeolocation'] 153 | const useIdle: typeof import('@vueuse/core')['useIdle'] 154 | const useInfiniteScroll: typeof import('@vueuse/core')['useInfiniteScroll'] 155 | const useIntersectionObserver: typeof import('@vueuse/core')['useIntersectionObserver'] 156 | const useInterval: typeof import('@vueuse/core')['useInterval'] 157 | const useIntervalFn: typeof import('@vueuse/core')['useIntervalFn'] 158 | const useKeyModifier: typeof import('@vueuse/core')['useKeyModifier'] 159 | const useLastChanged: typeof import('@vueuse/core')['useLastChanged'] 160 | const useLocalStorage: typeof import('@vueuse/core')['useLocalStorage'] 161 | const useMagicKeys: typeof import('@vueuse/core')['useMagicKeys'] 162 | const useManualRefHistory: typeof import('@vueuse/core')['useManualRefHistory'] 163 | const useMediaControls: typeof import('@vueuse/core')['useMediaControls'] 164 | const useMediaQuery: typeof import('@vueuse/core')['useMediaQuery'] 165 | const useMemoize: typeof import('@vueuse/core')['useMemoize'] 166 | const useMemory: typeof import('@vueuse/core')['useMemory'] 167 | const useMounted: typeof import('@vueuse/core')['useMounted'] 168 | const useMouse: typeof import('@vueuse/core')['useMouse'] 169 | const useMouseInElement: typeof import('@vueuse/core')['useMouseInElement'] 170 | const useMousePressed: typeof import('@vueuse/core')['useMousePressed'] 171 | const useMutationObserver: typeof import('@vueuse/core')['useMutationObserver'] 172 | const useNavigatorLanguage: typeof import('@vueuse/core')['useNavigatorLanguage'] 173 | const useNetwork: typeof import('@vueuse/core')['useNetwork'] 174 | const useNow: typeof import('@vueuse/core')['useNow'] 175 | const useOffsetPagination: typeof import('@vueuse/core')['useOffsetPagination'] 176 | const useOnline: typeof import('@vueuse/core')['useOnline'] 177 | const usePageLeave: typeof import('@vueuse/core')['usePageLeave'] 178 | const useParallax: typeof import('@vueuse/core')['useParallax'] 179 | const usePermission: typeof import('@vueuse/core')['usePermission'] 180 | const usePointer: typeof import('@vueuse/core')['usePointer'] 181 | const usePointerSwipe: typeof import('@vueuse/core')['usePointerSwipe'] 182 | const usePreferredColorScheme: typeof import('@vueuse/core')['usePreferredColorScheme'] 183 | const usePreferredDark: typeof import('@vueuse/core')['usePreferredDark'] 184 | const usePreferredLanguages: typeof import('@vueuse/core')['usePreferredLanguages'] 185 | const useRafFn: typeof import('@vueuse/core')['useRafFn'] 186 | const useRefHistory: typeof import('@vueuse/core')['useRefHistory'] 187 | const useResizeObserver: typeof import('@vueuse/core')['useResizeObserver'] 188 | const useRoute: typeof import('vue-router')['useRoute'] 189 | const useRouter: typeof import('vue-router')['useRouter'] 190 | const useScreenSafeArea: typeof import('@vueuse/core')['useScreenSafeArea'] 191 | const useScriptTag: typeof import('@vueuse/core')['useScriptTag'] 192 | const useScroll: typeof import('@vueuse/core')['useScroll'] 193 | const useScrollLock: typeof import('@vueuse/core')['useScrollLock'] 194 | const useSessionStorage: typeof import('@vueuse/core')['useSessionStorage'] 195 | const useShare: typeof import('@vueuse/core')['useShare'] 196 | const useSlots: typeof import('vue')['useSlots'] 197 | const useSpeechRecognition: typeof import('@vueuse/core')['useSpeechRecognition'] 198 | const useSpeechSynthesis: typeof import('@vueuse/core')['useSpeechSynthesis'] 199 | const useStorage: typeof import('@vueuse/core')['useStorage'] 200 | const useStorageAsync: typeof import('@vueuse/core')['useStorageAsync'] 201 | const useStyleTag: typeof import('@vueuse/core')['useStyleTag'] 202 | const useSwipe: typeof import('@vueuse/core')['useSwipe'] 203 | const useTemplateRefsList: typeof import('@vueuse/core')['useTemplateRefsList'] 204 | const useTextSelection: typeof import('@vueuse/core')['useTextSelection'] 205 | const useThrottle: typeof import('@vueuse/core')['useThrottle'] 206 | const useThrottledRefHistory: typeof import('@vueuse/core')['useThrottledRefHistory'] 207 | const useThrottleFn: typeof import('@vueuse/core')['useThrottleFn'] 208 | const useTimeAgo: typeof import('@vueuse/core')['useTimeAgo'] 209 | const useTimeout: typeof import('@vueuse/core')['useTimeout'] 210 | const useTimeoutFn: typeof import('@vueuse/core')['useTimeoutFn'] 211 | const useTimeoutPoll: typeof import('@vueuse/core')['useTimeoutPoll'] 212 | const useTimestamp: typeof import('@vueuse/core')['useTimestamp'] 213 | const useTitle: typeof import('@vueuse/core')['useTitle'] 214 | const useToggle: typeof import('@vueuse/core')['useToggle'] 215 | const useTransition: typeof import('@vueuse/core')['useTransition'] 216 | const useUrlSearchParams: typeof import('@vueuse/core')['useUrlSearchParams'] 217 | const useUserMedia: typeof import('@vueuse/core')['useUserMedia'] 218 | const useVibrate: typeof import('@vueuse/core')['useVibrate'] 219 | const useVirtualList: typeof import('@vueuse/core')['useVirtualList'] 220 | const useVModel: typeof import('@vueuse/core')['useVModel'] 221 | const useVModels: typeof import('@vueuse/core')['useVModels'] 222 | const useWakeLock: typeof import('@vueuse/core')['useWakeLock'] 223 | const useWebNotification: typeof import('@vueuse/core')['useWebNotification'] 224 | const useWebSocket: typeof import('@vueuse/core')['useWebSocket'] 225 | const useWebWorker: typeof import('@vueuse/core')['useWebWorker'] 226 | const useWebWorkerFn: typeof import('@vueuse/core')['useWebWorkerFn'] 227 | const useWindowFocus: typeof import('@vueuse/core')['useWindowFocus'] 228 | const useWindowScroll: typeof import('@vueuse/core')['useWindowScroll'] 229 | const useWindowSize: typeof import('@vueuse/core')['useWindowSize'] 230 | const watch: typeof import('vue')['watch'] 231 | const watchAtMost: typeof import('@vueuse/core')['watchAtMost'] 232 | const watchDebounced: typeof import('@vueuse/core')['watchDebounced'] 233 | const watchEffect: typeof import('vue')['watchEffect'] 234 | const watchIgnorable: typeof import('@vueuse/core')['watchIgnorable'] 235 | const watchOnce: typeof import('@vueuse/core')['watchOnce'] 236 | const watchPausable: typeof import('@vueuse/core')['watchPausable'] 237 | const watchThrottled: typeof import('@vueuse/core')['watchThrottled'] 238 | const watchWithFilter: typeof import('@vueuse/core')['watchWithFilter'] 239 | const whenever: typeof import('@vueuse/core')['whenever'] 240 | } 241 | export {} 242 | --------------------------------------------------------------------------------