implement login with vue
* use vue router * use pinia * use tailwindcss * use vite * transform /api/login and /api/login/callback into JSON APIs * add Access-Control-Allow-Credentials header * add TODO about JSON errors
This commit is contained in:
parent
d1b7786434
commit
4e343d49d0
|
@ -30,7 +30,7 @@ func NewLNAuth() (*LNAuth, error) {
|
||||||
return nil, fmt.Errorf("rand.Read error: %w", err)
|
return nil, fmt.Errorf("rand.Read error: %w", err)
|
||||||
}
|
}
|
||||||
k1hex := hex.EncodeToString(k1)
|
k1hex := hex.EncodeToString(k1)
|
||||||
url := []byte(fmt.Sprintf("https://%s/api/login?tag=login&k1=%s&action=login", env.PublicURL, k1hex))
|
url := []byte(fmt.Sprintf("https://%s/api/login/callback?tag=login&k1=%s&action=login", env.PublicURL, k1hex))
|
||||||
conv, err := bech32.ConvertBits(url, 8, 5, true)
|
conv, err := bech32.ConvertBits(url, 8, 5, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("bech32.ConvertBits error: %w", err)
|
return nil, fmt.Errorf("bech32.ConvertBits error: %w", err)
|
||||||
|
|
|
@ -37,6 +37,7 @@ func serveError(c echo.Context, code int) error {
|
||||||
c.Logger().Error(err)
|
c.Logger().Error(err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
// TODO return errors in JSON
|
||||||
if err = c.Stream(code, "text/html", f); err != nil {
|
if err = c.Stream(code, "text/html", f); err != nil {
|
||||||
c.Logger().Error(err)
|
c.Logger().Error(err)
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -34,11 +34,10 @@ func HandleLogin(sc context.ServerContext) echo.HandlerFunc {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
data = map[string]any{
|
data = map[string]any{
|
||||||
"session": c.Get("session"),
|
"lnurl": lnAuth.LNURL,
|
||||||
"lnurl": lnAuth.LNURL,
|
"qr": qr,
|
||||||
"qr": qr,
|
|
||||||
}
|
}
|
||||||
return sc.Render(c, http.StatusOK, "login.html", data)
|
return c.JSON(http.StatusOK, data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,13 +17,16 @@ func HandleCheckSession(sc context.ServerContext) echo.HandlerFunc {
|
||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
if cookie, err = c.Cookie("session"); err != nil {
|
if cookie, err = c.Cookie("session"); err != nil {
|
||||||
return echo.NewHTTPError(http.StatusBadRequest, map[string]string{"reason": "cookie required"})
|
// return echo.NewHTTPError(http.StatusBadRequest, map[string]string{"reason": "cookie required"})
|
||||||
|
return c.JSON(http.StatusBadRequest, map[string]string{"reason": "cookie required"})
|
||||||
}
|
}
|
||||||
s = db.Session{SessionId: cookie.Value}
|
s = db.Session{SessionId: cookie.Value}
|
||||||
if err = sc.Db.FetchSession(&s); err == sql.ErrNoRows {
|
if err = sc.Db.FetchSession(&s); err == sql.ErrNoRows {
|
||||||
return echo.NewHTTPError(http.StatusNotFound, map[string]string{"reason": "session not found"})
|
// return echo.NewHTTPError(http.StatusNotFound, map[string]string{"reason": "session not found"})
|
||||||
|
return c.JSON(http.StatusBadRequest, map[string]string{"reason": "session not found"})
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return echo.NewHTTPError(http.StatusInternalServerError)
|
// return echo.NewHTTPError(http.StatusInternalServerError)
|
||||||
|
return c.JSON(http.StatusInternalServerError, nil)
|
||||||
}
|
}
|
||||||
return c.JSON(http.StatusOK, map[string]string{"pubkey": s.Pubkey})
|
return c.JSON(http.StatusOK, map[string]string{"pubkey": s.Pubkey})
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,7 +25,6 @@ func mountMiddleware(e *echo.Echo, sc ServerContext) {
|
||||||
|
|
||||||
func addFrontendRoutes(e *echo.Echo, sc ServerContext) {
|
func addFrontendRoutes(e *echo.Echo, sc ServerContext) {
|
||||||
GET(e, sc, "/", handler.HandleIndex)
|
GET(e, sc, "/", handler.HandleIndex)
|
||||||
GET(e, sc, "/login", handler.HandleLogin)
|
|
||||||
POST(e, sc, "/logout", handler.HandleLogout)
|
POST(e, sc, "/logout", handler.HandleLogout)
|
||||||
GET(e, sc, "/user",
|
GET(e, sc, "/user",
|
||||||
handler.HandleUser,
|
handler.HandleUser,
|
||||||
|
@ -43,7 +42,8 @@ func addFrontendRoutes(e *echo.Echo, sc ServerContext) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func addBackendRoutes(e *echo.Echo, sc ServerContext) {
|
func addBackendRoutes(e *echo.Echo, sc ServerContext) {
|
||||||
GET(e, sc, "/api/login", handler.HandleLoginCallback)
|
GET(e, sc, "/api/login", handler.HandleLogin)
|
||||||
|
GET(e, sc, "/api/login/callback", handler.HandleLoginCallback)
|
||||||
GET(e, sc, "/api/session", handler.HandleCheckSession)
|
GET(e, sc, "/api/session", handler.HandleCheckSession)
|
||||||
GET(e, sc, "/api/invoice/:id",
|
GET(e, sc, "/api/invoice/:id",
|
||||||
handler.HandleInvoiceStatus,
|
handler.HandleInvoiceStatus,
|
||||||
|
|
|
@ -25,6 +25,11 @@ func New(ctx ServerContext) *Server {
|
||||||
Format: "${time_custom} ${method} ${uri} ${status}\n",
|
Format: "${time_custom} ${method} ${uri} ${status}\n",
|
||||||
CustomTimeFormat: "2006-01-02 15:04:05.00000-0700",
|
CustomTimeFormat: "2006-01-02 15:04:05.00000-0700",
|
||||||
}))
|
}))
|
||||||
|
e.Use(middleware.CORSWithConfig(middleware.CORSConfig{
|
||||||
|
AllowOrigins: []string{"http://localhost:4224", "https://delphi.market", "https://dev1.delphi.market"},
|
||||||
|
AllowCredentials: true,
|
||||||
|
AllowHeaders: []string{echo.HeaderOrigin, echo.HeaderContentType, echo.HeaderAccept},
|
||||||
|
}))
|
||||||
e.HTTPErrorHandler = httpErrorHandler
|
e.HTTPErrorHandler = httpErrorHandler
|
||||||
|
|
||||||
s = &Server{e}
|
s = &Server{e}
|
||||||
|
|
|
@ -4,12 +4,13 @@
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||||
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
<link rel="icon" href="/favicon.ico">
|
||||||
<title><%= htmlWebpackPlugin.options.title %></title>
|
<script type="module" src="/src/main.js"></script>
|
||||||
|
<title>delphi.market</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<noscript>
|
<noscript>
|
||||||
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
<strong>We're sorry but this site doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
||||||
</noscript>
|
</noscript>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
<!-- built files will be auto injected -->
|
<!-- built files will be auto injected -->
|
|
@ -3,27 +3,30 @@
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"serve": "vue-cli-service serve",
|
"dev": "vite --port 4224",
|
||||||
"build": "vue-cli-service build",
|
"build": "vite build",
|
||||||
"lint": "vue-cli-service lint"
|
"serve": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@vitejs/plugin-vue": "^4.4.0",
|
||||||
"core-js": "^3.8.3",
|
"core-js": "^3.8.3",
|
||||||
|
"pinia": "^2.1.7",
|
||||||
"register-service-worker": "^1.7.2",
|
"register-service-worker": "^1.7.2",
|
||||||
"vue": "^3.2.13"
|
"vite": "^4.5.0",
|
||||||
|
"vue": "^3.2.13",
|
||||||
|
"vue-router": "4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.12.16",
|
"@babel/core": "^7.12.16",
|
||||||
"@babel/eslint-parser": "^7.12.16",
|
"@babel/eslint-parser": "^7.12.16",
|
||||||
"@vue/cli-plugin-babel": "~5.0.0",
|
|
||||||
"@vue/cli-plugin-eslint": "~5.0.0",
|
|
||||||
"@vue/cli-plugin-pwa": "~5.0.0",
|
|
||||||
"@vue/cli-service": "~5.0.0",
|
|
||||||
"@vue/eslint-config-standard": "^6.1.0",
|
"@vue/eslint-config-standard": "^6.1.0",
|
||||||
|
"autoprefixer": "^10.4.16",
|
||||||
"eslint": "^7.32.0",
|
"eslint": "^7.32.0",
|
||||||
"eslint-plugin-import": "^2.25.3",
|
"eslint-plugin-import": "^2.25.3",
|
||||||
"eslint-plugin-node": "^11.1.0",
|
"eslint-plugin-node": "^11.1.0",
|
||||||
"eslint-plugin-promise": "^5.1.0",
|
"eslint-plugin-promise": "^5.1.0",
|
||||||
"eslint-plugin-vue": "^8.0.3"
|
"eslint-plugin-vue": "^8.0.3",
|
||||||
|
"postcss": "^8.4.31",
|
||||||
|
"tailwindcss": "^3.3.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
module.exports = {
|
||||||
|
plugins: {
|
||||||
|
tailwindcss: {},
|
||||||
|
autoprefixer: {}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,26 +1,41 @@
|
||||||
<template>
|
<template>
|
||||||
<img alt="Vue logo" src="./assets/logo.png">
|
<NavBar />
|
||||||
<HelloWorld msg="Welcome to Your Vue.js App" />
|
<div id="container">
|
||||||
|
<router-view />
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import HelloWorld from './components/HelloWorld.vue'
|
import NavBar from './components/NavBar'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'App',
|
name: 'App',
|
||||||
components: {
|
components: { NavBar }
|
||||||
HelloWorld
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { useSession } from './stores/session';
|
||||||
|
const session = useSession()
|
||||||
|
session.init()
|
||||||
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
background-color: #091833;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
#app {
|
#app {
|
||||||
font-family: Avenir, Helvetica, Arial, sans-serif;
|
font-family: Avenir, Helvetica, Arial, sans-serif;
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
color: #2c3e50;
|
color: #ffffff;
|
||||||
margin-top: 60px;
|
margin-top: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#container {
|
||||||
|
margin: 1em;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,66 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="hello">
|
|
||||||
<h1>{{ msg }}</h1>
|
|
||||||
<p>
|
|
||||||
For a guide and recipes on how to configure / customize this project,<br>
|
|
||||||
check out the
|
|
||||||
<a href="https://cli.vuejs.org" target="_blank" rel="noopener">vue-cli documentation</a>.
|
|
||||||
</p>
|
|
||||||
<h3>Installed CLI Plugins</h3>
|
|
||||||
<ul>
|
|
||||||
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-babel" target="_blank"
|
|
||||||
rel="noopener">babel</a></li>
|
|
||||||
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-pwa" target="_blank"
|
|
||||||
rel="noopener">pwa</a></li>
|
|
||||||
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-eslint" target="_blank"
|
|
||||||
rel="noopener">eslint</a></li>
|
|
||||||
</ul>
|
|
||||||
<h3>Essential Links</h3>
|
|
||||||
<ul>
|
|
||||||
<li><a href="https://vuejs.org" target="_blank" rel="noopener">Core Docs</a></li>
|
|
||||||
<li><a href="https://forum.vuejs.org" target="_blank" rel="noopener">Forum</a></li>
|
|
||||||
<li><a href="https://chat.vuejs.org" target="_blank" rel="noopener">Community Chat</a></li>
|
|
||||||
<li><a href="https://twitter.com/vuejs" target="_blank" rel="noopener">Twitter</a></li>
|
|
||||||
<li><a href="https://news.vuejs.org" target="_blank" rel="noopener">News</a></li>
|
|
||||||
</ul>
|
|
||||||
<h3>Ecosystem</h3>
|
|
||||||
<ul>
|
|
||||||
<li><a href="https://router.vuejs.org" target="_blank" rel="noopener">vue-router</a></li>
|
|
||||||
<li><a href="https://vuex.vuejs.org" target="_blank" rel="noopener">vuex</a></li>
|
|
||||||
<li><a href="https://github.com/vuejs/vue-devtools#vue-devtools" target="_blank" rel="noopener">vue-devtools</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="https://vue-loader.vuejs.org" target="_blank" rel="noopener">vue-loader</a></li>
|
|
||||||
<li><a href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener">awesome-vue</a></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
name: 'HelloWorld',
|
|
||||||
props: {
|
|
||||||
msg: String
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
|
||||||
<style scoped>
|
|
||||||
h3 {
|
|
||||||
margin: 40px 0 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul {
|
|
||||||
list-style-type: none;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
li {
|
|
||||||
display: inline-block;
|
|
||||||
margin: 0 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
color: #42b983;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
<template>
|
||||||
|
<div>home</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script></script>
|
|
@ -0,0 +1,25 @@
|
||||||
|
<template>
|
||||||
|
<a v-if="lnurl" :href="'lightning:' + lnurl">
|
||||||
|
<img v-if="qr" :src="'data:image/png;base64,' + qr" />
|
||||||
|
</a>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import { useSession } from '@/stores/session';
|
||||||
|
|
||||||
|
let qr = ref(null)
|
||||||
|
let lnurl = ref(null)
|
||||||
|
|
||||||
|
const session = useSession()
|
||||||
|
await (async () => {
|
||||||
|
try {
|
||||||
|
if (session.isAuthenticated) return
|
||||||
|
const s = await session.login()
|
||||||
|
qr = s.qr
|
||||||
|
lnurl = s.lnurl
|
||||||
|
} catch (err) {
|
||||||
|
console.error("error:", err.reason || err)
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
</script>
|
|
@ -0,0 +1,25 @@
|
||||||
|
<template>
|
||||||
|
<!-- eslint-disable -->
|
||||||
|
<pre>
|
||||||
|
_ _
|
||||||
|
| | ___ __ _(_)_ __
|
||||||
|
| |/ _ \ / _` | | '_ \
|
||||||
|
| | (_) | (_| | | | | |
|
||||||
|
|_|\___/ \__, |_|_| |_|
|
||||||
|
|___/ </pre>
|
||||||
|
<!-- eslint-enable -->
|
||||||
|
<Suspense>
|
||||||
|
<LoginQRCode class="flex justify-center m-3" />
|
||||||
|
</Suspense>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import LoginQRCode from './LoginQRCode.vue';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
pre {
|
||||||
|
font-family: monospace;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,40 @@
|
||||||
|
<template>
|
||||||
|
<header class="flex flex-row text-center justify-center pt-1">
|
||||||
|
<nav>
|
||||||
|
<router-link to="/">home</router-link>
|
||||||
|
<router-link to="/user" v-if="session.isAuthenticated">user</router-link>
|
||||||
|
<router-link to="/login" v-else href="/login">login</router-link>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { useSession } from '@/stores/session'
|
||||||
|
const session = useSession()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
nav {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: #8787a4;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
color: #ffffff;
|
||||||
|
background: #8787A4;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.selected {
|
||||||
|
color: #ffffff;
|
||||||
|
background: #8787A4;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav>a {
|
||||||
|
margin: 0 3px;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,3 @@
|
||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
|
@ -1,5 +1,29 @@
|
||||||
import { createApp } from 'vue'
|
import { createApp } from 'vue'
|
||||||
|
import { createPinia } from 'pinia'
|
||||||
|
import * as VueRouter from 'vue-router'
|
||||||
import App from './App.vue'
|
import App from './App.vue'
|
||||||
import './registerServiceWorker'
|
import './registerServiceWorker'
|
||||||
|
import './index.css'
|
||||||
|
|
||||||
createApp(App).mount('#app')
|
import HomeView from '@/components/HomeView'
|
||||||
|
import LoginView from '@/components/LoginView'
|
||||||
|
|
||||||
|
const routes = [
|
||||||
|
{
|
||||||
|
path: '/', component: HomeView
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/login', component: LoginView
|
||||||
|
}
|
||||||
|
]
|
||||||
|
const router = VueRouter.createRouter({
|
||||||
|
history: VueRouter.createWebHashHistory(),
|
||||||
|
routes
|
||||||
|
})
|
||||||
|
|
||||||
|
const pinia = createPinia()
|
||||||
|
|
||||||
|
const app = createApp(App)
|
||||||
|
app.use(router)
|
||||||
|
app.use(pinia)
|
||||||
|
app.mount('#app')
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
import { defineStore } from 'pinia'
|
||||||
|
import { computed, ref } from 'vue'
|
||||||
|
|
||||||
|
export const useSession = defineStore('session', () => {
|
||||||
|
let pubkey = ref(null)
|
||||||
|
// eslint-disable-next-line vue/no-ref-as-operand
|
||||||
|
const isAuthenticated = computed(() => !!pubkey)
|
||||||
|
|
||||||
|
async function init () {
|
||||||
|
try {
|
||||||
|
const { pubkey } = await checkSession()
|
||||||
|
if (pubkey) {
|
||||||
|
console.log('authenticated as', pubkey)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
console.log('unauthenticated')
|
||||||
|
} catch (err) {
|
||||||
|
console.error('error:', err.reason || err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkSession () {
|
||||||
|
const url = window.origin + '/api/session'
|
||||||
|
return fetch(url, {
|
||||||
|
credentials: 'include'
|
||||||
|
})
|
||||||
|
.then(r => {
|
||||||
|
const body = r.json()
|
||||||
|
pubkey = body.pubkey
|
||||||
|
return body
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function login () {
|
||||||
|
const url = window.origin + '/api/login'
|
||||||
|
return fetch(url, { credentials: 'include' }).then(r => r.json())
|
||||||
|
}
|
||||||
|
|
||||||
|
return { pubkey, isAuthenticated, init, checkSession, login }
|
||||||
|
})
|
|
@ -0,0 +1,10 @@
|
||||||
|
/** @type {import('tailwindcss').Config} */
|
||||||
|
module.exports = {
|
||||||
|
content: [
|
||||||
|
'./src/**/*.{html,js,vue}'
|
||||||
|
],
|
||||||
|
theme: {
|
||||||
|
extend: {}
|
||||||
|
},
|
||||||
|
plugins: []
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
import { fileURLToPath, URL } from 'node:url'
|
||||||
|
import { defineConfig } from 'vite'
|
||||||
|
import vue from '@vitejs/plugin-vue'
|
||||||
|
|
||||||
|
// https://vitejs.dev/config/
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [vue()],
|
||||||
|
resolve: {
|
||||||
|
extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json', '.vue'],
|
||||||
|
alias: {
|
||||||
|
'@': fileURLToPath(new URL('./src', import.meta.url))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
5418
vue/yarn.lock
5418
vue/yarn.lock
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue