Search code examples
cssvue.jslaravel-vapor

How to add css image backgrounds in Vue-js so Laravel Vapor can rewrite the url to the correct S3/CDN url


I am trying to deploy a Laravel + Vue JS project to Laravel Vapor. Everything is working as expected, except for my background images, applied using styles in a Vue JS single file component. The URL's point to my domain/lamba instance, not the cdn provided by Vapor.

My existing style, which works on my local environment is:

.slide1 {
  background-image: url("/images/banner1.jpg");
}

When deployed to vapor the image url is : https://damp-pine-l46hzzzbsa5p.vapor-farm-a1.com/images/banner1.jpg

And returns a 404, as it is expected to be the url of the cdn.

When I use the image from the base blade file, the image url is rewritten to: https://d16wfy3cif7yy.cloudfront.net/e9ce46ed-bf61-4c85-8bd0-b136f1d15a33/images/banner1.jpg as expected.

I have made a few attempts at following other posts relating to vue-js/laravel-mix urls, however all have failed, and most won't even allow npm run to complete without error.

After doing more research, it seems I missed part of the vapor documentation: https://docs.vapor.build/1.0/projects/deployments.html#code-splitting-dynamic-imports that talks about dynamic imports. This is obviously the intended way of doing it, rather than have the vapor-cli do the rewrite, however I cannot for the life of me get it working.

I have tried adding the suggested code to my webpack.mix file, and still no luck. The image URLs still point to the domain, not to the cdn.

I have also followed many tutorials/stack overflow questions that talk about relative vs module assets, and following any of these causes npm to throw an error that the image could not be resolved.

My webpack.mix.js file:

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

/*
 |--------------------------------------------------------------------------
 | Mix Asset Management
 |--------------------------------------------------------------------------
 |
 | Mix provides a clean, fluent API for defining some Webpack build steps
 | for your Laravel application. By default, we are compiling the Sass
 | file for the application as well as bundling up all the JS files.
 |
 */

mix.js('resources/js/app.js', 'public/js')
    .sass('resources/sass/app.scss', 'public/css');

if (mix.inProduction()) {
    const ASSET_URL = process.env.ASSET_URL + "/";

    mix.webpackConfig(webpack => {
        return {
            plugins: [
                new webpack.DefinePlugin({
                    "process.env.ASSET_PATH": JSON.stringify(ASSET_URL)
                })
            ],
            output: {
                publicPath: ASSET_URL
            }
        };
    });
}

And this is my vue-component that is not loading images correctly

<template>
  <div class="heroheader-container">
    <carousel
      :scrollPerPage="false"
      :perPage="1"
      :autoplay="true"
      :autoplay-timeout="4000"
      :autoplay-hover-pause="true"
      :loop="true"
      :pagination-enabled="false"
    >
      <slide class="slide slide1">
        <div class="slide-content">

        </div>
      </slide>
      <slide class="slide slide2">Slide 2</slide>
      <slide class="slide slide3">Slide 3</slide>
    </carousel>
  </div>
</template>

<script>
export default {
  data() {
    return {
      //
    };
  }
};
</script>

<style>
:root {
  --carousel-height: 75vh;
}

.heroheader-container {
  width: 100%;
  margin-left: 0px;
  padding: 0px;
  top: 0px;
  position: absolute;
}

.carousel {
  width: 100%;
  height: var(--carousel-height);
}

.slide1 {
  background-image: url("/images/banner2.jpg");
}

.slide2 {
  background-image: url("/images/banner3.jpg");
}

.slide3 {
  background-image: url("/images/banner4.jpg");
}

.slide {
  background-position: center center;
  background-size: cover;
  height: var(--carousel-height);
  padding-top: 150px;
}

.slide-content {
  position: relative;
  text-align: center;
  color: #efefef;
}
</style>

As a PHP developer (new to vue), I forked and edited the laravel/vapor-cli in about 5 minutes to rewrite these style urls in the compiled js. This works, however it seems there is a better way, I'm just missing something! Any help would be appreciated.


Solution

  • The solution to this was to use a computed property in my vue component, and inline the background image style, in combination with vapor's documentation. In the end my webpack.mix file ended up being:

    mix.js('resources/js/app.js', 'public/js')
        .sass('resources/sass/app.scss', 'public/css');
    
    if (mix.inProduction()) {
        const ASSET_URL = process.env.ASSET_URL;
    
        mix.webpackConfig(webpack => {
            return {
                plugins: [
                    new webpack.DefinePlugin({
                        "process.env.ASSET_PATH": JSON.stringify(ASSET_URL)
                    })
                ],
                output: {
                    publicPath: ASSET_URL
                }
            };
        });
    }
    

    Note: this is very slightly different to the vapor documentation that defines

    process.env.ASSET_URL + '/'
    

    I added the '/' in my component, which you can see below.

    <template>
      <div class="container-fluid heroheader-container">
        <div class="row">
          <div class="col-12 p-0 m-0">
            <carousel>
              <slide :style="{ 'background-image': 'url(' + slide1image + ')' }">
                <div class="slide-content">
                </div>
              </slide>
              <slide :style="{ 'background-image': 'url(' + slide2image + ')' }">
                <div class="slide-content">
                </div>
              </slide>
              <slide :style="{ 'background-image': 'url(' + slide3image + ')' }">
                <div class="slide-content">
                </div>
              </slide>
            </carousel>
          </div>
        </div>
      </div>
    </template>
    
    <script>
    export default {
      computed: {
        assetPath() {
          return process.env.ASSET_PATH || "";
        },
        slide1image() {
          return this.assetPath + "/images/banner1.jpg";
        },
        slide2image() {
          return this.assetPath + "/images/banner2.jpg";
        },
        slide3image() {
          return this.assetPath + "/images/banner3.jpg";
        }
      }
    };
    </script>
    

    Note the computed assetPath and computed image properties, which are then bound as inline styles on the slide divs.

    Thanks to Dan Wall for the assistance in solving this. Hopefully it is useful to someone out there.