Using vite, I did a npm run build
for my mini react app.
It created a dist
folder containing index.html page with the assets folder.
But I want the <div id="root"></div>
from my index.html to be inserted into an already built website (custom traditional LAMP based without a framework) which will sit in one DIV at one corner or side of the webpage.
<script type="module" crossorigin src="/assets/index-lmRs6HEq.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-n_ryQ3BS.css">
<div id="root"></div>
I am looking for a clean build process without the need to support older methodologies since this is a new React (18/19) app.
You need to configure Vite to bundle your app and then inject the whole bundle into a PHP view.
Something like this would be a good start
vite.config.js
// IMPORTANT image urls in CSS works fine
// BUT you need to create a symlink on dev server to map this folder during dev:
// ln -s {path_to_project_source}/src/assets {path_to_public_html}/assets
// on production everything will work just fine
// (this happens because our Vite code is outside the server public access,
// if it were, we could use https://vitejs.dev/config/server-options.html#server-origin)
import path from 'node:path';
export default defineConfig(({ mode }) => {
return {
plugins: [
liveReload([
// Watch PHP files for live reloading
path.join(__dirname, '/(App|Config|Views)/**/*.php'),
path.join(__dirname, '/../public/*.php'),
]),
],
root: 'src',
base: mode === 'production' ? '/dist/' : '/',
build: {
// output dir for production build
outDir: '../../public/dist',
emptyOutDir: true,
// emit manifest so PHP can find the hashed files
manifest: true,
// our entry
rollupOptions: {
input: path.resolve(__dirname, 'src/main.ts'),
},
},
server: {
// we need a strict port to match on PHP side
// change freely, but update on PHP to match the same port
// tip: choose a different port per project to run them at the same time
strictPort: true,
port: 5133,
hmr: {
host: 'localhost',
},
},
};
});
Then you could have something like this in a controller
?php
namespace App\Controller\Home;
use \Core\View;
use \App\Config;
class Pizza extends \Core\Controller
{
public function __construct()
{
//self::$session = Api::getSession();
}
public static function indexPage()
{
$message = $_ENV['APP_ENV'];
$viteFunction = function ($path) {
// Development mode - point to the Vite dev server
if ($_ENV['APP_ENV'] === 'development') {
return [
'js' => "http://localhost:5133/" . $path,
'css' => [] // No CSS handling in development mode
];
}
// Production mode - load the manifest to get the hashed files
$manifestPath = $_SERVER['DOCUMENT_ROOT'] . '/public/dist/.vite/manifest.json';
if (!file_exists($manifestPath)) {
throw new Exception("Vite manifest not found at {$manifestPath}");
}
$manifest = json_decode(file_get_contents($manifestPath), true);
if (!isset($manifest[$path])) {
throw new Exception("Asset {$path} not found in the Vite manifest.");
}
// Get the JS file path
$jsFilePath = '/public/dist/' . $manifest[$path]['file'];
// Check if there are associated CSS files
$cssFilePaths = isset($manifest[$path]['css']) ? $manifest[$path]['css'] : [];
return [
'js' => $jsFilePath,
'css' => array_map(function ($css) {
return '/public/dist/' . $css;
}, $cssFilePaths)
];
};
View::render(
"Home/Pizza.php",
[
'message' => $message,
'vite' => $viteFunction('main.js')
]
);
}
}
Then, in a PHP view
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Home</title>
<!-- Load the CSS files (if any) -->
<?php if (isset($vite['css'])): ?>
<?php foreach ($vite['css'] as $cssFile): ?>
<link rel="stylesheet" href="<?= $cssFile ?>" />
<?php endforeach; ?>
<?php endif; ?>
</head>
<body>
<p>message >> <?= $message ?></p>
<p>Environment >> <?= $_ENV['APP_ENV']; ?></p>
<p>🍕🍕🍕</p>
<div id="app"></div>
<!-- Load the JS file -->
<script type="module" src="<?= $vite['js'] ?>"></script>
</body>
</html>
You might need to whitelist a few characters while using this approach
public static function clean(&$data) {
if (is_string($data)) {
$data = htmlspecialchars($data, ENT_QUOTES, 'UTF-8');
}
}
With this kind of configuration, you can achieve a working setup for Tailwind and use any Vue3 packages from the entire ecosystem.
App.vue
<script setup>
import { ref } from 'vue'
import TodoItem from './components/Simple.vue'
const groceryList = ref([
{ id: 0, text: 'Vegetables' },
{ id: 1, text: 'Cheese' },
{ id: 2, text: 'Whatever else humans are supposed to eat!! 💖' }
]);
</script>
<template>
<ol>
<TodoItem
v-for="item in groceryList"
:todo="item"
:key="item.id"
></TodoItem>
</ol>
</template>
Here was my main.js
import './styles/tailwind.css'
import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App)
app.mount('#app')
tailwind.config.js
export default {
content: [
"./index.html",
"./src/**/*.{js,ts,vue}",
"../App/Views/**/*.{php,html,js}",
"../public/js/**/*.js",
],
}
This is not too difficult actually because you can troubleshoot everything step by step (focus on having the JS bundle properly exported first), then you need to accomodate your PHP structure to deliver the JS file.