kanak09 wrote a reply+100 XP
5mos ago
Thanks all for your help.
@vincent15000 My vite file was included in my first message but the formatting is destroyed and doesnt format the whole file... Here it is again
START VITE.CONFIG.JS FILE
import vue from "@vitejs/plugin-vue"; import laravel from "laravel-vite-plugin"; import path from "path"; import { defineConfig, type UserConfig } from "vite"; import compression from "vite-plugin-compression"; import purge from "@erbelion/vite-plugin-laravel-purgecss"; import { laravelTranslationsPlugin } from "./vite/translation"; import { fixCssReferences } from "./vite/fixCSSReferences"; import { wayfinder } from "@laravel/vite-plugin-wayfinder";
const isProd = process.env.NODE_ENV === "production"; const isClever = process.env.PROD_ENV === "clevercloud";
return {
server: {
host: "0.0.0.0", // important for Docker
port: 5173,
strictPort: true,
hmr: {
host: "localhost", // or your local IP for dev
},
},
plugins: [
purge({
paths: [
"resources/{js,views}/**/*.{blade.php,svelte,vue,html,ts}",
],
safelist: {
// Preserve all scoped component styles (data-v-* attributes)
deep: [/data-v-.*$/],
// Add specific component classes that might be purged
standard: [
"password-toggle-btn",
"password-input-container",
"password-rules",
"rule-indicator",
"bullet",
],
greedy: [
/^password-/, // Preserve all password-related classes
/\[data-v-/, // Preserve all scoped styles
],
},
}),
laravel({
input: ["resources/js/app.ts"],
refresh: true,
}),
vue({
template: {
transformAssetUrls: {
base: null,
includeAbsolute: false,
},
},
}),
wayfinder(),
laravelTranslationsPlugin(),
fixCssReferences(), // Fix CSS references in manifest to use hashed filenames
// Compression gzip/brotli uniquement si pas sur Clever Cloud
isProd &&
!isClever &&
compression({
algorithm: "gzip",
compressionOptions: { level: 9 },
ext: ".gz",
threshold: 10240, // Only compress files larger than 10KB
}),
isProd &&
!isClever &&
compression({
algorithm: "brotliCompress",
compressionOptions: { level: 11 },
ext: ".br",
threshold: 10240,
}),
].filter(Boolean),
resolve: {
alias: {
"@": path.resolve(__dirname, "./resources/js"),
},
},
build: {
sourcemap: false,
cssCodeSplit: true,
chunkSizeWarningLimit: 800,
minify: "esbuild", //terser
target: "es2020",
rollupOptions: {
output: {
manualChunks(id) {
// sépare toutes les dépendances node_modules dans un chunk vendor
if (id.includes("node_modules")) {
return "vendor";
}
},
//chunkFileNames: "assets/[name]-[hash].js",
/*
assetFileNames: (assetInfo) => {
// Use names array instead of deprecated name property
const name = assetInfo.names?.[0] || "";
// For CSS files, return the exact name without hash placeholders
// The fixCssReferences plugin will handle the hash mapping in manifest
if (name.endsWith(".css")) {
return `assets/${name}`;
}
// For other assets (fonts, images), use content hash for cache busting
return "assets/[name]-[hash][extname]";
},
*/
},
},
},
optimizeDeps: {
include: ["vue", "@inertiajs/vue3", "axios", "lodash"],
},
};
});
END VITE.CONFIG.JS FILE
@mega_aleksandar I tried to remove the output but same result and as @jussimannisto said it should not interfer as it is a complete normal configuration, best example is for the vendor files.
Anyway for all, I found a solution. For sure it's not the best and it should not be like that, but thanks to AI I wrote a vite plugin that fixes my css references in the manifest regarding what has been generated and it works like a charm ! I know it's not a normal solution don't have choice for now.
If anyone has a brillant idea i still take it. Cheers
Here is the vite plugin for asset fixes
START VITE PLUGIN // Plugin to fix CSS references in manifest and chunks // Best practice: Keep hashed CSS files on disk for cache busting, // but ensure manifest.json correctly references them import { Plugin } from "vite";
export function fixCssReferences(): Plugin { return { name: 'fix-css-references', enforce: 'post', generateBundle(options, bundle) { // Map to store CSS filename mappings (non-hashed -> hashed) const cssMap: Map<string, string> = new Map();
// First pass: Build a map of CSS files with their hashes
for (const fileName in bundle) {
if (fileName.endsWith('.css') && fileName.includes('-')) {
// Extract the base name without hash (e.g., "Home-b1476d91.css" -> "Home.css")
const baseName = fileName.replace(/-[a-f0-9]{8}\.css$/, '.css');
cssMap.set(baseName, fileName);
}
}
// Second pass: Update CSS references in all chunks
for (const fileName in bundle) {
const chunk = bundle[fileName];
if (chunk.type === 'chunk' && 'viteMetadata' in chunk) {
const metadata = chunk.viteMetadata as any;
// Update CSS imports in metadata (importedCss is a Set)
if (metadata?.importedCss && metadata.importedCss instanceof Set) {
const updatedCss = new Set<string>();
for (const cssFile of metadata.importedCss) {
const hashedVersion = cssMap.get(cssFile);
updatedCss.add(hashedVersion || cssFile);
}
metadata.importedCss = updatedCss;
}
}
}
},
};
}
END VITE PLUGIN
kanak09 started a new conversation+100 XP
5mos ago
Hello CoMmunity,
[TLDR] After "npm run build", my local website searches for /build/assets/Home-CAF9ffrI.css but real file is under /build/assets/Home-CAF9ffrI-b1476d91.css. A hash is added to the file but not in the manifest.json
I am struggling so much building to production my Laravel-Vue-Inertia app. For now everything was running ultra well in dev mode using vitejs. "npm run dev" or "vite" commands work perfectly fine and all the css / js / vue components are displaying ok. Now that i am building to prod "npm run build" , the compilation is successful but the website is a blank page with 404 on all assets url.
In the browser console there is 404 for http://myvirtualhost.local/build/assets/Home-CAF9ffrI.css but the file is under http://myvirtualhost.local/build/assets/Home-CAF9ffrI-b1476d91.css A hash b1476d91 is added by vite on the compilation time i guess, but in the manifest.json I have somehting like
"css": [ "assets/Home-CAF9ffrI.css" ]
and in the built entry point js, i also have the filename without the hash.
I tried to manually update the JS entry point by updating all references to their references with hash and then the website works...
So what did i miss in the config to have such a behavior ?
Thank you very much for help
Please find below the main files : app.blade.php , vite.config.js, example of one entry point elearning.ts
------ app.blade.php -------
{{-- Inline script to detect system dark mode preference and apply it immediately --}}
<script>
(function() {
const appearance = '{{ $appearance ?? "system" }}';
if (appearance === 'system') {
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
if (prefersDark) {
document.documentElement.classList.add('dark');
}
}
})();
</script>
@if (!app()->isProduction())
<title inertia>{{ config('app.name') }}</title>
@endif
<!--
-->
<link rel="icon" href="/favicon.ico" sizes="any" />
<link rel="icon" href="/favicon.png" type="image/svg+xml" />
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
@php
if( request()->routeIs('admin.*') ){
$entry = 'admin';
}else if(request()->routeIs('elearning.*') ){
$entry = 'elearning';
} else {
$entry = 'web';
}
@endphp
@vite('resources/js/' . $entry . '.ts')
@inertiaHead
---------- vite.config.js -------------
import vue from "@vitejs/plugin-vue"; import laravel from "laravel-vite-plugin"; import path from "path"; import { defineConfig } from "vite"; import compression from "vite-plugin-compression"; import purge from "@erbelion/vite-plugin-laravel-purgecss"; import { visualizer } from "rollup-plugin-visualizer"; import { laravelTranslationsPlugin } from "./vite/translation"; import { wayfinder } from "@laravel/vite-plugin-wayfinder";
export default defineConfig({ server: { host: "0.0.0.0", // important for Docker port: 5173, strictPort: true, hmr: { host: "localhost", // or your local IP for dev }, }, plugins: [ purge({ paths: ["resources/{js,views}/**/*.{blade.php,svelte,vue,html,ts}"], safelist: [], }), visualizer({ open: true, // Automatically opens the visualizer in your browser }), laravel({ input: ["resources/js/web.ts", "resources/js/elearning.ts"], ssr: "resources/js/ssr.ts", refresh: true, }), vue({ template: { transformAssetUrls: { base: null, includeAbsolute: false, }, }, }), wayfinder(), laravelTranslationsPlugin(), //typescriptTransformer(), compression({ algorithm: "gzip" }), compression({ algorithm: "brotliCompress" }), ], resolve: { alias: { "@": path.resolve(__dirname, "./resources/js"), }, }, build: { sourcemap: false, rollupOptions: { input: { web: path.resolve(__dirname, "resources/js/web.ts"), elearning: path.resolve(__dirname, "resources/js/elearning.ts"), }, output: { manualChunks(id) { // sépare toutes les dépendances node_modules dans un chunk vendor if (id.includes("node_modules")) { return "vendor"; } }, }, }, }, });
---------- baseEntrypoint.ts -------
import { createInertiaApp } from "@inertiajs/vue3"; import { resolvePageComponent } from "laravel-vite-plugin/inertia-helpers"; import { createApp, h } from "vue"; import { initializeTheme } from "./composables/useAppearance"; import { createI18n } from "vue-i18n"; import fr from "@/i18n/fr.json"; import { Tooltip } from "bootstrap";
const appName = import.meta.env.VITE_APP_NAME;
export function bootstrapLayout(options: { pagesDir: string; pageGlob: Record<string, any>; }) {
const i18n = createI18n({
legacy: false,
locale: "fr",
fallbackLocale: "fr",
messages: { fr },
});
function initializeTooltips() {
const tooltipTriggerList = document.querySelectorAll(
'[data-bs-toggle="tooltip"]',
);
tooltipTriggerList.forEach((el) => new Tooltip(el));
}
createInertiaApp({
title: (title) => (title ? `${title} - ${appName}` : appName),
resolve: (name) =>
resolvePageComponent(`./pages/${name}.vue`, options.pageGlob),
setup({ el, App, props, plugin }) {
const app = createApp({ render: () => h(App, props) })
.use(plugin)
.use(i18n);
app.mount(el);
initializeTooltips();
initializeTheme();
},
progress: {
color: "#3b86ec",
includeCSS: true,
showSpinner: false,
},
});
}
----- elearning.ts ----------
import './assets/scss/Elearning/main.scss';
import { bootstrapLayout } from "./bootstapLayout"; import { DefineComponent } from "vue";
baseEntrypoint({ pagesDir: "Elearning", pageGlob: import.meta.glob<DefineComponent>("./pages/Elearning/**/*.vue"), });