UNPKG

sveltekit-adapter-html-like

Version:

SvelteKit adapter for HTML-like template engines such as PHP, Blade, Handlebars, etc.

189 lines (162 loc) 5.13 kB
import { basename, dirname, join, relative } from 'node:path'; import { cosmiconfig, defaultLoaders } from 'cosmiconfig'; import { JSDOM } from 'jsdom'; import { minify as minifier } from 'html-minifier-terser'; import { promises as $fs } from 'node:fs'; import crypto from 'node:crypto'; import glob from 'tiny-glob'; import JSON5 from 'json5'; import prettier from 'prettier'; import TOML from '@iarna/toml'; const { readFile, writeFile } = $fs; /** @type {import('./index').AdapterOptions} */ const defaultOptions = { pages: 'build', assets: 'build', fallback: '', precompress: false, minify: false, injectTo: {}, prettify: true, targetExtension: '.html', replace: [] }; export async function transformFiles(builder, userOptions) { const options = { ...defaultOptions, ...userOptions }; const sessionUUID = crypto.randomUUID(); const htmlFiles = await glob('**/*.html', { cwd: options.pages, dot: true, absolute: true, filesOnly: true }); await Promise.all( htmlFiles.map(async (htmlFile) => { const htmlContents = await readFile(htmlFile, 'utf8'); const dom = new JSDOM(htmlContents); const targetElements = options?.injectTo ? Object.keys(options.injectTo) : []; targetElements.map((targetElement) => { if (!['head', 'body'].includes(targetElement)) { builder.log.warn(`Skipping unsupported injection element: ${targetElement}`); return; } const injectToPositions = Object.keys(options.injectTo[targetElement]); injectToPositions.map((injectToPosition) => { if (!['beforebegin', 'afterbegin', 'beforeend', 'afterend'].includes(injectToPosition)) { builder.log.warn( `Skipping unsupported insertAdjacentHTML position: ${injectToPosition}` ); return; } const injectToPositions = Array.isArray(options.injectTo[targetElement][injectToPosition]) ? options.injectTo[targetElement][injectToPosition] : Array(options.injectTo[targetElement][injectToPosition]); injectToPositions.map((injectToText) => { const injectToHash = crypto.createHash('sha256').update(injectToText).digest('hex'); const injectToTag = `<!-- inject:${sessionUUID}:${injectToHash} -->`; if (!options.replace.some((item) => item.from === injectToTag)) { options.replace.push({ from: injectToTag, to: injectToText }); } builder.log.minor(`Injecting to ${injectToPosition}: '${injectToText}'`); dom.window.document[targetElement].insertAdjacentHTML(injectToPosition, injectToTag); }); }); }); let outputHTML; try { outputHTML = options.minify ? await minifier(dom.serialize(), { collapseWhitespace: true, minifyCSS: true, minifyJS: true, removeComments: false, removeRedundantAttributes: true, useShortDoctype: true }) : options.prettify ? prettier.format(dom.serialize(), await getPrettierConfig()) : dom.serialize(); builder.log.minor('Formatting markup'); } catch (err) { builder.log.error('Formatting markup failed'); throw Error(err); } const outFile = `${basename(htmlFile, '.html')}${options.targetExtension}`; const outPath = join(dirname(htmlFile), outFile); const phpContents = options.replace && Object.values(options.replace)?.length ? options.replace.reduce((previousValue, currentValue) => { const replacer = currentValue.many ? 'replaceAll' : 'replace'; if (!currentValue.from.startsWith('<!-- inject:sessionUUID:')) { builder.log.minor( `Replacing ${currentValue.many ? 'all ' : ''}'${currentValue.from}' → '${ currentValue.to }'` ); } return previousValue[replacer](currentValue.from, currentValue.to); }, outputHTML) : outputHTML; try { builder.log.minor(`Writing to ${relative(options.pages, outPath)}`); await writeFile(outPath, phpContents); if (outPath !== htmlFile) { builder.log.minor(`Deleting ${relative(options.pages, htmlFile)}`); builder.rimraf(htmlFile); } } catch (err) { throw Error(err); } }) ); } export async function getPrettierConfig() { const explorer = cosmiconfig('prettier', { searchPlaces: [ 'package.json', '.prettierrc', '.prettierrc.json', '.prettierrc.yaml', '.prettierrc.yml', '.prettierrc.json5', '.prettierrc.js', '.prettierrc.cjs', 'prettier.config.js', 'prettier.config.cjs', '.prettierrc.toml' ], loaders: { '.toml': (filePath, content) => { try { return TOML.parse(content); } catch (error) { error.message = `TOML Error in ${filePath}:\n${error.message}`; throw error; } }, '.json5': (filePath, content) => { try { return JSON5.parse(content); } catch (error) { error.message = `TOML Error in ${filePath}:\n${error.message}`; throw error; } }, noExt: defaultLoaders['.json'] } }); let results = await explorer.search(); if (results) { return results.config; } return { parser: 'html', printWidth: Infinity }; }