1

Currently the app has 2 entry points for the JS and CSS files in the blade layout file:

{{-- layout.blade.php --}}
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />

    <title>{{ $title ?? config('app.name') }}</title>
    <link rel="icon" href="/favicon.ico" sizes="any">
    <link rel="icon" href="/favicon.svg" type="image/svg+xml">
    <link rel="apple-touch-icon" href="/apple-touch-icon.png">

    @vite(['resources/css/app.css', 'resources/js/app.js'])
</head>

But, I have many other JS and CSS files inside /resources/js and /resources/css.

How am I supposed to deal with it?

Let's say I have another blade view that uses the above layout:

{{-- my_view.blade.php --}}
@extends('layouts.app')
@section('content')
@endsection

And I want to use some JS scripts and CSS files only for this page, what is the way to do it alongside Vite? Because currently the layout takes only the 2 main entry points.

What I did was to use glob to add all the css files and js files:

// app.js
import.meta.glob('../css/*.css', { eager: true });
import.meta.glob('../js/*.js', { eager: true });

But that's clearly not how I am supposed to work with Vite, because right now, every Blade view that uses this layout loads all the CSS and all the JS files

So how am I supposed to let's say, add only myscript.js and mystyle.css to my_view.blade.php while using Vite best practices?

5
  • 1
    Multiple JS and CSS files can be shipped; you can include each one individually in the laravel() plugin inside vite.config.js. On the client side, you load only those you want to use on a given view, but you can choose from the values passed to the laravel() plugin. Commented 3 hours ago
  • @rozsazoltan you are correct, I need to delete this question or at least update the title. I just got too frustrated trying to find answers. Do you have a better suggestion for a title or I should delete the question? Commented 3 hours ago
  • @rozsazoltan oh so that's quite cumbersome, if I have many scripts and CSS files I am going to do it one-by-one? Commented 3 hours ago
  • 1
    In vite.config.js you can read the file structure and automatically populate the array, but I think that’s beyond the scope of the question. Commented 3 hours ago
  • @rozsazoltan is it not the same as I did with glob? Or it's different? Because right now how I use it with glob it also automatically populate it (However not the vite.config.js file, but maybe it's similar?) Do you maybe have a link that shows how I can do what you said? Commented 3 hours ago

1 Answer 1

1

You can specify any number of sources for the laravel-vite-plugin:

vite.config.js

export default defineConfig({
    plugins: [
        laravel([
            'resources/css/welcome.css',
            'resources/js/welcome.js',
            'resources/css/app.css',
            'resources/js/app.js',
            'resources/css/admin.css',
            'resources/js/admin.js',
        ]),
    ],
});

Finally, you use in each view only the ones you actually need.

resources/views/welcome.blade.php

<!DOCTYPE html>
<head>
    {{-- ... --}}
 
    @vite(['resources/css/welcome.css', 'resources/js/welcome.js'])
</head>

resources/views/app.blade.php

<!DOCTYPE html>
<head>
    {{-- ... --}}
 
    @vite(['resources/css/app.css', 'resources/js/app.js'])
</head>

resources/views/admin.blade.php

<!DOCTYPE html>
<head>
    {{-- ... --}}
 
    @vite(['resources/css/admin.css', 'resources/js/admin.js'])
</head>

As you mentioned in the question, it's possible that you already have a separate <head>. In this case, most modern browsers will handle a secondary correctly, but you can pass variables from outside to layout.blade.php and then use them in @vite. (Reference: How to pass variable to @extends blade and Can HTML contain two HEAD tags)

resources/views/layout.blade.php

<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />

    <title>{{ $title ?? config('app.name') }}</title>
    <link rel="icon" href="/favicon.ico" sizes="any">
    <link rel="icon" href="/favicon.svg" type="image/svg+xml">
    <link rel="apple-touch-icon" href="/apple-touch-icon.png">

    @if(isset($sources))
        @vite($sources)
    @else
        @vite(['resources/js/app.css', 'resources/js/app.js'])
    @endif
</head>

resources/views/admin.blade.php

@extends('layout', ['sources' => ['resources/js/admin.css', 'resources/css/admin.js']])

@section('content')
    <h1>Hello World</h1>
@endsection

Of course, you can also assemble the array passed to the plugin automatically. If your structure is known, you can read the file system accordingly and automatically collect the file paths.

For example, if you assume that every file in the js and css folders should be a separate source, but I ignore the subfolders, I would do something like this (when building the source, for instance, files used from the js/utils subfolder are bundled into the source by Vite, so you don't need to worry about that):

vite.config.js

import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';
import fs from 'fs';
import path from 'path';

function getFilesFromDir(dir) {
    return fs.readdirSync(dir)
        .filter(file => {
            const ext = path.extname(file);
            return (!ignoreFiles.includes(file) && (exts.length === 0 || exts.includes(ext)));
        })
        .map(file => path.join(dir, file).replace(/\\/g, '/'));
}

const cssFiles = getFilesFromDir('resources/css', ['.css']);
const jsFiles = getFilesFromDir('resources/js', ['.js']);

export default defineConfig({
    plugins: [
        laravel([
            ...cssFiles,
            ...jsFiles,
        ]),
    ],
});

Note: It's just a quick illustrative example; there might be a better alternative.

Sign up to request clarification or add additional context in comments.

8 Comments

Well, in that case you lose Vite's bundling capabilities, and you might run into issues with caching as well. Vite solves this with a manifest, so when you create a new build, the files get new query parameters, meaning you never have to worry about caches. I would definitely ship them via the laravel-vite-plugin, just automated if there are so many that manual maintenance isn't feasible.
Otherwise, it might be worth considering setting up a JS framework so that effectively you only have one source. Even sharing routes between PHP and JS works quite well now - previously with ziggy, and more recently with wayfinder. But even Inertia might be interesting for you.
Thank you got it, for the caching issues I have been using PHP's filemtime , for example <script src="{{ asset('js/script.js') }}?v={{ filemtime('js/script.js') }}"></script>. It has been working nicely so far. But also I noticed, your answer does not cover part of my question, or it does and it needs some clarification: In your examples, you put all the @vite directives inside the <head>, but what if I am using a layout that already has <head>, as in my example (I edited it to show it more clearly). So that the blade view extends it including the head. Where do I put the vite then?
I think there's no problem if you include multiple <head> elements in the DOM; the browser will interpret them correctly. Reference: Can HTML contain two HEAD tags - But updated my answer with extra case.
Or maybe I could use @stack directive like <head>@stack('vite')</head> and then push to it from another blade file i.e. @push('vite') @vite(['resources/js/script.js'] @endpush? Need to test it, not sure it will work though
@pileup Hmm. That's an interesting directive, yeah, you're right. I always ship a frontend separated from Blade - it's really convenient to work behind Vue or Svelte and keep Laravel just for the backend. I couldn't go back to using only Laravel + Blade.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.