Friday, February 28, 2025

How To Use Vite Build Artifacts in Plain PHP Pages

Introduction

As a developer, I have had the opportunity to migrate several ordinary procedural PHP page websites into Laravel, a popular PHP framework. One of the key benefits of using Laravel is its seamless integration with Vite, a modern development server and build tool. Vite helps to build and manage CSS and JavaScript artifacts, making it easier to develop and maintain large web applications.

However, when migrating a website, it's often desirable to do so in stages, which can lead to a dilemma about how to manage CSS and JS artifacts between the new and old code. This can be further complicated by the fact that most older code runs JavaScript in the global scope, whereas Vite artifacts are generally used in the module scope. In this blog post, we'll explore how to bridge the gap between these two different approaches and leverage the advantages of using NPM to manage JavaScript versions and Vite to process CSS and JavaScript.

Understanding Global and Module Scopes in JavaScript

When including JavaScript on a page, it's important to understand the difference between loading a script normally versus as an ES module, since each behaves differently in terms of scope and import/export capabilities. Let's take a look at an example. In the global scope, variables and functions are attached to the global object (window in browsers):

/* Global scope */
var globalVariable = 'Hello, world!';
console.log(globalVariable); /* Output: Hello, world! */

In contrast, variables and functions defined in the module scope are not attached to the global object:

/* Module scope */
const moduleVariable = 'Hello, world!';
console.log(moduleVariable); /* Output: Hello, world! */
console.log(window.moduleVariable); /* Output: undefined */

The module scope provides a way to encapsulate variables and functions and prevent them from polluting the global namespace.

Considerations:

  • Global scope (classic scripts): Scripts loaded without type="module" run in the global scope. Any top-level variables or functions they define become properties of the window object.
  • Module scope (ES modules): Scripts loaded with type="module" use the JavaScript ES module system. Each module has its own scope, so top-level variables and functions do not automatically become global.
  • Load and execution order: Classic scripts execute in the order they appear. Module scripts are deferred and execute after HTML parsing.
  • Mixing modules and globals: If you include both, module scripts will not automatically see variables from global scripts unless explicitly referenced.

Using Vite Artifacts in Plain PHP Pages

So, how can we use Vite artifacts in plain PHP pages, while still retaining the ability to run JavaScript in the global scope? The answer lies in using the viteStaticCopy plugin to copy the raw JavaScript file managed by NPM into the folder of our choice with the name of our choice.

Install the static copy plugin using NPM:

npm install vite-plugin-static-copy

Here's a complete working Vite configuration, including the viteStaticCopy plugin in our vite.config.js file:

import {defineConfig} from "vite";
import react from "@vitejs/plugin-react";
import laravel from "laravel-vite-plugin";
import {glob} from "glob";
import basicSsl from "@vitejs/plugin-basic-ssl";
import inject from "@rollup/plugin-inject";
import {viteStaticCopy} from 'vite-plugin-static-copy';

/* Have Vite process all these resources */
let inputs = glob.sync(`resources/css/**/*.css`, {nodir: true});
inputs.push(...glob.sync(`resources/scss/**/*.scss`, {nodir: true}));
inputs.push(...glob.sync(`resources/js/**/*.js`, {nodir: true}));
inputs.push(...glob.sync(`resources/js/**/*.jsx`, {nodir: true}));
inputs.push(...glob.sync(`resources/js/**/*.ts`, {nodir: true}));
inputs.push(...glob.sync(`resources/js/**/*.tsx`, {nodir: true}));

export default defineConfig({
    plugins: [
        basicSsl(),
        inject({
            $: "jquery",
            jQuery: "jquery",
        }),
        react(),
        laravel({
            input: inputs,
            refresh: true,
        }),
        viteStaticCopy({
            targets: [
                {
                    src: 'node_modules/jquery/dist/jquery.js',
                    dest: 'assets/vendor',
                    rename: 'jquery.js'
                }
            ]
        })
    ],
    resolve: {
        alias: {
            "@": "/resources/js",
        },
        extensions: [".js", ".ts", ".jsx", ".tsx"],
    },
    build: {
        sourcemap: true,
        rollupOptions: {
            output: {
                entryFileNames: "assets/[name].[hash].js",
                chunkFileNames: "assets/[name].[hash].js",
                assetFileNames: (assetInfo) => {
                    /* Preload fonts only if they are woff or woff2 */
                    if (assetInfo.extname === ".woff2" || assetInfo.extname === ".woff") {
                        return "fonts/[name].[hash][extname]";
                    }
                    return "assets/[name].[hash][extname]";
                },
            },
        },
    },
});

The viteStaticCopy(targets) configuration will copy the jQuery library from the node_modules directory into the assets/vendor directory and rename it to jquery.js. Note that you can choose any destination folder and file name you like.

Run npm run build to generate assets.

We can then use this library in our plain PHP pages, while still retaining the ability to run JavaScript in the global scope:

<?php
const BUILD_PATH = '/build/';

$manifest = json_decode(file_get_contents(public_path('build/manifest.json')), true);

$appCss = BUILD_PATH . $manifest['resources/scss/app.scss']['file'];
$appJs = BUILD_PATH . $manifest['resources/js/app.js']['file'];
$jqueryJs = BUILD_PATH . '/assets/vendor/jquery.js' ;
?>

<!-- The processed CSS file can be linked like any other CSS file -->
<link href="<?= $appCss ?>" rel="stylesheet" type="text/css"/>
<!-- The module type is required if you have used any imports in app.js, which will usually be the case -->
<script type="module" src="<?= $appJs ?>"></script>
<!-- With any un-processed JavaScript files, you can use the normal global scope -->
<script type="text/javascript" src="<?= $jqueryJs ?>"></script>

<script type="module">
    <!-- This uses the jQuery include in the app.js file processed by Vite -->
    $('body').prepend("<h1>Hi!</h1>");
</script>
<script type="text/javascript">
    <!-- This uses the jQuery from the the raw jquery.js file loaded into the global scope -->
    $(function() {
        $('body').append("<h1>Bye!</h1>");
    });
</script>

In this example, we're using the viteStaticCopy plugin to copy the jQuery library into the assets/vendor directory, and then using it in our plain PHP page, while still using Vite to build and manage our CSS and JavaScript artifacts.

Conclusion

In conclusion, using Vite artifacts in plain PHP pages can be a challenge, especially when it comes to managing CSS and JavaScript artifacts between old and new code. However, by leveraging the advantages of using NPM to manage JavaScript versions and Vite to process CSS and JavaScript, we can bridge the gap between these two different approaches.

By understanding the difference between global and module scopes in JavaScript, we can use Vite artifacts in plain PHP pages, while still retaining the ability to run JavaScript in the global scope. The viteStaticCopy plugin provides a way to copy raw JavaScript files managed by NPM into the folder of our choice with the name of our choice, making it easier to use Vite artifacts in plain PHP pages.

I hope this blog post has provided a helpful guide on how to use Vite artifacts in plain PHP pages. By following these steps, you can leverage the advantages of using Vite and NPM to manage your CSS and JavaScript artifacts, while still supporting older code that runs in the global scope.