laravel / vite-plugin

Laravel plugin for Vite.
MIT License
798 stars 150 forks source link

Vite with CDN, but still load manifest.json in public path #249

Closed newarifrh closed 1 year ago

newarifrh commented 1 year ago

Vite Plugin Version

4.0.0

Laravel Version

10.10

Node Version

20.5.1

NPM Version

9.8.0

Operating System

Linux

OS Version

-

Web browser and version

-

Running in Sail?

No

Description

Hi, I created a Github Action to build my assets and deploy those assets to Digital Ocean. The assets are build and uploaded successfully, I've also set the ASSET_URL to the correct Digital Ocean. But the @vite blade directive seems still using the manifest.json from the original public folder, instead of from the ASSET_URL.

Dockerfile Laravel Project

# Stage 1: Build the Vite frontend
FROM node:20 AS frontend-builder

WORKDIR /app

# Copy the package.json and install dependencies
COPY package*.json .

# Install dependencies
RUN npm install

# Copy the Laravel application to the working directory
COPY . .

# Build vite
RUN npm run build

# Stage 2: Build and run the Laravel backend
FROM php:8.2-fpm

# Install system dependencies
RUN apt-get update && apt-get install -y \
    libpng-dev \
    libjpeg-dev \
    libpq-dev \
    libfreetype6-dev \
    zip \
    unzip \
    supervisor \
    git \
    curl \
    libzip-dev \
    && rm -rf /var/lib/apt/lists/*

# Install PHP extensions
RUN docker-php-ext-configure gd --with-freetype --with-jpeg \
    && docker-php-ext-install gd pdo pdo_pgsql zip

# Install and Enable MongoDB extension
RUN pecl install mongodb && \
    docker-php-ext-enable mongodb

# Install Composer
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer

# Create a directory for custom PHP configuration
RUN mkdir -p /usr/local/etc/php/conf.d

# Copy the custom PHP configuration file into the container
COPY custom-php.ini /usr/local/etc/php/conf.d/custom-php.ini

# Set working directory
WORKDIR /var/www/html

# Copy the composer files and install dependencies
COPY composer.json .

# Clear Composer cache
RUN composer clear-cache

# Install Laravel dependencies
RUN composer install --optimize-autoloader --no-dev --no-interaction --no-plugins --no-scripts

# Copy the Laravel application to the working directory
COPY . .

# Set permissions for the storage and bootstrap/cache directories
RUN chown -R www-data:www-data /var/www/html/storage /var/www/html/bootstrap/cache

# Set permissions for the log file
RUN chmod -R 777 storage

# Copy the built frontend from the previous stage to the Laravel public directory
COPY --from=frontend-builder /app/public /var/www/html/public

# Remove unnecessary files
RUN rm -rf .env.example .git

# Expose port 9001 for PHP-FPM
EXPOSE 9001

# Copy the Supervisor configuration file
COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf

# Start Supervisor to manage Laravel Queue workers and PHP-FPM
CMD ["/usr/bin/supervisord", "-n", "-c", "/etc/supervisor/conf.d/supervisord.conf"]

Github Action Deploy Static Asset

name: CI/CD Static Assets Panel to CDN Server

on:
  pull_request:
    types:
      - closed
    branches: [ "staging" ]
    # paths:
    #   - public/**

jobs:
  if_merged:
    if: github.event.pull_request.merged == true
    name: Deploy Static Assets Panel to CDN Server
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Install dependencies
        run: npm ci
      - name: Build
        run: npm run build
      - name: Deploy
        uses: BetaHuhn/do-spaces-action@v2
        with:
          access_key: ${{ secrets.AWS_ACCESS_KEY_ID}}
          secret_key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          space_name: ${{ secrets.AWS_S3_BUCKET }}
          space_region: ${{ secrets.AWS_REGION }}
          source: public
          out_dir: panel
          cdn_domain: cdn-staging.politica.id
          permission: public-read

Deploy Laravel Proejct to Server


name: CI/CD Politica Panel to Staging Server

on:
  pull_request:
    types:
      - closed
    branches: [ "staging" ]

jobs:
  if_merged:
    if: github.event.pull_request.merged == true
    name: Deploy Politica Panel to Staging Server
    runs-on: ubuntu-latest

    steps:
      - name: Checkout Repository
        uses: actions/checkout@v2

      - name: Log in to Docker Hub
        uses: docker/login-action@v1
        with:
          username: ${{ secrets.DOCKER_HUB_USERNAME }}
          password: ${{ secrets.DOCKER_HUB_TOKEN }}

      - name: Build and Push Docker Image
        run: |
          docker build --no-cache -t politica:panel-staging .
          docker tag politica:panel-staging politicaid/politica:panel-staging
          docker push politicaid/politica:panel-staging

      - name: SSH into the Server and Deploy
        uses: appleboy/ssh-action@master
        with:
          host: ${{ secrets.STAGING_SERVER_HOST }}
          username: ${{ secrets.STAGING_SERVER_USERNAME }}
          password: ${{ secrets.STAGING_SERVER_PASSWORD }}
          script: |
            docker pull politicaid/politica:panel-staging
            docker service update politica-panel-staging_panel --force --image politicaid/politica:panel-staging

Example view

<!DOCTYPE html>
<html lang="en" dir="ltr" class="light nav-floating">

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta name="csrf-token" content="{{ csrf_token() }}">
    <title>{{ config('app.name') }} - {{ $title }}</title>
    <x-favicon />

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

<body class="font-inter dashcode-app" id="body_class">
    <div class="app-wrapper">
        <x-sidebar />

        <div class="flex flex-col justify-between min-h-screen">
            <div>
                <!-- BEGIN: header -->
                <x-header />
                <!-- BEGIN: header -->

                <div class="content-wrapper transition-all duration-150 ltr:ml-0 xl:ltr:ml-[248px] rtl:mr-0 xl:rtl:mr-[248px]"
                    id="content_wrapper">
                    <div class="page-content">
                        <div class="transition-all duration-150 container-fluid" id="page_layout">
                            <main id="content_layout">
                                <!-- Page Content -->
                                {{ $slot }}
                            </main>
                        </div>
                    </div>
                </div>
            </div>

            <!-- BEGIN: footer -->

            <!-- BEGIN: footer -->

        </div>
    </div>

    @vite(['resources/js/app.js', 'resources/js/main.js'])

    @stack('scripts')
</body>

</html>

Steps To Reproduce

npm run build

jessarcher commented 1 year ago

Hi @newarifrh,

The @vite directive needs to read the file into PHP and parse the JSON to determine the appropriate path for the requested asset(s). While it's possible to use file_get_contents with a URL, making a network request for this probably isn't ideal and isn't supported.

You can see the code for fetching the manifest at https://github.com/laravel/framework/blob/c6e4fa8004cfee4eca3372fe89ee4fd66b97b56d/src/Illuminate/Foundation/Vite.php#L694-L726

You could extend this class, override some methods, and bind it in the container, but I wouldn't recommend it. Even with Laravel Vapor, we deploy the assets to a CDN but still read the manifest.json file locally.

newarifrh commented 1 year ago

Thanks @jessarcher!

but at some point, the build on CDN and local is different. How to ensure the manifest.json in CDN and local is the same?

jessarcher commented 1 year ago

As long as your assets are deployed first and the assets from the previous deployment are kept around until after the deployment, it should be fine.

If an asset hasn't changed, it'll have the same hash in the file name. Otherwise, it will have a new hash. If you're copying them to the same directory then new and old assets can live together. You'd just need to figure out how you want to purge the old assets once you no longer need them.

stephan-v commented 11 months ago

I am also running into this issue at the moment.

The only way to actually get a manifest.json file is to do a production run with vite using vite build. I am currently performing this step on AWS codebuild so vite build run in an isolated environment, pushes the files to S3 and then destroys that environment.

It wouldn't make sense to now also have to run vite build on my production environment just create a manifest.json file when all the files I actually need are already present in my S3 bucket. Especially since it doesn't just create the manifest but also all other files.

If I were to have a lot of EC2 instances on AWS they would all have a bunch of build files I don't need just to ensure that my manifest is there.

What is the workflow supposed to be for this? @jessarcher

timacdonald commented 10 months ago

@stephan-v with this setup you would also need to copy the built manifest.json file to your servers or have them download the file on deploy.

timacdonald commented 10 months ago

You could also create an extended Vite class that overrides the manifest* methods to retrieve the manifest from your CDN. You'd likely want to cache it locally still though, so having it copied / downloaded as part of your deployment feels like the way to go IMO.