Skip to content

Lightweight starter kit to use ViteJS 4 for WordPress plugin and theme development. Includes frontend and backend (PHP) utilities

Notifications You must be signed in to change notification settings

brandonkramer/wp-vite-starter

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

62 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

vite logo

WP ViteJS 4 starter kit

Simple lightweight starter kit to make use of Vite's blazing fast and efficient frontend build tool for WordPress plugin and theme development. Comes with packages that provide front-end & back-end (PHP) utilities. The "src" folder includes JS, PostCSS, images, fonts, SVG and a gutenberg block as examples.

You can read more about ViteJS on vitejs.dev


πŸ“š Table of contents

🏁 Quick start

Install packages and autoloader.

yarn install
composer install

Build our assets into the 'build' folder.

yarn build

πŸ’» Other commands

Build assets in development mode.

yarn build-dev

Builds assets in real-time and watches over files.

yarn watch
yarn watch-dev

Start ViteJS dev server.

yarn start

πŸ“¦ What's inside

The idea is to keep the starter kit simple so additional things can be added for different projects, and so it can be integrated into different theme and plugin structures and maintained through node/composer packages.

ViteJS Config

The config is extending a base config from the @wp-strap/vite package through a plugin which is opinionated and configured for WordPress development which can be overwritten. It ensures the following:

  • Updates/refreshes the dev server (HMR/Hot Module Replacement) when a change is made inside PHP files
  • Encapsulates JS bundles to prevent mix-up of global variables (with other plugins/themes) after minification
  • Collects images, SVG and font files from folders and emits them to make them transformable by plugins
  • Esbuild is configured to make ReactJS code work inside .js files instead of the default .jsx
  • Esbuild for minification which is turned off for development mode
  • Esbuild sourcemaps are added for development mode
  • JS entries are automatically included from first-level folders inside the src folder using fast-glob (e.g., js/my-script.js, blocks/my-block.js).
  • CSS entries are also automatically included in the same way, bundled and compiled without importing them into JS files which is more suitable for WordPress projects.
  • Vite Plugin Image Optimizer is included that optimizes images and SVG files that we emit

PostCSS config

PostCSS is added through the Vite base config file and is currently configured with the following:

  • TailwindCSS to add a utility-first CSS framework that scans all of our files, and only generate styles and classes that we use.
  • TailwindCSS nesting to unwrap nested rules similar to SASS.
  • PostCSS import that can consume local files, node modules or web_modules using the @import rule.
  • Autoprefixer to parse CSS and add vendor prefixes to CSS rules using values from Can I Use.
  • PostCSS combine duplicated selectors that detects and combines duplicated CSS selectors.

TailwindCSS config

TailwindCSS is added through the PostCSS config file and is currently only configured with the following:

  • Paths to find TailwindCSS classes for optimization. This will make sure it only generates styles that are needed.
  • A prefix that will be added to all of Tailwind’s generated utility classes. This can be really useful to prevent naming conflicts with other themes and plugins.

Read here more about all the cool stuff you can configure with Tailwind.

PHP / Composer

Composer includes the wp-strap/vite package that exposes some classes that helps you generate asset URLs from the manifest.json that you can register or enqueue. It also enables you to use HMR (hot module replacement) when the ViteJS dev server is running.

The classes follow PSR practices with interfaces, so it can be included trough OOP with dependency injection and IoC containers. It also provides a Facade class that allows you to use static methods anywhere you like if that's your jam.

Example with using the facade:

use WPStrap\Vite\Assets;

// Resolves instance and registers project configurations
Assets::register([
    'dir' => plugin_dir_path(__FILE__), // or get_stylesheet_directory() for themes
    'url' => plugins_url(\basename(__DIR__)) // or get_stylesheet_directory_uri() for themes
    'version' => '1.0.2', // Set a global version (optional)
    'deps' => [ 'scripts' => [], 'styles' => [] ]  // Set global dependencies (optional)
]);

// Listens to ViteJS dev server and makes adjustment to make HMR work
Assets::devServer()->start();

// returns: https://your-site.com/wp-content/plugins/your-plugin/build/js/main.oi4h32d.js
Assets::get('js/main.js') 

// Alternatively you can use these as well which will be more targeted to specific folders
// and for some of the methods you don't need to write the file extension
Assets::js('main') 
Assets::css('main') 
Assets::image('bird-on-black.jpg') 
Assets::svg('instagram') 
Assets::font('SourceSerif4Variable-Italic.ttf.woff2')

// Example of enqueuing the scripts
add_action('wp_enqueue_scripts', function () {

    // You can enqueue & register the tradtional way using global data
    wp_enqueue_script('my-handle', Assets::js('main'), Assets::deps('scripts'), Assets::version());
    wp_enqueue_style('my-handle', Assets::css('main'), Assets::deps('styles'), Assets::version());
    
    // Or use a more simple method that includes the global deps & version
    Assets::enqueueStyle('my-handle', 'main');
    
    // Which also comes with some handy chained methods
    Assets::enqueueScript('my-handle', 'main', ['another-dep'])
        ->useAsync()
        ->useAttribute('key', 'value')
        ->localize('object_name', ['data' => 'data'])
        ->appendInline('<script>console.log("hello");</script>');
});

Example with using instances

use WPStrap\Vite\Assets;
use WPStrap\Vite\AssetsService;
use WPStrap\Vite\DevServer;

// Instantiates the Asset service and registers project configurations
$assets = new AssetsService();
$assets->register([
    'dir' => plugin_dir_path(__FILE__), // or get_stylesheet_directory() for themes
    'url' => plugins_url(\basename(__DIR__)) // or get_stylesheet_directory_uri() for themes
]);

// Listens to ViteJS dev server and makes adjustment to make HMR work
(new DevServer($assets))->start();

$assets->get('js/main.js'); 
$assets->js('main') 
$assets->css('main') 
$assets->image('bird-on-black.jpg') 
$assets->svg('instagram') 
$assets->font('SourceSerif4Variable-Italic.ttf.woff2')

// Traditional 
wp_enqueue_script('my-handle', $this->assets->js('main'), $this->assets->deps('scripts'), $this->assets->version());
wp_enqueue_style('my-handle', $this->assets->css('main'), $this->assets->deps('styles'), $this->assets->version());

// Custom methods
$this->assets->enqueueStyle('my-handle', 'main');
$this->assets->enqueueScript('my-handle', 'main', ['another-dep'])
    ->useAsync()
    ->useAttribute('key', 'value')
    ->localize('object_name', ['data' => 'data'])
    ->appendInline('<script>console.log("hello");</script>');

// You can also use the facade based on this instance.
Assets::setFacade($assets);
Assets::get('css/main.css');

Example with using instances wih functions

use WPStrap\Vite\AssetsInterface;
use WPStrap\Vite\AssetsService;
use WPStrap\Vite\DevServer;

function assets(): AssetsInterface {
     static $assets;
     
     if(!isset($assets)) {
        $assets = (new AssetsService())->register([
            'dir' => plugin_dir_path(__FILE__), 
            'url' => plugins_url(\basename(__DIR__)),
            'version' => '1.0.0'
        ]);
     }
     
     return $assets;
}

(new DevServer(assets()))->start();


add_action('wp_enqueue_scripts', function () {

    // Traditional
    wp_enqueue_script('my-handle', assets()->js('main'), assets()->deps('scripts'), assets()->version());
    wp_enqueue_style('my-handle', assets()->css('main'), assets()->deps('styles'), assets()->version());
    
    // Using custom methods
    assets()->enqueueStyle('my-handle', 'main');
    assets()->enqueueScript('my-handle', ['Main', 'main'], ['another-dep'])
        ->useAsync()
        ->useAttribute('key', 'value')
        ->localize('object_name', ['data' => 'data'])
        ->appendInline('<script>console.log("hello");</script>');
});

Example with using the League Container

use League\Container\Container;
use WPStrap\Vite\Assets;
use WPStrap\Vite\AssetsInterface;
use WPStrap\Vite\AssetsService;
use WPStrap\Vite\DevServer;
use WPStrap\Vite\DevServerInterface;

$container = new Container();
$container->add(AssetsInterface::class)->setConcrete(AssetsService::class)->addMethodCall('register', [
    'dir' => plugin_dir_path(__FILE__), 
    'url' => plugins_url(\basename(__DIR__)) 
]);
$container->add(DevServerInterface::class)->setConcrete(DevServer::class)->addArgument(AssetsInterface::class);

$assets = $container->get(AssetsInterface::class);
$devServer = $container->get(DevServerInterface::class);

$devServer->start();

$assets->get('main/main.css');

// You can also set a PSR container as a facade accessor
Assets::setFacadeAccessor($container);
Assets::get('main/main.css')

DevServer

Assets::devServer()->start(3000'); OR (new DevServer($assets))->start('3000');

The dev server class is responsible for listening to the ViteJS dev server using CURL, checking if it's running locally on port 3000 which you can adjust using the optional param from the start() method as seen above.

If it can validate the dev server is running, it will inject viteJS scripts provided from the dev server, filter all asset urls and load source files instead (from the assets::get(), assets:css(), assets::js() etc. methods), and alter the script tags to make sure the source files can be loaded as modules for HMR.

This should only be run on local/dev environments. As it's using CURL on each request, so you don't want to run this on production.

πŸš€ Setup vite dev server

Setting it up with DDEV

Within DDEV you need to make sure the port we'll use is being exposed and routed accordingly. We will be using port 3000 which is set by default in the ViteJS config. This can be done with this simple add-on: https://github.com/wp-strap/ddev-vite

Let's assume you've done the following:

  • Git clone wp-vite-starter into wordpress/wp-content/plugins with the folder name wp-vite-starter
  • Run the yarn install, composer install and yarn build commands to install all the packages and build + compile the example files from the repo
  • Created a plugin file called wp-vite-starter.php in the plugin folder with the following basic contents: (and activated the plugin)
<?php
/*
Plugin Name: WP Vite Starter
Description: Playground
Version: 1.0.0
*/

use WPStrap\Vite\Assets;

/**
 * Require composer autoloader.
 */
require 'vendor/autoload.php';


Assets::register([
    'dir' => plugin_dir_path(__FILE__),
    'url' => plugins_url(\basename(__DIR__))
]);

Assets::devServer()->start();

\add_action('wp_enqueue_scripts', function () {
    wp_enqueue_script('main', Assets::js('main'));
    wp_enqueue_style('main', Assets::css('main'));
});

You now need to spin up the Vite dev server using SSH so it runs on the local/docker environment using ddev ssh:

ddev ssh
 
cd wordpress/wp-content/plugins/wp-vite-starter

yarn install 
yarn start

Refresh the browser while you're on wp-vite-playground.ddev.site and you should be able to see a script on https://wp-vite-playground.ddev.site:3000/@vite/client

The Assets::devServer()->start() function will listen to this page and inject the scripts into the site. The dev server + HMR should be working now: If you make a change in src/css/main.pcss it should automatically inject the changes into the page without refreshing the page:

hmr-gif

πŸŽ’ Other configurations

The following things can also be configured:

Change rules for asset files that are copied

With the userOptions.assets.rules param you're able to add additional asset folders by adding additional test rules aside to images/svg/fonts, and you can customize the default ones as well:

viteWPConfig({
    assets: {
        rules: {
            images: /png|jpe?g|svg|gif|tiff|bmp|ico/i,
            svg: /png|jpe?g|svg|gif|tiff|bmp|ico/i,
            fonts: /ttf|woff|woff2/i
        }
    }
})

Customize header & footer of bundle encapsulation

You can customize the way it encapsulates bundles by using the userOptions.bundles param:

viteWPConfig({
    bundles: {
        banner: '/*My Custom Project*/(function(){', // Adds a comment before each bundle
        footer: '})();'
    }
})

Change src and build folders

With the root and outDir params you're able to change the source and build folders, name them differently or change the paths.

viteWPConfig({
    root: 'assets',
    outDir: 'dist',
})

You will need to change these in the PHP register method as well.

$assets->register([
    /* .... */
    'root' => 'assets', 
    'outDir' =>> 'dist'
]);

Use an entry point and setup domain folder structure

Aside to root and outDir you can define and set an entry which will try to find asset files from this entry point inside the root folder.

viteWPConfig({
    root: 'src', 
    outDir: 'build', 
    entry: 'Static', // <-----
})

For example if you set root to "src" and entry to "Static" like the above, you're able to maintain different bundles within different domain folders and mix it up with PHP files:

my-custom-plugin/
β”œβ”€β”€ src/                  
β”‚   β”œβ”€β”€ Blocks/
β”‚   β”‚    β”œβ”€β”€ Blocks.php
β”‚   β”‚    └── Static/     
β”‚   β”‚         β”œβ”€β”€ css/  
β”‚   β”‚         β”œβ”€β”€ js/  
β”‚   β”‚         └── images/  
β”‚   β”œβ”€β”€ Admin/             
β”‚   β”‚    β”œβ”€β”€ Admin.php
β”‚   β”‚    └── Static/
β”‚   β”‚         β”œβ”€β”€ css/  
β”‚   β”‚         β”œβ”€β”€ js/  
β”‚   β”‚         └── images/  
β”‚   β”œβ”€β”€ Main/        
β”‚   β”‚    β”œβ”€β”€ Main.php     
β”‚   β”‚    └── Static/
β”‚   β”‚         β”œβ”€β”€ css/  
β”‚   β”‚         β”œβ”€β”€ js/  
β”‚   β”‚         └── images/  
β”œβ”€β”€ build/                  
β”‚   β”œβ”€β”€ css/             
β”‚   β”œβ”€β”€ js/              
β”‚   β”œβ”€β”€ images/          

when using this approach you can use the PHP asset functions a bit differently:

// When you use the get method you need to call the whole path
Assets::get('Main/Static/js/main.js')

// When using these methods you are able to use the first param to point to the domain and second param to point to the file.
Assets::js('Blocks', 'example-block')
Assets::css('Main', 'main')
Assets::image('Admin', 'bird-on-black.jpg')
Assets::svg('Main', 'instagram')

Assets:enqueueScript('my-handle', ['Admin', 'admin-page'])
Assets:enqueueStyle('my-handle', ['Blocks', 'example-block'])

$assets->js('Blocks', 'example-block')
$assets->css('Main', 'main')
$assets->image('Admin', 'bird-on-black.jpg')
$assets->svg('Main', 'instagram')

$assets->enqueueScript('my-handle', ['Admin', 'admin-page'])
$assets->enqueueStyle('my-handle', ['Blocks', 'example-block'])

The entry is set to "Static" by default inside the php register method, when you want to name this differently, you need to configure it here as well.

$assets->register([
      /* .... */
    'entry' =>> 'Assets'
]);

External Dependencies

This is inspired by kucrut; If you have a JavaScript package that relies on WordPress modules, such as @wordpress/i18n, you have the option to define them as externals using the rollup-plugin-external-globals plugin and the wpGlobals() function

yarn add -D rollup-plugin-external-globals
import wpGlobals from '@wp-strap/vite';
import externalGlobals from 'rollup-plugin-external-globals';

export default defineConfig({

    /* ViteJS plugins */
    plugins: [
        
        /* External globals */
        externalGlobals({
            ...wpGlobals(),
            ...{'some-registered-script-handle': 'GlobalVar'}
        })
    ],

});

Releases

No releases published

Packages

No packages published