stratboy's avatar

How to properly load page-specific scripts both in development and production?

Hi, if for just one page I use

asset('resources/js/seed-create.js')

to load a page-specific js in development, then I'll have to change the path in production, I guess, so it's not optimal at all. Also, not sure it'll load a compiled version like @vite. So I guess there must be some smarter way of doing this simple task, maybe. How to?

0 likes
5 replies
LaryAI's avatar
Level 58

To properly load page-specific scripts in both development and production environments without having to change the path manually, you can use a build tool like Laravel Mix or Vite, which are commonly used in Laravel projects. These tools allow you to compile and version your assets in a way that works seamlessly in different environments.

Here's how you can do it with Laravel Mix:

  1. Install Laravel Mix by following the instructions in the documentation: https://laravel.com/docs/mix

  2. Place your seed-create.js file in the resources/js directory.

  3. In your webpack.mix.js file, add an entry for your script:

const mix = require('laravel-mix');

mix.js('resources/js/seed-create.js', 'public/js');
  1. Run npm run dev for development or npm run prod for production. This will compile your assets and place them in the public/js directory.

  2. Use the mix helper function in your blade template to include the script. This function automatically handles versioning and the correct path:

@if (mix('js/seed-create.js'))
    <script src="{{ mix('js/seed-create.js') }}"></script>
@endif

The mix function will point to the correct version of your file for both development and production.

If you're using Vite, which is a more modern build tool and comes with Laravel 9+, the process is slightly different:

  1. Install Vite by following the instructions in the documentation: https://laravel.com/docs/vite

  2. Place your seed-create.js file in the resources/js directory.

  3. In your vite.config.js file, configure Vite to handle your script:

// vite.config.js
import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';

export default defineConfig({
    plugins: [
        laravel({
            input: [
                'resources/js/seed-create.js',
                // ... other inputs
            ],
            refresh: true,
        }),
    ],
});
  1. Run npm run dev for development or npm run build for production.

  2. Use the @vite directive in your blade template to include the script:

@vite('resources/js/seed-create.js')

The @vite directive will include the correct script tag for your environment.

By using Laravel Mix or Vite, you can avoid manually changing paths between development and production, and you can take advantage of features like versioning and minification.

Snapey's avatar

Just don't have to use asset. Just load the scripts with the public path of the script

You should not be using scripts from the resources directory.

Either place them in the public folder, or use Vite to move a copy of them to the public folder.

stratboy's avatar

@Snapey Ok but vite generates versioned assets in public/build/assets like this: seed-create-811fa48e.js so I have no idea how to reference those files, because of the last part 811fa48e.js. Or, maybe I should read the manifest.json in some way and then get the right file based on the mapping?

stratboy's avatar

@Snapey And generally speaking, what I'm trying to do is having the main/shared scripts loaded from the main layout component (as per laravel defaults) and then load page-specific scripts from other blade templates/components. In a manner that the syntax would work and not change both in development and production. How to? How is it usually done in Laravel?

stratboy's avatar

@Snapey Ok, currently I ended up with this 'solution', but I don't know if it's the best one. For sure, I do not have HMR for the page-specific scripts.

  • For shared assets I use @vite on the HEAD section of the main layout component.

  • For page-specific assets I made an helper (App/Helpers/AssetHelpers.php) which reads the Vite manifest and lets to retrieve the versioned asset:

namespace App\Helpers;

use Illuminate\Support\Facades\Log;

class AssetHelpers{

  private $assets_manifest = null;

  private static function get_assets_manifest($to_json = true){
    $manifest_path = public_path('build/manifest.json');
    $contents = '';

    if(is_file($manifest_path)){
      $handle = fopen($manifest_path, 'rb');
  
      if ($handle) {
        try {
          if (flock($handle, LOCK_SH)) {
            clearstatcache(true, $manifest_path);
            $contents = fread($handle, filesize($manifest_path) ?: 1);
            flock($handle, LOCK_UN);
          }
        } finally {
          fclose($handle);
        }
      }
    }

    if(!empty($contents) && $to_json) return json_decode($contents, true);
    return $contents;
  }

  public static function get_asset_from_manifest($filename = null){
    $assets_map = AssetHelpers::get_assets_manifest();
    return isset($assets_map[$filename]) ? asset('build/' . $assets_map[$filename]['file']) : '';
  }
}

Then I share a function to all views from the boot() method of the appServiceProvider:

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\View;
use Illuminate\Support\Facades\Log;

class AppServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        //
    }

    public function boot(): void
    {
        View::composer('*', function ($view) {
            $lr_get_asset = function ($asset_path = null) { return AssetHelpers::get_asset_from_manifest($asset_path); };
            $view->with('lr_get_asset', $lr_get_asset);
        });
    }
}

And call it from views like this:

@push('scripts')
    <script src="{{ $lr_get_asset('resources/js/seed-create.js') }}"></script>
@endpush

By logging some from inside, I see that apparently AssetHelpers::get_asset_from_manifest() is not called once per request, but only when $lr_get_asset gets called from a view. So for now I'm happy. But not sure I'm doing the best way.

Please or to participate in this conversation.