Switch from Create React App to Vite

This commit is contained in:
Colin Dawson 2026-02-19 20:55:13 +00:00
parent cd758b657f
commit 91ee3b8255
23 changed files with 127 additions and 299 deletions

5
.env
View File

@ -1,5 +0,0 @@
NODE_ENV=development
API_URL=http://localhost:3001/api/
EXTERNAL_LOGIN=true
CKEDITOR_LICENSE_KEY=GPL
STICKER_TEXT=Source

21
.vscode/tasks.json vendored
View File

@ -15,37 +15,34 @@
"background": {
"activeOnStart": true,
"beginsPattern": "^.*",
"endsPattern": "listening on|started|ready"
"endsPattern": "proxy"
}
}
},
{
"label": "Start CRA",
"label": "Start Vite",
"type": "process",
"command": "npm",
"args": ["start"],
"args": ["run", "dev"],
"isBackground": true,
"runOptions": {
"reevaluateOnRerun": true,
"terminateOnExit": true
},
"options": {
"env": {
"BROWSER": "none"
}
},
"problemMatcher": {
"pattern": { "regexp": ".*" },
"pattern": {
"regexp": ".*"
},
"background": {
"activeOnStart": true,
"beginsPattern": "^.*",
"endsPattern": "Compiled successfully|webpack compiled"
"beginsPattern": "vite",
"endsPattern": "ready in"
}
}
},
{
"label": "Start All",
"dependsOn": ["Start Proxy", "Start CRA"],
"dependsOn": ["Start Proxy", "Start Vite"],
"dependsOrder": "parallel"
},
{

View File

@ -1,32 +1,28 @@
#Stage 1 - the build process
# Stage 1 - Build Vite app
FROM node:latest as build-deps
WORKDIR /app
#COPY package.json .
COPY . .
RUN npm install
RUN npm run build
RUN npm run build-css
#Stage 2 - the production environment
# Stage 2 - Production environment (Nginx)
FROM nginx:latest
ENV EXTERNAL_LOGIN=true
RUN apt-get update && apt-get upgrade -y && \
apt-get install -y nodejs \
npm # note this one
WORKDIR /usr/share/nginx/html
# Copy Nginx config
COPY nginx/nginx.conf /etc/nginx/conf.d/default.conf
COPY --from=build-deps /app/build /usr/share/nginx/html
copy --from=build-deps /app/public/styles.css /usr/share/nginx/html
# copy .env.example as .env to the container
COPY .env.example .env
# Copy built Vite output
COPY --from=build-deps /app/dist /usr/share/nginx/html
RUN npm install -g cross-env
RUN npm install -g runtime-env-cra
EXPOSE 80
EXPOSE 443
CMD ["/bin/sh", "-c", "runtime-env-cra && nginx -g \"daemon off;\""]
# Create env.js at container startup
# This injects runtime environment variables into window._env_
CMD ["/bin/sh", "-c", "\
echo \"window._env_ = { \
API_URL: '${API_URL}', \
EXTERNAL_LOGIN: true, \
CKEDITOR_LICENSE_KEY: '${CKEDITOR_LICENSE_KEY}', \
STICKER_TEXT: '${STICKER_TEXT}' \
};\" > /usr/share/nginx/html/env.js && \
nginx -g 'daemon off;'"]

View File

@ -1,77 +0,0 @@
//const { styles } = require( '@ckeditor/ckeditor5-dev-utils' );
module.exports = function override(config, env) {
if (!config.plugins) {
config.plugins = [];
}
for ( const rule of config.module.rules )
{
if (rule.oneOf !== undefined) {
//loader: require.resolve('file-loader'),
rule.oneOf[2].use[1].options = {
// Exclude `js` files to keep the "css" loader working as it injects
// its runtime that would otherwise be processed through the "file" loader.
// Also exclude `html` and `json` extensions so they get processed
// by webpack's internal loaders.
exclude: [
/\.(js|mjs|jsx|ts|tsx)$/,
/\.html$/,
/\.json$/,
/ckeditor5-[^/\\]+[/\\]theme[/\\]icons[/\\][^/\\]+\.svg$/,
/ckeditor5-[^/\\]+[/\\]theme[/\\].+\.css$/
],
name: 'static/media/[name].[hash:8].[ext]',
}
//test: cssRegex,
rule.oneOf[5].exclude = [
/\.module\.css$/,
/ckeditor5-[^/\\]+[/\\]theme[/\\].+\.css$/,
];
//test: cssModuleRegex,
rule.oneOf[6].exclude = [
/ckeditor5-[^/\\]+[/\\]theme[/\\].+\.css$/,
];
//Added items
// rule.oneOf.unshift( {
// test: /ckeditor5-[^/\\]+[/\\]theme[/\\].+\.css$/,
// use: [
// {
// loader: 'style-loader',
// options: {
// injectType: 'singletonStyleTag',
// attributes: {
// 'data-cke': true
// }
// }
// },
// 'css-loader',
// {
// loader: 'postcss-loader',
// options: {
// postcssOptions: styles.getPostCssConfig( {
// themeImporter: {
// themePath: require.resolve( '@ckeditor/ckeditor5-theme-lark' )
// },
// minify: true
// } )
// }
// }
// ]
// }
// );
// rule.oneOf.unshift( {
// test: /ckeditor5-[^/\\]+[/\\]theme[/\\]icons[/\\][^/\\]+\.svg$/,
// use: [ 'raw-loader' ]
// }
// );
}
}
return config;
}

45
index.html Normal file
View File

@ -0,0 +1,45 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="/favicon.ico" />
<!-- Runtime environment variables -->
<script src="/env.js"></script>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta name="description" content="e-suite application" />
<link rel="apple-touch-icon" href="/logo192.png" />
<link rel="manifest" href="/manifest.json" />
<link rel="stylesheet" href="/styles.css" />
<title>e-suite</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validate/1.19.3/jquery.validate.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validation-unobtrusive/3.2.12/jquery.validate.unobtrusive.js"></script>
<style>
html,
body {
background: #fff;
color-scheme: light dark;
}
@media (prefers-color-scheme: dark) {
html,
body {
background: #212529;
}
}
</style>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

View File

@ -44,7 +44,6 @@
"react-helmet-async": "^2.0.5",
"react-i18next": "^16.5.4",
"react-router-dom": "^7.13.0",
"react-scripts": "^5.0.1",
"react-toastify": "^11.0.5",
"react-toggle": "^4.1.3",
"runtime-env-cra": "^0.2.4",
@ -54,13 +53,10 @@
"scripts": {
"build-css": "sass src/Sass/global.scss public/styles.css",
"watch-css": "nodemon -e scss -x \"npm run build-css\" ",
"start-react": "cross-env NODE_ENV=development runtime-env-cra --config-name=./public/runtime-env.js && react-app-rewired start",
"prestart": "node scripts/generate-locales.js",
"start": "concurrently \"npm run start-react\" \"npm run watch-css\" ",
"prebuild": "node scripts/generate-locales.js",
"build": "react-app-rewired build",
"test": "react-app-rewired test",
"eject": "react-scripts eject",
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"i18n:extract": "i18next \"src/**/*.{ts,tsx,js,jsx}\" \"!src/components/common/ckeditor/**\" --config i18next-parser.config.js",
"i18n:unused": "i18n-unused display-unused",
"i18n:missed": "i18n-unused display-missed",
@ -87,9 +83,10 @@
"devDependencies": {
"@types/node": "^22.13.5",
"@types/react-toggle": "^4.0.5",
"@vitejs/plugin-react": "^5.1.4",
"i18n-unused": "^0.19.0",
"i18next-parser": "^9.3.0",
"react-app-rewired": "^2.2.1",
"typescript": "^4.9.5"
"typescript": "^5.9.3",
"vite": "^7.3.1"
}
}

6
public/env.js Normal file
View File

@ -0,0 +1,6 @@
window._env_ = {
API_URL: "http://localhost:3001/api/",
EXTERNAL_LOGIN: true,
CKEDITOR_LICENSE_KEY: "GPL",
STICKER_TEXT: "Source",
};

View File

@ -1,67 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<!-- Runtime environment variables -->
<script src="%PUBLIC_URL%/runtime-env.js"></script>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<link rel="stylesheet" href="/styles.css" />
<title>e-suite</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validate/1.19.3/jquery.validate.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validation-unobtrusive/3.2.12/jquery.validate.unobtrusive.js"></script>
<style>
html,
body {
background: #fff;
color-scheme: light dark;
}
@media (prefers-color-scheme: dark) {
html,
body {
background: #212529;
}
}
</style>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>

View File

@ -1 +0,0 @@
window.__RUNTIME_CONFIG__ = {"NODE_ENV":"development","API_URL":"http://localhost:3001/api/","EXTERNAL_LOGIN":"true","CKEDITOR_LICENSE_KEY":"GPL","STICKER_TEXT":"Source"};

View File

@ -2,6 +2,8 @@ import { useState, useEffect, useRef } from "react";
import { CKEditor } from "@ckeditor/ckeditor5-react";
import Field from "./plugins/field/field";
import { CKEDITOR_LICENSE_KEY } from "../../../environment";
import {
DecoupledEditor,
AccessibilityHelp,
@ -198,7 +200,7 @@ export default function TextEditor(props) {
customfields = props.customFields;
}
const licenseKey = window.__RUNTIME_CONFIG__?.CKEDITOR_LICENSE_KEY || "GPL";
const licenseKey = CKEDITOR_LICENSE_KEY || "GPL";
const editorConfig = {
licenseKey: licenseKey,

4
src/environment.ts Normal file
View File

@ -0,0 +1,4 @@
export const API_URL = window._env_.API_URL;
export const EXTERNAL_LOGIN = window._env_.EXTERNAL_LOGIN;
export const CKEDITOR_LICENSE_KEY = window._env_.CKEDITOR_LICENSE_KEY;
export const STICKER_TEXT = window._env_.STICKER_TEXT;

View File

@ -10,22 +10,8 @@ export const availableLocales = [
"fr-CA",
"fr-FR",
"hi-IN",
"ur-AE",
"ur-AF",
"ur-AU",
"ur-BH",
"ur-CA",
"ur-GB",
"ur-IN",
"ur-KW",
"ur-OM",
"ur-PK",
"ur-QA",
"ur-SA",
"ur-TJ",
"ur-US",
"ur-UZ",
"ur-ZA"
"ur-PK"
] as const;
export const baseLocales = [
@ -67,69 +53,13 @@ export const fallbackLng = {
"hi-IN": [
"en"
],
"ur-AE": [
"ur",
"en"
],
"ur-AF": [
"ur",
"en"
],
"ur-AU": [
"ur",
"en"
],
"ur-BH": [
"ur",
"en"
],
"ur-CA": [
"ur",
"en"
],
"ur-GB": [
"ur",
"en"
],
"ur-IN": [
"ur",
"en"
],
"ur-KW": [
"ur",
"en"
],
"ur-OM": [
"ur",
"en"
],
"ur-PK": [
"ur",
"en"
],
"ur-QA": [
"ur",
"en"
],
"ur-SA": [
"ur",
"en"
],
"ur-TJ": [
"ur",
"en"
],
"ur-US": [
"ur",
"en"
],
"ur-UZ": [
"ur",
"en"
],
"ur-ZA": [
"ur",
"en"
]
} as const;

View File

@ -1,13 +0,0 @@
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3"><g fill="#61DAFB"><path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/><circle cx="420.9" cy="296.5" r="45.7"/><path d="M520.5 78.1z"/></g></svg>

Before

Width:  |  Height:  |  Size: 2.6 KiB

View File

@ -1,4 +1,4 @@
import "./i18n/i18n";
//import "./i18n/i18n";
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";

View File

@ -1,8 +1,9 @@
import { EXTERNAL_LOGIN } from "../../../environment";
import ExternalLoginForm from "./ExternalLoginForm";
import InternalLoginForm from "./InternalLoginForm";
const LoginForm: React.FC = () => {
if (window.__RUNTIME_CONFIG__.EXTERNAL_LOGIN) {
if (EXTERNAL_LOGIN) {
return (
<>
<ExternalLoginForm url="/account/login" />

View File

@ -2,13 +2,14 @@ import React, { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { Namespaces } from "../../../i18n/i18n";
import authentication from "../services/authenticationService";
import { EXTERNAL_LOGIN } from "../../../environment";
const Logout: React.FC = () => {
const { t } = useTranslation(Namespaces.Common);
useEffect(() => {
authentication.logout();
if (window.__RUNTIME_CONFIG__.EXTERNAL_LOGIN) {
if (EXTERNAL_LOGIN) {
window.location.href = "/account/logout";
} else {
window.location.href = "/";

View File

@ -1,14 +1,14 @@
import { STICKER_TEXT } from "../../../environment";
const isBlank = (value: string | null | undefined) =>
!value || value.trim().length === 0;
const Sticker: React.FC = () => {
if (isBlank(window.__RUNTIME_CONFIG__.STICKER_TEXT)) {
if (isBlank(STICKER_TEXT)) {
return null;
}
return (
<span className="sticker">{window.__RUNTIME_CONFIG__.STICKER_TEXT}</span>
);
return <span className="sticker">{STICKER_TEXT}</span>;
};
export default Sticker;

View File

@ -3,6 +3,7 @@ import Cookies from "js-cookie";
import httpService from "../../../services/httpService";
import { IEmailUserAction } from "../models/IEmailUserAction";
import JwtToken from "../models/JwtToken";
import { EXTERNAL_LOGIN } from "../../../environment";
const apiEndpoint = "/Authentication";
//const tokenKey = "token";
@ -37,7 +38,7 @@ async function refreshToken() {
const fiveMinutesFromNow: Date = new Date(Date.now() + 5 * 60 * 1000);
if (currentUser.expiry < fiveMinutesFromNow) {
const refreshTokenRoute = window.__RUNTIME_CONFIG__.EXTERNAL_LOGIN
const refreshTokenRoute = EXTERNAL_LOGIN
? "/../account/refreshToken"
: apiEndpoint + "/refreshToken";

View File

@ -1,4 +1,10 @@
import React from "react";
import {
API_URL,
CKEDITOR_LICENSE_KEY,
EXTERNAL_LOGIN,
STICKER_TEXT,
} from "../../environment";
const EnvPage: React.FC = () => {
return (
@ -9,23 +15,16 @@ const EnvPage: React.FC = () => {
window.__RUNTIME_CONFIG__.NODE_ENV ={" "}
{window.__RUNTIME_CONFIG__.NODE_ENV}
</p>
<p>
window.__RUNTIME_CONFIG__.API_URL = {window.__RUNTIME_CONFIG__.API_URL}
</p>
<p>window.__RUNTIME_CONFIG__.API_URL = {API_URL}</p>
<p>
window.__RUNTIME_CONFIG__.EXTERNAL_LOGIN ={" "}
{window.__RUNTIME_CONFIG__.EXTERNAL_LOGIN ? "true" : "false"}
{EXTERNAL_LOGIN ? "true" : "false"}
</p>
<p>
window.__RUNTIME_CONFIG__?.CKEDITOR_LICENSE_KEY ={" "}
{window.__RUNTIME_CONFIG__?.CKEDITOR_LICENSE_KEY === "GPL"
? "GPL"
: "hidden"}
</p>
<p>
window.__RUNTIME_CONFIG__.STICKER_TEXT ={" "}
{window.__RUNTIME_CONFIG__.STICKER_TEXT}
{CKEDITOR_LICENSE_KEY === "GPL" ? "GPL" : "hidden"}
</p>
<p>window.__RUNTIME_CONFIG__.STICKER_TEXT = {STICKER_TEXT}</p>
</>
);
};

View File

@ -6,6 +6,7 @@ import axios, {
} from "axios";
import { isValid, parseISO } from "date-fns";
import { toast } from "react-toastify";
import { API_URL } from "../environment";
Object.defineProperty(BigInt.prototype, "toJSON", {
get() {
@ -55,7 +56,7 @@ export function setupInterceptorsTo(
return axiosInstance;
}
axios.defaults.baseURL = window.__RUNTIME_CONFIG__.API_URL;
axios.defaults.baseURL = API_URL; // window.__RUNTIME_CONFIG__.API_URL;
setupInterceptorsTo(axios);
export function setJwt(jwt: string | null) {

View File

@ -4,8 +4,8 @@ Write-Host "Stopping development tasks..."
# Find and stop processes by command line
$processes = Get-WmiObject Win32_Process | Where-Object {
($_.CommandLine -like '*proxy.cmd*') -or
($_.CommandLine -like '*npm*start*') -or
($_.CommandLine -like '*react-scripts*')
($_.CommandLine -like '*npm*run*dev*') -or
($_.CommandLine -like '*vite*')
}
foreach ($proc in $processes) {

12
vite.config.ts Normal file
View File

@ -0,0 +1,12 @@
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
export default defineConfig({
plugins: [react()],
server: {
port: 3000,
strictPort: true,
host: true,
allowedHosts: ["host.docker.internal"],
},
});