davidhirtz / yii2-vite
Vite integration for Yii 2 framework
Requires
- php: ^8.3
- davidhirtz/yii2-skeleton: ^3.8
Requires (Dev)
- phpstan/phpstan: ^2.1
- phpunit/phpunit: ^12.5
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
Vite for the Yii 2 skeleton: registers an entry's script,
stylesheets and modulepreload links from Vite's manifest, or from the dev server while it runs.
Version 3 requires davidhirtz/yii2-skeleton ^3.8. Projects outside the skeleton stay on ^0.5 (branch v0).
Vite
build.manifest is required. The defaults below expect the build in web/dist:
export default defineConfig({ build: { manifest: true, outDir: 'web/dist', rollupOptions: { input: ['resources/js/app.ts', 'resources/css/print.scss'], }, }, });
Reloading on PHP
Vite does not watch PHP. @tailwindcss/vite reloads the page only for a file its last scan found, so a view created
while the dev server runs neither reloads nor gets its classes until another scanned file changes, and PHP outside the
@source globs never reloads. This plugin reloads for a PHP file Vite has no module for and invalidates the
stylesheets, so Tailwind scans again:
import {defineConfig, isCSSRequest, type ModuleNode, type Plugin} from 'vite'; const reloadPhp = (paths = ['app', 'config', 'messages', 'resources/views']): Plugin => ({ name: 'reload-php', apply: 'serve', configureServer(server) { server.watcher.add(paths); const reload = (file: string): void => { if (!file.endsWith('.php') || server.moduleGraph.getModulesByFile(file)?.size) { return; } const seen = new Set<ModuleNode>(); for (const module of server.moduleGraph.idToModuleMap.values()) { if (isCSSRequest(module.id!)) { server.moduleGraph.invalidateModule(module, seen, Date.now(), true); } } server.ws.send({type: 'full-reload'}); }; server.watcher.on('add', reload); server.watcher.on('change', reload); }, }); export default defineConfig({ plugins: [tailwindcss(), reloadPhp()], });
Configuration
The bundle registers the vite component, so a project configures only what differs from these defaults:
'components' => [ 'vite' => [ 'baseUrl' => '@web/dist', 'basePath' => '@root/web/dist', // `build.outDir`, read for stylesheets to inline 'manifestPath' => '@root/web/dist/.vite/manifest.json', 'useDevServer' => YII_ENV_DEV, 'checkDevServer' => true, 'devServerUrl' => 'http://localhost:5173', 'devServerInternalUrl' => null, // where PHP reaches the dev server, if not at `devServerUrl` 'devServerTimeout' => 0.1, // seconds 'inlineCssMaxSize' => 0, // bytes, uncompressed; `0` links every stylesheet 'preloadJsModule' => true, // a `modulepreload` link for a `registerJsModule()` entry ], ],
While useDevServer is on, every request checks for the dev server by opening a TCP connection to
devServerInternalUrl, at most devServerTimeout long. When it answers, entries load from it with @vite/client
for hot module replacement; when not, from the manifest. checkDevServer => false skips the check and always uses
the dev server.
Inline stylesheets
With inlineCssMaxSize set, an entry whose stylesheets together are no larger than that many bytes renders them as
<style> tags instead of links, saving the render-blocking request. It suits a small site that loads the page once
and swaps the content afterwards (AjaxRouteTrait): registered in the layout, the styles reach the first response
alone. An entry's stylesheets are inlined all or none, so their order holds; one an earlier entry already linked or
inlined stays as it is. Relative url()s and the source map comment are made absolute against baseUrl. The dev
server always links.
'vite' => [ 'inlineCssMaxSize' => 12_000, // roughly 3 kB gzipped ],
An inlined stylesheet is not cached by the browser, so every full page load carries it again.
Usage
use Hirtz\Vite\Vite; // The entry's script tag, its stylesheets and a `modulepreload` link for every chunk it imports Vite::current()->register('resources/js/app.ts'); // A stylesheet entry, with options for the link tags Vite::current()->register('resources/css/print.scss', ['media' => 'print']); // Options of the script tag Vite::current()->register('resources/js/app.ts', jsOptions: ['position' => View::POS_HEAD]); // Imported by the page's module script, its default export called with the arguments (`View::registerJsModule()`). // A `modulepreload` link for the entry starts its request from the head, `'preloadJsModule' => false` leaves it out Vite::current()->registerJsModule('resources/js/gallery.ts', ['selector' => '#gallery']); // The URL of any manifest entry, an image included; registers nothing Vite::current()->getUrl('resources/images/logo.svg');
Script and modulepreload tags carry the view's CSP nonce. A sha384 integrity in the manifest (as written by
e.g. vite-plugin-manifest-sri) is added to both.