• Skip to secondary menu
  • Skip to main content
  • Skip to primary sidebar
  • Home
  • Projects
  • Products
  • Themes
  • Tools
  • Request for Quote

Vengala Vinay

Having 12+ Years of Experience in Software Development

  • Home
  • WordPress
  • PHP
    • Codeigniter
  • Django
  • Magento
  • Selenium
  • Server
Home » Automating CI/CD Workflows for Enterprise Asset Compilation Pipelines (Vite, Webpack, and Tailwind) in Legacy Core PHP Implementations

Automating CI/CD Workflows for Enterprise Asset Compilation Pipelines (Vite, Webpack, and Tailwind) in Legacy Core PHP Implementations

Diagnosing Asset Compilation Bottlenecks in Legacy PHP Projects

Enterprise asset compilation, particularly within legacy core PHP implementations, often presents unique challenges. When integrating modern frontend tooling like Vite, Webpack, or Tailwind CSS, performance regressions or build failures can be insidious. This post delves into advanced diagnostic techniques to pinpoint and resolve these issues, focusing on common pitfalls in CI/CD pipelines.

Analyzing CI/CD Pipeline Performance with Vite

Vite’s lightning-fast development server relies on native ES modules. However, its production build process, which leverages Rollup, can still become a bottleneck. A common culprit in CI environments is inefficient dependency caching or suboptimal configuration.

First, let’s examine a typical CI job script for a Vite build. We’ll look for opportunities to optimize dependency installation and caching.

Optimizing `npm install` and Caching in CI

In a CI environment (e.g., GitLab CI, GitHub Actions), ensuring that `node_modules` are cached effectively is paramount. A naive `npm install` on every run is a significant performance drain.

Example: GitLab CI Configuration for Vite

Consider this GitLab CI snippet. The key is the `cache` directive for `node_modules`.

stages:
  - build

variables:
  NODE_VERSION: "18"

cache:
  key:
    files:
      - package-lock.json
  paths:
    - node_modules/

build_assets:
  stage: build
  image: node:${NODE_VERSION}
  script:
    - echo "Installing Node.js dependencies..."
    - npm ci --prefer-offline --no-audit --progress=false
    - echo "Building Vite assets..."
    - npm run build
  artifacts:
    paths:
      - public/build/
    expire_in: 1 week

Diagnostic Steps:

  • Cache Key Invalidation: Ensure the `cache:key:files` correctly points to `package-lock.json` (or `yarn.lock`). Any change in dependencies invalidates the cache, forcing a fresh install.
  • `npm ci` vs. `npm install`: `npm ci` is generally preferred in CI as it performs a clean install based strictly on `package-lock.json`, avoiding potential inconsistencies. The `–prefer-offline` flag attempts to use cached packages first, speeding up installation if the cache is valid.
  • Dependency Size: If `node_modules` is excessively large, investigate unused dev dependencies or consider using tools like `npm-check-updates` to prune outdated packages.
  • Build Time Analysis: Add `echo` statements before and after `npm run build` to measure its duration. If the build itself is slow, the issue might be within Vite’s configuration (e.g., excessive plugins, large assets being processed inefficiently).

Vite Configuration for Production Optimization

The `vite.config.js` (or `.ts`) file is critical. For legacy PHP projects, ensuring assets are correctly outputted and hashed is vital for cache busting.

import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin'; // Assuming Laravel Vite plugin for PHP integration

export default defineConfig({
    plugins: [
        laravel({
            input: [
                'resources/css/app.css',
                'resources/js/app.js',
            ],
            refresh: true,
        }),
    ],
    build: {
        // Output directory relative to project root
        outDir: 'public/build',
        // Enable hashing for cache busting
        rollupOptions: {
            output: {
                entryFileNames: '[name]-[hash].js',
                chunkFileNames: '[name]-[hash].js',
                assetFileNames: '[name]-[hash].[ext]',
            },
        },
    },
});

Diagnostic Steps:

  • `outDir` Configuration: Verify that `outDir` points to a directory accessible by your PHP application (e.g., `public/build`).
  • Hashing: Ensure `rollupOptions.output` is configured for hashing. This is crucial for cache-busting in production. Without hashes, browsers might serve stale assets.
  • `laravel-vite-plugin` (or equivalent): If using a framework-specific plugin, check its configuration for manifest generation. The manifest file (e.g., `public/build/manifest.json`) maps original asset names to their hashed versions, which your PHP code will use to include the correct files.
  • Large Files: Use Rollup’s `visualizer` plugin (`rollup-plugin-visualizer`) during development to identify large chunks or dependencies that might be slowing down the build or increasing bundle size.

Webpack Configuration and Performance Tuning

Webpack, while powerful, can be notoriously complex. Performance issues in CI often stem from its configuration, plugin load, and caching strategies.

Leveraging Webpack’s Cache

Webpack 5 introduced persistent caching, which can dramatically speed up subsequent builds. This is essential for CI/CD.

const path = require('path');

module.exports = {
  // ... other webpack configurations
  cache: {
    type: 'filesystem', // Enable filesystem caching
    buildDependencies: {
      // Add files that, if changed, will cause the entire cache to invalidate
      config: [__filename], // Invalidate cache if webpack.config.js changes
    },
    cacheDirectory: path.resolve(__dirname, '.webpack_cache'), // Specify cache directory
  },
  output: {
    filename: '[name].[contenthash].js', // Use contenthash for cache busting
    path: path.resolve(__dirname, 'public/dist'), // Output directory
    clean: true, // Clean the output directory before emit.
  },
  // ... other configurations like module, plugins, etc.
};

Diagnostic Steps:

  • Cache Directory: Ensure the `cacheDirectory` is correctly set and is included in your CI’s caching mechanism (e.g., GitLab CI’s `paths` or GitHub Actions’ `actions/cache`).
  • `buildDependencies`: Properly configure `buildDependencies` to invalidate the cache only when necessary. Including `__filename` is standard.
  • `contenthash`: Use `[contenthash]` in `output.filename` for robust cache busting. This ensures that only files with changed content get new hashes.
  • `clean: true`: While useful for local development, `clean: true` in CI might be redundant if you’re managing output via artifacts and cache invalidation. It can add overhead. Consider disabling it if your CI artifact strategy is robust.

Analyzing Webpack Build Performance

When builds are slow, profiling is key. Webpack’s `–profile` and `–json` flags are invaluable.

# In your CI script or package.json
"scripts": {
  "build": "webpack --mode production --profile --json > webpack-stats.json"
}

This command generates a `webpack-stats.json` file. You can then upload this file and analyze it using tools like:

  • Webpack Analyse: https://webpack.js.org/guides/code-splitting/#preventing-the-few-big-bundles-problem (official guide)
  • Webpack Bundle Analyzer: A popular plugin that visualizes bundle content.
  • Official Webpack Analyse Tool: https://webpack.js.org/api/stats/#stats-object

Diagnostic Steps:

  • Identify Large Modules: Look for unexpectedly large modules or dependencies in the analysis. This might indicate unnecessary libraries being included or inefficient code splitting.
  • Plugin Overhead: Some plugins can significantly increase build times. Analyze the time spent by each plugin.
  • Loader Performance: Check the performance of loaders (e.g., Babel, Sass). Ensure they are configured efficiently and are not processing files unnecessarily.
  • Parallelism: For CPU-bound tasks like transpilation, consider using `thread-loader` or `parallel-webpack` to leverage multiple CPU cores, though this adds complexity.

Tailwind CSS Compilation in CI

Tailwind CSS, especially with its Just-In-Time (JIT) engine, is generally fast. However, in CI, the primary concern is ensuring it processes only the necessary files and that the output is correctly integrated.

Optimizing Tailwind’s `content` Configuration

The `content` array in `tailwind.config.js` tells Tailwind where to scan for class names. An overly broad configuration can slow down builds.

/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [
    "./templates/**/*.php", // Scan all PHP files in templates directory
    "./src/**/*.js",       // Scan all JS files in src directory
    "./components/**/*.vue", // Example for Vue components
  ],
  theme: {
    extend: {},
  },
  plugins: [],
}

Diagnostic Steps:

  • Specificity: Ensure the `content` paths are as specific as possible. Avoid overly broad globs like `**/*.*` if not necessary.
  • File Types: Only include file types that actually contain Tailwind classes. If your PHP templates don’t directly embed classes but use templating logic, adjust accordingly.
  • CI Environment Differences: Sometimes, file paths or permissions can differ slightly between local development and CI. Verify that the paths in `content` are accessible and correct in the CI environment.
  • Purging Unused CSS: Tailwind’s JIT engine automatically purges unused CSS based on the `content` configuration. If you’re not seeing expected classes removed, double-check the `content` paths.

Integrating Tailwind with Build Tools (Vite/Webpack)

Tailwind is typically integrated as a PostCSS plugin within Vite or Webpack. Ensure the PostCSS configuration is correct.

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

export default defineConfig({
    plugins: [
        laravel({
            input: ['resources/css/app.css', 'resources/js/app.js'],
            refresh: true,
        }),
    ],
    css: {
        postcss: {
            plugins: [
                require('tailwindcss'),
                require('autoprefixer'),
            ],
        },
    },
});
// webpack.config.js example
const tailwindcss = require('tailwindcss');
const autoprefixer = require('autoprefixer');

module.exports = {
  // ...
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          'style-loader', // or MiniCssExtractPlugin.loader
          'css-loader',
          {
            loader: 'postcss-loader',
            options: {
              postcssOptions: {
                plugins: [
                  tailwindcss,
                  autoprefixer,
                ],
              },
            },
          },
        ],
      },
    ],
  },
  // ...
};

Diagnostic Steps:

  • PostCSS Configuration: Verify that `tailwindcss` and `autoprefixer` are correctly listed in the `postcss.plugins` array. The order matters; Tailwind typically comes first.
  • CSS Entry Points: Ensure your main CSS file (e.g., `resources/css/app.css`) imports Tailwind directives (`@tailwind base; @tailwind components; @tailwind utilities;`).
  • CI Environment Setup: Confirm that `postcss`, `tailwindcss`, and `autoprefixer` are installed as dependencies (`npm install –save-dev …`).

Legacy PHP Integration and Asset Manifests

The most significant challenge in legacy PHP projects is integrating the compiled assets. Modern frameworks often provide helpers to read asset manifests (like Vite’s `manifest.json` or Webpack’s `manifest.json`). In a core PHP setup, you’ll need to implement this manually.

Reading Asset Manifests in PHP

Assume your build process outputs a `manifest.json` file in `public/build/`. You need PHP code to read this and generate the correct `` or `