Vue3 全家桶 + TS+ Vite2 + element-plus 搭建簡潔時尚的博客網(wǎng)站實(shí)戰(zhàn)及踩坑記

五一期間,花了 3 天時間,邊學(xué) Vue3 和 Vite2,邊重構(gòu)自己的項(xiàng)目,終于都用 Vue3 + TypeScript + Vite2 + Vuex4 + Vue-Router4 + element-plus 重構(gòu)完啦!
終于完成一項(xiàng)心心念念的 2021 年度目標(biāo)了 ??
項(xiàng)目地址:
https://github.com/biaochenxuying/blog-vue-typescript

效果
效果圖:
pc 端

移動端

完整效果請看:
https://biaochenxuying.cn
功能
已經(jīng)完成功能
[x] 登錄 [x] 注冊 [x] 文章列表 [x] 文章歸檔 [x] 標(biāo)簽 [x] 關(guān)于 [x] 點(diǎn)贊與評論 [x] 留言 [x] 歷程 [x] 文章詳情(支持代碼語法高亮) [x] 文章詳情目錄 [x] 移動端適配 [x] github 授權(quán)登錄
前端主要技術(shù)
所有技術(shù)都是當(dāng)前最新的。
vue:^3.0.5 typescript : ^4.1.3 element-plus: ^1.0.2-beta.41 vue-router : ^4.0.6 vite: ^2.2.3 vuex: ^4.0.0 axios: ^0.21.1 highlight.js: ^10.7.2 marked:^2.0.3
1. 初化化項(xiàng)目
用 vite-app 創(chuàng)建項(xiàng)目
yarn create vite-app <project-name>
# 或者
npm init vite-app <project-name>
然后按照提示操作即可!
進(jìn)入項(xiàng)目,安裝依賴
cd <project-name>
yarn # 或 npm i
運(yùn)行項(xiàng)目
yarn dev
打開瀏覽器 http://localhost:3000 查看
2. 引入 TypeScript
在創(chuàng)建項(xiàng)目的時候可以 TypeScript 的,如果你選擇了 TypeScript ,可以忽略第 2 個步驟。
加入 ts 依賴
yarn add --dev typescript
在 項(xiàng)目根目錄下創(chuàng)建 TypeScript 的配置文件 tsconfig.json
{
"compilerOptions": {
// 允許從沒有設(shè)置默認(rèn)導(dǎo)出的模塊中默認(rèn)導(dǎo)入。這并不影響代碼的輸出,僅為了類型檢查。
"allowSyntheticDefaultImports": true,
// 解析非相對模塊名的基準(zhǔn)目錄
"baseUrl": ".",
"esModuleInterop": true,
// 從 tslib 導(dǎo)入輔助工具函數(shù)(比如 __extends, __rest等)
"importHelpers": true,
// 指定生成哪個模塊系統(tǒng)代碼
"module": "esnext",
// 決定如何處理模塊。
"moduleResolution": "node",
// 啟用所有嚴(yán)格類型檢查選項(xiàng)。
// 啟用 --strict相當(dāng)于啟用 --noImplicitAny, --noImplicitThis, --alwaysStrict,
// --strictNullChecks和 --strictFunctionTypes和--strictPropertyInitialization。
"strict": true,
// 生成相應(yīng)的 .map文件。
"sourceMap": true,
// 忽略所有的聲明文件( *.d.ts)的類型檢查。
"skipLibCheck": true,
// 指定ECMAScript目標(biāo)版本
"target": "esnext",
// 要包含的類型聲明文件名列表
"types": [
],
"isolatedModules": true,
// 模塊名到基于 baseUrl的路徑映射的列表。
"paths": {
"@/*": [
"src/*"
]
},
// 編譯過程中需要引入的庫文件的列表。
"lib": [
"ESNext",
"DOM",
"DOM.Iterable",
"ScriptHost"
]
},
"include": [
"src/**/*.ts",
"src/**/*.tsx",
"src/**/*.vue",
"tests/**/*.ts",
"tests/**/*.tsx"
],
"exclude": [
"node_modules"
]
}
在 src 目錄下新加 shim.d.ts 文件
/* eslint-disable */
import type { DefineComponent } from 'vue'
declare module '*.vue' {
const component: DefineComponent<{}, {}, any>
export default component
}
把 main.js 修改成 main.ts
在根目錄,打開 Index.html
<script type="module" src="/src/main.js"></script>
修改為:
<script type="module" src="/src/main.ts"></script>
3. 引入 eslint
安裝 eslint prettier 依賴
@typescript-eslint/parser @typescr ipt-eslint/eslint-plugin 為 eslint 對 typescript 支持。
yarn add --dev eslint prettier eslint-config-prettier eslint-plugin-prettier eslint-plugin-vue @typescript-eslint/parser @typescr ipt-eslint/eslint-plugin
在根目錄下建立 eslint 配置文件:.eslintrc.js
module.exports = {
parser: 'vue-eslint-parser',
parserOptions: {
parser: '@typescript-eslint/parser',
ecmaVersion: 2020,
sourceType: 'module',
ecmaFeatures: {
jsx: true
}
},
extends: [
'plugin:vue/vue3-recommended',
'plugin:@typescript-eslint/recommended',
'prettier/@typescript-eslint',
'plugin:prettier/recommended'
],
rules: {
'@typescript-eslint/ban-ts-ignore': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-var-requires': 'off',
'@typescript-eslint/no-empty-function': 'off',
'vue/custom-event-name-casing': 'off',
'no-use-before-define': 'off',
// 'no-use-before-define': [
// 'error',
// {
// functions: false,
// classes: true,
// },
// ],
'@typescript-eslint/no-use-before-define': 'off',
// '@typescript-eslint/no-use-before-define': [
// 'error',
// {
// functions: false,
// classes: true,
// },
// ],
'@typescript-eslint/ban-ts-comment': 'off',
'@typescript-eslint/ban-types': 'off',
'@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-unused-vars': [
'error',
{
argsIgnorePattern: '^h$',
varsIgnorePattern: '^h$'
}
],
'no-unused-vars': [
'error',
{
argsIgnorePattern: '^h$',
varsIgnorePattern: '^h$'
}
],
'space-before-function-paren': 'off',
quotes: ['error', 'single'],
'comma-dangle': ['error', 'never']
}
};
建立 prettier.config.js
module.exports = {
printWidth: 100,
tabWidth: 2,
useTabs: false,
semi: false, // 未尾逗號
vueIndentScriptAndStyle: true,
singleQuote: true, // 單引號
quoteProps: 'as-needed',
bracketSpacing: true,
trailingComma: 'none', // 未尾分號
jsxBracketSameLine: false,
jsxSingleQuote: false,
arrowParens: 'always',
insertPragma: false,
requirePragma: false,
proseWrap: 'never',
htmlWhitespaceSensitivity: 'strict',
endOfLine: 'lf'
}
4. vue-router、vuex
npm install vue-router@4 vuex
4.1 vuex
在根目錄下創(chuàng)建 store/index.ts
import { InjectionKey } from 'vue'
import { createStore, Store } from 'vuex'
export interface State {
count: number
}
export const key: InjectionKey<Store<State>> = Symbol()
export const store = createStore<State>({
state() {
return {
count: 0
}
},
mutations: {
increment(state) {
state.count++
}
}
})
main.ts 修改
import { createApp } from 'vue'
import { store, key } from './store'
import App from './App'
import './index.css'
const app = createApp(App)
app.use(store, key)
app.mount('#app')
components/HelloWord.vue 修改
<template>
<h1>{{ msg }}</h1>
<button @click="inCrement"> count is: </button>
<p>{{ count }}</p>
</template>
<script>
import { defineComponent, computed } from 'vue'
import { useStore } from 'vuex'
import { key } from '../store'
export default defineComponent({
name: 'HelloWorld',
props: {
msg: {
type: String,
default: ''
}
},
setup() {
const store = useStore(key)
const count = computed(() => store.state.count)
return {
count,
inCrement: () => store.commit('increment')
}
}
})
</script>
4.2 vue-router
在 src 目錄下建立 router/index.ts,內(nèi)容如下:
import { createRouter, createWebHistory, RouteRecordRaw } from "vue-router";
import HelloWorld from "../components/HelloWorld.vue";
const routes: Array<RouteRecordRaw> = [
{
path: "/",
name: "HelloWorld",
component: HelloWorld,
},
{
path: "/about",
name: "About",
// route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () =>
import(/* webpackChunkName: "About" */ "../components/About.vue")
}
];
const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes,
});
export default router;
再新建一個 components/About.vue 文件,內(nèi)容如下:
<template>
<img
alt="Vue logo"
src="../assets/logo.png"
/>
<h1>{{ msg }}</h1>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
name: 'About',
data() {
return {
msg: 'Hello Vue 3.0 + Vite!'
}
},
setup() {}
})
</script>
再修改 main.ts
import { createApp } from 'vue'
import { store, key } from './store'
import router from "./router";
import App from './App'
import './index.css'
const app = createApp(App)
app.use(store, key)
app.use(router)
app.mount('#app')
再訪問 http://localhost:3000/

和 http://localhost:3000/about 即可

5. 加入 Element Plus
5.1 安裝 element-plus
全局安裝
npm install element-plus --save
5.2 引入 Element Plus
你可以引入整個 Element Plus,或是根據(jù)需要僅引入部分組件。我們先介紹如何引入完整的 Element。
完整引入
在 main.js 中寫入以下內(nèi)容:
import { createApp } from 'vue'
import ElementPlus from 'element-plus';
import router from "./router";
import 'element-plus/lib/theme-chalk/index.css';
import App from './App.vue';
import './index.css'
const app = createApp(App)
app.use(ElementPlus)
app.use(router)
app.mount('#app')
以上代碼便完成了 Element Plus 的引入。需要注意的是,樣式文件需要單獨(dú)引入。
按需引入
借助 babel-plugin-component,我們可以只引入需要的組件,以達(dá)到減小項(xiàng)目體積的目的。
首先,安裝 babel-plugin-component:
npm install babel-plugin-component -D
然后,將 .babelrc 修改為:
{
"plugins": [
[
"component",
{
"libraryName": "element-plus",
"styleLibraryName": "theme-chalk"
}
]
]
}
接下來,如果你只希望引入部分組件,比如 Button 和 Select,那么需要在 main.js 中寫入以下內(nèi)容:
import { createApp } from 'vue'
import { store, key } from './store';
import router from "./router";
import { ElButton, ElSelect } from 'element-plus';
import App from './App.vue';
import './index.css'
const app = createApp(App)
app.component(ElButton.name, ElButton);
app.component(ElSelect.name, ElSelect);
/* or
* app.use(ElButton)
* app.use(ElSelect)
*/
app.use(store, key)
app.use(router)
app.mount('#app')
app.mount('#app')
更詳細(xì)的安裝方法請看 快速上手。
5.3 全局配置
在引入 Element Plus 時,可以傳入一個全局配置對象。
該對象目前支持 size 與 zIndex 字段。size 用于改變組件的默認(rèn)尺寸,zIndex 設(shè)置彈框的初始 z-index(默認(rèn)值:2000)。按照引入 Element Plus 的方式,具體操作如下:
完整引入 Element:
import { createApp } from 'vue'
import ElementPlus from 'element-plus';
import App from './App.vue';
const app = createApp(App)
app.use(ElementPlus, { size: 'small', zIndex: 3000 });
按需引入 Element:
import { createApp } from 'vue'
import { ElButton } from 'element-plus';
import App from './App.vue';
const app = createApp(App)
app.config.globalProperties.$ELEMENT = option
app.use(ElButton);
按照以上設(shè)置,項(xiàng)目中所有擁有 size 屬性的組件的默認(rèn)尺寸均為 'small',彈框的初始 z-index 為 3000。
5.4 配置 vite.config.ts
其中 proxy 和 alias 是和 vue-cli 區(qū)別比較大的地方。
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import styleImport from 'vite-plugin-style-import'
import path from 'path'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue(),
styleImport({
libs: [
{
libraryName: 'element-plus',
esModule: true,
ensureStyleFile: true,
resolveStyle: (name) => {
return `element-plus/lib/theme-chalk/${name}.css`;
},
resolveComponent: (name) => {
return `element-plus/lib/${name}`;
},
}
]
})
],
/**
* 在生產(chǎn)中服務(wù)時的基本公共路徑。
* @default '/'
*/
base: './',
/**
* 與“根”相關(guān)的目錄,構(gòu)建輸出將放在其中。如果目錄存在,它將在構(gòu)建之前被刪除。
* @default 'dist'
*/
// outDir: 'dist',
server: {
// hostname: '0.0.0.0',
host: "localhost",
port: 3001,
// // 是否自動在瀏覽器打開
// open: true,
// // 是否開啟 https
// https: false,
// // 服務(wù)端渲染
// ssr: false,
proxy: {
'/api': {
target: 'http://localhost:3333/',
changeOrigin: true,
ws: true,
rewrite: (pathStr) => pathStr.replace('/api', '')
},
},
},
resolve: {
// 導(dǎo)入文件夾別名
alias: {
'@': path.resolve(__dirname, './src'),
views: path.resolve(__dirname, './src/views'),
components: path.resolve(__dirname, './src/components'),
utils: path.resolve(__dirname, './src/utils'),
less: path.resolve(__dirname, "./src/less"),
assets: path.resolve(__dirname, "./src/assets"),
com: path.resolve(__dirname, "./src/components"),
store: path.resolve(__dirname, "./src/store"),
mixins: path.resolve(__dirname, "./src/mixins")
},
}
})
踩到坑
在 npm run dev 打包時不報錯,但是在 npm run build 時卻報錯了,build 的時候會把 node_modules 里面的文件也編譯,所以挺多 element-plus 的類型文件報錯了。
把 tsconfig.json 里面的 include 和 exclude 修改一下就不會了,配置如下
{
"compilerOptions": {
"target": "esnext",
"module": "esnext",
"moduleResolution": "node",
"strict": true,
"jsx": "preserve",
"sourceMap": true,
// 忽略 this 的類型檢查, Raise error on this expressions with an implied any type.
"noImplicitThis": false,
"resolveJsonModule": true,
"esModuleInterop": true,
"lib": ["esnext", "dom"],
"types": ["vite/client"]
},
"include": ["/src/**/*.ts", "/src/**/*.d.ts", "/src/**/*.tsx", "/src/**/*.vue"],
// ts 排除的文件
"exclude": ["node_modules"]
}
Vue3 + vite2 打包出來的文件和原來 vue2 版的差別也挺大的,由原來 2.5M 直接變成了 1.8M ,amazing!

最后

項(xiàng)目代碼大多都是 2 年前的,還有很多可以優(yōu)化的地方,這次重構(gòu)的過程沒對原來的樣式和代碼做什么改動,沒那么多時間,加上我懶 ??
這次就升級了主要框架與相應(yīng)的 ui 庫,過了一遍 Vue3 中的 API,發(fā)現(xiàn)很多 Vue3 中新的 API 都用不上,主要是要熟練一下 Vue3 和 Vite2 項(xiàng)目搭建,這假期也算有所收獲。
具體項(xiàng)目源碼請看:
https://github.com/biaochenxuying/blog-vue-typescript
至此,一個基于 Vue3 全家桶 + Vite2 + TypeScript + Element Plus 的開發(fā)環(huán)境已經(jīng)搭建完畢,現(xiàn)在就可以編寫代碼了,各個組件的使用方法請參閱它們各自的文檔。
不得不說 Vue3 + Element Plus + Vite + TypeScript 是真的香!
推薦一個 Vue3 相關(guān)的資料匯總:Vue3 的學(xué)習(xí)教程匯總、源碼解釋項(xiàng)目、支持的 UI 組件庫、優(yōu)質(zhì)實(shí)戰(zhàn)項(xiàng)目,相信你會挖到礦哦!
推薦一個 Vue3 相關(guān)的資料匯總:Vue3 的學(xué)習(xí)教程匯總、源碼解釋項(xiàng)目、支持的 UI 組件庫、優(yōu)質(zhì)實(shí)戰(zhàn)項(xiàng)目,相信你會挖到礦哦!
推薦閱讀
