mirror of
https://github.com/immich-app/immich.git
synced 2025-05-24 01:12:58 -04:00
feat: use sveltekit served by nest.js
This commit is contained in:
parent
e822e3eca9
commit
38376c5a08
@ -53,6 +53,7 @@ COPY --from=prod /usr/src/app/node_modules ./node_modules
|
||||
COPY --from=prod /usr/src/app/dist ./dist
|
||||
COPY --from=prod /usr/src/app/bin ./bin
|
||||
COPY --from=web /usr/src/app/build /build/www
|
||||
COPY --from=web /usr/src/app/node_modules /build/node_modules
|
||||
COPY server/resources resources
|
||||
COPY server/package.json server/package-lock.json ./
|
||||
COPY server/start*.sh ./
|
||||
|
@ -2,8 +2,7 @@ import { NestFactory } from '@nestjs/core';
|
||||
import { NestExpressApplication } from '@nestjs/platform-express';
|
||||
import { json } from 'body-parser';
|
||||
import cookieParser from 'cookie-parser';
|
||||
import { existsSync } from 'node:fs';
|
||||
import sirv from 'sirv';
|
||||
import type { Handler, NextFunction, Request, Response } from 'express';
|
||||
import { ApiModule } from 'src/app.module';
|
||||
import { excludePaths, serverVersion } from 'src/constants';
|
||||
import { ImmichEnvironment } from 'src/enum';
|
||||
@ -42,24 +41,21 @@ async function bootstrap() {
|
||||
useSwagger(app, { write: isDev });
|
||||
|
||||
app.setGlobalPrefix('api', { exclude: excludePaths });
|
||||
if (existsSync(resourcePaths.web.root)) {
|
||||
// copied from https://github.com/sveltejs/kit/blob/679b5989fe62e3964b9a73b712d7b41831aa1f07/packages/adapter-node/src/handler.js#L46
|
||||
// provides serving of precompressed assets and caching of immutable assets
|
||||
app.use(
|
||||
sirv(resourcePaths.web.root, {
|
||||
etag: true,
|
||||
gzip: true,
|
||||
brotli: true,
|
||||
extensions: [],
|
||||
setHeaders: (res, pathname) => {
|
||||
if (pathname.startsWith(`/_app/immutable`) && res.statusCode === 200) {
|
||||
res.setHeader('cache-control', 'public,max-age=31536000,immutable');
|
||||
}
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
const svelteHandler = `${resourcePaths.web.root}/handler.js`;
|
||||
try {
|
||||
const { handler } = (await import(svelteHandler)) as { handler: Handler };
|
||||
app.use((req: Request, res: Response, next: NextFunction) => {
|
||||
if (req.url.startsWith('/api') || excludePaths.some((path) => req.url.startsWith(path))) {
|
||||
return next();
|
||||
}
|
||||
return handler(req, res, next);
|
||||
});
|
||||
} catch {
|
||||
logger.log('Not serving SvelteKit.');
|
||||
}
|
||||
app.use(app.get(ApiService).ssr(excludePaths));
|
||||
|
||||
// app.use(app.get(ApiService).ssr(excludePaths));
|
||||
|
||||
const server = await (host ? app.listen(port, host) : app.listen(port));
|
||||
server.requestTimeout = 24 * 60 * 60 * 1000;
|
||||
|
@ -5,8 +5,8 @@ TYPESCRIPT_SDK=/usr/src/open-api/typescript-sdk
|
||||
npm --prefix "$TYPESCRIPT_SDK" install
|
||||
npm --prefix "$TYPESCRIPT_SDK" run build
|
||||
|
||||
UPSTREAM="${IMMICH_SERVER_URL:-http://immich-server:2283/}"
|
||||
until wget --spider --quiet "${UPSTREAM}/api/server/config"; do
|
||||
UPSTREAM="${IMMICH_SERVER_URL:-http://immich-server:2283}"
|
||||
until wget --spider --quiet "${UPSTREAM}/api/server/ping"; do
|
||||
echo 'waiting for api server...'
|
||||
sleep 1
|
||||
done
|
||||
|
132
web/package-lock.json
generated
132
web/package-lock.json
generated
@ -42,9 +42,9 @@
|
||||
"@eslint/js": "^9.18.0",
|
||||
"@faker-js/faker": "^9.3.0",
|
||||
"@socket.io/component-emitter": "^3.1.0",
|
||||
"@sveltejs/adapter-static": "^3.0.8",
|
||||
"@sveltejs/adapter-node": "^5.2.12",
|
||||
"@sveltejs/enhanced-img": "^0.4.4",
|
||||
"@sveltejs/kit": "^2.15.2",
|
||||
"@sveltejs/kit": "^2.20.7",
|
||||
"@sveltejs/vite-plugin-svelte": "^5.0.3",
|
||||
"@testing-library/jest-dom": "^6.4.2",
|
||||
"@testing-library/svelte": "^5.2.6",
|
||||
@ -70,7 +70,7 @@
|
||||
"prettier-plugin-sort-json": "^4.1.1",
|
||||
"prettier-plugin-svelte": "^3.3.3",
|
||||
"rollup-plugin-visualizer": "^5.14.0",
|
||||
"svelte": "^5.25.3",
|
||||
"svelte": "^5.28.1",
|
||||
"svelte-check": "^4.1.5",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"tslib": "^2.6.2",
|
||||
@ -1756,6 +1756,89 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@rollup/plugin-commonjs": {
|
||||
"version": "28.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-28.0.3.tgz",
|
||||
"integrity": "sha512-pyltgilam1QPdn+Zd9gaCfOLcnjMEJ9gV+bTw6/r73INdvzf1ah9zLIJBm+kW7R6IUFIQ1YO+VqZtYxZNWFPEQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@rollup/pluginutils": "^5.0.1",
|
||||
"commondir": "^1.0.1",
|
||||
"estree-walker": "^2.0.2",
|
||||
"fdir": "^6.2.0",
|
||||
"is-reference": "1.2.1",
|
||||
"magic-string": "^0.30.3",
|
||||
"picomatch": "^4.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.0.0 || 14 >= 14.17"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"rollup": "^2.68.0||^3.0.0||^4.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"rollup": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@rollup/plugin-commonjs/node_modules/is-reference": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz",
|
||||
"integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/estree": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@rollup/plugin-json": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/plugin-json/-/plugin-json-6.1.0.tgz",
|
||||
"integrity": "sha512-EGI2te5ENk1coGeADSIwZ7G2Q8CJS2sF120T7jLw4xFw9n7wIOXHo+kIYRAoVpJAN+kmqZSoO3Fp4JtoNF4ReA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@rollup/pluginutils": "^5.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"rollup": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@rollup/plugin-node-resolve": {
|
||||
"version": "16.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-16.0.1.tgz",
|
||||
"integrity": "sha512-tk5YCxJWIG81umIvNkSod2qK5KyQW19qcBF/B78n1bjtOON6gzKoVeSzAE8yHCZEDmqkHKkxplExA8KzdJLJpA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@rollup/pluginutils": "^5.0.1",
|
||||
"@types/resolve": "1.20.2",
|
||||
"deepmerge": "^4.2.2",
|
||||
"is-module": "^1.0.0",
|
||||
"resolve": "^1.22.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"rollup": "^2.78.0||^3.0.0||^4.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"rollup": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@rollup/pluginutils": {
|
||||
"version": "5.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.4.tgz",
|
||||
@ -2074,14 +2157,20 @@
|
||||
"acorn": "^8.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@sveltejs/adapter-static": {
|
||||
"version": "3.0.8",
|
||||
"resolved": "https://registry.npmjs.org/@sveltejs/adapter-static/-/adapter-static-3.0.8.tgz",
|
||||
"integrity": "sha512-YaDrquRpZwfcXbnlDsSrBQNCChVOT9MGuSg+dMAyfsAa1SmiAhrA5jUYUiIMC59G92kIbY/AaQOWcBdq+lh+zg==",
|
||||
"node_modules/@sveltejs/adapter-node": {
|
||||
"version": "5.2.12",
|
||||
"resolved": "https://registry.npmjs.org/@sveltejs/adapter-node/-/adapter-node-5.2.12.tgz",
|
||||
"integrity": "sha512-0bp4Yb3jKIEcZWVcJC/L1xXp9zzJS4hDwfb4VITAkfT4OVdkspSHsx7YhqJDbb2hgLl6R9Vs7VQR+fqIVOxPUQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@rollup/plugin-commonjs": "^28.0.1",
|
||||
"@rollup/plugin-json": "^6.1.0",
|
||||
"@rollup/plugin-node-resolve": "^16.0.0",
|
||||
"rollup": "^4.9.5"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@sveltejs/kit": "^2.0.0"
|
||||
"@sveltejs/kit": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@sveltejs/enhanced-img": {
|
||||
@ -2433,6 +2522,13 @@
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/resolve": {
|
||||
"version": "1.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz",
|
||||
"integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/supercluster": {
|
||||
"version": "7.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/supercluster/-/supercluster-7.1.3.tgz",
|
||||
@ -3531,6 +3627,13 @@
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/commondir": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
|
||||
"integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/concat-map": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||
@ -5443,6 +5546,13 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/is-module": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz",
|
||||
"integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/is-number": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
|
||||
@ -8178,9 +8288,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/svelte": {
|
||||
"version": "5.27.3",
|
||||
"resolved": "https://registry.npmjs.org/svelte/-/svelte-5.27.3.tgz",
|
||||
"integrity": "sha512-MK16NUEFwAunCkdJpIIJ6hvKElx0zFlKMqQd7NAIugMfrL0YeOH8VEn5pg9g2Q6RLj2JrGJL6c0zaAwmXx/nHQ==",
|
||||
"version": "5.28.2",
|
||||
"resolved": "https://registry.npmjs.org/svelte/-/svelte-5.28.2.tgz",
|
||||
"integrity": "sha512-FbWBxgWOpQfhKvoGJv/TFwzqb4EhJbwCD17dB0tEpQiw1XyUEKZJtgm4nA4xq3LLsMo7hu5UY/BOFmroAxKTMg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@ampproject/remapping": "^2.3.0",
|
||||
|
@ -58,9 +58,9 @@
|
||||
"@eslint/js": "^9.18.0",
|
||||
"@faker-js/faker": "^9.3.0",
|
||||
"@socket.io/component-emitter": "^3.1.0",
|
||||
"@sveltejs/adapter-static": "^3.0.8",
|
||||
"@sveltejs/adapter-node": "^5.2.12",
|
||||
"@sveltejs/enhanced-img": "^0.4.4",
|
||||
"@sveltejs/kit": "^2.15.2",
|
||||
"@sveltejs/kit": "^2.20.7",
|
||||
"@sveltejs/vite-plugin-svelte": "^5.0.3",
|
||||
"@testing-library/jest-dom": "^6.4.2",
|
||||
"@testing-library/svelte": "^5.2.6",
|
||||
@ -86,7 +86,7 @@
|
||||
"prettier-plugin-sort-json": "^4.1.1",
|
||||
"prettier-plugin-svelte": "^3.3.3",
|
||||
"rollup-plugin-visualizer": "^5.14.0",
|
||||
"svelte": "^5.25.3",
|
||||
"svelte": "^5.28.1",
|
||||
"svelte-check": "^4.1.5",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"tslib": "^2.6.2",
|
||||
|
@ -10,18 +10,30 @@ function getNumber(string: string | null, fallback: number) {
|
||||
}
|
||||
return Number.parseInt(string);
|
||||
}
|
||||
|
||||
const getItem = (key: string) => {
|
||||
if (!globalThis.localStorage) {
|
||||
const error = new Error('test');
|
||||
Error.stackTraceLimit = Infinity;
|
||||
console.log('local storage is not available', error.stack);
|
||||
return null;
|
||||
}
|
||||
|
||||
return globalThis.localStorage.getItem(key);
|
||||
};
|
||||
|
||||
export const TUNABLES = {
|
||||
LAYOUT: {
|
||||
WASM: getBoolean(localStorage.getItem('LAYOUT.WASM'), false),
|
||||
WASM: getBoolean(getItem('LAYOUT.WASM'), false),
|
||||
},
|
||||
TIMELINE: {
|
||||
INTERSECTION_EXPAND_TOP: getNumber(localStorage.getItem('TIMELINE_INTERSECTION_EXPAND_TOP'), 500),
|
||||
INTERSECTION_EXPAND_BOTTOM: getNumber(localStorage.getItem('TIMELINE_INTERSECTION_EXPAND_BOTTOM'), 500),
|
||||
INTERSECTION_EXPAND_TOP: getNumber(getItem('TIMELINE_INTERSECTION_EXPAND_TOP'), 500),
|
||||
INTERSECTION_EXPAND_BOTTOM: getNumber(getItem('TIMELINE_INTERSECTION_EXPAND_BOTTOM'), 500),
|
||||
},
|
||||
ASSET_GRID: {
|
||||
NAVIGATE_ON_ASSET_IN_VIEW: getBoolean(localStorage.getItem('ASSET_GRID.NAVIGATE_ON_ASSET_IN_VIEW'), false),
|
||||
NAVIGATE_ON_ASSET_IN_VIEW: getBoolean(getItem('ASSET_GRID.NAVIGATE_ON_ASSET_IN_VIEW'), false),
|
||||
},
|
||||
IMAGE_THUMBNAIL: {
|
||||
THUMBHASH_FADE_DURATION: getNumber(localStorage.getItem('THUMBHASH_FADE_DURATION'), 150),
|
||||
THUMBHASH_FADE_DURATION: getNumber(getItem('THUMBHASH_FADE_DURATION'), 150),
|
||||
},
|
||||
};
|
||||
|
@ -15,6 +15,7 @@
|
||||
import { navigate } from '$lib/utils/navigation';
|
||||
import { assetViewingStore } from '$lib/stores/asset-viewing.store';
|
||||
import { tick } from 'svelte';
|
||||
import { browser } from '$app/environment';
|
||||
|
||||
interface Props {
|
||||
data: PageData;
|
||||
@ -57,41 +58,45 @@
|
||||
<title>{title}</title>
|
||||
<meta name="description" content={description} />
|
||||
</svelte:head>
|
||||
{#if passwordRequired}
|
||||
<header>
|
||||
<ControlAppBar showBackButton={false}>
|
||||
{#snippet leading()}
|
||||
<ImmichLogoSmallLink />
|
||||
{/snippet}
|
||||
{#if browser}
|
||||
{#if passwordRequired}
|
||||
<header>
|
||||
<ControlAppBar showBackButton={false}>
|
||||
{#snippet leading()}
|
||||
<ImmichLogoSmallLink />
|
||||
{/snippet}
|
||||
|
||||
{#snippet trailing()}
|
||||
<ThemeButton />
|
||||
{/snippet}
|
||||
</ControlAppBar>
|
||||
</header>
|
||||
<main
|
||||
class="relative h-dvh overflow-hidden bg-immich-bg px-6 max-md:pt-[var(--navbar-height-md)] pt-[var(--navbar-height)] dark:bg-immich-dark-bg sm:px-12 md:px-24 lg:px-40"
|
||||
>
|
||||
<div class="flex flex-col items-center justify-center mt-20">
|
||||
<div class="text-2xl font-bold text-immich-primary dark:text-immich-dark-primary">{$t('password_required')}</div>
|
||||
<div class="mt-4 text-lg text-immich-primary dark:text-immich-dark-primary">
|
||||
{$t('sharing_enter_password')}
|
||||
</div>
|
||||
<div class="mt-4">
|
||||
<form class="flex gap-x-2" novalidate {onsubmit}>
|
||||
<PasswordField autocomplete="off" bind:password placeholder="Password" />
|
||||
<Button type="submit">{$t('submit')}</Button>
|
||||
</form>
|
||||
{#snippet trailing()}
|
||||
<ThemeButton />
|
||||
{/snippet}
|
||||
</ControlAppBar>
|
||||
</header>
|
||||
<main
|
||||
class="relative h-dvh overflow-hidden bg-immich-bg px-6 max-md:pt-[var(--navbar-height-md)] pt-[var(--navbar-height)] dark:bg-immich-dark-bg sm:px-12 md:px-24 lg:px-40"
|
||||
>
|
||||
<div class="flex flex-col items-center justify-center mt-20">
|
||||
<div class="text-2xl font-bold text-immich-primary dark:text-immich-dark-primary">
|
||||
{$t('password_required')}
|
||||
</div>
|
||||
<div class="mt-4 text-lg text-immich-primary dark:text-immich-dark-primary">
|
||||
{$t('sharing_enter_password')}
|
||||
</div>
|
||||
<div class="mt-4">
|
||||
<form class="flex gap-x-2" novalidate {onsubmit}>
|
||||
<PasswordField autocomplete="off" bind:password placeholder="Password" />
|
||||
<Button type="submit">{$t('submit')}</Button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
{/if}
|
||||
|
||||
{#if !passwordRequired && sharedLink?.type == SharedLinkType.Album}
|
||||
<AlbumViewer {sharedLink} />
|
||||
{/if}
|
||||
{#if !passwordRequired && sharedLink?.type == SharedLinkType.Individual}
|
||||
<div class="immich-scrollbar">
|
||||
<IndividualSharedViewer {sharedLink} {isOwned} />
|
||||
</div>
|
||||
</main>
|
||||
{/if}
|
||||
|
||||
{#if !passwordRequired && sharedLink?.type == SharedLinkType.Album}
|
||||
<AlbumViewer {sharedLink} />
|
||||
{/if}
|
||||
{#if !passwordRequired && sharedLink?.type == SharedLinkType.Individual}
|
||||
<div class="immich-scrollbar">
|
||||
<IndividualSharedViewer {sharedLink} {isOwned} />
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
|
@ -5,6 +5,8 @@ import { getAssetInfoFromParam } from '$lib/utils/navigation';
|
||||
import { getMySharedLink, isHttpError } from '@immich/sdk';
|
||||
import type { PageLoad } from './$types';
|
||||
|
||||
export const ssr = true;
|
||||
|
||||
export const load = (async ({ params }) => {
|
||||
const { key } = params;
|
||||
await authenticate({ public: true });
|
||||
|
@ -22,6 +22,7 @@
|
||||
import { t } from 'svelte-i18n';
|
||||
import { run } from 'svelte/legacy';
|
||||
import '../app.css';
|
||||
import { browser } from '$app/environment';
|
||||
|
||||
interface Props {
|
||||
children?: Snippet;
|
||||
@ -44,6 +45,10 @@
|
||||
theme.value = globalThis.matchMedia('(prefers-color-scheme: dark)').matches ? Theme.DARK : Theme.LIGHT;
|
||||
}
|
||||
|
||||
if (!browser) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (theme.value === Theme.LIGHT) {
|
||||
document.documentElement.classList.remove('dark');
|
||||
} else {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import adapter from '@sveltejs/adapter-static';
|
||||
import adapter from '@sveltejs/adapter-node';
|
||||
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
|
||||
import dotenv from 'dotenv';
|
||||
|
||||
@ -14,10 +14,7 @@ const config = {
|
||||
},
|
||||
preprocess: vitePreprocess(),
|
||||
kit: {
|
||||
adapter: adapter({
|
||||
fallback: 'index.html',
|
||||
precompress: true,
|
||||
}),
|
||||
adapter: adapter(),
|
||||
alias: {
|
||||
$lib: 'src/lib',
|
||||
'$lib/*': 'src/lib/*',
|
||||
|
Loading…
x
Reference in New Issue
Block a user