vue3 + ts +vite

438 阅读3分钟

大体步骤

配置部分

  • 引入路由
  • vite 配置别名
  • 配置cross 新建.env

样式部分

  • 安装less
  • 重置css
  • 引入全局less 变量
  • 引入element UI

axios部分

  • 引入axios 封装拦截
  • 封装api

其他

  • 引入工具函数

npm 安装插件

  • @types/node
  • axios-mapper
  • element-plus
  • vue-router
  • cross-env
  • less
  • less-loader

cross-env 配置

// package.json
{
  "scripts": {
    "dev": "vite",
    "build": "vue-tsc --noEmit && vite build",
    "preview": "vite preview"
  },

  "scripts": {
    "dev": "vite",
    "test": "cross-env vite build --mode test",
    "build": "cross-env vite build --mode production",
    "preview": "vite preview"
  },
}

在 src 目录下添加两个入口文件 

项目目录下 修改 index.html文件 // entry-client.ts import { createSSRApp } from 'vue'; import App from './App.vue';

const app = createSSRApp(App); app.mount('#app', true);

// entry-server.js import { createSSRApp } from 'vue'; import App from './App.vue'; import { renderToString } from '@vue/server-renderer';

export async function render(url, manifest) { const app = createSSRApp(App); const context = {}; const appHtml = await renderToString(app, context); return { appHtml }; }

新建node端web服务器入口文件(开发环境): server-env.js ,官方推荐 express,安装node包: yarn add -D express const fs = require('fs'); const path = require('path'); const express = require('express'); const { createServer: createViteServer } = require('vite');

async function createServer() { const app = express();

const vite = await createViteServer({ server: { middlewareMode: true }, });

app.use(vite.middlewares);

app.use('*', async (req, res) => { // serve index.html - we will tackle this next const url = req.originalUrl;

try {
  // 1. Read index.html
  let template = fs.readFileSync(path.resolve(__dirname, 'index.html'), 'utf-8');

  // 2. Apply vite HTML transforms.
  template = await vite.transformIndexHtml(url, template);

  // 3. Load the server entry. vite.ssrLoadModule
  const { render } = await vite.ssrLoadModule('/src/entry-server.js');

  // 4. render the app HTML.
  const { appHtml } = await render(url);

  // 5. Inject the app-rendered HTML into the template.
  const html = template.replace(``, appHtml);

  // 6. Send the rendered HTML back.
  res.status(200).set({ 'Content-Type': 'text/html' }).end(html);
} catch (e) {
  // If an error is caught,
  vite.ssrFixStacktrace(e);
  console.error(e);
  res.status(500).end(e.message);
}

});

app.listen(3000, () => { console.log('http://localhost:3000'); }); }

createServer();

终端运行 yarn dev, 浏览器打开:http://localhost:3000/  网页右键“显示页面源码”、

生产环境打包,package.json新增 build 相关命令

//package.json "scripts": { "dev": "node server-env.js", "build:client": "vite build --outDir dist/client --ssrManifest", "build:server": "vite build --outDir dist/server --ssr src/entry-server.js ", "build": "yarn build:client && yarn build:server", "preview": "yarn build && node server.js" }, 新建 node 端web服务器入口文件(生产环境): server.js ,个人选择 koa搭建生产环境服务器,安装 node 包:yarn add -D koa koa-static

// server.js const fs = require('fs'); const path = require('path'); const Koa = require('koa'); const staticPath = require('koa-static');

const app = new Koa(); const resolve = (p) => path.resolve(__dirname, p);

const template = fs.readFileSync(resolve('./dist/client/index.html'), 'utf-8'); const manifest = require('./dist/client/ssr-manifest.json'); const render = require('./dist/server/entry-server.js').render;

app.use(staticPath(resolve('./dist/client'), { index: false }));

app.use(async (ctx, next) => { const url = ctx.req.url; try { const { appHtml } = await render(url, manifest);

let html = template.replace(``, appHtml);

ctx.body = html;

} catch (error) { console.log(error); next(); } });

app.listen(3000, () => { console.log('http://localhost:3000'); });

终端运行: yarn preview,浏览器打开:http://localhost:3000。最简 ssr 改造完成。

安装生产上必备的 vue 全家桶: scss vuex vue-router 首先安装scss支持: yarn add -D sass.  

安装vue-router 和 vuex :  yarn add vuex@next vue-router@next  vuex-router-sync@next

新建 src/store/index.ts 和 src/router/index.ts 两个文件

// src/router/index.ts

import { createRouter, createWebHistory, createMemoryHistory } from 'vue-router';

export default function () { const routerHistory = import.meta.env.SSR === false ? createWebHistory() : createMemoryHistory();

return createRouter({ history: routerHistory, routes: [ { path: '/', name: 'home', component: () => import('../views/Home.vue'), }, { path: '/about', name: 'about', component: () => import('../views/About.vue'), }, { path: '/:catchAll(.)', name: '404', component: () => import('../views/404.vue'), meta: { title: '404 Not Found', }, }, ], }); }

// src/store/index.ts

import { createStore as _createStore } from 'vuex';

export default function createStore() { return _createStore({ state: { message: 'Hello vite2 vue3 ssr', }, mutations: {}, actions: { fetchMessage: ({ state }) => { return new Promise((resolve) => { setTimeout(() => { state.message = 'Hello vite2 vue3 ssr typescript scss vuex vue-router'; resolve(0); }, 200); }); }, }, modules: {}, }); }

新建对应的 src/views/页面 Home.vue  About.vue  404.vue, 略。

修改 entry-client.ts 和 entry-server.js文件,加入相应的 vuex 和 router

// entry-client.ts

import { createSSRApp } from 'vue'; import App from './App.vue'; import { sync } from 'vuex-router-sync';

import createStore from './store'; import createRouter from './router';

const router = createRouter(); const store = createStore(); sync(store, router);

const app = createSSRApp(App); app.use(router).use(store);

router.beforeResolve((to, from, next) => { next(); });

router.isReady().then(() => { app.mount('#app', true); });

// entry-server.js

import { createSSRApp } from 'vue'; import App from './App.vue'; import { renderToString } from '@vue/server-renderer';

import createStore from './store'; import createRouter from './router'; import { sync } from 'vuex-router-sync';

export async function render(url, manifest) { const router = createRouter(); const store = createStore(); sync(store, router);

const app = createSSRApp(App); app.use(router).use(store);

router.push(url);

await router.isReady();

const context = {}; const appHtml = await renderToString(app, context); return { appHtml }; }

终端运行: yarn dev 查看开发环境效果。终端运行: yarn preview 查看生产环境效果。

第一阶段源码: github.com/damowangzhu…

服务端预取数据 asyncData 服务端预取数据采用 vue2的 asyncData 方式。

新建 vue-extend.d.ts 文件

// vue-extend.d.ts

import { RouteRecordRaw } from 'vue-router';

export interface AsyncDataContextType { route: RouteRecordRaw; store: any; // 类型不决 用 any。 -.-! }

declare module '@vue/runtime-core' { interface ComponentCustomOptions { asyncData?(context: AsyncDataContextType): Promise; } }