web/frpc: upgrade vue and element-plus (#3322)

This commit is contained in:
fatedier
2023-02-20 23:52:55 +08:00
committed by GitHub
Unverified
parent 24f0b3afa5
commit fe8374e99b
36 changed files with 2910 additions and 5801 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+16 -1
View File
@@ -1 +1,16 @@
<!doctype html> <html lang=en> <head> <meta charset=utf-8> <title>frp client admin UI</title> <link rel="shortcut icon" href="favicon.ico"></head> <body> <div id=app></div> <script type="text/javascript" src="manifest.js?5d5774096cf5c1b4d5af"></script><script type="text/javascript" src="vendor.js?dc42700731a508d39009"></script></body> </html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>frp client admin UI</title>
<script type="module" crossorigin src="./index-fec891f3.js"></script>
<link rel="stylesheet" href="./index-26827c97.css">
</head>
<body>
<div id="app"></div>
</body>
</html>
-1
View File
@@ -1 +0,0 @@
!function(e){function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}var r=window.webpackJsonp;window.webpackJsonp=function(t,c,u){for(var i,a,f,l=0,s=[];l<t.length;l++)a=t[l],o[a]&&s.push(o[a][0]),o[a]=0;for(i in c)Object.prototype.hasOwnProperty.call(c,i)&&(e[i]=c[i]);for(r&&r(t,c,u);s.length;)s.shift()();if(u)for(l=0;l<u.length;l++)f=n(n.s=u[l]);return f};var t={},o={1:0};n.e=function(e){function r(){i.onerror=i.onload=null,clearTimeout(a);var n=o[e];0!==n&&(n&&n[1](new Error("Loading chunk "+e+" failed.")),o[e]=void 0)}var t=o[e];if(0===t)return new Promise(function(e){e()});if(t)return t[2];var c=new Promise(function(n,r){t=o[e]=[n,r]});t[2]=c;var u=document.getElementsByTagName("head")[0],i=document.createElement("script");i.type="text/javascript",i.charset="utf-8",i.async=!0,i.timeout=12e4,n.nc&&i.setAttribute("nonce",n.nc),i.src=n.p+""+e+".js?"+{0:"dc42700731a508d39009"}[e];var a=setTimeout(r,12e4);return i.onerror=i.onload=r,u.appendChild(i),c},n.m=e,n.c=t,n.i=function(e){return e},n.d=function(e,r,t){n.o(e,r)||Object.defineProperty(e,r,{configurable:!1,enumerable:!0,get:t})},n.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(r,"a",r),r},n.o=function(e,n){return Object.prototype.hasOwnProperty.call(e,n)},n.p="",n.oe=function(e){throw console.error(e),e}}([]);
File diff suppressed because one or more lines are too long
-14
View File
@@ -1,14 +0,0 @@
{
"presets": [
["es2015", { "modules": false }]
],
"plugins": [
[
"component",
{
"libraryName": "element-ui",
"styleLibraryName": "theme-chalk"
}
]
]
}
+30
View File
@@ -0,0 +1,30 @@
/* eslint-env node */
require("@rushstack/eslint-patch/modern-module-resolution");
module.exports = {
root: true,
extends: [
"plugin:vue/vue3-essential",
"eslint:recommended",
"@vue/eslint-config-typescript",
"@vue/eslint-config-prettier",
],
parserOptions: {
ecmaVersion: "latest",
},
rules: {
"@typescript-eslint/no-unused-vars": [
"warn",
{
argsIgnorePattern: "^_",
varsIgnorePattern: "^_",
},
],
"vue/multi-word-component-names": [
"error",
{
ignores: ["Overview"],
},
],
},
};
+26 -4
View File
@@ -1,6 +1,28 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
.DS_Store
node_modules/
dist/
npm-debug.log
dist
dist-ssr
coverage
*.local
/cypress/videos/
/cypress/screenshots/
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.vscode/settings.json
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
+1
View File
@@ -0,0 +1 @@
{}
+8 -1
View File
@@ -1,6 +1,13 @@
.PHONY: dist build
.PHONY: dist build preview lint
build:
@npm run build
dev:
@npm run dev
preview:
@npm run preview
lint:
@npm run lint
+25
View File
@@ -0,0 +1,25 @@
# frpc-dashboard
## Project Setup
```sh
yarn install
```
### Compile and Hot-Reload for Development
```sh
make dev
```
### Type-Check, Compile and Minify for Production
```sh
make build
```
### Lint with [ESLint](https://eslint.org/)
```sh
make lint
```
+8
View File
@@ -0,0 +1,8 @@
/* eslint-disable */
/* prettier-ignore */
// @ts-nocheck
// Generated by unplugin-auto-import
export {}
declare global {
}
+25
View File
@@ -0,0 +1,25 @@
/* eslint-disable */
/* prettier-ignore */
// @ts-nocheck
// Generated by unplugin-vue-components
// Read more: https://github.com/vuejs/core/pull/3399
import '@vue/runtime-core'
export {}
declare module '@vue/runtime-core' {
export interface GlobalComponents {
ClientConfigure: typeof import('./src/components/ClientConfigure.vue')['default']
ElButton: typeof import('element-plus/es')['ElButton']
ElCol: typeof import('element-plus/es')['ElCol']
ElInput: typeof import('element-plus/es')['ElInput']
ElMenu: typeof import('element-plus/es')['ElMenu']
ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
ElRow: typeof import('element-plus/es')['ElRow']
ElTable: typeof import('element-plus/es')['ElTable']
ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
Overview: typeof import('./src/components/Overview.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
}
}
+1
View File
@@ -0,0 +1 @@
/// <reference types="vite/client" />
@@ -8,8 +8,7 @@
<body>
<div id="app"></div>
<!--<script src="https://code.jquery.com/jquery-3.2.0.min.js"></script>-->
<!--<script src="//cdn.bootcss.com/echarts/3.4.0/echarts.min.js"></script>-->
<script type="module" src="/src/main.ts"></script>
</body>
</html>
+26 -37
View File
@@ -1,46 +1,35 @@
{
"name": "frpc-web",
"description": "An admin web ui for frp client.",
"author": "fatedier",
"name": "-frpc-dashboard",
"version": "0.0.0",
"private": true,
"scripts": {
"dev": "webpack-dev-server -d --inline --hot --env.dev",
"build": "rimraf dist && webpack -p --progress --hide-modules"
"dev": "vite",
"build": "run-p type-check build-only",
"preview": "vite preview",
"build-only": "vite build",
"type-check": "vue-tsc --noEmit",
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore"
},
"dependencies": {
"element-ui": "^2.5.3",
"vue": "^2.5.22",
"vue-resource": "^1.5.1",
"vue-router": "^3.0.2",
"whatwg-fetch": "^3.0.0"
},
"engines": {
"node": ">=6"
"element-plus": "^2.2.28",
"vue": "^3.2.45",
"vue-router": "^4.1.6"
},
"devDependencies": {
"autoprefixer": "^9.4.7",
"babel-core": "^6.26.3",
"babel-eslint": "^10.0.1",
"babel-loader": "^7.1.5",
"babel-plugin-component": "^1.1.1",
"babel-preset-es2015": "^6.24.1",
"css-loader": "^2.1.0",
"eslint": "^5.12.1",
"eslint-config-enough": "^0.3.4",
"eslint-loader": "^2.1.1",
"file-loader": "^3.0.1",
"html-loader": "^0.5.5",
"html-webpack-plugin": "^2.24.1",
"less": "^3.9.0",
"less-loader": "^4.1.0",
"postcss-loader": "^3.0.0",
"rimraf": "^2.6.3",
"style-loader": "^0.23.1",
"url-loader": "^1.1.2",
"vue-loader": "^15.6.2",
"vue-template-compiler": "^2.5.22",
"webpack": "^2.7.0",
"webpack-cli": "^3.2.1",
"webpack-dev-server": "^3.1.14"
"@rushstack/eslint-patch": "^1.1.4",
"@types/node": "^18.11.12",
"@vitejs/plugin-vue": "^4.0.0",
"@vue/eslint-config-prettier": "^7.0.0",
"@vue/eslint-config-typescript": "^11.0.0",
"@vue/tsconfig": "^0.1.3",
"eslint": "^8.22.0",
"eslint-plugin-vue": "^9.3.0",
"npm-run-all": "^4.1.5",
"prettier": "^2.7.1",
"typescript": "~4.7.4",
"unplugin-auto-import": "^0.14.3",
"unplugin-vue-components": "^0.24.0",
"vite": "^4.0.0",
"vue-tsc": "^1.0.12"
}
}
-5
View File
@@ -1,5 +0,0 @@
module.exports = {
plugins: [
require('autoprefixer')()
]
}

Before

Width:  |  Height:  |  Size: 9.4 KiB

After

Width:  |  Height:  |  Size: 9.4 KiB

+13 -11
View File
@@ -8,7 +8,13 @@
<section>
<el-row>
<el-col id="side-nav" :xs="24" :md="4">
<el-menu default-active="1" mode="vertical" theme="light" router="false" @select="handleSelect">
<el-menu
default-active="1"
mode="vertical"
theme="light"
router="false"
@select="handleSelect"
>
<el-menu-item index="/">Overview</el-menu-item>
<el-menu-item index="/configure">Configure</el-menu-item>
<el-menu-item index="">Help</el-menu-item>
@@ -26,16 +32,12 @@
</div>
</template>
<script>
export default {
methods: {
handleSelect(key, path) {
if (key == '') {
window.open("https://github.com/fatedier/frp")
}
}
}
<script setup lang="ts">
const handleSelect = (key: string) => {
if (key == "") {
window.open("https://github.com/fatedier/frp");
}
};
</script>
<style>
@@ -51,7 +53,7 @@
}
.header-color {
background: #58B7FF;
background: #58b7ff;
}
#content {
+102
View File
@@ -0,0 +1,102 @@
<template>
<div>
<el-row id="head">
<el-button type="primary" @click="fetchData">Refresh</el-button>
<el-button type="primary" @click="uploadConfig">Upload</el-button>
</el-row>
<el-input
type="textarea"
autosize
v-model="textarea"
placeholder="frpc configrue file, can not be empty..."
></el-input>
</div>
</template>
<script setup lang="ts">
import { ref } from "vue";
import { ElMessage, ElMessageBox } from "element-plus";
let textarea = ref("");
const fetchData = () => {
fetch("/api/config", { credentials: "include" })
.then((res) => {
return res.text();
})
.then((text) => {
textarea.value = text;
})
.catch(() => {
ElMessage({
showClose: true,
message: "Get configure content from frpc failed!",
type: "warning",
});
});
};
const uploadConfig = () => {
ElMessageBox.confirm(
"This operation will upload your frpc configure file content and hot reload it, do you want to continue?",
"Notice",
{
confirmButtonText: "Yes",
cancelButtonText: "No",
type: "warning",
}
)
.then(() => {
if (textarea.value == "") {
ElMessage({
message: "Configure content can not be empty!",
type: "warning",
});
return;
}
fetch("/api/config", {
credentials: "include",
method: "PUT",
body: textarea.value,
})
.then(() => {
fetch("/api/reload", { credentials: "include" })
.then(() => {
ElMessage({
type: "success",
message: "Success",
});
})
.catch((err) => {
ElMessage({
showClose: true,
message: "Reload frpc configure file error, " + err,
type: "warning",
});
});
})
.catch(() => {
ElMessage({
showClose: true,
message: "Put config to frpc and hot reload failed!",
type: "warning",
});
});
})
.catch(() => {
ElMessage({
message: "Canceled",
type: "info",
});
});
};
fetchData();
</script>
<style>
#head {
margin-bottom: 30px;
}
</style>
-93
View File
@@ -1,93 +0,0 @@
<template>
<div>
<el-row id="head">
<el-button type="primary" @click="fetchData">Refresh</el-button>
<el-button type="primary" @click="uploadConfig">Upload</el-button>
</el-row>
<el-input type="textarea" autosize v-model="textarea" placeholder="frpc configrue file, can not be empty..."></el-input>
</div>
</template>
<script>
export default {
data() {
return {
textarea: ''
}
},
created() {
this.fetchData()
},
watch: {
'$route': 'fetchData'
},
methods: {
fetchData() {
fetch('/api/config', {credentials: 'include'})
.then(res => {
return res.text()
}).then(text => {
this.textarea= text
}).catch( err => {
this.$message({
showClose: true,
message: 'Get configure content from frpc failed!',
type: 'warning'
})
})
},
uploadConfig() {
this.$confirm('This operation will upload your frpc configure file content and hot reload it, do you want to continue?', 'Notice', {
confirmButtonText: 'Yes',
cancelButtonText: 'No',
type: 'warning'
}).then(() => {
if (this.textarea == "") {
this.$message({
type: 'warning',
message: 'Configure content can not be empty!'
})
return
}
fetch('/api/config', {
credentials: 'include',
method: 'PUT',
body: this.textarea,
}).then(() => {
fetch('/api/reload', {credentials: 'include'})
.then(() => {
this.$message({
type: 'success',
message: 'Success'
})
}).catch(err => {
this.$message({
showClose: true,
message: 'Reload frpc configure file error, ' + err,
type: 'warning'
})
})
}).catch(err => {
this.$message({
showClose: true,
message: 'Put config to frpc and hot reload failed!',
type: 'warning'
})
})
}).catch(() => {
this.$message({
type: 'info',
message: 'Canceled'
})
})
}
}
}
</script>
<style>
#head {
margin-bottom: 30px;
}
</style>
+60 -44
View File
@@ -3,13 +3,37 @@
<el-row>
<el-col :md="24">
<div>
<el-table :data="status" stripe style="width: 100%" :default-sort="{prop: 'type', order: 'ascending'}">
<el-table
:data="status"
stripe
style="width: 100%"
:default-sort="{ prop: 'type', order: 'ascending' }"
>
<el-table-column prop="name" label="name"></el-table-column>
<el-table-column prop="type" label="type" width="150"></el-table-column>
<el-table-column prop="local_addr" label="local address" width="200"></el-table-column>
<el-table-column prop="plugin" label="plugin" width="200"></el-table-column>
<el-table-column prop="remote_addr" label="remote address"></el-table-column>
<el-table-column prop="status" label="status" width="150"></el-table-column>
<el-table-column
prop="type"
label="type"
width="150"
></el-table-column>
<el-table-column
prop="local_addr"
label="local address"
width="200"
></el-table-column>
<el-table-column
prop="plugin"
label="plugin"
width="200"
></el-table-column>
<el-table-column
prop="remote_addr"
label="remote address"
></el-table-column>
<el-table-column
prop="status"
label="status"
width="150"
></el-table-column>
<el-table-column prop="err" label="info"></el-table-column>
</el-table>
</div>
@@ -18,58 +42,50 @@
</div>
</template>
<script>
export default {
data() {
return {
status: new Array(),
}
},
created() {
this.fetchData()
},
watch: {
'$route': 'fetchData'
},
methods: {
fetchData() {
fetch('/api/status', {credentials: 'include'})
.then(res => {
return res.json()
}).then(json => {
this.status = new Array()
<script setup lang="ts">
import { ref } from "vue";
import { ElMessage } from "element-plus";
let status = ref<any[]>([]);
const fetchData = () => {
fetch("/api/status", { credentials: "include" })
.then((res) => {
return res.json();
})
.then((json) => {
status.value = new Array();
for (let s of json.tcp) {
this.status.push(s)
status.value.push(s);
}
for (let s of json.udp) {
this.status.push(s)
status.value.push(s);
}
for (let s of json.http) {
this.status.push(s)
status.value.push(s);
}
for (let s of json.https) {
this.status.push(s)
status.value.push(s);
}
for (let s of json.stcp) {
this.status.push(s)
status.value.push(s);
}
for (let s of json.sudp) {
this.status.push(s)
status.value.push(s);
}
for (let s of json.xtcp) {
this.status.push(s)
status.value.push(s);
}
}).catch( err => {
this.$message({
})
.catch(() => {
ElMessage({
showClose: true,
message: 'Get status info from frpc failed!',
type: 'warning'
})
})
}
}
}
message: "Get status info from frpc failed!",
type: "warning",
});
});
};
fetchData();
</script>
<style>
</style>
<style></style>
-52
View File
@@ -1,52 +0,0 @@
import Vue from 'vue'
// import ElementUI from 'element-ui'
import {
Button,
Form,
FormItem,
Row,
Col,
Table,
TableColumn,
Menu,
MenuItem,
MessageBox,
Message,
Input
} from 'element-ui'
import lang from 'element-ui/lib/locale/lang/en'
import locale from 'element-ui/lib/locale'
import 'element-ui/lib/theme-chalk/index.css'
import './utils/less/custom.less'
import App from './App.vue'
import router from './router'
import 'whatwg-fetch'
locale.use(lang)
Vue.use(Button)
Vue.use(Form)
Vue.use(FormItem)
Vue.use(Row)
Vue.use(Col)
Vue.use(Table)
Vue.use(TableColumn)
Vue.use(Menu)
Vue.use(MenuItem)
Vue.use(Input)
Vue.prototype.$msgbox = MessageBox;
Vue.prototype.$confirm = MessageBox.confirm
Vue.prototype.$message = Message
//Vue.use(ElementUI)
Vue.config.productionTip = false
new Vue({
el: '#app',
router,
template: '<App/>',
components: { App }
})
+12
View File
@@ -0,0 +1,12 @@
import { createApp } from "vue";
import "element-plus/dist/index.css";
import App from "./App.vue";
import router from "./router";
// import './assets/custom.css'
const app = createApp(App);
app.use(router);
app.mount("#app");
-18
View File
@@ -1,18 +0,0 @@
import Vue from 'vue'
import Router from 'vue-router'
import Overview from '../components/Overview.vue'
import Configure from '../components/Configure.vue'
Vue.use(Router)
export default new Router({
routes: [{
path: '/',
name: 'Overview',
component: Overview
},{
path: '/configure',
name: 'Configure',
component: Configure,
}]
})
+21
View File
@@ -0,0 +1,21 @@
import { createRouter, createWebHashHistory } from "vue-router";
import Overview from "../components/Overview.vue";
import ClientConfigure from "../components/ClientConfigure.vue";
const router = createRouter({
history: createWebHashHistory(),
routes: [
{
path: "/",
name: "Overview",
component: Overview,
},
{
path: "/configure",
name: "ClientConfigure",
component: ClientConfigure,
},
],
});
export default router;
-22
View File
@@ -1,22 +0,0 @@
@color: red;
.el-form-item {
span {
margin-left: 15px;
}
}
.demo-table-expand {
font-size: 0;
label {
width: 90px;
color: #99a9bf;
}
.el-form-item {
margin-right: 0;
margin-bottom: 0;
width: 50%;
}
}
-13
View File
@@ -1,13 +0,0 @@
class ProxyStatus {
constructor(status) {
this.name = status.name
this.type = status.type
this.status = status.status
this.err = status.err
this.local_addr = status.local_addr
this.plugin = status.plugin
this.remote_addr = status.remote_addr
}
}
export {ProxyStatus}
+8
View File
@@ -0,0 +1,8 @@
{
"extends": "@vue/tsconfig/tsconfig.node.json",
"include": ["vite.config.*", "vitest.config.*", "cypress.config.*", "playwright.config.*"],
"compilerOptions": {
"composite": true,
"types": ["node"]
}
}
+16
View File
@@ -0,0 +1,16 @@
{
"extends": "@vue/tsconfig/tsconfig.web.json",
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
},
"references": [
{
"path": "./tsconfig.config.json"
}
]
}
+29
View File
@@ -0,0 +1,29 @@
import { fileURLToPath, URL } from "node:url";
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import AutoImport from "unplugin-auto-import/vite";
import Components from "unplugin-vue-components/vite";
import { ElementPlusResolver } from "unplugin-vue-components/resolvers";
// https://vitejs.dev/config/
export default defineConfig({
base: "",
plugins: [
vue(),
AutoImport({
resolvers: [ElementPlusResolver()],
}),
Components({
resolvers: [ElementPlusResolver()],
}),
],
resolve: {
alias: {
"@": fileURLToPath(new URL("./src", import.meta.url)),
},
},
build: {
assetsDir: "",
},
});
-107
View File
@@ -1,107 +0,0 @@
const path = require('path')
var webpack = require('webpack')
var HtmlWebpackPlugin = require('html-webpack-plugin')
var VueLoaderPlugin = require('vue-loader/lib/plugin')
var url = require('url')
var publicPath = ''
module.exports = (options = {}) => ({
entry: {
vendor: './src/main'
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: options.dev ? '[name].js' : '[name].js?[chunkhash]',
chunkFilename: '[id].js?[chunkhash]',
publicPath: options.dev ? '/assets/' : publicPath
},
resolve: {
extensions: ['.js', '.vue', '.json'],
alias: {
'vue$': 'vue/dist/vue.esm.js',
'@': path.resolve(__dirname, 'src'),
}
},
module: {
rules: [{
test: /\.vue$/,
loader: 'vue-loader'
}, {
test: /\.js$/,
use: ['babel-loader'],
exclude: /node_modules/
}, {
test: /\.html$/,
use: [{
loader: 'html-loader',
options: {
root: path.resolve(__dirname, 'src'),
attrs: ['img:src', 'link:href']
}
}]
}, {
test: /\.less$/,
loader: 'style-loader!css-loader!postcss-loader!less-loader'
}, {
test: /\.css$/,
use: ['style-loader', 'css-loader', 'postcss-loader']
}, {
test: /favicon\.png$/,
use: [{
loader: 'file-loader',
options: {
name: '[name].[ext]?[hash]'
}
}]
}, {
test: /\.(png|jpg|jpeg|gif|eot|ttf|woff|woff2|svg|svgz)(\?.+)?$/,
exclude: /favicon\.png$/,
use: [{
loader: 'url-loader',
options: {
limit: 10000
}
}]
}]
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
names: ['vendor', 'manifest']
}),
new HtmlWebpackPlugin({
favicon: 'src/assets/favicon.ico',
template: 'src/index.html'
}),
new webpack.NormalModuleReplacementPlugin(/element-ui[\/\\]lib[\/\\]locale[\/\\]lang[\/\\]zh-CN/, 'element-ui/lib/locale/lang/en'),
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: '"production"'
}
}),
new webpack.optimize.UglifyJsPlugin({
sourceMap: false,
comments: false,
compress: {
warnings: false
}
}),
new VueLoaderPlugin()
],
devServer: {
host: '127.0.0.1',
port: 8010,
proxy: {
'/api/': {
target: 'http://127.0.0.1:8080',
changeOrigin: true,
pathRewrite: {
'^/api': ''
}
}
},
historyApiFallback: {
index: url.parse(options.dev ? '/assets/' : publicPath).pathname
}
}//,
//devtool: options.dev ? '#eval-source-map' : '#source-map'
})
+2364 -5269
View File
File diff suppressed because it is too large Load Diff
+4 -25
View File
@@ -1,46 +1,25 @@
# frps-dashboard
This template should help get you started developing with Vue 3 in Vite.
## Recommended IDE Setup
[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin).
## Type Support for `.vue` Imports in TS
TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin) to make the TypeScript language service aware of `.vue` types.
If the standalone TypeScript plugin doesn't feel fast enough to you, Volar has also implemented a [Take Over Mode](https://github.com/johnsoncodehk/volar/discussions/471#discussioncomment-1361669) that is more performant. You can enable it by the following steps:
1. Disable the built-in TypeScript Extension
1) Run `Extensions: Show Built-in Extensions` from VSCode's command palette
2) Find `TypeScript and JavaScript Language Features`, right click and select `Disable (Workspace)`
2. Reload the VSCode window by running `Developer: Reload Window` from the command palette.
## Customize configuration
See [Vite Configuration Reference](https://vitejs.dev/config/).
## Project Setup
```sh
npm install
yarn install
```
### Compile and Hot-Reload for Development
```sh
npm run dev
make dev
```
### Type-Check, Compile and Minify for Production
```sh
npm run build
make build
```
### Lint with [ESLint](https://eslint.org/)
```sh
npm run lint
make lint
```