Add React Support #3
setup-typescript-react
into master
1 year ago
@ -1,4 +1,34 @@
|
||||
.idea
|
||||
.vs
|
||||
.code
|
||||
vendor
|
||||
composer.lock
|
||||
*.phar
|
||||
/dist
|
||||
|
||||
views-mappings.php
|
||||
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
package-lock.json
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 70 KiB |
After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 19 KiB |
After Width: | Height: | Size: 53 KiB |
After Width: | Height: | Size: 48 KiB |
After Width: | Height: | Size: 84 KiB |
@ -0,0 +1,150 @@
|
||||
This documentation file explains how to start a development server on your
|
||||
machine, and how it works under the hood.
|
||||
|
||||
# How to run the project on my local computer
|
||||
|
||||
|
||||
1) Use phpstorm to run a local php server:
|
||||
* Go to configuration > add new configuration
|
||||
* Select "PHP Built-in Web Server", then enter options as follow:
|
||||

|
||||
- port 8080
|
||||
- name the configuration "RunServer" to be more explicit
|
||||
- place the "Document Root" in `/public`
|
||||
- host is localhost
|
||||
* Click apply, OK
|
||||
* Now run it.
|
||||
|
||||
If you go to `http://localhost:8080` you'll see a blank page.
|
||||
This is expected ! On your browser, open inspection tab (ctrl+shift+i) go to network and refresh the page.
|
||||

|
||||
|
||||
We can see that the browser requested the `SampleForm.tsx` react view (located in `/front/views`), but the server could not find it.
|
||||
But, on what server was it requested ?
|
||||
Remember that the `localhost:8080` is a _php_ server, and thus not able to handle requests about our react / typescript files.
|
||||
If we take a look at the request, we'll see that the url does not targets `localhost:8080`, but `localhost:5173`.
|
||||
|
||||

|
||||
|
||||
`localhost:5173` is the react development server, it is able to serve our react front view files.
|
||||
Let's run the react development server.
|
||||
It is a simple as running `npm start` in a new terminal (be sure to run it in the repository's directory).
|
||||

|
||||
|
||||
You should see something like this, it says that the server was opened on port `5173`, thats our react development server !
|
||||
Now refresh your page, you should now see all request being fulfilled and a form appearing !
|
||||
|
||||

|
||||
|
||||
Caution: **NEVER** directly connect on the `localhost:5173` node development server, always pass through the php (`localhost:8080`) server.
|
||||
|
||||
# How it works
|
||||
I'm glad you are interested in how that stuff works, it's a bit tricky, lets go.
|
||||
If you look at our `index.php` (located in `/public` folder), you'll see that it is our gateway, it uses an `AltoRouter` that dispatches the request's process to a controller.
|
||||
We can see that there are two registered routes, the `GET:/` (that then calls `SampleFormController#displayForm()`) and `POST:/result` (that calls `SampleFormController#displayResults()`).
|
||||
Implementation of those two methods are very simple: there is no verification to make nor model to control, thus they directly sends the view back to the client.
|
||||
|
||||
here's the implementation of the `SampleFormController`
|
||||
```php
|
||||
require_once "react-display.php";
|
||||
class SampleFormController {
|
||||
public function displayForm() {
|
||||
send_react_front("views/SampleForm.tsx", []);
|
||||
}
|
||||
|
||||
public function displayResults(array $request) {
|
||||
send_react_front("views/DisplayResults.tsx", $request);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
As our views are now done using react (and defined under the `front/views` folder), we need to use the `send_react_front($viewURI, $viewArguments)` php function (located in the `src/react-render.php` file) to render a react view.
|
||||
|
||||
If you look at the `send_react_front($viewURI, $viewArguments)` function, you'll see that is simply loads the file `src/react-display-file.php` with given arguments.
|
||||
The file is a simple html5 template with a `<script>` block in the `<body>` section.
|
||||
The script block imports the requested view and will render it.
|
||||
The view entry is a function, named in PascalCase, which __must__ be be exported by default (`export default function MyReactView(args: {..})`).
|
||||
|
||||
```html
|
||||
<!--
|
||||
here's the magic.
|
||||
imports the given view URL, and assume that the view exports a function named `Component`.
|
||||
see ViewRenderer.tsx::renderView for more info
|
||||
-->
|
||||
<script type="module">
|
||||
import {renderView} from "<?= asset("ViewRenderer.tsx") ?>"
|
||||
import Component from "<?= asset($url) ?>"
|
||||
renderView(Component, <?= json_encode($arguments) ?>)
|
||||
</script>
|
||||
```
|
||||
|
||||
here's how it renders if you do a request to `http://localhost:8080/`.
|
||||
|
||||

|
||||
The index.php's router says that for a `GET` on the `/` url, we call the `SampleFormController#displayForm` method.
|
||||
This method then uses the `send_react_front`, to render the `views/SampleForm.tsx` react element, with no arguments (an empty array).
|
||||
The view file **must export by default its react function component**.
|
||||
|
||||
## Server Profiles
|
||||
If you go on the staging server, you'll see that, for the exact same request equivalent, the generated `src/render-display-file` file changes :
|
||||

|
||||
(we can also see that much less files are downloaded than with our localhost aka development server).
|
||||
|
||||
Remember that react components and typescript files needs to be transpiled to javascript before being executable by a browser.
|
||||
The generated file no longer requests the view to a `localhost:5173` or a `maxou.dev:5173` server,
|
||||
now our react components are directly served by the same server, as they have been pre-compiled by our CI (see `/ci/.drone.yml` and `/ci/build_react.msh`) into valid js files that can directly be send to the browser.
|
||||
If you go back to our `index.php` file, you'll see that it requires a `../config.php` file, if you open it,
|
||||
you'll see that it defines the `asset(string $uri)` function that is used by the `src/react-display-file.php`,
|
||||
in the `<script>` block we talked earlier.
|
||||
|
||||
By default, the `/config.php` file uses the `dev-config-profile.php` profile,
|
||||
the file is replaced with `prod-config-file.php` by the CI when deploying to the staging server (see the pipeline "prepare php" step in `/ci/.drone.yml`)
|
||||
|
||||
The two profiles declares an `_asset(string $uri)` function, used by the `/config.php::asset` method, but with different implementations :
|
||||
### Development profile
|
||||
```php
|
||||
$hostname = getHostName();
|
||||
$front_url = "http://$hostname:5173";
|
||||
|
||||
function _asset(string $assetURI): string {
|
||||
global $front_url;
|
||||
return $front_url . "/" . $assetURI;
|
||||
}
|
||||
|
||||
|
||||
```
|
||||
The simplest profile, simply redirect all assets to the development server
|
||||
|
||||
### Production profile
|
||||
Before the CD deploys the generated files to the server,
|
||||
it generates a `/views-mappings.php` file that will map the react views file names to their compiled javascript files :
|
||||
|
||||
```php
|
||||
const ASSETS = [
|
||||
// react / typescript path (relative to /front) => its compiled js file name.
|
||||
'views/SampleForm.tsx' => 'front/views/SampleForm.tsx-82fdeb9a.js',
|
||||
'views/DisplayResults.tsx' => 'front/views/DisplayResults.tsx-ed098cf4.js',
|
||||
... // other files that does not have to be directly used by the `send_react_front()` function
|
||||
];
|
||||
```
|
||||
The `_asset` function will then get the right javascript for the given typescript file.
|
||||
```php
|
||||
require "../views-mappings.php";
|
||||
|
||||
function _asset(string $assetURI): string {
|
||||
// use index.php's base path
|
||||
global $basePath;
|
||||
// If the asset uri does not figure in the available assets array,
|
||||
// fallback to the uri itself.
|
||||
return $basePath . "/" . (ASSETS[$assetURI] ?? $assetURI);
|
||||
}
|
||||
```
|
||||
|
||||
## React views conventions.
|
||||
Conventions regarding our react views __must be respected in order to be renderable__.
|
||||
|
||||
### The `render(any)` function
|
||||
Any React view component needs to be default exported in order to be imported and used from PHP. Those components will receive as props the arguments that the PHP server has transmitted.
|
||||
The `arguments` parameter is used to pass data to the react component.
|
||||
|
||||
If you take a look at the `front/views/SampleForm.tsx` view, here's the definition of its render function :
|
@ -0,0 +1,52 @@
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: "Deploy on maxou.dev"
|
||||
|
||||
volumes:
|
||||
- name: server
|
||||
temp: {}
|
||||
|
||||
trigger:
|
||||
event:
|
||||
- push
|
||||
|
||||
steps:
|
||||
- image: node:latest
|
||||
name: "build node"
|
||||
volumes: &outputs
|
||||
- name: server
|
||||
path: /outputs
|
||||
commands:
|
||||
- curl -L moshell.dev/setup.sh > /tmp/moshell_setup.sh
|
||||
- chmod +x /tmp/moshell_setup.sh
|
||||
- echo n | /tmp/moshell_setup.sh
|
||||
|
||||
- /root/.local/bin/moshell ci/build_react.msh
|
||||
|
||||
- image: composer:latest
|
||||
name: "prepare php"
|
||||
volumes: *outputs
|
||||
commands:
|
||||
- mkdir -p /outputs/public
|
||||
# this sed command will replace the included `profile/dev-config-profile.php` to `profile/prod-config-file.php` in the config.php file.
|
||||
- sed -iE 's/\\/\\*PROFILE_FILE\\*\\/\\s*".*"/"profiles\\/prod-config-profile.php"/' config.php
|
||||
- composer install && composer update
|
||||
- rm profiles/dev-config-profile.php
|
||||
- mv src config.php profiles vendor /outputs/
|
||||
|
||||
- image: eeacms/rsync:latest
|
||||
name: Deliver on staging server branch
|
||||
depends_on:
|
||||
- "prepare php"
|
||||
- "build node"
|
||||
volumes: *outputs
|
||||
environment:
|
||||
SERVER_PRIVATE_KEY:
|
||||
from_secret: SERVER_PRIVATE_KEY
|
||||
commands:
|
||||
- mkdir ~/.ssh
|
||||
- echo "$SERVER_PRIVATE_KEY" > ~/.ssh/id_rsa
|
||||
- chmod 0600 ~/.ssh
|
||||
- chmod 0500 ~/.ssh/id_rsa*
|
||||
- rsync -avz -e "ssh -p 80 -o 'StrictHostKeyChecking=no'" --delete /outputs/* iqball@maxou.dev:/server/nginx/IQBall/$DRONE_BRANCH
|
||||
|
@ -0,0 +1,31 @@
|
||||
#!/usr/bin/env moshell
|
||||
|
||||
npm build react
|
||||
mkdir -p /outputs/public
|
||||
|
||||
apt update && apt install jq -y
|
||||
npm install
|
||||
npm run build -- --base=/IQBall/public
|
||||
|
||||
// Read generated mappings from build
|
||||
val result = $(jq -r 'to_entries|map(.key + " " +.value.file)|.[]' dist/manifest.json)
|
||||
val mappings = $result.split('\n')
|
||||
|
||||
|
||||
echo '<?php\nconst ASSETS = [' > views-mappings.php
|
||||
|
||||
while $mappings.len() > 0 {
|
||||
val mapping = $mappings.pop().unwrap();
|
||||
val mapping = $mapping.split(' ');
|
||||
val source_file = $mapping[0]
|
||||
val build_file = $mapping[1]
|
||||
echo "\t'$source_file' => '$build_file'," >> views-mappings.php
|
||||
}
|
||||
|
||||
echo "];" >> views-mappings.php
|
||||
|
||||
chmod +r views-mappings.php
|
||||
|
||||
// moshell does not supports file patterns
|
||||
bash <<< "mv dist/* public/* front/assets/ /outputs/public/"
|
||||
mv views-mappings.php /outputs/
|
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
// `dev-config-profile.php` by default.
|
||||
// on production server the included profile is `prod-config-profile.php`.
|
||||
// Please do not touch.
|
||||
require /*PROFILE_FILE*/ "profiles/dev-config-profile.php";
|
||||
|
||||
CONST SUPPORTS_FAST_REFRESH = _SUPPORTS_FAST_REFRESH;
|
||||
|
||||
/**
|
||||
* Maps the given relative source uri (relative to the `/front` folder) to its actual location depending on imported profile.
|
||||
* @param string $assetURI relative uri path from `/front` folder
|
||||
* @return string valid url that points to the given uri
|
||||
*/
|
||||
function asset(string $assetURI): string {
|
||||
return _asset($assetURI);
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,19 @@
|
||||
import ReactDOM from "react-dom/client";
|
||||
import React, {FunctionComponent} from "react";
|
||||
|
||||
/**
|
||||
* Dynamically renders a React component, with given arguments
|
||||
* @param Component the react component to render
|
||||
* @param args the arguments to pass to the react component.
|
||||
*/
|
||||
export function renderView(Component: FunctionComponent, args: {}) {
|
||||
const root = ReactDOM.createRoot(
|
||||
document.getElementById('root') as HTMLElement
|
||||
);
|
||||
|
||||
root.render(
|
||||
<React.StrictMode>
|
||||
<Component {...args}/>
|
||||
</React.StrictMode>
|
||||
);
|
||||
}
|
After Width: | Height: | Size: 3.8 KiB |
After Width: | Height: | Size: 2.6 KiB |
After Width: | Height: | Size: 5.2 KiB |
After Width: | Height: | Size: 9.4 KiB |
@ -0,0 +1,12 @@
|
||||
import ReactDOM from "react-dom/client";
|
||||
import React from "react";
|
||||
|
||||
|
||||
export default function DisplayResults({password, username}: any) {
|
||||
return (
|
||||
<div>
|
||||
<p>username: {username}</p>
|
||||
<p>password: {password}</p>
|
||||
</div>
|
||||
)
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
|
||||
export default function SampleForm() {
|
||||
return (
|
||||
<div>
|
||||
<h1>Hello, this is a sample form made in react !</h1>
|
||||
<form action="result" method="POST">
|
||||
<label>your name: </label>
|
||||
<input type="text" id="name" name="username"/>
|
||||
<label>your password: </label>
|
||||
<input type="password" id="password" name="password"/>
|
||||
<input type="submit" value="click me!"/>
|
||||
</form>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
|
@ -1,22 +0,0 @@
|
||||
<?php
|
||||
|
||||
global $router;
|
||||
require "./vendor/autoload.php";
|
||||
|
||||
use App\Controller\HelloPageController;
|
||||
|
||||
// routes initialization
|
||||
$router = new AltoRouter();
|
||||
// hello page controllers
|
||||
$helloController = new HelloPageController();
|
||||
$router->map("GET", "/", fn() => $helloController->display());
|
||||
|
||||
$match = $router->match();
|
||||
|
||||
if ($match == null) {
|
||||
// TODO redirect to a 404 not found page instead (issue #1)
|
||||
header('HTTP/1.1 404 Not Found');
|
||||
exit(1);
|
||||
}
|
||||
|
||||
call_user_func($match['target']);
|
@ -0,0 +1,33 @@
|
||||
{
|
||||
"name": "iqball_web",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@testing-library/jest-dom": "^5.17.0",
|
||||
"@testing-library/react": "^13.4.0",
|
||||
"@testing-library/user-event": "^13.5.0",
|
||||
"@types/jest": "^27.5.2",
|
||||
"@types/node": "^16.18.59",
|
||||
"@types/react": "^18.2.31",
|
||||
"@types/react-dom": "^18.2.14",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"typescript": "^5.2.2",
|
||||
"vite": "^4.5.0",
|
||||
"web-vitals": "^2.1.4"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "vite --host",
|
||||
"build": "vite build",
|
||||
"test": "vite test"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
"react-app",
|
||||
"react-app/jest"
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-react": "^4.1.0"
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
$hostname = getHostName();
|
||||
$front_url = "http://$hostname:5173";
|
||||
|
||||
const _SUPPORTS_FAST_REFRESH = true;
|
||||
|
||||
function _asset(string $assetURI): string {
|
||||
global $front_url;
|
||||
return $front_url . "/" . $assetURI;
|
||||
}
|
||||
|
@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
// This file only exists on production servers, and defines the available assets mappings
|
||||
// in an `ASSETS` array constant.
|
||||
require "../views-mappings.php";
|
||||
|
||||
const _SUPPORTS_FAST_REFRESH = false;
|
||||
|
||||
|
||||
function _asset(string $assetURI): string {
|
||||
// use index.php's base path
|
||||
global $basePath;
|
||||
// If the asset uri does not figure in the available assets array,
|
||||
// fallback to the uri itself.
|
||||
return $basePath . "/" . (ASSETS[$assetURI] ?? $assetURI);
|
||||
}
|
@ -0,0 +1,42 @@
|
|||||||
<?php
|
|||||||
|
|||||||
require "../vendor/autoload.php";
|
|||||||
require "../config.php";
|
|||||||
|
|||||||
use App\Controller\SampleFormController;
|
|||||||
|
|||||||
/**
|
|||||||
* relative path of the index.php's directory from the server's document root.
|
|||||||
*/
|
|||||||
function get_base_path() {
|
|||||||
// find the server path of the index.php file
|
|||||||
$basePath = dirname(substr(__FILE__, strlen($_SERVER['DOCUMENT_ROOT'])));
|
|||||||
|
|||||||
$c = $basePath[strlen($basePath) - 1];
|
|||||||
|
|||||||
if ($c == "/" || $c == "\\") {
|
|||||||
$basePath = substr($basePath, 0, strlen($basePath) - 1);
|
|||||||
}
|
|||||||
return $basePath;
|
|||||||
}
|
|||||||
|
|||||||
$basePath = get_base_path();
|
|||||||
|
|||||||
// routes initialization
|
|||||||
$router = new AltoRouter();
|
|||||||
$router->setBasePath($basePath);
|
|||||||
|
|||||||
$sampleFormController = new SampleFormController();
|
|||||||
$router->map("GET", "/", fn() => $sampleFormController->displayForm());
|
|||||||
$router->map("POST", "/result", fn() => $sampleFormController->displayResults($_POST));
|
|||||||
|
|||||||
maxime.batista marked this conversation as resolved
|
|||||||
$match = $router->match();
|
|||||||
|
|||||||
if ($match == null) {
|
|||||||
// TODO redirect to a 404 not found page instead (issue #1)
|
|||||||
http_response_code(404);
|
|||||||
echo "Page non trouvée";
|
|||||||
exit(1);
|
|||||||
}
|
|||||||
|
|||||||
call_user_func($match['target']);
|
@ -1,10 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
|
||||
class HelloPageController {
|
||||
public function display() {
|
||||
require_once "src/View/hello.html";
|
||||
}
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
require_once __DIR__ . "/../react-display.php";
|
||||
|
||||
class SampleFormController {
|
||||
public function displayForm() {
|
||||
send_react_front("views/SampleForm.tsx", []);
|
||||
}
|
||||
|
||||
public function displayResults(array $request) {
|
||||
send_react_front("views/DisplayResults.tsx", $request);
|
||||
}
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport"
|
||||
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<title>Document</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<h1>Hello</h1>
|
||||
<h3>World</h3>
|
||||
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,46 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<script type="module">
|
||||
<?php
|
||||
if (SUPPORTS_FAST_REFRESH) {
|
||||
$asset_server = asset("");
|
||||
echo "
|
||||
import RefreshRuntime from '{$asset_server}front/@react-refresh'
|
||||
RefreshRuntime.injectIntoGlobalHook(window)
|
||||
window.\$RefreshReg$ = () => {}
|
||||
window.\$RefreshSig$ = () => (type) => type
|
||||
window.__vite_plugin_react_preamble_installed__ = true
|
||||
";
|
||||
}
|
||||
?>
|
||||
|
||||
</script>
|
||||
|
||||
<link rel="icon" href="<?= asset("assets/favicon.ico") ?>">
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport"
|
||||
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<title>Document</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div id="root"></div>
|
||||
|
||||
<!--
|
||||
here's the magic.
|
||||
imports the given view URL, and assume that the view exports a function named `Component`.
|
||||
see ViewRenderer.tsx::renderView for more info
|
||||
-->
|
||||
<script type="module">
|
||||
import {renderView} from "<?= asset("ViewRenderer.tsx") ?>"
|
||||
import Component from "<?= asset($url) ?>"
|
||||
renderView(Component, <?= json_encode($arguments) ?>)
|
||||
</script>
|
||||
|
||||
<script>
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* sends a react view to the user client.
|
||||
* @param string $url url of the react file to render
|
||||
* @param array $arguments arguments to pass to the rendered react component
|
||||
* The arguments must be a json-encodable key/value dictionary.
|
||||
* @return void
|
||||
*/
|
||||
function send_react_front(string $url, array $arguments) {
|
||||
// the $url and $argument values are used into the included file
|
||||
require_once "react-display-file.php";
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es2021",
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"esnext"
|
||||
],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx"
|
||||
},
|
||||
"include": [
|
||||
"front"
|
||||
]
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
import {defineConfig} from "vite";
|
||||
import react from '@vitejs/plugin-react'
|
||||
import fs from "fs";
|
||||
|
||||
|
||||
function resolve_entries(dirname: string): [string, string][] {
|
||||
|
||||
//exclude assets
|
||||
if (dirname == "front/assets") {
|
||||
return []
|
||||
}
|
||||
|
||||
return fs.readdirSync(dirname).flatMap(file_name => {
|
||||
if (fs.lstatSync(`${dirname}/${file_name}`).isFile()) {
|
||||
return [[`${dirname}/${file_name}`, `${dirname}/${file_name}`]]
|
||||
} else {
|
||||
return resolve_entries(`${dirname}/${file_name}`)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export default defineConfig({
|
||||
root: 'front',
|
||||
base: '/front',
|
||||
build: {
|
||||
target: 'es2021',
|
||||
assetsDir: '',
|
||||
outDir: "../dist",
|
||||
manifest: true,
|
||||
rollupOptions: {
|
||||
input: Object.fromEntries(resolve_entries("front")),
|
||||
preserveEntrySignatures: "allow-extension"
|
||||
}
|
||||
},
|
||||
plugins: [
|
||||
react()
|
||||
]
|
||||
})
|
Headers must be sent before any content.