Skip to content

Commit

Permalink
ViteHelper and documentation update
Browse files Browse the repository at this point in the history
  • Loading branch information
dakujem committed Jan 3, 2023
1 parent 51c3acb commit 724d641
Show file tree
Hide file tree
Showing 2 changed files with 188 additions and 18 deletions.
146 changes: 128 additions & 18 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,27 +61,55 @@ More info in the [Vite's Backend integration guide](https://vitejs.dev/guide/bac
> so that you don't have to move the build files manually after each build.

## Bridge usage
### Troubleshooting configuration

Either `ViteBridge::makePassiveEntryLocator` friction reducer can be used,
or custom entry locator setup can be composed.
The simplest way to test if the configuration works is by dropping these snippets
into your PHP-served HTML templates and observing the output.

To pre-generate cache for production, use `ViteBundleLocator::populateCache`.
Start the development servers (both Vite `npm run serve` and PHP),\
then drop this into the HTML template:
```php
<?php echo Dakujem\Peat\ViteHelper::populateDevelopmentAssets('src/main.js', 'http://localhost:5173'); ?>
```
It should produce `<script>` tags to development assets and the JS app should load from the server.\
If it does not, check the entry name and the Vite server URL and port.
The entry name should align with `build.rollupOptions.input` option.

Next, to test a bundle,\
build a bundle by running `npm run build`,\
move the dist files into your PHP server public root directory (or configure `build.outDir` option),\
then replace the previous snippet with this one (replace `my-js-widget` with a proper dir):
```php
<?php echo Dakujem\Peat\ViteHelper::extractAssets('src/main.js', './my-js-widget/manifest.json', '/my-js-widget'); ?>
```
It should produce `<script>` and `<link>` tags for your JS and CSS.\
Pay attention to the path to the manifest file, as it will change according to where you run the snippet from. Adjust as needed.\
Understand that `'./my-js-widget/manifest.json'` is a server path, while `'/my-js-widget'` is part of a URL prefixing the assets
(that is, where you moved the dist files to, relative to the PHP script in your public root).\
Also note that '/my-js-widget' is absolute, you may need to add your project's base path or use relative offsets (see below).

To get asset URLs (or HTML tags), use the `ViteLocatorContract::entry` method (see the example below).
Once this works, I suggest you move on to configure the bridge service (see below).

> 💡
>
> If none of the above works, read the [Vite's Backend integration guide](https://vitejs.dev/guide/backend-integration.html),
> try to figure out what HTML serves your JS app correctly,
> then compare it to what Peat outputs
> and tweak the variables accordingly.
### Cache

It is also possible to improve performance
by exporting the manifest contents into a PHP cache file,
then including it instead of parsing the JSON file.
## Bridge usage

This is achieved by calling `ViteBuildLocator::populateCache()`
as one of the build steps during the deployment/ci process.
The most straight-forward way is to register `ViteBridge` as a service in your service container.

Depending on your running environment,
this service would create a suitable "entry locator" (`ViteLocatorContract`),
which populates assets for Vite entries.

To get asset URLs (or HTML tags), use the `ViteLocatorContract::entry` method (see the example below).


## Example
### Example

Assume JS sources are located in `<project>/js/src` and the public dir is `<project>/public`,
`my-js-widget` may be replaced with any path.
Expand All @@ -103,38 +131,120 @@ export default defineConfig({
});
```

Register a service in your service container:
Configure `ViteBridge` service along these lines:
```php
$bridgeService = new ViteBridge(
manifestFile: ROOT_DIR . '/public/my-js-widget/manifest.json',
cacheFile: TEMP_DIR . '/vite.php', // can be any writable file
assetPath: 'my-js-widget', // relative path from /public to the dir where the manifest is located
assetPathPrefix: '/my-js-widget', // all asset paths from the manifest will be prefixed by this value
devServerUrl: 'http://localhost:5173',
);
```

And use it:
And use it directly:
```php
$locator = $bridgeService->makePassiveEntryLocator(useDevServer: $isDevelopment);
$html = (string) $locator->entry('src/main.js');
```

Then later in a template
```php
<head>
<?php echo $html; ?>
</head>
```

The above will feed all the necessary HTML tags for `main.js` entrypoint to the `$html` variable,
for both the _devleopment server_ and any _bundle_ (depending on the `$isDevelopment` variable).

You may want to register a method that uses the locator to be called from within your templates.
You may want to register a method that uses the locator to be called from within your templates, something like this
```php
$vite = function (string $entryName) use ($locator) {
return $locator->entry($entryName)
}
```

Then later in a template you would only call
```php
<head>
<?php echo $vite('src/main.js'); ?>
</head>
```

In Twig or Latte, for example, you may register a filter to be used as follows
```twig
<head>
{{ vite('src/main.js') }}
</head>
```


To populate cache, run:
## Production setup

In production environments, performance is critical.

To avoid reading and parsing the JSON manifest file on every request,
Peat allows you to parse the JSON manifest contents once and export them as a PHP file.
Peat then includes that optimized file instead of reading the JSON manifest.

> 💡
>
> Be sure to enable this caching mechanism in production environments.
> In high load scenarios, including a tiny PHP file is **much faster** than parsing a JSON file on every request.
To populate the cache file for production, call `ViteBundleLocator::populateCache`.
```php
$bridgeService->populateCache();
```

However,
this file **must be re-populated** every time the manifest file changes (on every Vite build).

This is achieved by calling `ViteBuildLocator::populateCache()`
as one of the build steps during the deployment/CI process.

> If you are not using a deployment pipeline or CI for deployment,
> I suggest you compare file timestamps of the cache file and the manifest file,
> or include the cache file in your cache-purging process.

## Handling relative paths

So far we used **absolute paths** to the assets. Which is **recommended**.

> 💡
>
> The `assetPathPrefix` should contain the **project's base path** plus path to the manifest file and should be absolute.
However, if you need to use relative paths, you are able to.

`ViteLocatorContract::entry` method accepts second parameter called **"relative offset"**,
which is designed for cases where `assetPathPrefix` needs to be prefixed per-call.

The parameter should be used in scripts that are not located in public document root.\
The parameter should typically contain strings like `..`, `../..`, etc. leading to the public root.

Do not use this parameter when using absolute paths, as it will break the generated URIs.


## Advanced use

Instead of using `ViteBridge::makePassiveEntryLocator` friction reducer,
a custom entry locator setup can be composed.
The locator must implement `ViteLocatorContract` interface.

Two locators are provided to help you compose your own locator setup:
- `CollectiveLocator` works with plain callables or other locators to create a locator stack (fallback)
- `ConditionalLocator` allows to add runtime conditions to enable/disable a locator in a stack

See `ViteBridge` source code for inspiration.


## Compatibility

Please note that this tool (Peat) is tightly coupled with the workings of Vite.

Currently, Peat supports Vite versions `2` and above.
Currently, Peat supports Vite versions `v2`, `v3`, `v4` and later.

| PHP | Peat | Vite.js |
|:----------|:-----|:----------|
Expand Down
60 changes: 60 additions & 0 deletions src/ViteHelper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<?php

declare(strict_types=1);

namespace Dakujem\Peat;

use RuntimeException;

/**
* A friction reducer used for simple scripts and easy integration, or for configuration troubleshooting.
*
* Do not use this in high-load production environments for performance reasons (see readme for information).
*
* @author Andrej Rypak <[email protected]>
*/
final class ViteHelper
{
/**
* This is used to extract assets (HTML tags) for a given entry point (typically `main.js`) from a manifest file
* generated by Vite. The manifest is generated when building a bundle by running `vite build`.
* This can directly be echoed into your HTML template.
*
* This call does not use cache and reads the manifest file directly.
*
* @param string $entryName typically `main.js`
* @param string $manifestFile server path to Vite-generated manifest file
* @param string $assetPathPrefix absolute or relative path from your document root (/public, /www, /web, etc.) to the dir where the manifest file is located
* @param string|null $relativeOffset offset of the current script to the public root; this value comes before $assetPathPrefix and is used when $assetPathPrefix contains a relative path
* @return ViteEntryAsset
*/
public static function extractAssets(
string $entryName,
string $manifestFile,
string $assetPathPrefix = '',
?string $relativeOffset = null
): ViteEntryAsset {
$bridgeService = new ViteBridge($manifestFile, null, $assetPathPrefix);
$locator = $bridgeService->makePassiveEntryLocator();
$entry = $locator->entry($entryName, $relativeOffset);
if ($entry === null) {
throw new RuntimeException(sprintf('Vite entry named %s not found in given manifest located at %s.', $entryName, $manifestFile));
}
return $entry;
}

/**
* This is used to populate assets (HTML tags) for a given entry point (typically `main.js`)
* when using the development server of Vite (e.g. localhost).
*
* @param string $entryName typically `main.js`
* @param string $developmentServerUrl the URL where Vite server listens, by default this would be http://localhost:5173
* @return ViteEntryAsset
*/
public static function populateDevelopmentAssets(
string $entryName,
string $developmentServerUrl
): ViteEntryAsset {
return (new ViteServerLocator($developmentServerUrl))->entry($entryName);
}
}

0 comments on commit 724d641

Please sign in to comment.