Closed cgundermann closed 5 months ago
By definition every async component gets bundled. With the current setup of how Kirby injects plugins, code splitting won't work. Kirby will copy the index.js
and every other plugin asset into a dedicated hashed folder inside media/panel
. Relative imports of async components won't work once the plugin is bundled, because the hash is erratic.
However, you can create an assets
folder and put bundled modules into it. I'm doing so in my commercial plugins.
When your bundles are ready, you can get a list of all plugin asset URLs (including the hashed folder names) and pass it to your component. For the section plugin of my Kirby Copilot, it works as follows:
Given your section code:
return [
'copilot' => [
'computed' => [
'assets' => function () {
/** @var \Kirby\Cms\App */
$kirby = $this->kirby();
$plugin = $kirby->plugin('johannschopplich/copilot');
return $plugin
->assets()
->clone()
->map(fn (PluginAsset $asset) => [
'filename' => $asset->filename(),
'url' => $asset->url()
])
->values();
}
]
]
];
I've written asset utils:
/**
* @typedef {object} PluginAsset
* @property {string} filename - The name of the asset.
* @property {string} url - The URL of the asset.
*/
/** @type {PluginAsset[]} */
let _assets = [];
const moduleCache = new Map();
export async function registerPluginAssets(assets) {
if (!Array.isArray(assets)) {
throw new TypeError("Expected an array of assets");
}
if (_assets.length > 0) return;
_assets = assets;
}
export function resolvePluginAsset(filename) {
if (_assets.length === 0) {
throw new Error("Plugin assets are not registered");
}
const asset = _assets.find((asset) => asset.filename === filename);
if (!asset) {
throw new Error(`Plugin asset "${filename}" not found`);
}
return asset;
}
export async function getModule(filename) {
if (!filename.endsWith(".js")) {
filename += ".js";
}
if (moduleCache.has(filename)) {
return moduleCache.get(filename);
}
const asset = resolvePluginAsset(filename);
const mod = await import(/* @vite-ignore */ asset.url);
moduleCache.set(filename, mod);
return mod;
}
Finally, you can lazily import a given module when you need it in your component:
// Do this once when the section or field is loaded
registerPluginAssets(response.assets);
// Then, get you code lazily in any composable or async function
const { createMistral, createOpenAI, streamText } = await getModule("ai");
Dear Johann,
thanks a lot for taking the time to explain and provide an example! This makes things a lot clearer to me. I'll definitely give Your approach a go! =) All the best! ☀️ Cris
Hello Johann,
first of all, thanks so much for kirbyup. It makes panel plugin development so much easier, especially for me as an eternal vue beginner! =)
May I ask if it's generally possible to use code splitting with
kirbyup
or would I have to write my own build script with a custom vite / rollup config? If it's possible, I'd be very happy if You can give me a little hint on how I'd have to set up thekirbyup.config.js
.I have tried to set up an async component, but during build, everything gets bundled into the plugins'
index.js
file. Or is my way of defining an async vue component just wrong?Thanks so much in advance! All the best!