Skip to content

Instantly share code, notes, and snippets.

@k1sul1
Created June 8, 2022 11:44
Show Gist options
  • Save k1sul1/8d513550ef3e33ed094dcda75ad54c7b to your computer and use it in GitHub Desktop.
Save k1sul1/8d513550ef3e33ed094dcda75ad54c7b to your computer and use it in GitHub Desktop.
"Basic" Vite & WP theme setup
<?php
namespace k1;
class ViteAsset {
public $file = null;
public $src = null;
public $isEntry = false;
public $isDynamicEntry = false;
public $dynamicImports = null;
public $imports = null;
public $css = null;
public $assets = null;
public function __construct($rawAsset) {
$this->file = $rawAsset->file;
$this->src = $rawAsset->src ?? null;
$this->isEntry = $rawAsset->isEntry ?? false;
$this->isDynamicEntry = $rawAsset->isDynamicEntry ?? false;
$this->dynamicImports = $rawAsset->dynamicImports ?? null;
$this->imports = $rawAsset->imports ?? null;
$this->css = $rawAsset->css ?? null;
$this->assets = $rawAsset->assets ?? null;
}
}
class Vite {
public $manifest = [];
// handle prefix
public $hp = "k1";
public $buildDir = "dist";
public $serverUrl = "http://localhost:8888";
public $serverPort = 8888;
protected $dev = false;
public function __construct(string $path) {
$this->manifest = (array) json_decode(file_get_contents($path));
if ($this->devServerExists()) {
$this->dev = true;
}
}
public function isDev() {
return $this->dev;
}
public function isCSS(string $filename) {
return strpos($filename, '.css') !== false;
}
public function isJS(string $filename) {
// $js = strpos($filename, '.js') !== false;
// $ts = strpos($filename, '.js') !== false;
if (strpos($filename, '.js') !== false) {
return true;
} else if (strpos($filename, '.ts') !== false) {
return true;
} else {
return false;
}
}
public function enqueueJS(string $filename, $dependencies = [], $inFooter = true) {
$handle = basename($filename);
$handle = "{$this->hp}-$handle";
wp_enqueue_script(
$handle,
$filename,
$dependencies,
null,
$inFooter
);
add_filter('script_loader_tag', function($tag, $h, $src) use ($handle) {
if ($h === 'vite/client-js') {
// continue as normal
} else if ($handle !== $h) {
return $tag;
}
$type = strpos($src, '-legacy') > 0 ? 'nomodule' : 'type="module"';
$tag = '<script id="' . esc_attr($handle) . '"' . $type . ' src="' . esc_url($src) . '"></script>';
return $tag;
} , 10, 3);
return $handle;
}
public function enqueueCSS(string $filename, $dependencies = []) {
$handle = basename($filename);
$handle = "{$this->hp}-$handle";
wp_enqueue_style(
$handle,
$filename,
$dependencies,
null,
);
return $handle;
}
public function maybeEnqueueCSS(ViteAsset $asset, array $handles = []) {
if ($asset->css && !$this->isDev()) {
$files = $asset->css;
foreach ($files as $i => $css) {
$cssFile = $this->withBuildDirectory($css);
$handles[] = $this->enqueueCSS($cssFile);
}
}
return $handles;
}
public function enqueue(string $assetName, $dependencies = [], $options = []) {
$asset = $this->getAsset($assetName);
if (!$asset) {
$message = "Unable to enqueue asset $assetName. It wasn't present in the {$this->name} manifest. ";
throw new \Exception($message);
}
$isJS = $this->isJS($asset->file);
$filename = $this->getAssetFilename($asset);
$imports = $asset->imports;
$handles = [];
if ($isJS) {
$handles[] = $this->enqueueJS($filename, $dependencies, true);
}
if ($imports) {
foreach ($imports as $import) {
$subAsset = $this->getAsset($import);
$handles = $this->maybeEnqueueCSS($subAsset, $handles);
}
}
$handles = $this->maybeEnqueueCSS($asset, $handles);
return $handles;
}
public function getAsset(string $assetName) {
$raw = $this->manifest[$assetName];
return !empty($raw) ? new ViteAsset($raw) : false;
}
public function getAssetFilename(ViteAsset $asset, $forBrowser = true) {
// $asset = $this->getAsset($assetName);
if (!$this->isDev()) {
$filename = $asset->file;
$filename = $this->withBuildDirectory($filename, $forBrowser);
} else {
// $filename = $this->serverUrl . '/' . $assetName;
$filename = $this->serverUrl . '/' . $asset->src;
}
return $filename;
}
/**
* The asset manifest doesn't know of WP, it assumes the files are available in webroot.
*/
public function withBuildDirectory(string $filename, $forBrowser = true) {
return ($forBrowser ? \get_stylesheet_directory_uri() : \get_stylesheet_directory()) . "/{$this->buildDir}/$filename";
}
/**
* This is the best way I found to check for the existence.
*
* fopen throws warnings if it can't find whatever it's looking for. I suppressed those with STFU operator and cleared the error.
*
* If I don't do that, you get to enjoy a php-error class in wp-admin which adds whitespace. Don't ask me how long it took me to figure that out.
*/
private function devServerExists(){
if (\k1\isProd()) {
return false;
}
// This should work on Mac & Linux. It does not work from the client!
// If you're not using Docker, first, what is wrong with you? Second, I've got you covered.
$file = @fopen("http://host.docker.internal:{$this->serverPort}/@vite/client", "r");
if (!$file) {
error_clear_last();
// Not running on Docker? This should work.
$file = @fopen($this->serverUrl . "/@vite/client", "r");
if (!$file) {
error_clear_last();
return false;
}
}
fclose($file);
return true;
}
}
<?php
require_once 'classes/class.vite.php';
// could also be a global if you don't care, in my case it's stored in a wrapper class
$app->manifests['vite'] = new \k1\Vite(__DIR__ . '/dist/manifest.json');
function injectViteIfDev() {
$app = app();
if ($app->manifests['vite']->isDev()) { ?>
<script type="module" src="http://localhost:8888/@vite/client"></script>
<script type="module">
import RefreshRuntime from 'http://localhost:8888/@react-refresh'
RefreshRuntime.injectIntoGlobalHook(window)
window.$RefreshReg$ = () => {}
window.$RefreshSig$ = () => (type) => type
window.__vite_plugin_react_preamble_installed__ = true
</script><?php
}
}
add_action('admin_head', '\k1\injectViteIfDev');
add_action('wp_head', '\k1\injectViteIfDev');
$localizeData = [
'lang' => $app->i18n->getLanguage(),
'path' => get_stylesheet_directory_uri(),
'wpurl' => get_site_url(),
'i18n' => $strings,
];
remove_action( 'wp_head', 'print_emoji_detection_script', 7 );
add_action('wp_enqueue_scripts', function() use ($app, $localizeData) {
$build = $app->manifests['vite'];
if (!$build->isDev()) {
// Legacy bundle for browsers that don't support modules.
// Only gets downloaded by older browsers,
// which is why I don't care about this issue:
// https://github.com/vitejs/vite/issues/2062#issuecomment-782385406
$legacyPolyfill = $build->enqueue('vite/legacy-polyfills')[0];
$handles = $build->enqueue('src/client-legacy.tsx', ['react', 'react-dom', $legacyPolyfill]);
}
// This is the modern bundle. This one also imports CSS.
// $handles = $build->enqueue('src/js/app.js', $handles[0]);
// React is included in the vendor deps.
$handles = $build->enqueue('src/client.tsx', ['react', 'react-dom'] /* ['react'] */);
wp_localize_script($handles[0], 'wptheme', $localizeData);
});
add_action('admin_enqueue_scripts', function() use ($app, $localizeData) {
$build = $app->manifests['vite'];
// React is included in the vendor deps.
$handles = $build->enqueue('src/admin.tsx', []);
wp_localize_script($handles[0], 'wptheme', $localizeData);
});
add_action('enqueue_block_editor_assets', function() use ($app, $localizeData) {
$build = $app->manifests['vite'];
$handles = $build->enqueue('src/gutenberg.tsx', []);
wp_localize_script($handles[0], 'wptheme', $localizeData);
});
import { defineConfig } from "vite"
import react from "@vitejs/plugin-react"
import { viteExternalsPlugin } from "vite-plugin-externals"
import { resolve, basename } from "path"
import legacy from "@vitejs/plugin-legacy"
const themeFolder = basename(__dirname)
// https://vitejs.dev/config/
export default defineConfig({
server: {
port: 8888,
origin: "http://localhost:8888",
},
plugins: [
react(),
viteExternalsPlugin({
react: "React",
"react-dom": "ReactDOM",
}),
legacy({
targets: ["defaults", "not IE 11"],
}),
],
base: `/wp-content/themes/${themeFolder}/dist/`,
build: {
manifest: true,
minify: true,
target: "es2015",
rollupOptions: {
input: {
client: resolve(__dirname, "src/client.tsx"),
admin: resolve(__dirname, "src/admin.tsx"),
gutenberg: resolve(__dirname, "src/gutenberg.tsx"),
},
},
},
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment