koa+Vite+vue3+ts+pinia构建项目

宣高朗
2023-12-01

一、 初始化构建项目

npm create vite myProject -- --template vue-ts

 注:Vite 需要 Node.js 版本 14.18+,16+。然而,有些模板需要依赖更高的 Node 版本才能正常运行,当你的包管理器发出警告时,请注意升级你的 Node 版本。

二、创建服务器

使用 Koa2

1. 安装koa

npm i koa --save && npm i @types/koa --save-dev

 2. 安装中间件(koa-connect)

npm i koa-connect --save

3. 安装koa处理跨域(koa2-cors)

npm i koa2-cors --save

4. 安装中间件(koa-bodyparser)

     利用koa-bodyparser来处理POST请求参数

npm i koa-bodyparser --save

POST请求参数的处理有2种方式:

  • 利用node.js原生的querystring.parse()方式拿到前端传过来的数据。
  • 利用第三方中间件koa-bodyparser

注:

  1. 本模块不支持解析multipart表单格式数据,请使用co-busboy解析multipart表单格式数据。
  2.  middleware的顺序很重要,这个koa-bodyparser必须在router之前被注册到app对象上

 5. 注册中间件

  • 新增bin/app.js文件
const Koa = require('koa');

const cors = require('koa2-cors')

const bodyParser = require('koa-bodyparser')

(async () => {
    const app = new Koa();
    app.use(cors())
    app.use(bodyParser())

    app.use(async (ctx, next) => {
      console.log(ctx)
        ctx.body = `<!DOCTYPE html>
      <html lang="en">
        <head><title>koa2 + vite + ts + vue3 + vue-router</title></head>
        <body>
          <h1 style="text-align: center;">Hello</h1>
        </body>
      </html>`;
    });
    
    // parse request body:

    app.listen(9000, () => {
        console.log('server is listening in 9000');
    });
})();

 6. node进行启动服务

node bin/app.js

三、安装模板引擎

npm i nunjucks --save

nunjucks文档:Nunjucks 中文文档

 廖雪峰nunjucks文档:使用Nunjucks - 廖雪峰的官方网站

  • 创建config/templates.js
const nunjucks = require('nunjucks');

function createEnv(path, opts) {
  var autoescape = opts.autoescape === undefined ? true : opts.autoescape,
    noCache = opts.noCache || false,
    watch = opts.watch || false,
    throwOnUndefined = opts.throwOnUndefined || false,
    env = new nunjucks.Environment(
      new nunjucks.FileSystemLoader(path, {
        noCache: noCache,
        watch: watch,
      }),
      {
        autoescape: autoescape,
        throwOnUndefined: throwOnUndefined,
      }
    );
  if (opts.filters) {
    for (var f in opts.filters) {
      env.addFilter(f, opts.filters[f]);
    }
  }
  return env;
}

function templates(path, opts) {
  var env = createEnv(path, opts);
  return async (ctx, next) => {
    ctx.render = function (view, model) {
      ctx.response.body = env.render(view, Object.assign({}, ctx.state || {}, model || {}));
      ctx.response.type = "text/html";
    };
    await next();
  };
}

module.exports = templates;

  • bin/app.js
// nunjucks 模板渲染
const templates = require('../config/templates')

// add nunjucks as ./:
app.use(
  templates('./', {
    noCache: !isProduction,
    watch: !isProduction
  })
)

四、路由配置

1. 安装 koa-router

npm i koa-router --save

2. 创建config/controller.js

const router = require('koa-router')()
router.staticFiles = {}

// add url-route in /controllers:

function interception(str) {
  return str.substring(0, str.lastIndexOf('/'))
}
function addMapping(mapping) {
  for (const url in mapping) {
    if (url.startsWith('GET ')) {
      const path = url.substring(4)
      const funStr = mapping[url].toString()
      if (funStr.indexOf('ctx.render') > 0) {
        const str1 = funStr.match(/ctx.render[\s]*\([\s]*'(\S*)', \{/g)[0]
        const str2 = str1.replace(/ctx.render[\s]*\([\s]*'/g, '')
        const str3 = str2.replace(/'[\s]*,[\s]*\{/g, '')
        const strUrl = url.replace('GET ', '')
        if (strUrl != '/') {
          router.staticFiles[interception(strUrl)] = interception(str3)
        }
      }
      router.get(path, mapping[url])
      console.log(`register URL mapping: GET ${path}`);
    } else if (url.startsWith('POST ')) {
      const path = url.substring(5)
      router.post(path, mapping[url])
      console.log(`register URL mapping: POST ${path}`)
    } else if (url.startsWith('PUT ')) {
      const path = url.substring(4)
      router.put(path, mapping[url])
      console.log(`register URL mapping: PUT ${path}`)
    } else if (url.startsWith('DELETE ')) {
      const path = url.substring(7)
      router.del(path, mapping[url])
      console.log(`register URL mapping: DELETE ${path}`)
    } else {
      console.log(`invalid URL: ${url}`)
    }
  }
}

function addControllers(filePath) {
  const fs = require('fs')
  fs.readdirSync(filePath).filter(f => {
    if (fs.statSync(filePath + '/' + f).isFile()) {
      if (f.endsWith('.js')) {
        const mapping = require(filePath + '/' + f)()
        addMapping(mapping)
      }
    }
    if (fs.statSync(filePath + '/' + f).isDirectory()) {
      addControllers(filePath + '/' + f)
    }
  })
}

module.exports = function (dir, app) {
  // 读取/routes目录的路由配置
  const controllers_dir = dir || '../routes'
  addControllers(__dirname + '/' + controllers_dir)
  return router.routes()
}

3. routes/admin/index.js配置

module.exports = function () {
  return {
    'GET /': async (ctx, next) => {
      const pageInfo = {
        title: '页面标题'
      }
      ctx.render('src/mobile/official/dist/index.html', {
        pageInfo
      })
    }
  }
}

五、集成 vue-router

1. 安装vue-router

npm i vue-router --save

2. 新增项目模块(test)

  • src/test/index.html(页面)
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no, width=device-width" />
    <meta name="format-detection" content="telephone=no" />
  </head>
  <body>
    <div id="app"></div>
    <script type="module" src="./main.ts"></script>
  </body>
</html>
  • src/test/views/index.vue (子页面)
<template>
  <h1>内容</h1>
</template>

<script setup lang="ts">
import { inject } from 'vue'
const title = 'test'
</script>
  • test/router/index.ts (路由配置)
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'

const routes: RouteRecordRaw[] = [
  {
    path: '/',
    redirect: to => {
      return '/index'
    }
  },
  {
    path: '/index',
    name: 'Index',
    component: () => import('../views/index.vue')
  }
]

const router = createRouter({
  history: createWebHistory(),
  routes
})

export default router
  • test/main.ts(main.js)
import { createApp } from 'vue'
import App from './App.vue'
import router from './router/index'
import { createPinia } from 'pinia'

const app = createApp(App)
app.use(createPinia)
app.use(router)
app.mount('#app')

六、Vite配置(vite.config.ts)

Vite 会自动解析根目录下名为vite.config.ts的文件

配置 Vite | Vite 官方中文文档

1. 基本配置(vite.config.ts)

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { resolve } from 'path'
import { existsSync } from 'fs'

// 获取模块路径
const modulePath = process.env.npm_config_path.endsWith('/')
  ? process.env.npm_config_path.slice(0, -1)
  : process.env.npm_config_path

const projectDirname = resolve(__dirname, './' + modulePath)

let publicPath = '//res.test.com'

if (!modulePath || !existsSync(projectDirname)) {
  // 路径不存在,停止运行
  console.log('\x1b[40m \x1b[31m 模块路径错误,请检查路径 \x1b[0m')
  process.exit(0)
}

export default defineConfig(({ mode }) => {
  const alias: Record<string, string> = {
    '@': resolve(__dirname, 'src'),
    '@admin': resolve(__dirname, 'src/admin'),
    '@mobile': resolve(__dirname, 'src/mobile'),
    '@pc': resolve(__dirname, 'src/pc')
  }
  // 路径存在,配置入口/出口路径
  const moduleName = modulePath.split('/').reverse()[0]
  const project_pages = {}
  project_pages[moduleName] = resolve(__dirname, modulePath + '/index.html')
  return {
    // https://cn.vitejs.dev/guide/#index-html-and-project-root
    root: modulePath, // 项目根目录
    base: mode === 'production' ? publicPath + modulePath.replace(/^(.\/)?src+/, '') + '/' : '/',
    plugins: [
      vue()
    ],
    resolve: {
      extensions: ['.js', '.ts', '.vue', '.json'],
      alias
    },
    server: {
      port: 8081,
      open: false,
      proxy: {}
    },
    build: {
      rollupOptions: {
        input: project_pages,
        output: {
          dir: resolve(__dirname, modulePath + '/dist'),
          chunkFileNames: 'static/js/[name]-[hash].js',
          entryFileNames: 'static/js/[name]-[hash].js',
          assetFileNames: 'static/[ext]/[name]-[hash].[ext]'
        }
      },
      minify: 'terser',
      terserOptions: {
        compress: {
          drop_console: true,
          drop_debugger: true
        }
      }
    }
  }
})

1. element-plus 按需引入

首先你需要安装 unplugin-vue-components 和 unplugin-auto-import这两款插件

npm install -D unplugin-vue-components unplugin-auto-import

vite.config.ts

import AutoImport from 'unplugin-auto-import/vite'
import viteCompression from 'vite-plugin-compression'

export default defineConfig(({ mode }) => {
  return {
    plugins: [
      vue(),
      AutoImport({
        imports: ['vue', 'vue-router'], // 自动导入vue和vue-router相关函数
        eslintrc: {
          enabled: false, // 默认false, true启用。生成一次就可以,避免每次工程启动都生成
          filepath: './.eslintrc-auto-import.json', // 生成json文件
          globalsPropValue: true
        }
      }),
      
      // gzip压缩 生产环境生成 .gz 文件
      viteCompression({
        verbose: true,
        disable: false,
        threshold: 10240,
        algorithm: 'gzip',
        ext: '.gz'
      })
    ]
  }
})

2. Vant按需引入

npm i vite-plugin-style-import -D

vite.config.ts

注:2.0版本需要使用的是createStyleImportPlugin不要使用styleImprot了

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { resolve } from 'path'
import { existsSync } from 'fs'

//2.0之后版本用createStyleImportPlugin
import { createStyleImportPlugin, VantResolve } from 'vite-plugin-style-import';


export default defineConfig(({ mode }) => {
  const alias: Record<string, string> = {
    '@': resolve(__dirname, 'src'),
    '@admin': resolve(__dirname, 'src/admin'),
    '@mobile': resolve(__dirname, 'src/mobile'),
    '@pc': resolve(__dirname, 'src/pc')
  }
  // 路径存在,配置入口/出口路径
  const moduleName = modulePath.split('/').reverse()[0]
  const project_pages = {}
  project_pages[moduleName] = resolve(__dirname, modulePath + '/index.html')
  return {
    root: modulePath,
    base: mode === 'production' ? '//res.test.com' + modulePath.replace(/^(.\/)?src+/, '') + '/' : '/',
    plugins: [
      vue(),
      createStyleImportPlugin({
        resolves: [VantResolve()],
        libs: [
          {
            libraryName: 'vant',
            esModule: true,
            resolveStyle: name => `../es/${name}/style`
          }
        ]
      })
    ],
    resolve: {
      extensions: ['.js', '.ts', '.vue', '.json'],
      alias
    },
    server: {
      port: 8081,
      open: false,
      proxy: {}
    },
    build: {
      rollupOptions: {
        input: project_pages,
        output: {
          dir: resolve(__dirname, modulePath + '/dist'),
          chunkFileNames: 'static/js/[name]-[hash].js',
          entryFileNames: 'static/js/[name]-[hash].js',
          assetFileNames: 'static/[ext]/[name]-[hash].[ext]'
        }
      },
      minify: 'terser',
      terserOptions: {
        compress: {
          drop_console: true,
          drop_debugger: true
        }
      }
    }
  }
})

如出现 Error: Cannot find module ‘consola‘
则需要安装consola(由于改依赖包有使用到consola)

npm i consola -D

七、本地启动服务、打包项目的命令设置

package.json

{
  "name": "myProject",
  "private": true,
  "version": "0.0.0",
  "scripts": {
    "dev": "vite",
    "vuedev": "vite serve --force --",
    "build": "vue-tsc && vite build",
    "vuebuild": "vue-tsc --noEmit && vite build --",
    "preview": "vite preview"
  },
  "dependencies": {
    "axios": "^1.3.4",
    "element-plus": "^2.3.0",
    "koa": "^2.14.1",
    "koa-bodyparser": "^4.3.0",
    "koa-connect": "^2.1.0",
    "koa-router": "^12.0.0",
    "koa2-cors": "^2.0.6",
    "nunjucks": "^3.2.3",
    "pinia": "^2.0.33",
    "qs": "^6.11.1",
    "vant": "^4.1.0",
    "vite-plugin-compression": "^0.5.1",
    "vite-plugin-style-import": "^2.0.0",
    "vue": "^3.2.45",
    "vue-router": "^4.1.6"
  },
  "devDependencies": {
    "@types/koa": "^2.13.5",
    "@vitejs/plugin-vue": "^4.0.0",
    "consola": "^2.15.3",
    "cross-env": "^7.0.3",
    "less": "^4.1.3",
    "typescript": "^4.9.3",
    "unplugin-auto-import": "^0.15.1",
    "unplugin-vue-components": "^0.24.1",
    "vite": "^4.1.0",
    "vue-tsc": "^1.0.24"
  }
}
npm run vuedev --path=src/test

 类似资料: