PluginProbe
FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration / 1.95
FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration v1.95
2.0.15 2.0.12 2.0.10 2.0.4 2.0.1 2.0.0 1.95.3 1.95.2 1.95 1.91.6 trunk 1.11 1.12 1.13 1.20 1.21 1.22 1.23 1.30 1.31 1.32 1.35 1.40 1.41 1.45 All 41 releases
fluent-boards / app / Vite.php

Vite.php in FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration 1.95, at app/Vite.php

421 lines 12.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace FluentBoards\App;
4
5 use FluentBoards\Framework\Support\Arr;
6
7 class Vite
8 {
9 private array $moduleScripts = [];
10 private bool $isScriptFilterAdded = false;
11 private string $viteHostProtocol = 'http://';
12 private string $viteHost = 'localhost';
13 private string $vitePort = '5175';
14 private string $resourceDirectory = 'resources/';
15
16 protected static ?Vite $instance = null;
17 public ?string $lastJsHandle = null;
18 private ?array $manifestData = null;
19 private array $enqueuedChunkCss = [];
20
21 public function __construct()
22 {
23 $serverConfigPath = FLUENT_BOARDS_PLUGIN_PATH . 'config' . DIRECTORY_SEPARATOR . 'vite.json';
24 if (file_exists($serverConfigPath)) {
25 $serverConfig = json_decode(file_get_contents($serverConfigPath));
26 $this->viteHost = $serverConfig->host ?: $this->viteHost;
27 $this->viteHostProtocol = $serverConfig->protocol ?: $this->viteHostProtocol;
28 $this->vitePort = $serverConfig->port ?: $this->vitePort;
29 }
30
31 add_filter('script_loader_tag', [$this, 'maybeConvertToModule'], 999, 3);
32 }
33
34 public function maybeConvertToModule($tag, $handle, $src): string
35 {
36 $isViteScript = false;
37 $assetBase = FLUENT_BOARDS_PLUGIN_URL . 'assets/';
38
39 if (strpos($src, 'localhost:' . $this->vitePort) !== false || strpos($src, '@vite/client') !== false) {
40 $isViteScript = true;
41 }
42
43 if (in_array($handle, $this->moduleScripts)) {
44 $isViteScript = true;
45 }
46
47 if (!$this->shouldServeViaDevServer()) {
48 $assetPatterns = [
49 $assetBase . 'admin/',
50 ];
51 foreach ($assetPatterns as $pattern) {
52 if (strpos($src, $pattern) !== false && strpos($src, '.js') !== false) {
53 $excludePatterns = ['/libs/', '/vendor/', 'purify.min.js'];
54 $isExcluded = false;
55 foreach ($excludePatterns as $exclude) {
56 if (strpos($src, $exclude) !== false) {
57 $isExcluded = true;
58 break;
59 }
60 }
61 if (!$isExcluded) {
62 $isViteScript = true;
63 break;
64 }
65 }
66 }
67 }
68
69 if ($isViteScript) {
70 if (strpos($tag, 'type="module"') !== false || strpos($tag, "type='module'") !== false) {
71 return $tag;
72 }
73 $tag = preg_replace('/<script\s+/', '<script type="module" crossorigin ', $tag);
74 }
75
76 return $tag;
77 }
78
79 private static function getInstance(): Vite
80 {
81 if (static::$instance === null) {
82 static::$instance = new static();
83 if (!static::$instance->usingDevMode() || !static::$instance->isViteServerRunning()) {
84 (static::$instance)->loadViteManifest();
85 }
86 }
87
88 return static::$instance;
89 }
90
91 private function loadViteManifest()
92 {
93 if (!empty($this->manifestData)) {
94 return;
95 }
96
97 $manifestPath = FLUENT_BOARDS_PLUGIN_PATH . 'config' . DIRECTORY_SEPARATOR . 'vite_config.php';
98
99 if (file_exists($manifestPath)) {
100 $this->manifestData = require $manifestPath;
101 }
102
103 if (empty($this->manifestData)) {
104 $this->manifestData = [];
105 }
106 }
107
108 public static function enqueueScript($handle, $src, $dependency = [], $version = null, $inFooter = false): Vite
109 {
110 return static::getInstance()->enqueue_script(
111 $handle, $src, $dependency, $version, $inFooter
112 );
113 }
114
115 private function enqueue_script($handle, $src, $dependency = [], $version = null, $inFooter = false): Vite
116 {
117 $this->moduleScripts[] = $handle;
118 $this->lastJsHandle = $handle;
119
120 if (!$this->isScriptFilterAdded) {
121 add_filter('script_loader_tag', function ($tag, $handle, $src) {
122 return $this->addModuleToScript($tag, $handle, $src);
123 }, 10, 3);
124 $this->isScriptFilterAdded = true;
125 }
126
127 if ($this->shouldServeViaDevServer()) {
128 $srcPath = $this->getVitePath() . $src;
129 } else {
130 $assetFile = $this->getFileFromManifest($src);
131 $srcPath = $this->getProductionFilePath($assetFile);
132 }
133
134 if (empty($srcPath)) {
135 return $this;
136 }
137
138 $version = empty($version) ? FLUENT_BOARDS_PLUGIN_VERSION : $version;
139
140 wp_enqueue_script($handle, $srcPath, $dependency, $version, $inFooter);
141
142 return $this;
143 }
144
145 private function addModuleToScript($tag, $handle, $src): string
146 {
147 if (in_array($handle, $this->moduleScripts)) {
148 if ($this->usingDevMode() || strpos($src, 'assets/') !== false) {
149 $tag = '<script type="module" crossorigin src="' . esc_url($src) . '" id="' . esc_attr($handle) . '-js"></script>' . "\n";
150 } else {
151 $tag = '<script type="module" src="' . esc_url($src) . '" id="' . esc_attr($handle) . '-js"></script>' . "\n";
152 }
153 }
154 return $tag;
155 }
156
157 private function getFileFromManifest($src)
158 {
159 if (isset($this->manifestData[$this->resourceDirectory . $src])) {
160 return $this->manifestData[$this->resourceDirectory . $src];
161 }
162
163 return '';
164 }
165
166 private function getProductionFilePath($file): string
167 {
168 if (!isset($file['file'])) {
169 return '';
170 }
171
172 $assetPath = static::getAssetPath();
173 $this->ensureChunkCssIsLoaded($file);
174
175 return ($assetPath . $file['file']);
176 }
177
178 private array $mergedIntoStyleCss = [
179 'admin/vendor.css',
180 'admin/vendor-element-plus.css',
181 ];
182
183 private function ensureChunkCssIsLoaded($file)
184 {
185 $assetPath = static::getAssetPath();
186 $cssFiles = $this->collectChunkCssFiles($file);
187
188 foreach ($cssFiles as $cssPath) {
189 if (isset($this->enqueuedChunkCss[$cssPath])) {
190 continue;
191 }
192
193 if (in_array($cssPath, $this->mergedIntoStyleCss)) {
194 $this->enqueuedChunkCss[$cssPath] = true;
195 continue;
196 }
197
198 wp_enqueue_style(
199 'fluent_boards_vite_css_' . md5($cssPath),
200 $assetPath . $cssPath,
201 [],
202 FLUENT_BOARDS_PLUGIN_VERSION
203 );
204
205 $this->enqueuedChunkCss[$cssPath] = true;
206 }
207 }
208
209 private function collectChunkCssFiles($file, &$visited = []): array
210 {
211 $cssFiles = [];
212
213 if (!is_array($file)) {
214 return $cssFiles;
215 }
216
217 $fileId = isset($file['file']) ? $file['file'] : md5(wp_json_encode($file));
218 if (isset($visited[$fileId])) {
219 return $cssFiles;
220 }
221 $visited[$fileId] = true;
222
223 if (isset($file['css']) && is_array($file['css'])) {
224 foreach ($file['css'] as $path) {
225 if (is_string($path) && $path !== '') {
226 $cssFiles[] = $path;
227 }
228 }
229 }
230
231 if (isset($file['imports']) && is_array($file['imports'])) {
232 foreach ($file['imports'] as $importKey) {
233 if (!isset($this->manifestData[$importKey]) || !is_array($this->manifestData[$importKey])) {
234 continue;
235 }
236 $cssFiles = array_merge($cssFiles, $this->collectChunkCssFiles($this->manifestData[$importKey], $visited));
237 }
238 }
239
240 return array_values(array_unique($cssFiles));
241 }
242
243 public function with($params)
244 {
245 if (!is_array($params) || !Arr::isAssoc($params) || empty($this->lastJsHandle)) {
246 $this->lastJsHandle = null;
247 return;
248 }
249
250 foreach ($params as $key => $val) {
251 wp_localize_script($this->lastJsHandle, $key, $val);
252 }
253 $this->lastJsHandle = null;
254 }
255
256 public static function enqueueStyle($handle, $src, $dependency = [], $version = null, $media = 'all')
257 {
258 static::getInstance()->enqueue_style(
259 $handle, $src, $dependency, $version, $media
260 );
261 }
262
263 private function enqueue_style($handle, $src, $dependency = [], $version = null, $media = 'all')
264 {
265 if ($this->shouldServeViaDevServer()) {
266 $srcPath = $this->getVitePath() . $src;
267 } else {
268 $assetFile = $this->getFileFromManifest($src);
269 $srcPath = $this->getProductionFilePath($assetFile);
270 }
271
272 if (empty($srcPath)) {
273 return;
274 }
275
276 $version = empty($version) ? FLUENT_BOARDS_PLUGIN_VERSION : $version;
277
278 wp_enqueue_style($handle, $srcPath, $dependency, $version, $media);
279 }
280
281 public static function enqueueStaticScript($handle, $src, $dependency = [], $version = null, $inFooter = false): Vite
282 {
283 $version = empty($version) ? FLUENT_BOARDS_PLUGIN_VERSION : $version;
284
285 return static::getInstance()->enqueue_static_script(
286 $handle, $src, $dependency, $version, $inFooter
287 );
288 }
289
290 private function enqueue_static_script($handle, $src, $dependency = [], $version = null, $inFooter = false): Vite
291 {
292 $version = empty($version) ? FLUENT_BOARDS_PLUGIN_VERSION : $version;
293
294 wp_enqueue_script(
295 $handle,
296 $this->getStaticEnqueuePath($src),
297 $dependency,
298 $version,
299 $inFooter
300 );
301
302 return $this;
303 }
304
305 private function getStaticEnqueuePath($path): string
306 {
307 if ($this->shouldServeViaDevServer()) {
308 return $this->getVitePath() . $path;
309 }
310
311 return $this->get_asset_url($path);
312 }
313
314 public static function enqueueStaticStyle($handle, $src, $dependency = [], $version = null, $media = 'all')
315 {
316 $version = empty($version) ? FLUENT_BOARDS_PLUGIN_VERSION : $version;
317
318 static::getInstance()->enqueue_static_style(
319 $handle, $src, $dependency, $version, $media
320 );
321 }
322
323 private function enqueue_static_style($handle, $src, $dependency = [], $version = null, $media = 'all')
324 {
325 $version = empty($version) ? FLUENT_BOARDS_PLUGIN_VERSION : $version;
326
327 wp_enqueue_style(
328 $handle,
329 $this->getStaticEnqueuePath($src),
330 $dependency,
331 $version,
332 $media
333 );
334 }
335
336 public static function underDevelopment(): bool
337 {
338 return static::getInstance()->usingDevMode();
339 }
340
341 public function usingDevMode(): bool
342 {
343 $app = App::getInstance();
344 return $app->config->get('app.env') === 'dev';
345 }
346
347 private function shouldServeViaDevServer(): bool
348 {
349 return $this->usingDevMode() && $this->isViteServerRunning();
350 }
351
352 private function isViteServerRunning(): bool
353 {
354 static $isRunning = null;
355
356 if ($isRunning !== null) {
357 return $isRunning;
358 }
359
360 $viteUrl = $this->viteHostProtocol . $this->viteHost . ':' . $this->vitePort . '/@vite/client';
361
362 $response = wp_remote_get($viteUrl, [
363 'timeout' => 1,
364 'sslverify' => false
365 ]);
366
367 $isRunning = !is_wp_error($response) && wp_remote_retrieve_response_code($response) === 200;
368
369 return $isRunning;
370 }
371
372 public function getVitePath(): string
373 {
374 $protocol = rtrim($this->viteHostProtocol, ':/');
375 $host = rtrim($this->viteHost, '/');
376 $port = $this->vitePort;
377 $resource = ltrim($this->resourceDirectory, '/');
378
379 return sprintf('%s://%s:%s/%s', $protocol, $host, $port, $resource);
380 }
381
382 public static function getAssetUrl($path = ''): string
383 {
384 return esc_url(static::getInstance()->get_asset_url($path) ?? '');
385 }
386
387 private function get_asset_url($path = ''): string
388 {
389 if ($this->shouldServeViaDevServer()) {
390 return $this->getVitePath() . $path;
391 }
392
393 return FLUENT_BOARDS_PLUGIN_URL . 'assets/' . ltrim($path, '/');
394 }
395
396 static function getAssetPath(): string
397 {
398 return FLUENT_BOARDS_PLUGIN_URL . 'assets/';
399 }
400
401 public static function injectViteClient()
402 {
403 $vite = static::getInstance();
404
405 if ($vite->shouldServeViaDevServer()) {
406 $protocol = rtrim($vite->viteHostProtocol, ':/');
407 $host = rtrim($vite->viteHost, '/');
408 $port = $vite->vitePort;
409
410 $viteClientUrl = sprintf('%s://%s:%s/@vite/client', $protocol, $host, $port);
411 $handle = 'fluent_boards_vite_client';
412
413 if (!in_array($handle, $vite->moduleScripts, true)) {
414 $vite->moduleScripts[] = $handle;
415 }
416
417 wp_enqueue_script($handle, $viteClientUrl, [], null, false);
418 }
419 }
420 }
421