Skip to content

Commit

Permalink
chore(global): init project with groundwork
Browse files Browse the repository at this point in the history
  • Loading branch information
sapachev committed Jan 22, 2023
1 parent 574ff8c commit feb826d
Show file tree
Hide file tree
Showing 19 changed files with 1,878 additions and 2 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
node_modules/
dist/
38 changes: 36 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,36 @@
# error-pages
HTTP Error Pages
## Custom HTTP Error Pages

Lightweight tool to create own static HTTP Error Pages in minimalistic responsive and accessible design features and customization support including localization.

# Features

* Static pages with simple design
* Lightweight and fast running
* Extensibility
* Customization
* Localization (i18n)
* Accessibility (a11y)
* Built-in web-server config generator (Nginx)


# How to use

Requirements: installed Node.js 11+

1. Checkout this repository to your local machine
2. Run `npm i` command to install dependencies
3. Run `npm run build` command to compile error pages and config
4. Copy static html-files from `dist` directory to your server
5. Copy and apply config snippet from `dist` to your web-server configuration


# How to improve default pages

* *Extensibility* A new pages can be added by adding new json-files in `scr/<locale>` directory. The page name must follow to format `<HTTP-code>.json` (`<HTTP-code>` is Number, related to specific HTTP status code). You can put any additional data to json-files, that you want to display on a page. In case of common variables, you can use `common.json` file to define them.
* *Customization* By editing default theme you can add anything you want. In case if you want to have own page desing, you can create a new theme and apply it by editing `config.json` file. All assets (images, fonts, etc) must be placed to `assets` directory. By default [mustache.js](https://www.npmjs.com/package/mustache) is used as a template engine and [Tailwind](https://tailwindcss.com/) as a CSS framework. Entry point of Tailwind styles must be located in `themes/<name>/assets/css/main.tcss` file. Custom Tailwind theme settings can be added to `theme.tailwind.config.js` file located in a root of theme directory. Also Tailwind can be disabled by editing `tailwind` option in `config.js`.
* *Localization* If you need to change default text messages, then you can simply edit existing files in`src/<locale>` directory according to your needs. If you want to create your own localization, just simply add new locale directory and create set of source files. After new locale adding, change `locale` property in `config.json` file, located in a root.


# Contributing

You are very welcome to contribute into this project with improvements, localizations, bug fixes and so on.
5 changes: 5 additions & 0 deletions config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"tailwind": true,
"theme": "minimalistic",
"locale": "en"
}
174 changes: 174 additions & 0 deletions index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
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, SRC_CODE_PATTERN, TAILWIND_STYLE } from './lib/constants';
import { Config, SatusCode, TemplateVariables } from "./lib/interfaces";

async function compile(config: Config) {
try {
let vars: TemplateVariables = {
locale: config.locale,
};

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})
)
);

console.log(`INFO: ${codesVars.size} pages was successfully created`);
}
else {
throw new Error('No source data to render error pages');
}
} catch (err) {
throw err;
}
}

async function execTailwind(config: Config) {
try {
if (config.tailwind) {
const input = `${DEFAULTS.THEMES}/${config.theme}/assets/css/${DEFAULTS.TAILWIND_ENTRY}`;
const output = `${DEFAULTS.DIST}/${config.locale}/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`);
}
} catch (err) {
throw err;
}
}

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

async function renderPage(template: string, vars: TemplateVariables) {
try {
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+' });

} catch (err) {
throw err;
}
}

async function readConfig(): Promise<Config> {
try {
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;
} catch (err) {
throw err;
}
}

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

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

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

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

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

if (exists) {
await cp(
path,
`${DEFAULTS.DIST}/${config.locale}/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`);
}
} catch (err) {
throw err;
}
}

readConfig()
.then(async (config) => {
await prepare(config);
await compile(config);
await execTailwind(config);
await copyAssets(config);
})
.catch(err => {
console.error(`
An error happened during compile process. Please, check 'README.md' to get more details about calling this process.
Error Message:
${err.message}
Error Stack:
${err.stack}
`);
});
7 changes: 7 additions & 0 deletions lib/_constants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
exports.DefaultPaths = {
CONFIG: './config.json',
SRC: './src',
DIST: './dist',
THEMES: './themes',
TAILWIND_ENTRY: 'main.tcss'
}
13 changes: 13 additions & 0 deletions lib/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Defaults } from './interfaces.js';
import { DefaultPaths } from './_constants.js';

export const DEFAULTS: Defaults = {
CONFIG: DefaultPaths.CONFIG,
SRC: DefaultPaths.SRC,
DIST: DefaultPaths.DIST,
THEMES: DefaultPaths.THEMES,
TAILWIND_ENTRY: DefaultPaths.TAILWIND_ENTRY,
}

export const SRC_CODE_PATTERN = /^[0-9]{3}(?=\.json$)/i;
export const TAILWIND_STYLE = /\.tcss$/i;
20 changes: 20 additions & 0 deletions lib/interfaces.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
export interface Defaults {
CONFIG: string,
SRC: string,
DIST: string,
THEMES: string,
TAILWIND_ENTRY: string,
}

export interface Config {
tailwind: boolean,
theme: string,
locale: string,
}

export type SatusCode = number;

export interface TemplateVariables {
locale: string;
[key: string]: string|number;
}
Loading

0 comments on commit feb826d

Please sign in to comment.