| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148 |
- import fs from "node:fs/promises";
- import {constants} from "node:fs";
- import os from "os";
- import path from "path";
- import htmlMinifier from "html-minifier-terser";
- import esbuild from "esbuild";
- import forceRelativePath from "./relativePathPlugin.js";
- const parseComponent = async (file, prod)=>{
- const dir = path.dirname(file);
- let data = {};
- if(path.extname(file) === ".neovan"){
- data = await getNeovanData(file);
- }else{
- data = await parseHtml(file);
- }
- const bundle = await createBundle(data, prod);
- if(data.tmpDir) fs.rm(data.tmpDir, {recursive: true, force: true});
- return bundle;
- }
- const getNeovanData = async (index)=>{
- const neovan = await fs.readFile(index, "utf-8");
- const parentPath = path.dirname(index);
-
- const html = neovan.slice(neovan.indexOf("<@html>") + 11, neovan.indexOf("<@/html>"));
- let css = "";
- let js = "";
- const cssIndex = neovan.indexOf("<@style>");
- if(cssIndex >= 0){
- css = neovan.slice(cssIndex + 8, neovan.indexOf("<@/style>"));
- }
- const jsIndex = neovan.indexOf("<@script>")
- if(jsIndex >= 0){
- js = neovan.slice(jsIndex + 9, neovan.indexOf("<@/script>"));
- }
- const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "neovan-"));
- const cssFile = path.join(tmpDir, `${path.basename(index, ".neovan")}.css`);
- const jsFile = path.join(tmpDir, `${path.basename(index, ".neovan")}.js`);
- await Promise.all([
- fs.writeFile(cssFile, css),
- fs.writeFile(jsFile, js)
- ]);
- return {
- html: html,
- css: cssFile,
- js: jsFile,
- dir: parentPath,
- tmpDir: tmpDir
- };
- }
- const parseHtml = async (index)=>{
- const parentPath = path.dirname(index);
- const basename = path.basename(index, ".html");
- const cssPath = path.join(parentPath, `${basename}.css`);
- const jsPath = path.join(parentPath, `${basename}.js`);
- const proms = [
- fs.readFile(index, "utf-8"),
- fs.access(cssPath, constants.F_OK),
- fs.access(jsPath, constants.F_OK)
- ];
- let [html, css, js] = await Promise.allSettled(proms);
- return {
- html: html.value,
- css: css.status === "fulfilled" ? cssPath : null,
- js: js.status === "fulfilled" ? jsPath : null,
- dir: parentPath
- };
- }
- const createBundle = async (data, prod)=>{
- const entryPoints = [];
- if(data.css) entryPoints.push(data.css);
- if(data.js) entryPoints.push(data.js);
- data.html = await addComponents(data.html, data.dir);
- const plugins = [];
- if(data.tmpDir) plugins.push(forceRelativePath(data.dir));
- const esbuildProm = esbuild.build({
- entryPoints: entryPoints,
- bundle: true,
- minify: prod,
- write: false,
- plugins: plugins,
- outdir: "/"
- });
- const htmlProm = htmlMinifier.minify(data.html, {
- collapseBooleanAttributes: true,
- collapseInlineTagWhitespace: true,
- collapseWhitespace: true,
- decodeEntities: true,
- html5: true,
- includeAutoGeneratedTags: false,
- noNewlinesBeforeTagClose: true,
- removeComments: true,
- useShortDoctype: true
- });
- const [buildData, html] = await Promise.all([esbuildProm, htmlProm]);
- const comps = {html: html};
- for(let i = 0; i < buildData.outputFiles.length; i++){
- const ext = path.extname(buildData.outputFiles[i].path).replace(".", "");
- comps[ext] = buildData.outputFiles[i].text;
- }
- return mergeFiles(comps);
- }
- const mergeFiles = (comps)=>{
- let cssIndex = comps.html.indexOf("</head>");
- cssIndex = cssIndex < 0 ? 0 : cssIndex;
- comps.css = comps.css ? `<style>${comps.css}</style>` : "";
- const html = `${comps.html.slice(0, cssIndex)}${comps.css}${comps.html.slice(cssIndex)}`;
- let jsIndex = html.indexOf("</body>");
- jsIndex = jsIndex < 0 ? html.length : jsIndex;
- comps.js = comps.js ? `<script>${comps.js}</script>` : "";
- return `${html.slice(0, jsIndex)}${comps.js}${html.slice(jsIndex)}`;
- }
- const addComponents = async (html, dir)=>{
- let importStart = 0;
- for(let i = 0; i < html.length; i++){
- if(html[i] === "@"){
- if(html[i-1] === "<"){
- importStart = i + 1;
- }else if(html[i+1] === ">"){
- const importString = html.substring(importStart, i).trim();
- const comp = await parseComponent(path.join(dir, importString))
- html = html.slice(0, importStart - 2) + comp + html.slice(i+2);
- }
- }
- }
- return html;
- }
- export default parseComponent;
|