mirror of
https://github.com/xoseperez/espurna.git
synced 2026-02-20 09:41:35 +01:00
244 lines
6.9 KiB
JavaScript
244 lines
6.9 KiB
JavaScript
import * as path from 'node:path';
|
|
import * as url from 'node:url';
|
|
import * as fs from 'node:fs/promises';
|
|
|
|
import { minify as htmlMinifyImpl } from 'html-minifier-terser';
|
|
|
|
import { MODULE_PRESETS } from './html/preset.mjs';
|
|
import { maybeInline, needElement } from './html/inline.mjs';
|
|
import { defineConfig } from 'vite';
|
|
import { JSDOM } from 'jsdom';
|
|
|
|
import {
|
|
HTML_DIR,
|
|
VENDOR_DIR,
|
|
PRESET_DIR,
|
|
BUILD_DIR,
|
|
} from './gulpfile.mjs';
|
|
|
|
// vite build pipeline is simplified compared to the gulp one
|
|
// assets are mostly handled through the rollup <-> vite
|
|
//
|
|
// two parts are not
|
|
// - index.html pre-generation has to happen before build starts
|
|
// - modules stripping before and after index.html is pre-gened
|
|
//
|
|
// compared to (some) of the external plugins
|
|
// - https://www.npmjs.com/package/vite-plugin-singlefile
|
|
// https://www.npmjs.com/package/vite-plugin-inline-source
|
|
//
|
|
// html assets are sourced from the OutputBundle after the build
|
|
// no extra config modifications; html inline handled differently
|
|
//
|
|
// - https://www.npmjs.com/package/vite-plugin-html
|
|
// https://www.npmjs.com/package/vite-plugin-html-inject
|
|
//
|
|
// inline-source custom element used instead of templating
|
|
// package JSDOM dependency instead of plugin parser(s)
|
|
|
|
/** @import { PluginOption } from 'vite' */
|
|
|
|
/**
|
|
* @param {import("./html/preset.mjs").Modules} modules
|
|
* @returns {PluginOption}
|
|
*/
|
|
function stripModules(modules) {
|
|
return {
|
|
name: stripModules.name,
|
|
enforce: 'pre',
|
|
async transformIndexHtml(html) {
|
|
const dom = new JSDOM(html);
|
|
const doc = dom.window.document;
|
|
|
|
for (const elem of Array.from(doc.querySelectorAll('[class*=module-]'))) {
|
|
if (!needElement(elem, modules)) {
|
|
elem.parentElement?.removeChild(elem);
|
|
}
|
|
}
|
|
|
|
return dom.serialize();
|
|
}
|
|
};
|
|
}
|
|
|
|
/**
|
|
* @returns {PluginOption}
|
|
*/
|
|
function inlineHtmlPre() {
|
|
const sources = [];
|
|
|
|
return {
|
|
name: inlineHtmlPre.name,
|
|
enforce: 'pre',
|
|
configureServer(server) {
|
|
server.watcher.on('change', (file) => {
|
|
if (file.endsWith('.html')) {
|
|
server.ws.send({
|
|
type: 'full-reload',
|
|
path: '*',
|
|
});
|
|
}
|
|
});
|
|
},
|
|
async transformIndexHtml(html) {
|
|
const dom = new JSDOM(html);
|
|
const doc = dom.window.document;
|
|
|
|
for (const elem of Array.from(doc.querySelectorAll('inline-source'))) {
|
|
const src = elem.getAttribute('src');
|
|
if (!src) {
|
|
continue;
|
|
}
|
|
|
|
const data = await fs.readFile(path.join(HTML_DIR, src));
|
|
elem.outerHTML = data.toString();
|
|
|
|
sources.push(src);
|
|
}
|
|
|
|
return dom.serialize();
|
|
}
|
|
};
|
|
}
|
|
|
|
/**
|
|
* @returns {PluginOption}
|
|
*/
|
|
function inlineAssetsPost() {
|
|
return {
|
|
name: inlineAssetsPost.name,
|
|
apply: 'build',
|
|
enforce: 'post',
|
|
async transformIndexHtml(html, ctx) {
|
|
// possibly, everything was already handled by some other plugin
|
|
if (!ctx.bundle) {
|
|
return html;
|
|
}
|
|
|
|
/**
|
|
* vite is expected to clean-up src=..., so no need to resolve(src)
|
|
* bundle at this point should contain every remaining asset on the page
|
|
* @param {string} src
|
|
*/
|
|
function load(src) {
|
|
const output = (ctx.bundle ?? {})[src];
|
|
if (!output) {
|
|
return '';
|
|
}
|
|
|
|
let code = '';
|
|
if (output.type === 'asset' && typeof output.source === 'string') {
|
|
code = output.source;
|
|
}
|
|
if (output.type === 'chunk') {
|
|
code = output.code;
|
|
}
|
|
|
|
return code;
|
|
}
|
|
|
|
/**
|
|
* output bundle should be kept empty, only index.html is written
|
|
* @param {string} src
|
|
*/
|
|
function post(src) {
|
|
delete (ctx.bundle ?? {})[src];
|
|
}
|
|
|
|
const dom = new JSDOM(html);
|
|
const doc = dom.window.document;
|
|
|
|
for (const elem of Array.from(doc.querySelectorAll('link,script'))) {
|
|
await maybeInline(dom, elem, {load, post});
|
|
}
|
|
|
|
return dom.serialize();
|
|
}
|
|
};
|
|
}
|
|
|
|
/**
|
|
* @returns {PluginOption}
|
|
*/
|
|
function htmlMinify() {
|
|
return {
|
|
name: htmlMinify.name,
|
|
apply: 'build',
|
|
enforce: 'post',
|
|
async transformIndexHtml(html) {
|
|
html = await htmlMinifyImpl(html, {
|
|
collapseWhitespace: true,
|
|
removeComments: true,
|
|
minifyCSS: false,
|
|
minifyJS: false,
|
|
});
|
|
|
|
return html;
|
|
},
|
|
};
|
|
}
|
|
|
|
/**
|
|
* @param {string} name
|
|
* @returns {PluginOption}
|
|
*/
|
|
function rename(name) {
|
|
return {
|
|
name: rename.name,
|
|
apply: 'build',
|
|
enforce: 'post',
|
|
generateBundle(_, bundle) {
|
|
bundle['index.html'].fileName = `index.${name}.html`;
|
|
},
|
|
};
|
|
}
|
|
|
|
export default defineConfig(async ({ mode }) => {
|
|
if (!MODULE_PRESETS.has(mode)) {
|
|
throw new Error(`'mode' has to be one of: ${Array.from(MODULE_PRESETS).join(', ')}`);
|
|
}
|
|
|
|
const preset = path.join(PRESET_DIR, mode);
|
|
const constants = url.pathToFileURL(
|
|
path.join(preset, 'constants.mjs'));
|
|
|
|
const { MODULES: modules } = await import(constants.href);
|
|
|
|
return {
|
|
// build order is declared by the plugin, but make sure 'pre' happen exactly like this
|
|
// stripModules has to also help out the inlineHtmlPre, since it is not aware of the module deps
|
|
plugins: [
|
|
stripModules(modules),
|
|
inlineHtmlPre(),
|
|
stripModules(modules),
|
|
inlineAssetsPost(),
|
|
htmlMinify(),
|
|
rename(mode),
|
|
],
|
|
// vite-specific overrides, both for the build and importmap helper script
|
|
resolve: {
|
|
alias: [
|
|
{find: '/vendor', replacement: VENDOR_DIR},
|
|
{find: '@build-preset', replacement: preset},
|
|
],
|
|
},
|
|
// one should be very careful when injecting relative paths in the html / js / configs
|
|
// step into the HTML_DIR by default (aka `vite ... html/` in the cli)
|
|
root: HTML_DIR,
|
|
envDir: preset,
|
|
// rollup settings stay mostly the same, with the exception of the @jaames/iro workaround
|
|
build: {
|
|
modulePreload: {
|
|
polyfill: false,
|
|
},
|
|
emptyOutDir: true,
|
|
outDir: BUILD_DIR,
|
|
rollupOptions: {
|
|
treeshake: {
|
|
moduleSideEffects: () => false,
|
|
},
|
|
},
|
|
},
|
|
};
|
|
});
|