initial commit
This commit is contained in:
commit
7a146ec027
|
|
@ -0,0 +1,8 @@
|
||||||
|
dist/
|
||||||
|
node_modules/
|
||||||
|
./node_modules/
|
||||||
|
./dist/
|
||||||
|
./dist
|
||||||
|
./node_modules
|
||||||
|
node_modules/*
|
||||||
|
dist/*
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
export default {
|
||||||
|
base: '/'
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>extera music.</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
|
||||||
|
<script src="src/index.tsx" type="module"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,39 @@
|
||||||
|
{
|
||||||
|
"name": "frontmusic",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"description": "",
|
||||||
|
"type": "module",
|
||||||
|
"dependencies": {
|
||||||
|
"@emotion/react": "^11.13.3",
|
||||||
|
"@emotion/styled": "^11.13.0",
|
||||||
|
"@esbuild-plugins/node-globals-polyfill": "^0.2.3",
|
||||||
|
"@mui/icons-material": "^6.1.2",
|
||||||
|
"@mui/material": "^6.1.2",
|
||||||
|
"@rollup/plugin-inject": "^5.0.5",
|
||||||
|
"@vanilla-extract/css": "^1.16.0",
|
||||||
|
"@vanilla-extract/vite-plugin": "^4.0.16",
|
||||||
|
"@vitejs/plugin-react": "^4.3.2",
|
||||||
|
"react": "^18.3.1",
|
||||||
|
"react-dom": "^18.3.1",
|
||||||
|
"react-lazy-load-image-component": "^1.6.2",
|
||||||
|
"react-router-dom": "^6.26.2",
|
||||||
|
"typescript": "^5.6.2",
|
||||||
|
"vite-plugin-pwa": "^0.20.5",
|
||||||
|
"vite-plugin-static-copy": "^1.0.6",
|
||||||
|
"vite-plugin-top-level-await": "^1.4.4"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/react": "^18.3.11",
|
||||||
|
"@types/react-dom": "^18.3.0",
|
||||||
|
"@types/react-lazy-load-image-component": "^1.6.4",
|
||||||
|
"@types/react-router-dom": "^5.3.3",
|
||||||
|
"sass-embedded": "^1.79.4",
|
||||||
|
"vite": "^5.4.8"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
import React from "react";
|
||||||
|
import { Router, RouterProvider } from "react-router-dom";
|
||||||
|
import createRouter from "./Router";
|
||||||
|
import { createTheme, ThemeProvider } from "@mui/material";
|
||||||
|
|
||||||
|
export default function App() {
|
||||||
|
const router = createRouter();
|
||||||
|
const theme = createTheme({
|
||||||
|
palette: {
|
||||||
|
mode: 'dark'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ThemeProvider theme={theme}>
|
||||||
|
<RouterProvider router={router} />
|
||||||
|
</ThemeProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,48 @@
|
||||||
|
import React from "react";
|
||||||
|
import {
|
||||||
|
createHashRouter,
|
||||||
|
createRoutesFromElements,
|
||||||
|
Outlet,
|
||||||
|
Route
|
||||||
|
} from 'react-router-dom';
|
||||||
|
import AppHeader from "./components/AppHeader";
|
||||||
|
import { Box, useTheme } from "@mui/material";
|
||||||
|
import Landing from "./pages/Landing";
|
||||||
|
import Page from "./components/Page";
|
||||||
|
import Search from "./pages/Search";
|
||||||
|
|
||||||
|
export default function createRouter() {
|
||||||
|
const theme = useTheme();
|
||||||
|
const router = createRoutesFromElements(
|
||||||
|
<Route>
|
||||||
|
<Route
|
||||||
|
path='/'
|
||||||
|
element={
|
||||||
|
<Page>
|
||||||
|
<Outlet />
|
||||||
|
</Page>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Route
|
||||||
|
path='/'
|
||||||
|
element={
|
||||||
|
<>
|
||||||
|
<AppHeader />
|
||||||
|
<Landing />
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path='/search'
|
||||||
|
element={
|
||||||
|
<Search />
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Route>
|
||||||
|
</Route>
|
||||||
|
);
|
||||||
|
|
||||||
|
return createHashRouter(router, {
|
||||||
|
basename: '/'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
import React from "react";
|
||||||
|
import { AppBar, IconButton, InputBaseProps, Toolbar, Typography } from '@mui/material';
|
||||||
|
import { SearchContainer, SearchIcon, SearchIconWrapper, SearchInputBase } from "./Search";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
|
type AppHeaderProps = {
|
||||||
|
showSearch?: boolean;
|
||||||
|
inputProps?: InputBaseProps;
|
||||||
|
};
|
||||||
|
export default function AppHeader({ showSearch, inputProps }: AppHeaderProps) {
|
||||||
|
const navTo = useNavigate();
|
||||||
|
return (
|
||||||
|
<AppBar position='static'>
|
||||||
|
<Toolbar>
|
||||||
|
{showSearch ? (
|
||||||
|
<SearchContainer>
|
||||||
|
<SearchIconWrapper><SearchIcon /></SearchIconWrapper>
|
||||||
|
<SearchInputBase
|
||||||
|
placeholder='Search songs...'
|
||||||
|
{...inputProps}
|
||||||
|
/>
|
||||||
|
</SearchContainer>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Typography variant="h6" component='div' flexGrow={1}>
|
||||||
|
Extera Music
|
||||||
|
</Typography>
|
||||||
|
<IconButton
|
||||||
|
onClick={() => navTo('/search')}
|
||||||
|
size='large'
|
||||||
|
edge='end'
|
||||||
|
>
|
||||||
|
<SearchIcon />
|
||||||
|
</IconButton>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Toolbar>
|
||||||
|
</AppBar>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
import { Box, Skeleton, useTheme } from "@mui/material";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
export default function LoadingCard() {
|
||||||
|
const theme = useTheme();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
display='flex'
|
||||||
|
flexDirection='column'
|
||||||
|
>
|
||||||
|
<Skeleton
|
||||||
|
variant='rectangular'
|
||||||
|
width={120}
|
||||||
|
height={120}
|
||||||
|
/>
|
||||||
|
<Skeleton variant='text' width={(Math.random() * (110 - 30)) + 30} />
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function LoadingSearchSongCard() {
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
display='flex'
|
||||||
|
flexDirection='row'
|
||||||
|
gap='0.5rem'
|
||||||
|
>
|
||||||
|
<Skeleton
|
||||||
|
width={64}
|
||||||
|
height={64}
|
||||||
|
/>
|
||||||
|
<Box
|
||||||
|
display='flex'
|
||||||
|
flexDirection='column'
|
||||||
|
>
|
||||||
|
<Skeleton width={(Math.random() * (110 - 60)) + 60} variant='text' />
|
||||||
|
<Skeleton width={(Math.random() * (60 - 30)) + 30} variant='text' />
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
import { Box, useTheme } from "@mui/material";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
export default function Page(props: React.PropsWithChildren) {
|
||||||
|
const theme = useTheme();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
minHeight: '100vh',
|
||||||
|
backgroundColor: theme.palette.background.default,
|
||||||
|
color: theme.palette.text.primary
|
||||||
|
}}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function PageRoot(props: React.PropsWithChildren) {
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
import { Box } from "@mui/material";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
export default function Scroll({...props}: any) {
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
overflow: 'scroll'
|
||||||
|
}}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { styled, alpha } from '@mui/material/styles';
|
||||||
|
import InputBase from '@mui/material/InputBase';
|
||||||
|
import SearchIcon from '@mui/icons-material/Search';
|
||||||
|
|
||||||
|
const SearchContainer = styled('div')(({ theme }) => ({
|
||||||
|
position: 'relative',
|
||||||
|
borderRadius: theme.shape.borderRadius,
|
||||||
|
backgroundColor: alpha(theme.palette.common.white, 0.15),
|
||||||
|
'&:hover': {
|
||||||
|
backgroundColor: alpha(theme.palette.common.white, 0.25),
|
||||||
|
},
|
||||||
|
width: '100%',
|
||||||
|
[theme.breakpoints.up('sm')]: {
|
||||||
|
width: 'auto',
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
const SearchIconWrapper = styled('div')(({ theme }) => ({
|
||||||
|
padding: theme.spacing(0, 2),
|
||||||
|
height: '100%',
|
||||||
|
position: 'absolute',
|
||||||
|
//pointerEvents: '',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledInputBase = styled(InputBase)(({ theme }) => ({
|
||||||
|
color: 'inherit',
|
||||||
|
width: '100%',
|
||||||
|
'& .MuiInputBase-input': {
|
||||||
|
padding: theme.spacing(1, 1, 1, 0),
|
||||||
|
// vertical padding + font size from searchIcon
|
||||||
|
paddingLeft: `calc(1em + ${theme.spacing(4)})`,
|
||||||
|
transition: theme.transitions.create('width'),
|
||||||
|
width: '100%',
|
||||||
|
[theme.breakpoints.up('md')]: {
|
||||||
|
width: '20ch',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
export {
|
||||||
|
SearchContainer,
|
||||||
|
StyledInputBase as SearchInputBase,
|
||||||
|
SearchIconWrapper,
|
||||||
|
SearchIcon
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,67 @@
|
||||||
|
import { Box, Skeleton, Typography, useTheme } from "@mui/material";
|
||||||
|
import React, { MouseEventHandler } from "react";
|
||||||
|
import { LazyLoadImage } from 'react-lazy-load-image-component';
|
||||||
|
|
||||||
|
type SongCardProps = {
|
||||||
|
onClick?: MouseEventHandler;
|
||||||
|
thumbnailUrl?: string;
|
||||||
|
artist: string;
|
||||||
|
name: string;
|
||||||
|
album: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function SongCard({ onClick, thumbnailUrl, artist, name, album }: SongCardProps) {
|
||||||
|
const theme = useTheme();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
display='flex'
|
||||||
|
flexDirection='column'
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
width={120}
|
||||||
|
height={120}
|
||||||
|
src={thumbnailUrl}
|
||||||
|
/>
|
||||||
|
<Typography>
|
||||||
|
{name}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function SearchSongCard({ onClick, thumbnailUrl, artist, name, album }: SongCardProps) {
|
||||||
|
const theme = useTheme();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
display='flex'
|
||||||
|
flexDirection='row'
|
||||||
|
gap='0.5rem'
|
||||||
|
sx={{
|
||||||
|
':hover': {
|
||||||
|
backgroundColor: theme.palette.action.hover,
|
||||||
|
borderRadius: '1rem'
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onClick={onClick}
|
||||||
|
>
|
||||||
|
<LazyLoadImage
|
||||||
|
width={60}
|
||||||
|
height={60}
|
||||||
|
src={thumbnailUrl}
|
||||||
|
/>
|
||||||
|
<Box
|
||||||
|
display='flex'
|
||||||
|
flexDirection='column'
|
||||||
|
>
|
||||||
|
<Typography variant='subtitle1'>
|
||||||
|
{name}
|
||||||
|
</Typography>
|
||||||
|
<Typography variant='subtitle2'>
|
||||||
|
{artist}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
import { Box, Typography, useTheme } from "@mui/material";
|
||||||
|
import React from "react";
|
||||||
|
import LoadingCard from "../components/LoadingCard";
|
||||||
|
import Scroll from "../components/Scroll";
|
||||||
|
|
||||||
|
export default function Landing() {
|
||||||
|
const theme = useTheme();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
p: theme.spacing(1)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography variant='button'>Trending</Typography>
|
||||||
|
<Scroll display='flex' gap={theme.spacing(2)}>
|
||||||
|
<LoadingCard />
|
||||||
|
<LoadingCard />
|
||||||
|
<LoadingCard />
|
||||||
|
<LoadingCard />
|
||||||
|
<LoadingCard />
|
||||||
|
<LoadingCard />
|
||||||
|
<LoadingCard />
|
||||||
|
<LoadingCard />
|
||||||
|
<LoadingCard />
|
||||||
|
<LoadingCard />
|
||||||
|
<LoadingCard />
|
||||||
|
</Scroll>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,57 @@
|
||||||
|
import React, { KeyboardEvent, ReactNode, useRef, useState } from "react";
|
||||||
|
import { PageRoot } from "../components/Page";
|
||||||
|
import AppHeader from "../components/AppHeader";
|
||||||
|
import repeatArray from "../utils/repeatArray";
|
||||||
|
import { LoadingSearchSongCard } from "../components/LoadingCard";
|
||||||
|
import { searchSongs } from "../utils/MusicServer";
|
||||||
|
import { Box } from "@mui/material";
|
||||||
|
import { SearchSongCard } from "../components/SongCard";
|
||||||
|
|
||||||
|
export default function Search() {
|
||||||
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
|
const [results, setResults] = useState<ReactNode[]>([]);
|
||||||
|
const onKeyUp = async (evt: KeyboardEvent) => {
|
||||||
|
if (evt.key !== 'Enter') return;
|
||||||
|
const q = inputRef.current?.value;
|
||||||
|
if (typeof q !== 'string') return;
|
||||||
|
setResults(
|
||||||
|
repeatArray(<LoadingSearchSongCard />, 5)
|
||||||
|
);
|
||||||
|
const songs = await searchSongs(q);
|
||||||
|
const arr: ReactNode[] = [];
|
||||||
|
for (const song of songs) {
|
||||||
|
arr.push(
|
||||||
|
<SearchSongCard
|
||||||
|
album={song.album.name}
|
||||||
|
artist={song.artist.name}
|
||||||
|
name={song.name}
|
||||||
|
thumbnailUrl={song.thumbnails[0].url}
|
||||||
|
onClick={() => alert(song.name)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
setResults(arr);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<AppHeader
|
||||||
|
showSearch
|
||||||
|
inputProps={{
|
||||||
|
onKeyUp,
|
||||||
|
inputRef
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<PageRoot>
|
||||||
|
<Box
|
||||||
|
p='1rem'
|
||||||
|
display='flex'
|
||||||
|
flexDirection='column'
|
||||||
|
gap='0.5rem'
|
||||||
|
>
|
||||||
|
{results}
|
||||||
|
</Box>
|
||||||
|
</PageRoot>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,48 @@
|
||||||
|
type Artist = {
|
||||||
|
name: string;
|
||||||
|
artistId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type Thumbnail = {
|
||||||
|
url: string;
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
type Album = {
|
||||||
|
name: string;
|
||||||
|
albumId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type Song = {
|
||||||
|
type: 'SONG';
|
||||||
|
videoId: string;
|
||||||
|
name: string;
|
||||||
|
artist: Artist;
|
||||||
|
duration: number;
|
||||||
|
thumbnails: Thumbnail[];
|
||||||
|
album: Album;
|
||||||
|
};
|
||||||
|
|
||||||
|
async function doPost(p: string, json: any) {
|
||||||
|
const jsonString = JSON.stringify(json);
|
||||||
|
const response = await fetch(`http://localhost:33223${p}`, {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: jsonString,
|
||||||
|
method: 'POST'
|
||||||
|
});
|
||||||
|
if (!response.ok) throw await response.json();
|
||||||
|
return await response.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function doGet(p: string) {
|
||||||
|
const response = await fetch(`http://localhost:33223${p}`);
|
||||||
|
if (!response.ok) throw await response.json();
|
||||||
|
return await response.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function searchSongs(q: string): Promise<Song[]> {
|
||||||
|
return await doGet(`/music/search?q=${encodeURIComponent(q)}`);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
export default function repeatArray<T>(el: T, t: number): T[] {
|
||||||
|
const arr: T[] = [];
|
||||||
|
for (let i = 0; i < t; i ++) {
|
||||||
|
arr.push(el);
|
||||||
|
}
|
||||||
|
return arr;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
import React from "react";
|
||||||
|
import {createRoot} from 'react-dom/client';
|
||||||
|
import App from "./app/App";
|
||||||
|
|
||||||
|
import './index.scss';
|
||||||
|
|
||||||
|
const mountApp = () => {
|
||||||
|
const rootContainer = document.getElementById('root');
|
||||||
|
if (rootContainer === null) {
|
||||||
|
document.write('Root not found.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const root = createRoot(rootContainer);
|
||||||
|
|
||||||
|
root.render(<App />);
|
||||||
|
};
|
||||||
|
|
||||||
|
mountApp();
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"sourceMap": true,
|
||||||
|
"jsx": "react",
|
||||||
|
"target": "ES2016",
|
||||||
|
"module": "ES2020",
|
||||||
|
"lib": [
|
||||||
|
"ES2021",
|
||||||
|
"DOM"
|
||||||
|
],
|
||||||
|
"allowJs": true,
|
||||||
|
"strict": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"moduleResolution": "Node",
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"outDir": "dist",
|
||||||
|
"skipLibCheck": true
|
||||||
|
},
|
||||||
|
"exclude": [
|
||||||
|
"node_modules",
|
||||||
|
"dist"
|
||||||
|
],
|
||||||
|
"include": [
|
||||||
|
"src"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,57 @@
|
||||||
|
import { defineConfig } from 'vite';
|
||||||
|
import react from '@vitejs/plugin-react';
|
||||||
|
import { viteStaticCopy } from 'vite-plugin-static-copy';
|
||||||
|
import { vanillaExtractPlugin } from '@vanilla-extract/vite-plugin';
|
||||||
|
import { NodeGlobalsPolyfillPlugin } from '@esbuild-plugins/node-globals-polyfill';
|
||||||
|
import inject from '@rollup/plugin-inject';
|
||||||
|
import topLevelAwait from 'vite-plugin-top-level-await';
|
||||||
|
import buildConfig from './build.config';
|
||||||
|
|
||||||
|
const copyFiles = {
|
||||||
|
targets: [
|
||||||
|
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
appType: 'spa',
|
||||||
|
publicDir: false,
|
||||||
|
base: buildConfig.base,
|
||||||
|
server: {
|
||||||
|
port: 8080,
|
||||||
|
host: '0.0.0.0'
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
topLevelAwait({
|
||||||
|
// The export name of top-level await promise for each chunk module
|
||||||
|
promiseExportName: '__tla',
|
||||||
|
// The function to generate import names of top-level await promise in each chunk module
|
||||||
|
promiseImportName: (i) => `__tla_${i}`,
|
||||||
|
}),
|
||||||
|
viteStaticCopy(copyFiles),
|
||||||
|
vanillaExtractPlugin(),
|
||||||
|
react(),
|
||||||
|
],
|
||||||
|
optimizeDeps: {
|
||||||
|
esbuildOptions: {
|
||||||
|
define: {
|
||||||
|
global: 'globalThis',
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
// Enable esbuild polyfill plugins
|
||||||
|
NodeGlobalsPolyfillPlugin({
|
||||||
|
process: false,
|
||||||
|
buffer: true
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
build: {
|
||||||
|
outDir: 'dist',
|
||||||
|
sourcemap: true,
|
||||||
|
copyPublicDir: false,
|
||||||
|
rollupOptions: {
|
||||||
|
plugins: [inject({ Buffer: ['buffer', 'Buffer'] })],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
Loading…
Reference in New Issue