五一期间,花了 3 天时间,边学 Vue3 和 Vite2,边重构自己的项目,终于都用 Vue3 + TypeScript + Vite2 + Vuex4 + Vue-Router4 + element-plus 重构完啦!
终于完成一项心心念念的 2021 年度目标了 ✌️
项目地址:
https://github.com/biaochenxuying/blog-vue-typescript
效果
效果图:
pc 端
移动端
完整效果请看:
https://biaochenxuying.cn
功能
已经完成功能
[x] 登录
[x] 注册
[x] 文章列表
[x] 文章归档
[x] 标签
[x] 关于
[x] 点赞与评论
[x] 留言
[x] 历程
[x] 文章详情(支持代码语法高亮)
[x] 文章详情目录
[x] 移动端适配
[x] github 授权登录
前端主要技术
所有技术都是当前最新的。
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. 初化化项目
用 vite-app 创建项目
yarn create vite-app <project-name># 或者
npm init vite-app <project-name>
然后按照提示操作即可!
进入项目,安装依赖
cd <project-name>yarn # 或 npm i
运行项目
yarn dev
打开浏览器 http://localhost:3000 查看
2. 引入 TypeScript
在创建项目的时候可以 TypeScript 的,如果你选择了 TypeScript ,可以忽略第 2 个步骤。
加入 ts 依赖
yarn add --dev typescript
在 项目根目录下创建 TypeScript 的配置文件 tsconfig.json
{"compilerOptions": {// 允许从没有设置默认导出的模块中默认导入。这并不影响代码的输出,仅为了类型检查。"allowSyntheticDefaultImports": true,// 解析非相对模块名的基准目录"baseUrl": ".","esModuleInterop": true,// 从 tslib 导入辅助工具函数(比如 __extends, __rest等)"importHelpers": true,// 指定生成哪个模块系统代码"module": "esnext",// 决定如何处理模块。"moduleResolution": "node",// 启用所有严格类型检查选项。// 启用 --strict相当于启用 --noImplicitAny, --noImplicitThis, --alwaysStrict, // --strictNullChecks和 --strictFunctionTypes和--strictPropertyInitialization。"strict": true,// 生成相应的 .map文件。"sourceMap": true,// 忽略所有的声明文件( *.d.ts)的类型检查。"skipLibCheck": true,// 指定ECMAScript目标版本 "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
在根目录下创建 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,内容如下:
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 文件,内容如下:
<template><imgalt="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,或是根据需要仅引入部分组件。我们先介绍如何引入完整的 Element。
完整引入
在 main.js 中写入以下内容:
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 的引入。需要注意的是,样式文件需要单独引入。
按需引入
借助 babel-plugin-component,我们可以只引入需要的组件,以达到减小项目体积的目的。
首先,安装 babel-plugin-component:
npm install babel-plugin-component -D
然后,将 .babelrc 修改为:
{"plugins": [["component",{"libraryName": "element-plus","styleLibraryName": "theme-chalk"}]]
}
接下来,如果你只希望引入部分组件,比如 Button 和 Select,那么需要在 main.js 中写入以下内容:
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')
更详细的安装方法请看 快速上手。
5.3 全局配置
在引入 Element Plus 时,可以传入一个全局配置对象。
该对象目前支持 size
与 zIndex
字段。size
用于改变组件的默认尺寸,zIndex
设置弹框的初始 z-index(默认值: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);
按照以上设置,项目中所有拥有 size
属性的组件的默认尺寸均为 'small',弹框的初始 z-index 为 3000。
5.4 配置 vite.config.ts
其中 proxy 和 alias 是和 vue-cli 区别比较大的地方。
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}`;},}]})],/*** 在生产中服务时的基本公共路径。* @default '/'*/base: './',/*** 与“根”相关的目录,构建输出将放在其中。如果目录存在,它将在构建之前被删除。* @default 'dist'*/// outDir: 'dist',server: {// hostname: '0.0.0.0',host: "localhost",port: 3001,// // 是否自动在浏览器打开// open: true,// // 是否开启 https// https: false,// // 服务端渲染// ssr: false,proxy: {'/api': {target: 'http://localhost:3333/',changeOrigin: true,ws: true,rewrite: (pathStr) => pathStr.replace('/api', '')},},},resolve: {// 导入文件夹别名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!
最后
项目代码大多都是 2 年前的,还有很多可以优化的地方,这次重构的过程没对原来的样式和代码做什么改动,没那么多时间,加上我懒 ????
这次就升级了主要框架与相应的 ui 库,过了一遍 Vue3 中的 API,发现很多 Vue3 中新的 API 都用不上,主要是要熟练一下 Vue3 和 Vite2 项目搭建,这假期也算有所收获。
具体项目源码请看:
https://github.com/biaochenxuying/blog-vue-typescript
至此,一个基于 Vue3 全家桶 + Vite2 + TypeScript + Element Plus 的开发环境已经搭建完毕,现在就可以编写代码了,各个组件的使用方法请参阅它们各自的文档。
不得不说 Vue3 + Element Plus + Vite + TypeScript 是真的香!
推荐一个 Vue3 相关的资料汇总:Vue3 的学习教程汇总、源码解释项目、支持的 UI 组件库、优质实战项目,相信你会挖到矿哦!
推荐阅读
TypeScript 中提升幸福感的 10 个高级技巧