Node express Vite Vue

創建 Vite Vue

Vite 安裝與環境變數設定

相容性說明

Vite 需要 Node.js 版本 18+。 20+。 但是,某些模板需要更高的 Node.js 版本才能運作,如果您的套件管理器發出警告,請升級。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
npm create vite-extra@latest 專案名稱

create-vite@5.5.2
Ok to proceed? (y)
? Select a framework: › - Use arrow-keys. Return to submit.
Vanilla
❯ Vue
React
Preact
Lit
Svelte
Solid
Qwik
Others

參考官網

1
2
3
4
cd vite-project
npm install
npm run dev

專案架構

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
├── .vscode
├── public
│ └── vite.svg
├── src
│ ├── App.vue
│ ├── assets
│ │ └── vue.svg
│ ├── components
│ │ └── HelloWorld.vue
│ ├── main.js
│ └── style.css
├── .gitignore
├── index.html
├── package.json
├── package-lock.json
├── README.md
├── tsconfig.app.json
├── tsconfig.json
├── tsconfig.node.json
└── vite.config.js

安裝完成
gitHub上的說明

gitHub上的說明(vite ^5.4.1”)

創建 Express 伺服器

安裝 express 與nodemon concurrently

1
2
npm install express
npm install --save-dev nodemon concurrently

gitHub上的說明:安裝 express 與nodemon concurrently
專案新增server/index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
import express from "express";

const port = process.env.PORT || 3000;

const app = express();

app.get("/api/v1/hello", (_req, res) => {
res.json({ message: "Hello, world!" });
});

app.listen(port, () => {
console.log("Server listening on port", port);
});

修改package.json設定檔,新增一個主檔案並將 dev 命令替換為以下內容

1
2
3
4
5
6
7
8
9
10
11
   "version": "1.0.1",
"type": "module",
+ "main": "server/index.js",
"scripts": {
- "dev": "vite",
+ "dev:frontend": "vite",
+ "dev:backend": "nodemon server/index.js",
+ "dev": "concurrently 'npm:dev:frontend' 'npm:dev:backend'",
+ "build": "vite build",
"preview": "vite preview"
},

這樣Vite伺服器和Nodemon就會並行運作。
運行來啟動伺服器

1
npm run dev

如果我們訪問http://localhost:3000/api/v1/hello
現在有一個前端和一個後端正在運行,但它們還沒有互相溝通

1
2
3
{
"message": "Hello, world!"
}

gitHub上的說明:測試api/v1/hello運行

連接客戶端和伺服器

安裝ejs

1
npm install ejs

新增server/homepageRouter.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import express from "express";
import fs from "fs/promises";
import path from "path";

const router = express.Router();
//
const environment = process.env.NODE_ENV;

//開發中,獲取所有的數據(來自打包產生dist/manifest.json
router.get("/*", async (_req, res) => {
const data = {
environment,
manifest: await parseManifest(),
};
console.log('data', data)
res.render("index.html.ejs", data);
});

const parseManifest = async () => {
if (environment !== "production") return {};

const manifestPath = path.join(path.resolve(), "dist", "manifest.json");
console.log('manifestPath', manifestPath)
const manifestFile = await fs.readFile(manifestPath);
console.log(' manifestFile', manifestFile)
return JSON.parse(manifestFile);
};

export default router;

homepageRouter.js
新增server/assetsRouter.js,靜態圖片

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import express from "express";

const router = express.Router();
//支持的檔案類型
const supportedAssets = ["svg", "png", "jpg", "png", "jpeg", "mp4", "ogv"];

const assetExtensionRegex = () =>
{
//JS 把陣列 Array 所有元素併成字串,且可任意穿插符號的 join()
const formattedExtensionList = supportedAssets.join("|");
//JS Regex 正則表達式
return new RegExp(`/.+\.(${formattedExtensionList})$`);
};

router.get(assetExtensionRegex(), (req, res) => {
res.redirect(303, `http://localhost:5173/src${req.path}`);
});

export default router;

homepageRouter.js

修改server/index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import express from "express";
import path from "path";
import homepageRouter from "./homepageRouter.js";
import assetsRouter from "./assetsRouter.js";

const port = process.env.PORT || 3000;
//載入為靜態目錄
const publicPath = path.join(path.resolve(), "public");
const distPath = path.join(path.resolve(), "dist");

const app = express();

app.get("/api/v1/hello", (_req, res) => {
res.json({ message: "Hello, Lara!" });
});

//如果是生產階段就連結到dist/,否則連接到public與/src
if (process.env.NODE_ENV === "production") {
app.use("/", express.static(distPath));
} else {
app.use("/", express.static(publicPath));
app.use("/src", assetsRouter);
}

//將路由器連接到 Express 應用程式
app.use(homepageRouter);

app.listen(port, () => {
console.log("Server listening on port", port);
});

路由器

修改src/components/HelloWorld.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<script setup lang="ts">
import { ref } from 'vue'
const count = ref(0)
const serverHello = ref({})
fetch('/api/v1/hello')
.then((res) => res.json())
.then(({ message }) => {
serverHello.value = message
})
</script>

<template>
<h2>{{ serverHello }}</h2>
</template>

HelloWorld.vue

刪除index.html,在根目錄新增一個 views/index.html.ejs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + Vue</title>
<% if (environment === 'production') { %>
<link rel="stylesheet" href="<%= manifest['src/main.css'].file %>" />
<% } %>
</head>
<body>
<div id="app"></div>
<% if (environment === 'production') { %>
<script type="module" src="<%= manifest['src/main.ts'].file %>"></script>
<% } else { %>
<script type="module" src="http://localhost:5173/@vite/client"></script>
<script type="module" src="http://localhost:5173/src/main.ts"></script>
<% } %>
</body>
</html>

index.html.ejse

生產中運行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";

// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
// 新增打包
build: {
manifest: true,
rollupOptions: {
input: "./src/main.ts",
},
},
});

vite.config.ts 新增打包設定

執行打包,

1
npm run build

產生dist後,在.vite將manifest.json移動到dist根目錄,並修改manifest.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
"src/assets/vue.svg": {
"file": "vite.svg",
"src": "src/assets/vue.svg"//物件名與src名相同
},
"src/main.css": {
"file": "assets/main-CYBnthfA.css",//打包後的檔案
"src": "src/main.css"//物件名與src名相同
},
"src/main.ts": {
"file": "assets/main-CpTINVMW.js",//打包後的檔案
"name": "main",
"src": "src/main.ts",//物件名與src名相同
"isEntry": true,
"css": [
"assets/main-CYBnthfA.css"//打包後的檔案
]
}
}

demo