diff --git a/Plugin.php b/Plugin.php index b28b534..634dbc0 100644 --- a/Plugin.php +++ b/Plugin.php @@ -1,9 +1,12 @@ singleton(Bundler::class, Config::get('wpstudio.assetsmanifest::bundler')); + app()->singleton(ManifestReader::class, fn() => new ManifestReader( + Theme::getActiveTheme()->getPath( + sprintf( + '%s/%s/manifest.json', + Theme::getActiveTheme()->getDirName(), + Config::get('wpstudio.assetsmanifest::vite_path_build') + ) + ), + )); } /** @@ -53,7 +66,7 @@ class Plugin extends PluginBase public function registerComponents() { return [ - Manifest::class => 'manifest', + ]; } @@ -98,11 +111,14 @@ class Plugin extends PluginBase { return [ 'filters' => [ - 'manifest' => [TwigFilters::class, 'manifest'], + 'encoreAsset' => [TwigFilters::class, 'encoreAsset'], ], 'functions' => [ - 'manifest' => [TwigFilters::class, 'manifest'], + 'encoreAsset' => [TwigFilters::class, 'encoreAsset'], 'hmrAssets' => [TwigFilters::class, 'hmrAssets'], + 'viteEntrypointStyles' => [TwigFilters::class, 'viteEntrypointStyles'], + 'viteEntrypointAssets' => [TwigFilters::class, 'viteEntrypointAssets'], + 'viteDevClientScriptTag' => [TwigFilters::class, 'viteDevClientScriptTag'], ], ]; } diff --git a/README.md b/README.md new file mode 100644 index 0000000..8fbb4eb --- /dev/null +++ b/README.md @@ -0,0 +1,14 @@ +# Env file example + +## Vitejs +```bash +VITE_DEV_SERVER_PROTOCOL=http +VITE_DEV_SERVER_HOST=localhost +VITE_DEV_SERVER_PORT=3000 + +VITE_PATH_SRC=assets/src +VITE_DIR_ENTRYPOINTS=entrypoints +VITE_PATH_BUILD=assets/build +VITE_ENTRYPOINTS_FILE=entrypoints.json +VITE_DEV_ENABLED=true +``` diff --git a/classes/ManifestReader.php b/classes/ManifestReader.php index 4250802..88fdc52 100644 --- a/classes/ManifestReader.php +++ b/classes/ManifestReader.php @@ -21,7 +21,7 @@ class ManifestReader implements Arrayable throw new AssetsManifestException('Not readable: ' . $manifestPath); } - $this->manifest = collect(json_decode(file_get_contents($manifestPath))); + $this->manifest = collect(json_decode(file_get_contents($manifestPath), true)); } public function get(string $assetName) diff --git a/classes/TwigFilters.php b/classes/TwigFilters.php index 8b6c369..1a45803 100644 --- a/classes/TwigFilters.php +++ b/classes/TwigFilters.php @@ -1,28 +1,43 @@ get(Manifest::class); + $bundler = app()->get(Bundler::class); + assert($bundler instanceof Bundler); + $bundler->validateBundlerType(Bundler::BUNDLER_WEBPACK_ENCORE); + assert($bundler instanceof WebpackEncoreBundler); - assert($manifest instanceof Manifest); - - return $manifest->getManifestReader()->get($assetName); + return $bundler->getEntrypoint($assetName); } public static function hmrAssets() { if (\Config::get('app.debug')) { - $manifest = app()->get(Manifest::class); - assert($manifest instanceof Manifest); + $bundler = app()->get(Bundler::class); + assert($bundler instanceof Bundler); + $bundler->validateBundlerType(Bundler::BUNDLER_WEBPACK_ENCORE); + assert($bundler instanceof WebpackEncoreBundler); + + $manifestReader = app()->get(ManifestReader::class); + assert($bundler instanceof ManifestReader); $startsWithSubstring = 'vendors-node_modules'; - foreach ($manifest->getManifestReader()->toArray() as $assetName => $assetsFullPath) { + foreach ($manifestReader->toArray() as $assetName => $assetsFullPath) { if (starts_with($assetName, $startsWithSubstring)) { if (ends_with($assetName, '.css')) { echo '' . PHP_EOL; @@ -33,4 +48,48 @@ class TwigFilters } } } + + /** + * @param string $entrypointName + * @return string + * @throws AssetsManifestException + */ + public static function viteEntrypointStyles(string $entrypointName): string + { + $bundler = app()->get(Bundler::class); + assert($bundler instanceof Bundler); + $bundler->validateBundlerType(Bundler::BUNDLER_VITE); + assert($bundler instanceof ViteBundler); + + return $bundler->getEntrypointStylesheets($entrypointName); + } + + /** + * @param string $entrypointName + * @return string + * @throws AssetsManifestException + */ + public static function viteEntrypointAssets(string $entrypointName): string + { + $bundler = app()->get(Bundler::class); + assert($bundler instanceof Bundler); + $bundler->validateBundlerType(Bundler::BUNDLER_VITE); + assert($bundler instanceof ViteBundler); + + return $bundler->getEntrypointAssets($entrypointName); + } + + public static function viteDevClientScriptTag() + { + $bundler = app()->get(Bundler::class); + assert($bundler instanceof Bundler); + $bundler->validateBundlerType(Bundler::BUNDLER_VITE); + assert($bundler instanceof ViteBundler); + + if ($bundler->isViteDevEnabled()) { + return $bundler->getViteDevClientScriptTag(); + } + + return ''; + } } diff --git a/classes/bundlers/Bundler.php b/classes/bundlers/Bundler.php new file mode 100644 index 0000000..734c3eb --- /dev/null +++ b/classes/bundlers/Bundler.php @@ -0,0 +1,45 @@ + WebpackEncoreBundler::class, + self::BUNDLER_VITE => ViteBundler::class, + ]; + + protected ManifestReader $manifestReader; + + public function __construct(ManifestReader $manifestReader) + { + $this->manifestReader = $manifestReader; + } + + public function getBundlerType(): string + { + return array_flip(self::$bundlers)[get_class($this)]; + } + + public function validateBundlerType(string $bundlerType) + { + if ($this->getBundlerType() != $bundlerType) { + throw new AssetsManifestException( + sprintf( + 'Expected bundler type is %s, but actual %s', + $bundlerType, + $this->getBundlerType() + ) + ); + } + } + + public function getEntrypoint(string $entrypointPathInManifest): array + { + return $this->manifestReader->get($entrypointPathInManifest); + } +} diff --git a/classes/bundlers/ViteBundler.php b/classes/bundlers/ViteBundler.php new file mode 100644 index 0000000..e1769ba --- /dev/null +++ b/classes/bundlers/ViteBundler.php @@ -0,0 +1,166 @@ +getViteEntrypoint($entrypointName); + + $tags = collect(); + + if ($entrypoint->hasImports()) { + $entrypoint->getImports()->filter(fn(string $entrypointNameFromImports) => !$this->hasInjected($entrypointNameFromImports))->each( + fn(string $entrypointNameFromImports) => $this->injectAsset( + $entrypointNameFromImports, + $tags, + $this->getEntrypointAssets($entrypointNameFromImports) + ) + ); + } + if ($entrypoint->hasCss()) { + $entrypoint->getCss()->filter(fn(string $cssAssetRelativeFilePath) => !$this->hasInjected($cssAssetRelativeFilePath))->each( + fn(string $cssAssetRelativeFilePath) => $this->injectAsset( + $cssAssetRelativeFilePath, + $tags, + $this->getStylesheetTag($this->getViteAssetUrl($cssAssetRelativeFilePath)) + ) + ); + } + + if (!$this->hasInjected($entrypointName)) { + $this->injectAsset($entrypointName, $tags, $this->getScriptTag($entrypointName, $entrypoint)); + } + + return $tags->implode(PHP_EOL); + } + + public function getViteDevClientScriptTag(): string + { + return sprintf( + '', + sprintf( + '%s/@vite/client', + $this->getViteDevServerAddress(), + ) + ); + } + + /** + * @param string $entrypointName + * @return string + * @throws AssetsManifestException + */ + public function getEntrypointStylesheets(string $entrypointName): string + { + if ($this->isViteDevEnabled()) { + return ''; + } + + $entrypoint = $this->getViteEntrypoint($entrypointName); + + $stylesheetTags = collect(); + + if ($entrypoint->hasCss()) { + $entrypoint + ->getCss() + ->filter(fn(string $cssAssetRelativeFilePath) => !$this->hasInjected($cssAssetRelativeFilePath)) + ->each( + fn(string $cssAssetRelativeFilePath) => $this->injectAsset( + $cssAssetRelativeFilePath, + $stylesheetTags, + $this->getStylesheetTag($this->getViteAssetUrl($cssAssetRelativeFilePath) + ) + ) + ); + } + + return $stylesheetTags->implode(PHP_EOL); + } + + private function getStylesheetTag(string $href): string + { + return sprintf( + '', + $href, + ); + } + + private function getScriptTag(string $entrypointName, ViteEntrypoint $entrypoint): string + { + return sprintf( + '', + $this->isViteDevEnabled() ? + sprintf( + '%s/%s', + $this->getViteDevServerAddress(), + $this->getViteEntrypointSrc($entrypointName) + ) : + $this->getViteAssetUrl($entrypoint->getFile()), + !$this->isViteDevEnabled() ? 'async defer' : '', + ); + } + + private function getViteEntrypointSrc(string $entrypointName): string + { + return sprintf( + '%s/%s/%s', + Config::get('wpstudio.assetsmanifest::vite_path_src'), + Config::get('wpstudio.assetsmanifest::vite_dir_entrypoints'), + $entrypointName, + ); + } + + private function getViteEntrypoint(string $entrypointName): ViteEntrypoint + { + return new ViteEntrypoint($this->getEntrypoint( + !starts_with($entrypointName, '_') ? + $this->getViteEntrypointSrc($entrypointName) : + $entrypointName + )); + } + + private function getViteAssetUrl(string $relativeAssetsFilePath): string + { + return sprintf( + '/themes/%s/%s/%s', + Theme::getActiveTheme()->getDirName(), + Config::get('wpstudio.assetsmanifest::vite_path_build'), + $relativeAssetsFilePath + ); + } + + public function isViteDevEnabled(): bool + { + return Config::get('wpstudio.assetsmanifest::vite_dev_enabled'); + } + + private function getViteDevServerAddress(): string + { + return sprintf( + '%s://%s:%s', + Config::get('wpstudio.assetsmanifest::vite_dev_server_protocol'), + Config::get('wpstudio.assetsmanifest::vite_dev_server_host'), + Config::get('wpstudio.assetsmanifest::vite_dev_server_port'), + ); + } + + private function hasInjected(string $assetsUid): bool + { + return in_array($assetsUid, $this->assetsInjected); + } + + private function injectAsset(string $assetsUid, Collection $tags, string $assetTag): void + { + $this->assetsInjected[] = $assetsUid; + + $tags->add($assetTag); + } +} diff --git a/classes/bundlers/WebpackEncoreBundler.php b/classes/bundlers/WebpackEncoreBundler.php new file mode 100644 index 0000000..9736894 --- /dev/null +++ b/classes/bundlers/WebpackEncoreBundler.php @@ -0,0 +1,6 @@ +file = $entrypoint['file']; + + if (array_key_exists('src', $entrypoint)) { + $this->isEntry = $entrypoint['src']; + } + + if (array_key_exists('isEntry', $entrypoint)) { + $this->isEntry = $entrypoint['isEntry']; + } + + if (array_key_exists('imports', $entrypoint)) { + $this->imports = collect($entrypoint['imports']); + } + + if (array_key_exists('css', $entrypoint)) { + $this->css = collect($entrypoint['css']); + } + } + + public function getIsEntry(): bool + { + if (isset($this->isEntry) && $this->isEntry) { + return true; + } + + return false; + } + + public function hasImports() + { + return isset($this->imports) && $this->imports; + } + + public function getImports(): Collection + { + return $this->imports; + } + + public function hasCss() + { + return isset($this->css) && $this->css; + } + + public function getCss(): Collection + { + return $this->css; + } + + public function getFile(): string + { + return $this->file; + } + + public function getSrc(): string + { + return $this->src; + } +} diff --git a/components/Manifest.php b/components/Manifest.php deleted file mode 100644 index e7df347..0000000 --- a/components/Manifest.php +++ /dev/null @@ -1,61 +0,0 @@ - self::LANG_PREFIX . 'name', - 'description' => self::LANG_PREFIX . 'description', - ]; - } - - public function defineProperties(): array - { - return [ - 'path' => [ - 'title' => self::LANG_PREFIX . 'properties.path.title', - 'description' => self::LANG_PREFIX . 'properties.path.description', - 'validationPattern' => '^[^/].*/manifest.json', - 'validationMessage' => self::LANG_PREFIX . 'properties.path.validationMessage', - 'placeholder' => 'assets/build/manifest.json', - 'default' => 'assets/build/manifest.json', - 'showExternalParam' => false, - 'required' => true, - ] - ]; - } - - /** - * @return void - * @throws \Wpstudio\AssetsManifest\Classes\AssetsManifestException - */ - public function init(): void - { - app()->instance(Manifest::class, $this); - - $this->prepareVars(); - } - - /** - * @return void - * @throws \Wpstudio\AssetsManifest\Classes\AssetsManifestException - */ - private function prepareVars(): void - { - $this->manifestReader = new ManifestReader(Theme::getActiveTheme()->getPath() . '/' . $this->property('path')); - } - - public function getManifestReader(): ManifestReader - { - return $this->manifestReader; - } -} diff --git a/components/manifest/default.htm b/components/manifest/default.htm deleted file mode 100644 index ef63b50..0000000 --- a/components/manifest/default.htm +++ /dev/null @@ -1,3 +0,0 @@ -

This is the default markup for component manifest

- -You can delete this file if you want diff --git a/config/config.php b/config/config.php new file mode 100644 index 0000000..301c9bf --- /dev/null +++ b/config/config.php @@ -0,0 +1,10 @@ + env('VITE_DEV_ENABLED', true), + 'vite_dev_server_protocol' => env('VITE_DEV_SERVER_PROTOCOL', 'http'), + 'vite_dev_server_host' => env('VITE_DEV_SERVER_HOST', 'localhost'), + 'vite_dev_server_port' => env('VITE_DEV_SERVER_PORT', '3000'), + 'vite_path_src' => env('VITE_PATH_SRC', 'assets/src'), + 'vite_dir_entrypoints' => env('VITE_DIR_ENTRYPOINTS', 'entrypoints'), + 'vite_path_build' => env('VITE_PATH_BUILD', 'assets/build'), + 'bundler' => \Wpstudio\AssetsManifest\Classes\Bundlers\ViteBundler::class +];