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
Install packages and autoloader.
yarn install
composer install
Build our assets into the 'build' folder.
yarn build
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
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.
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 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 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.
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')
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.
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
intowordpress/wp-content/plugins
with the folder namewp-vite-starter
- Run the
yarn install
,composer install
andyarn 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:
The following things can also be configured:
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
}
}
})
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: '})();'
}
})
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'
]);
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'
]);
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'}
})
],
});