Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[9.x] Vite #42785

Merged
merged 14 commits into from
Jun 22, 2022
Merged

[9.x] Vite #42785

merged 14 commits into from
Jun 22, 2022

Conversation

jessarcher
Copy link
Member

@jessarcher jessarcher commented Jun 13, 2022

⚡ Vite is a modern frontend build tool that provides an extremely fast development environment and bundles your code for production.

This PR introduces a @vite Blade directive to the framework that will be used in combination with the official Laravel Vite Plugin. This PR does not remove or deprecate the existing mix() helper. There are also PRs to laravel/laravel as well as laravel/breeze and laravel/jetstream that will make this the default for new Laravel applications. There is also, of course, documentation.

The Laravel side of the implementation is similar to Mix and should feel quite familiar.

The main difference is that instead of using the mix() helper, you would now use the @vite Blade directive. The reason for the directive is that Laravel needs to output complete <script> and <link rel="stylesheet"> tags, rather than just a path as with Mix.

For the best experience with a Blade app, you will want to specify both CSS and JavaScript entry points, which would look something like this in your Blade layout template:

- <link rel="stylesheet" href="{{ mix('css/app.css') }}">
- <script src="{{ mix('js/app.js') }}" defer></script>
+ @vite(['resources/css/app.css', 'resources/js/app.js'])

In development mode, this will also load the Vite client, which will automatically reload your JavaScript and CSS when you make changes. In production mode, it will automatically load the versioned assets for you.

For the best experience with a SPA (e.g. Inertia), you will want to import your CSS via your JavaScript entry point(s) and then only specify the JavaScript entry point to the @vite directive, which would look something like this:

  import './bootstrap.js';
+ import '../css/app.css';
- <link rel="stylesheet" href="{{ mix('css/app.css') }}">
- <script src="{{ mix('js/app.js') }}" defer></script>
+ @vite('resources/js/app.js')

When importing your CSS via JavaScript, Vite will automatically inject a <style> tag in development mode that it will keep up to date. When building for production, a compiled app.{version}.css file will be generated that the @vite directive will automatically load via a <link rel="stylesheet"> tag.

When using React, you will also need to place a @viteReactRefresh directive above your @vite directive that will inject Vite's React Refresh Runtime.

Everything will be configured for you with Laravel's updated starter kits.

As with Mix, The @vite directive will automatically detect when the Vite development server is running, otherwise, it will use the compiled and versioned assets from the manifest.

The Laravel plugin also handles Vite SSR builds and works well with tools like Sail and Vapor.

The documentation will include full instructions, examples, and advanced configuration.

This implementation requires a specific Vite configuration that the official Laravel Vite Plugin handles for you. For existing applications, there are also some required changes documented in the upgrade guide.

@bram-pkg
Copy link
Contributor

Will the Vite manifest SRI plugin be supported by this? That would be nice.

@jessarcher
Copy link
Member Author

Will the Vite manifest SRI plugin be supported by this? That would be nice.

It's on my list of things to look at post-launch and should be easy to implement 👍

@bjhijmans
Copy link

- <link rel="stylesheet" href="{{ mix('css/app.css') }}">
- <script src="{{ mix('js/app.js') }}" defer></script>
+ @vite(['resources/css/app.css', 'resources/js/app.js'])

How would you add nonces to this vite call?

Co-authored-by: Lucas Michot <513603+lucasmichot@users.noreply.github.com>
@jessarcher jessarcher marked this pull request as ready for review June 22, 2022 02:17
@jessarcher jessarcher marked this pull request as draft June 22, 2022 02:17
@taylorotwell taylorotwell marked this pull request as ready for review June 22, 2022 18:07
@taylorotwell taylorotwell merged commit baf526e into 9.x Jun 22, 2022
@taylorotwell taylorotwell deleted the vite branch June 22, 2022 18:07
turanjanin pushed a commit to turanjanin/laravel-framework that referenced this pull request Jun 23, 2022
* Add Vite helpers

* Remove vite helper

* Add @viteReactRefresh directive

* Add docblock

* Remove unused import

* Automatically clean up after tests

* Use ASSET_URL when resolving assets

* support default entrypoint

* update docblock

* Linting

* Support CSS entry points

* Remove default entry points

Blade apps and SPAs have different entry point requirements so there is
no sensible default.

* Fix test namespace

Co-authored-by: Lucas Michot <513603+lucasmichot@users.noreply.github.com>

* Add missing import

Co-authored-by: Tim MacDonald <hello@timacdonald.me>
Co-authored-by: Lucas Michot <513603+lucasmichot@users.noreply.github.com>
@tabuna
Copy link
Contributor

tabuna commented Jun 27, 2022

Hi @jessarcher

The main difference is that instead of using the mix() helper, you would now use the @Vite Blade directive. The reason for the directive is that Laravel needs to output complete <script> and tags, rather than just a path as with Mix.

Is there a way to output the path like it was with mix() because it is often required to specify additional things like for link tags:

<link rel="stylesheet" type="text/css" href="{{  mix('/css/light.css') }}"
 media="(prefers-color-scheme: light)">

For script:

<script src="{{  mix('/app/part/landing.js') }}" 
data-turbo-eval="false" data-turbolinks-eval="false"></script>

@timacdonald
Copy link
Member

@tabuna there isn't a way to do this currently with Vite, however we are considering enabling the specification of arbitrary attributes on entry point tags.

@mikemand
Copy link
Contributor

@tabuna there isn't a way to do this currently with Vite, however we are considering enabling the specification of arbitrary attributes on entry point tags.

I was thinking about this also, about how I could add a CSP nonce to the script tag. What about a blade template that can be overridden? That way, instead of calling makeTag/makeScriptTag/makeStylesheetTag, the URLs are pushed to the collection instead and then passed to a blade template.

@nlvedwin
Copy link

after installing a fresh app
image
image

@timacdonald
Copy link
Member

@nlvedwin it looks like you are visiting the Vite dev server. You still need to utilise php artisan serve, Valet or similar.

For example if you are using Valet on your system, you should visit laravel-vite.test instead.

@aurukas
Copy link

aurukas commented Jun 29, 2022

facing the same issue as @nlvedwin. I use Sail with localhost already being exposed and accessible.
npm run dev loads dev server with localhost:3000 which in theory should load the page with hmr? Unfortunately it throws 404 instead.

@bram-pkg
Copy link
Contributor

bram-pkg commented Jun 29, 2022 via email

@aurukas
Copy link

aurukas commented Jun 29, 2022

No, you load the normal artisan serve URL, @.` will take of making it able to hot reload from the development server.

On Wed, 29 Jun 2022 at 17:28, Aurelijus Ušeckas @.
> wrote: facing the same issue as @nlvedwin https://github.com/nlvedwin. I use Sail with localhost already being exposed and accessible. npm run dev loads dev server with localhost:3000 which in theory should load the page with hmr? Unfortunately it throws 404 instead. — Reply to this email directly, view it on GitHub <#42785 (comment)>, or unsubscribe https://github.com/notifications/unsubscribe-auth/ADFQHI5AHHCQTHKLIJRJACDVRRTRBANCNFSM5YTKQLKA . You are receiving this because you commented.Message ID: @.***>

ahh OK. Got it now. Thanks! 🙏

@nlvedwin
Copy link

nlvedwin commented Jun 29, 2022

@nlvedwin it looks like you are visiting the Vite dev server. You still need to utilise php artisan serve, Valet or similar.

For example if you are using Valet on your system, you should visit laravel-vite.test instead.

@nlvedwin it looks like you are visiting the Vite dev server. You still need to utilise php artisan serve, Valet or similar.

For example if you are using Valet on your system, you should visit laravel-vite.test instead.

got it now, it feels like magic 🧙, thanks @timacdonald !

@mikemand
Copy link
Contributor

mikemand commented Jun 30, 2022

For others who are having a 404 when trying to check the assets (http://localhost:3000/build/css/app.css), run vite build first to see if there are any errors compiling. I moved from SASS to PostCSS at the same time as upgrading to Vite and had a few errors that PostCSS couldn't deal with (commented out @apply, for one).

It would be great if the dev server didn't swallow compile errors, but I thnk that's something to bring up to Vite.

@chrispage1
Copy link
Contributor

Hi all, referred here by @tabuna. For those of you who are looking for a direct replacement of the mix() helper, please see my Vite helper package on Packagist - https://packagist.org/packages/motomedialab/laravel-vite-helper

@timacdonald
Copy link
Member

Hey folks,

A lot of questions in this thread about adding a CSP nonce, Subresource integrity, or arbitrary attributes (turoblinks, media query stuff, etc) to the Vite generated tags.

I've just opened a PR to support all of those use-cases.

You can check it out here: #43442

Any feedback would be appreciated.

@bram-pkg
Copy link
Contributor

Awesome stuff @timacdonald, thank you so much!

@kitro
Copy link

kitro commented Aug 2, 2022

Great work @jessarcher 👍 ,
I think it will be nice if we can also use @vitejs/plugin-legacy to inject nomdule script tag. In some cases like using the iPhone with iOS 13 that not support modular bundles <script type="module">.
Thank you,

@kitro
Copy link

kitro commented Aug 2, 2022

I think it will be nice if we can also use @vitejs/plugin-legacy to inject nomdule script tag. In some cases like using the iPhone with iOS 13 that not support modular bundles <script type="module">.

After #43442 I get a workaround to inject legacy generated files

        Vite::useScriptTagAttributes(function (string $src, string $url, array|null $chunk, array|null $manifest) {
            if (in_array(
                $src,
                [
                    'vite/legacy-polyfills',
                    'app/resources/js/app/app-legacy.js',
                    'app/resources/sass/app/app-legacy.scss',
                ]
            )) {
                return [
                    'type' => 'text/javascript',
                    'nomodule',
                ];
            }

            return [];
        });

@FreekVR
Copy link

FreekVR commented Feb 15, 2023

Late comment, but while the above works (thanks!) it is still loading the legacy script via preloads for modern browsers

eg.
<link rel="modulepreload" href="/build/assets/app-legacy-d2b2bf94.js" />

So far I've been unable to find a workaround for this

@FreekVR
Copy link

FreekVR commented Feb 16, 2023

I was able to find an ondocumented callback for the preloads, in which returning false causes no preloads to be included.
By default, a normal or module preload will cause the legacy bundle(s) to be downloaded even in modern browsers.

The following worked for us in a ViteServiceProvider we added to the project.

<?php

namespace App\Providers;

use Illuminate\Support\Facades\Vite;
use Illuminate\Support\ServiceProvider;

class ViteServiceProvider extends ServiceProvider
{
    protected const NONMODULE_FILES = [
        'vite/legacy-polyfills-legacy',
        'resources/frontend/app/app-legacy.js',
    ];

    /**
     * Register services.
     *
     * @return void
     */
    public function register()
    {
        //
    }

    /**
     * Bootstrap services.
     *
     * @return void
     */
    public function boot()
    {
        Vite::usePreloadTagAttributes(function ($src, $url, $chunk, $manifest) {
            // Don't preload non-module files at all
            if (in_array($src, self::NONMODULE_FILES)) {
                return false;
            }
            // Use the default attributes for the rest
            return [];
        });
        Vite::useScriptTagAttributes(function (string $src, string $url, array|null $chunk, array|null $manifest) {
            if (in_array($src, self::NONMODULE_FILES)) {
                return [
                    'type' => 'text/javascript',
                    'nomodule',
                ];
            }
            return [];
        });
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.