Created
June 8, 2022 11:44
-
-
Save k1sul1/8d513550ef3e33ed094dcda75ad54c7b to your computer and use it in GitHub Desktop.
"Basic" Vite & WP theme setup
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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); | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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