setup drone and continuous deployment on maxou.dev

pull/3/head
Override-6 2 years ago committed by maxime.batista
parent ca5b525f22
commit 44e7556cbd

4
.gitignore vendored

@ -4,6 +4,9 @@
vendor
composer.lock
*.phar
/dist
views-mappings.php
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
@ -29,4 +32,3 @@ package-lock.json
npm-debug.log*
yarn-debug.log*
yarn-error.log*
>>>>>>> 85d7cd8 (setup ts-react)

@ -0,0 +1,28 @@
# How is react used with php
This document explains how we use react and php together,
and how to create a react component that can be sent to the front end from php.
## Folder tree of the project
```tree
.
├── ci // CI/CD related directory.
├── Documentation // Documentation directory.
├── front // React code goes here.
│ ├── views // React views goes here.
│ └── assets -> // assets goes here (images, svg etc)
├── profiles // PHP server environment profiles.
├── public // index.php goes here
└── src // php code goes here
├── Controller
├── Data
└── ...
```
we'll take a view later on each folder.
## Compilation Constraint
We use typescript and react for the front, which requires to be transpiled to Javascript in order to be executed by browsers.
The fact that our `.tsx` components (our views) needs to be compiled to a js file to be executed does not allows us to dumbly refer to their file path in the php source code.
## Use of ViteJS to build our react components
We use [ViteJS](https://vitejs.dev/guide/) to build the react components.

@ -0,0 +1,47 @@
kind: pipeline
type: docker
name: "Deploy on maxou.dev"
volumes:
- name: server
temp: {}
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
- 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 server
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/

@ -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/

@ -4,7 +4,9 @@
"App\\": "src/"
}
},
"include-path": ["src"],
"require": {
"altorouter/altorouter": "1.2.0"
"altorouter/altorouter": "1.2.0",
"ext-json": "*"
}
}

@ -0,0 +1,22 @@
<?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";
/**
* The URL to prepend when accessing front-end resources,
* please prefer using `asset(url)` instead.
*/
const FRONT_URL = FRONT_URL_CONSTANT;
/**
* 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);
}

@ -1,38 +0,0 @@
.App {
text-align: center;
}
.App-logo {
height: 40vmin;
pointer-events: none;
}
@media (prefers-reduced-motion: no-preference) {
.App-logo {
animation: App-logo-spin infinite 20s linear;
}
}
.App-header {
background-color: #282c34;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
color: white;
}
.App-link {
color: #61dafb;
}
@keyframes App-logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}

@ -1,9 +0,0 @@
import React from 'react';
import { render, screen } from '@testing-library/react';
import App from './App';
test('renders learn react link', () => {
render(<App />);
const linkElement = screen.getByText(/learn react/i);
expect(linkElement).toBeInTheDocument();
});

@ -1,26 +0,0 @@
import React from 'react';
import logo from './logo.svg';
import './App.css';
function App() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.tsx</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React here! ptn de merde hahahaha ! trest qzd
</a>
</header>
</div>
);
}
export default App;

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

Before

Width:  |  Height:  |  Size: 9.4 KiB

After

Width:  |  Height:  |  Size: 9.4 KiB

@ -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;
}

@ -1,49 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<script type="module" src="<?= FRONT_ADDR ?>/@vite/client"></script>
<meta charset="utf-8" />
<link rel="icon" href="/favicon.ico" />
<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="/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/
-->
<!--
Notice the use of 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", "/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`.
-->
<title>React App</title>
</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`.
-->
<script src="index.tsx" type="module"></script>
</body>
</html>

@ -1,19 +0,0 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
);
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

@ -1 +0,0 @@
/// <reference types="react-scripts" />

@ -1,15 +0,0 @@
import { ReportHandler } from 'web-vitals';
const reportWebVitals = (onPerfEntry?: ReportHandler) => {
if (onPerfEntry && onPerfEntry instanceof Function) {
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
getCLS(onPerfEntry);
getFID(onPerfEntry);
getFCP(onPerfEntry);
getLCP(onPerfEntry);
getTTFB(onPerfEntry);
});
}
};
export default reportWebVitals;

@ -1,5 +0,0 @@
// jest-dom adds custom jest matchers for asserting on DOM nodes.
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom';

@ -0,0 +1,24 @@
import ReactDOM from "react-dom/client";
import React from "react";
function DisplayResults({username, password}: any) {
return (
<div>
<p>username: {username}</p>
<p>password: {password}</p>
</div>
)
}
export function render(args: any) {
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
);
root.render(
<React.StrictMode>
<DisplayResults username={args.name} password={args.password} />
</React.StrictMode>
);
}

@ -0,0 +1,30 @@
import React from "react";
import ReactDOM from "react-dom/client";
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="name"/>
<label>your password: </label>
<input type="password" id="password" name="password"/>
<input type="submit" value="click me!"/>
</form>
</div>
)
}
export function render(args: any) {
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
);
root.render(
<React.StrictMode>
<SampleForm />
</React.StrictMode>
);
}

@ -12,15 +12,14 @@
"@types/react-dom": "^18.2.14",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-scripts": "5.0.1",
"typescript": "^4.9.5",
"vite": "^4.5.0",
"web-vitals": "^2.1.4"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
"start": "vite --host",
"build": "vite build",
"test": "vite test"
},
"eslintConfig": {
"extends": [
@ -39,5 +38,8 @@
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"@vitejs/plugin-react": "^4.1.0"
}
}

@ -0,0 +1,8 @@
<?php
const FRONT_URL_CONSTANT = "http://localhost:5173";
function _asset(string $assetURI): string {
return FRONT_URL_CONSTANT . "/" . $assetURI;
}

@ -0,0 +1,9 @@
<?php
require "../views-mappings.php";
const FRONT_URL_CONSTANT = "/IQBall/public";
function _asset(string $assetURI): string {
return FRONT_URL_CONSTANT . "/" . (ASSETS[$assetURI] ?? $assetURI);
}

@ -1 +0,0 @@
../front

@ -3,18 +3,30 @@
require "../vendor/autoload.php";
require "../config.php";
use App\Controller\HelloPageController;
use App\Controller\SampleFormController;
// find the base path of the index.php file
$basePath = dirname(substr(__FILE__, strlen($_SERVER['DOCUMENT_ROOT'])));
if (str_ends_with($basePath, "/")) {
$basePath = substr($basePath, 0, strlen($basePath) - 1);
}
// routes initialization
$router = new AltoRouter();
// hello page controllers
$helloController = new HelloPageController();
$router->map("GET", "/", fn() => $helloController->display());
$router->setBasePath($basePath);
$sampleFormController = new SampleFormController();
$router->map("GET", "/", fn() => $sampleFormController->displayForm());
$router->map("POST", "/result", fn() => $sampleFormController->displayResults($_POST));
$match = $router->match();
if ($match == null) {
// TODO redirect to a 404 not found page instead (issue #1)
echo "page non trouvée";
header('HTTP/1.1 404 Not Found');
exit(1);
}

@ -1,25 +0,0 @@
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

@ -1,3 +0,0 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:

@ -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 "react-display.php";
class SampleFormController {
public function displayForm() {
display_react_front("views/SampleForm.tsx", []);
}
public function displayResults(array $request) {
display_react_front("views/DisplayResults.tsx", $request);
}
}

@ -2,13 +2,10 @@
<html lang="en">
<head>
<script type="module">
import RefreshRuntime from "http://localhost:5173/@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">
@ -16,6 +13,13 @@
<title>Document</title>
</head>
<body id="root">
<script src="http://localhost:5173/index.tsx" type="module"></script>
<script type="module">
import {render} from "<?= asset($url) ?>"
render(<?= json_encode($arguments) ?>)
</script>
<script>
</script>
</body>
</html>

@ -0,0 +1,12 @@
<?php
/**
* sends a react view to the user.
* @param string $url url of the react file to render
* @param array $arguments arguments to pass to the rendered react component
* @return void
*/
function display_react_front(string $url, array $arguments) {
// the $url and $argument values are used into the included file
require_once "react-display-file.php";
}

@ -21,6 +21,6 @@
"jsx": "react-jsx"
},
"include": [
"src"
"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()
]
})
Loading…
Cancel
Save