Skip to content

Commit

Permalink
refactor: split code to separate files (stage 1) (#2)
Browse files Browse the repository at this point in the history
* refactor: split code to separate files (stage 1)

* 1.0.1
  • Loading branch information
sapachev authored Jan 25, 2023
1 parent cec40fc commit a3a870d
Show file tree
Hide file tree
Showing 9 changed files with 166 additions and 147 deletions.
150 changes: 8 additions & 142 deletions index.ts
Original file line number Diff line number Diff line change
@@ -1,148 +1,14 @@
import { promisify } from "util";
import { exec } from "child_process";
import { access, cp, readFile, readdir, rm, mkdir, writeFile } from "fs/promises";
import { render } from "mustache";
import { DEFAULTS } from "./lib/constants";
import { copyAssets, flush, readConfig } from "./lib/fs";
import { compile } from "./lib/compile";
import { buildTailwind } from "./lib/style";

import { DEFAULTS, SRC_CODE_PATTERN, TAILWIND_STYLE } from "./lib/constants";
import { Config, SatusCode, SnippetVariables, TemplateVariables } from "./lib/interfaces";

async function compile(config: Config) {
const pkg = await readJson(DEFAULTS.PACKAGE);
let vars: TemplateVariables = {
locale: config.locale,
version: pkg.version,
};

const template = await readFile(`${DEFAULTS.THEMES}/${config.theme}/template.html`).then(String);

const codesVars = new Map<SatusCode, TemplateVariables>();
await readdir(`${DEFAULTS.SRC}/${config.locale}/`).then((files) => {
return Promise.all(
files.map(async (file) => {
const srcVars: TemplateVariables = await readJson(`${DEFAULTS.SRC}/${config.locale}/${file}`);
const match = file.match(SRC_CODE_PATTERN);
if (match) {
codesVars.set(Number(match[0]), srcVars);
} else {
vars = { ...vars, ...srcVars };
}
})
);
});

if (codesVars.size > 0) {
await Promise.all(Array.from(codesVars).map(([code, codeVars]) => renderPage(template, { ...vars, ...codeVars, code }))).then(() => {
console.log(`INFO: ${codesVars.size} pages were successfully created`);
});

await readdir(`${DEFAULTS.SNIPPETS}/`).then((files) => {
return Promise.all(
files.map(async (file) => {
const snippet = await readFile(`${DEFAULTS.SNIPPETS}/${file}`).then(String);
return renderSnippet(file, snippet, { locale: config.locale, codes: Array.from(codesVars.keys()) });
})
);
});
} else {
throw new Error("No source data to render error pages");
}
}

async function execTailwind(config: Config) {
if (config.tailwind) {
const input = `${DEFAULTS.THEMES}/${config.theme}/${DEFAULTS.ASSETS}/css/${DEFAULTS.TAILWIND_ENTRY}`;
const output = `${DEFAULTS.DIST}/${config.locale}/${DEFAULTS.ASSETS}/css/${DEFAULTS.TAILWIND_ENTRY.replace(".tcss", ".css")}`;
const cmd = `INPUT="${input}" OUTPUT="${output}" npm run build:tailwind`;

console.log(`INFO: build Tailwind CSS styles`);
console.log(`INFO: run '${cmd}'`);

await promisify(exec)(cmd).then(() => {
console.log(`INFO: Tailwind CSS styles were built`);
});
} else {
console.log(`WARN: Tailwind CSS was disabled in config`);
}
}

async function readJson(path: string) {
return await readFile(path).then(String).then(JSON.parse);
}

async function renderPage(template: string, vars: TemplateVariables) {
if (!vars.code) {
throw new Error("No code variable to render page");
} else if (!vars.locale) {
throw new Error("No locale variable to render page");
}

const path = `${DEFAULTS.DIST}/${vars.locale}/${vars.code}.html`;

console.log(`INFO: render '${path}' page`);
await writeFile(path, render(template, vars), { flag: "w+" });
}

async function renderSnippet(name: string, template: string, vars: SnippetVariables) {
if (!vars.codes) {
throw new Error("No codes list to render config snippet");
} else if (!vars.locale) {
throw new Error("No locale variable to render config snippet");
}

const path = `${DEFAULTS.DIST}/${vars.locale}/${name}`;

console.log(`INFO: render '${path}' config snippet`);
await writeFile(path, render(template, vars), { flag: "w+" });
console.log(`INFO: config snippet '${name}' was successfully created`);
}

async function readConfig(): Promise<Config> {
const config = await readJson(DEFAULTS.CONFIG);

if (!config.theme) {
throw new Error(`Please set theme in your configuration: ${DEFAULTS.CONFIG}`);
} else if (!config.locale) {
throw new Error(`Please set locale in your configuration: ${DEFAULTS.CONFIG}`);
}

return config;
}

async function prepare(config: Config) {
console.log(`INFO: prepare build directory '${DEFAULTS.DIST}'`);

await rm(DEFAULTS.DIST, { force: true, recursive: true });
await mkdir(`${DEFAULTS.DIST}/${config.locale}/`, { recursive: true });
}

async function copyAssets(config: Config) {
console.log(`INFO: copying assets to build directory '${DEFAULTS.DIST}'`);

const path = `${DEFAULTS.THEMES}/${config.theme}/${DEFAULTS.ASSETS}`;

let exists = false;
try {
await access(path);
exists = true;
} catch (_) {}

if (exists) {
await cp(path, `${DEFAULTS.DIST}/${config.locale}/${DEFAULTS.ASSETS}`, {
recursive: true,
// Skip Tailwind styles to copy
filter: (src) => !TAILWIND_STYLE.test(src),
});
} else {
console.log(`INFO: no assets in '${config.theme}' theme directory`);
}
}

readConfig()
readConfig(DEFAULTS.CONFIG)
.then(async (config) => {
await prepare(config);
await flush(DEFAULTS.DIST);
await compile(config);
await execTailwind(config);
await copyAssets(config);
await buildTailwind(config);
await copyAssets(`${DEFAULTS.THEMES}/${config.theme}/${DEFAULTS.ASSETS}`, `${DEFAULTS.DIST}/${DEFAULTS.ASSETS}`);
})
.catch((err) => {
console.error(`
Expand Down
48 changes: 48 additions & 0 deletions lib/compile.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { readFile, readdir } from "fs/promises";

import { DEFAULTS, SRC_CODE_PATTERN } from "./constants";
import { Config, SatusCode, TemplateVariables } from "./interfaces";
import { renderPage, renderSnippet } from "./render";
import { readJson } from "./fs";

export async function compile(config: Config) {
const pkg = await readJson(DEFAULTS.PACKAGE);
let vars: TemplateVariables = {
locale: config.locale,
version: pkg.version,
};

const template = await readFile(`${DEFAULTS.THEMES}/${config.theme}/template.html`).then(String);

const codesVars = new Map<SatusCode, TemplateVariables>();
await readdir(`${DEFAULTS.SRC}/${config.locale}/`).then((files) => {
return Promise.all(
files.map(async (file) => {
const srcVars: TemplateVariables = await readJson(`${DEFAULTS.SRC}/${config.locale}/${file}`);
const match = file.match(SRC_CODE_PATTERN);
if (match) {
codesVars.set(Number(match[0]), srcVars);
} else {
vars = { ...vars, ...srcVars };
}
})
);
});

if (codesVars.size > 0) {
await Promise.all(Array.from(codesVars).map(([code, codeVars]) => renderPage(template, { ...vars, ...codeVars, code }))).then(() => {
console.log(`INFO: ${codesVars.size} pages were successfully created`);
});

await readdir(`${DEFAULTS.SNIPPETS}/`).then((files) => {
return Promise.all(
files.map(async (file) => {
const snippet = await readFile(`${DEFAULTS.SNIPPETS}/${file}`).then(String);
return renderSnippet(file, snippet, { codes: Array.from(codesVars.keys()) });
})
);
});
} else {
throw new Error("No source data to render error pages");
}
}
1 change: 0 additions & 1 deletion lib/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,3 @@ export const DEFAULTS: Defaults = {
};

export const SRC_CODE_PATTERN = /^[0-9]{3}(?=\.json$)/i;
export const TAILWIND_STYLE = /\.tcss$/i;
48 changes: 48 additions & 0 deletions lib/fs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { access, cp, readFile, rm, mkdir } from "fs/promises";

import { Config } from "./interfaces";
import { sourceStyleFilter } from "./style";

export async function readJson(path: string) {
return await readFile(path).then(String).then(JSON.parse);
}

export async function existenceCheck(path: string) {
let exists = false;
try {
await access(path);
exists = true;
} catch (_) {}
}

export async function readConfig(path: string): Promise<Config> {
const config = await readJson(path);

if (!config.theme) {
throw new Error(`Please set theme in your configuration: ${path}`);
} else if (!config.locale) {
throw new Error(`Please set locale in your configuration: ${path}`);
}

return config;
}

export async function flush(path: string) {
console.log(`INFO: prepare build directory '${path}'`);

await rm(path, { force: true, recursive: true });
await mkdir(path, { recursive: true });
}

export async function copyAssets(src: string, dst: string) {
console.log(`INFO: copying assets to build directory`);

if (existenceCheck(src)) {
await cp(src, dst, {
recursive: true,
filter: sourceStyleFilter,
});
} else {
console.log(`INFO: no assets in current theme directory`);
}
}
1 change: 0 additions & 1 deletion lib/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,4 @@ export interface TemplateVariables {

export interface SnippetVariables {
codes: SatusCode[];
locale: string;
}
30 changes: 30 additions & 0 deletions lib/render.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { writeFile } from "fs/promises";
import { render } from "mustache";

import { DEFAULTS } from "./constants";
import { SnippetVariables, TemplateVariables } from "./interfaces";

export async function renderPage(template: string, vars: TemplateVariables) {
if (!vars.code) {
throw new Error("No code variable to render page");
} else if (!vars.locale) {
throw new Error("No locale variable to render page");
}

const path = `${DEFAULTS.DIST}/${vars.code}.html`;

console.log(`INFO: render '${path}' page`);
await writeFile(path, render(template, vars), { flag: "w+" });
}

export async function renderSnippet(name: string, template: string, vars: SnippetVariables) {
if (!vars.codes) {
throw new Error("No codes list to render config snippet");
}

const path = `${DEFAULTS.DIST}/${name}`;

console.log(`INFO: render '${path}' config snippet`);
await writeFile(path, render(template, vars), { flag: "w+" });
console.log(`INFO: config snippet '${name}' was successfully created`);
}
29 changes: 29 additions & 0 deletions lib/style.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { promisify } from "util";
import { exec } from "child_process";

import { DEFAULTS } from "./constants";
import { Config } from "./interfaces";

export const TAILWIND_STYLE = /\.tcss$/i;

export async function buildTailwind(config: Config) {
if (config.tailwind) {
const input = `${DEFAULTS.THEMES}/${config.theme}/${DEFAULTS.ASSETS}/css/${DEFAULTS.TAILWIND_ENTRY}`;
const output = `${DEFAULTS.DIST}/${DEFAULTS.ASSETS}/css/${DEFAULTS.TAILWIND_ENTRY.replace(".tcss", ".css")}`;
const cmd = `INPUT="${input}" OUTPUT="${output}" npm run build:tailwind`;

console.log(`INFO: build Tailwind CSS styles`);
console.log(`INFO: run '${cmd}'`);

await promisify(exec)(cmd).then(() => {
console.log(`INFO: Tailwind CSS styles were built`);
});
} else {
console.log(`WARN: Tailwind CSS was disabled in config`);
}
}

export function sourceStyleFilter(path: string): boolean {
// Tailwind styles filter
return !TAILWIND_STYLE.test(path);
}
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "error-pages",
"version": "1.0.0",
"version": "1.0.1",
"description": "Lightweight tool to create static HTTP Error Pages in minimalistic adaptive and accessible design with customization and localization support",
"main": "index.ts",
"scripts": {
Expand Down

0 comments on commit a3a870d

Please sign in to comment.