Browse Source

+ vitejs support

develop
dimti 2 years ago
parent
commit
0c7f82ab7e
  1. 24
      Plugin.php
  2. 14
      README.md
  3. 2
      classes/ManifestReader.php
  4. 77
      classes/TwigFilters.php
  5. 45
      classes/bundlers/Bundler.php
  6. 166
      classes/bundlers/ViteBundler.php
  7. 6
      classes/bundlers/WebpackEncoreBundler.php
  8. 72
      classes/bundlers/vite/ViteEntrypoint.php
  9. 61
      components/Manifest.php
  10. 3
      components/manifest/default.htm
  11. 10
      config/config.php

24
Plugin.php

@ -1,9 +1,12 @@
<?php namespace Wpstudio\AssetsManifest;
use Backend;
use Cms\Classes\Theme;
use System\Classes\PluginBase;
use Wpstudio\AssetsManifest\Classes\Bundlers\Bundler;
use Wpstudio\AssetsManifest\Classes\ManifestReader;
use Wpstudio\AssetsManifest\Classes\TwigFilters;
use Wpstudio\Assetsmanifest\Components\Manifest;
use Config;
/**
* assetsmanifest Plugin Information File
@ -32,7 +35,17 @@ class Plugin extends PluginBase
*/
public function register()
{
app()->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'],
],
];
}

14
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
```

2
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)

77
classes/TwigFilters.php

@ -1,28 +1,43 @@
<?php namespace Wpstudio\AssetsManifest\Classes;
use Wpstudio\Assetsmanifest\Components\Manifest;
use Wpstudio\AssetsManifest\Classes\Bundlers\Bundler;
use Wpstudio\AssetsManifest\Classes\Bundlers\ViteBundler;
use Wpstudio\AssetsManifest\Classes\Bundlers\WebpackEncoreBundler;
class TwigFilters
{
public static function manifest($assetName): string
/**
* @param string $assetName
* @return string
* @throws AssetsManifestException
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/
public static function encoreAsset(string $assetName): string
{
$manifest = app()->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 '<link rel="stylesheet" href="' . $assetsFullPath . '">' . 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 '';
}
}

45
classes/bundlers/Bundler.php

@ -0,0 +1,45 @@
<?php namespace Wpstudio\AssetsManifest\Classes\Bundlers;
use Wpstudio\AssetsManifest\Classes\AssetsManifestException;
use Wpstudio\AssetsManifest\Classes\ManifestReader;
abstract class Bundler
{
const BUNDLER_WEBPACK_ENCORE = 'webpack-encore';
const BUNDLER_VITE = 'vite';
public static array $bundlers = [
self::BUNDLER_WEBPACK_ENCORE => 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);
}
}

166
classes/bundlers/ViteBundler.php

@ -0,0 +1,166 @@
<?php namespace Wpstudio\AssetsManifest\Classes\Bundlers;
use Cms\Classes\Theme;
use Illuminate\Support\Collection;
use Wpstudio\AssetsManifest\Classes\AssetsManifestException;
use Config;
use Wpstudio\AssetsManifest\Classes\Bundlers\Vite\ViteEntrypoint;
class ViteBundler extends Bundler
{
private array $assetsInjected = [];
public function getEntrypointAssets(string $entrypointName): string
{
$entrypoint = $this->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(
'<script type="module" src="%s"></script>',
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(
'<link rel="stylesheet" href="%s">',
$href,
);
}
private function getScriptTag(string $entrypointName, ViteEntrypoint $entrypoint): string
{
return sprintf(
'<script type="module" src="%s" %s></script>',
$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);
}
}

6
classes/bundlers/WebpackEncoreBundler.php

@ -0,0 +1,6 @@
<?php namespace Wpstudio\AssetsManifest\Classes\Bundlers;
class WebpackEncoreBundler extends Bundler
{
}

72
classes/bundlers/vite/ViteEntrypoint.php

@ -0,0 +1,72 @@
<?php namespace Wpstudio\AssetsManifest\Classes\Bundlers\Vite;
use Illuminate\Support\Collection;
class ViteEntrypoint
{
public string $file;
public ?string $src;
public ?bool $isEntry;
public ?Collection $imports;
public ?Collection $css;
public function __construct(array $entrypoint)
{
$this->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;
}
}

61
components/Manifest.php

@ -1,61 +0,0 @@
<?php namespace Wpstudio\Assetsmanifest\Components;
use Cms\Classes\ComponentBase;
use Cms\Classes\Theme;
use Wpstudio\AssetsManifest\Classes\ManifestReader;
class Manifest extends ComponentBase
{
const LANG_PREFIX = 'wpstudio.assetsmanifest::lang.components.manifest.';
private ?ManifestReader $manifestReader;
public function componentDetails(): array
{
return [
'name' => 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;
}
}

3
components/manifest/default.htm

@ -1,3 +0,0 @@
<p>This is the default markup for component manifest</p>
<small>You can delete this file if you want</small>

10
config/config.php

@ -0,0 +1,10 @@
<?php return [
'vite_dev_enabled' => 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
];
Loading…
Cancel
Save