npm create vite myProject -- --template vue-ts
注:Vite 需要 Node.js 版本 14.18+,16+。然而,有些模板需要依赖更高的 Node 版本才能正常运行,当你的包管理器发出警告时,请注意升级你的 Node 版本。
npm i koa --save && npm i @types/koa --save-dev
npm i koa-connect --save
npm i koa2-cors --save
利用koa-bodyparser来处理POST请求参数
npm i koa-bodyparser --save
POST请求参数的处理有2种方式:
node.js
原生的querystring.parse()
方式拿到前端传过来的数据。koa-bodyparser
注:
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');
});
})();
node bin/app.js
npm i nunjucks --save
nunjucks文档:Nunjucks 中文文档
廖雪峰nunjucks文档:使用Nunjucks - 廖雪峰的官方网站
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;
// nunjucks 模板渲染
const templates = require('../config/templates')
// add nunjucks as ./:
app.use(
templates('./', {
noCache: !isProduction,
watch: !isProduction
})
)
npm i koa-router --save
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
})
}
}
}
npm i vue-router --save
<!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>
<template>
<h1>内容</h1>
</template>
<script setup lang="ts">
import { inject } from 'vue'
const title = 'test'
</script>
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
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的文件
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
}
}
}
}
})
首先你需要安装 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'
})
]
}
})
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