| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161 |
- import fs from 'node:fs';
- import path from 'node:path';
- const ASSET_MARKER = '<!-- VITE_ASSETS -->';
- const DEFAULT_ENTRY = 'ui/frontend/js/frontend.js';
- const DEFAULT_TEMPLATES = [
- {
- source: 'ui/frontend/masterpage_template.tpl',
- output: 'ui/frontend/masterpage.tpl',
- },
- {
- source: 'ui/frontend/errors_template.tpl',
- output: 'ui/frontend/errors.tpl',
- },
- ];
- /**
- * Normalizes a path for comparisons with Vite manifest keys.
- *
- * @param {string} filePath Path to normalize.
- * @returns {string} A slash-separated path.
- * @sideEffects None.
- */
- function normalizeManifestPath(filePath) {
- return filePath.replaceAll(path.sep, '/');
- }
- /**
- * Resolves a path relative to the Vite project root.
- *
- * @param {string} projectRoot Vite project root.
- * @param {string} filePath Absolute or project-relative path.
- * @returns {string} An absolute path.
- * @sideEffects None.
- */
- function resolveProjectPath(projectRoot, filePath) {
- return path.resolve(projectRoot, filePath);
- }
- /**
- * Finds the manifest entry for the configured JavaScript entry.
- *
- * @param {Record<string, object>} manifest Parsed Vite manifest.
- * @param {string} projectRoot Vite project root.
- * @param {string} entryPath Configured JavaScript entry path.
- * @returns {object} The matching Vite manifest entry.
- * @sideEffects None.
- */
- function findEntry(manifest, projectRoot, entryPath) {
- const relativeEntry = normalizeManifestPath(path.relative(projectRoot, entryPath));
- const match = Object.entries(manifest).find(([source, asset]) =>
- asset.isEntry && (normalizeManifestPath(source) === relativeEntry || asset.src === relativeEntry),
- );
- if (!match) {
- throw new Error(`Vite manifest entry not found for ${relativeEntry}.`);
- }
- return match[1];
- }
- /**
- * Creates an asset URL from Vite's configured public base.
- *
- * @param {string} baseUrl Vite public base URL.
- * @param {string} assetPath Manifest asset path.
- * @returns {string} The public asset URL.
- * @sideEffects None.
- */
- function createAssetUrl(baseUrl, assetPath) {
- return `${baseUrl.replace(/\/?$/, '/')}${assetPath.replace(/^\/+/, '')}`;
- }
- /**
- * Builds script and stylesheet tags from a Vite manifest entry.
- *
- * @param {object} entry Vite manifest entry.
- * @param {string} baseUrl Vite public base URL.
- * @returns {string} HTML asset tags.
- * @sideEffects None.
- */
- function createAssetTags(entry, baseUrl) {
- const cssTags = (entry.css ?? []).map(
- assetPath => `\t<link rel="stylesheet" href="${createAssetUrl(baseUrl, assetPath)}">`,
- );
- const scriptTag = `\t<script type="module" src="${createAssetUrl(baseUrl, entry.file)}"></script>`;
- return [...cssTags, scriptTag].join('\n');
- }
- /**
- * Replaces the asset marker in a source template and writes its generated output.
- *
- * @param {string} projectRoot Vite project root.
- * @param {{source: string, output: string}} template Template source and output paths.
- * @param {string} assetTags Generated Vite asset tags.
- * @returns {void}
- * @sideEffects Reads and writes template files.
- */
- function generateTemplate(projectRoot, template, assetTags) {
- const sourcePath = resolveProjectPath(projectRoot, template.source);
- const outputPath = resolveProjectPath(projectRoot, template.output);
- const source = fs.readFileSync(sourcePath, 'utf8');
- const markerCount = source.split(ASSET_MARKER).length - 1;
- if (markerCount !== 1) {
- throw new Error(`${sourcePath} must contain exactly one ${ASSET_MARKER} marker.`);
- }
- fs.mkdirSync(path.dirname(outputPath), { recursive: true });
- fs.writeFileSync(outputPath, source.replace(ASSET_MARKER, assetTags), 'utf8');
- }
- /**
- * Creates a Vite plugin that generates F3 templates from the build manifest.
- *
- * @param {{entry?: string, templates?: Array<{source: string, output: string}>}} options Plugin configuration.
- * @returns {import('vite').Plugin} Vite plugin instance.
- * @sideEffects Writes generated templates after a successful build.
- */
- export default function viteTemplatePlugin({
- entry = DEFAULT_ENTRY,
- templates = DEFAULT_TEMPLATES,
- } = {}) {
- let resolvedConfig;
- return {
- name: 'vite-template-plugin',
- apply: 'build',
- /**
- * Stores the resolved Vite configuration for post-build path resolution.
- *
- * @param {import('vite').ResolvedConfig} config Resolved Vite configuration.
- * @returns {void}
- * @sideEffects Updates plugin state.
- */
- configResolved(config) {
- resolvedConfig = config;
- },
- /**
- * Generates F3 templates after Vite has written the manifest.
- *
- * @returns {void}
- * @sideEffects Reads the manifest and writes generated template files.
- */
- closeBundle() {
- const manifestName = resolvedConfig.build.manifest === true
- ? '.vite/manifest.json'
- : resolvedConfig.build.manifest;
- const manifestPath = resolveProjectPath(resolvedConfig.build.outDir, manifestName);
- const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
- const entryPath = resolveProjectPath(resolvedConfig.root, entry);
- const manifestEntry = findEntry(manifest, resolvedConfig.root, entryPath);
- const assetTags = createAssetTags(manifestEntry, resolvedConfig.base);
- templates.forEach(template => generateTemplate(resolvedConfig.root, template, assetTags));
- },
- };
- }
|