fast-ejs-builder 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/build.js ADDED
@@ -0,0 +1,259 @@
1
+ /**
2
+ * @typedef { {name: string, fullpath: string, path: string, isDir: boolean; ext: string} } File
3
+ */
4
+
5
+ const fs = require("fs");
6
+ const ejs = require("ejs");
7
+ const path = require("path");
8
+ const {
9
+ _has,
10
+ args,
11
+ _d,
12
+ _e,
13
+ _p,
14
+ _r,
15
+ root,
16
+ _tree,
17
+ _md,
18
+ _w,
19
+ _ds,
20
+ _cp,
21
+ } = require("../core");
22
+ const {
23
+ getConfig,
24
+ generateBaseDirs,
25
+ config,
26
+ getDatas,
27
+ configTailwindOutput,
28
+ } = require("../core/lib");
29
+ const buildTailwind = require("./tailwind");
30
+ const formatHtml = require("./prettier");
31
+
32
+ async function ejsbuild(code = 0) {
33
+ require("dotenv").config({ quiet: true, override: true });
34
+
35
+ const rebuilt = code != 0;
36
+ await getConfig();
37
+
38
+ if (rebuilt) _d("◌ Rebuilding...");
39
+ else _d(`◌ Building HTML to '${config.build.output}'`);
40
+ const start = new Date();
41
+
42
+ generateBaseDirs();
43
+ const { globalData, localData } = await getDatas();
44
+
45
+ /** @param {File } file */
46
+ function getOutputName(file) {
47
+ let normalized = path
48
+ .relative(config.pages.dir, file.path)
49
+ .replaceAll(path.sep, "/");
50
+ if (file.ext != "ejs") return { normalized, original: normalized };
51
+ normalized = normalized.replace(path.extname(normalized), "");
52
+
53
+ const original = normalized;
54
+ if (
55
+ config.build.useIndexRouting &&
56
+ normalized != "index" &&
57
+ !normalized.endsWith("/index")
58
+ )
59
+ normalized += "/index";
60
+
61
+ return { normalized, original };
62
+ }
63
+
64
+ // generate tailwind file only if detected any view
65
+ if (
66
+ _tree(config.pages.dir).filter((f) => !f.isDir && f.ext == "ejs").length > 0
67
+ ) {
68
+ await buildTailwind();
69
+ }
70
+
71
+ const pages_files = _tree(config.pages.dir).filter((f) => !f.isDir);
72
+
73
+ const allOutputNames = pages_files.map((f) => getOutputName(f));
74
+ const built = [];
75
+
76
+ /** @param {File } file */
77
+ async function writeFile(file) {
78
+ const outputName = getOutputName(file);
79
+ const hasConflict = !!allOutputNames.find(
80
+ (f) => f.original == outputName.normalized,
81
+ );
82
+
83
+ const outputBase = hasConflict
84
+ ? outputName.original
85
+ : outputName.normalized;
86
+ let output = _p(`${config.build.output}/${outputBase}`);
87
+ if (file.ext != "ejs") {
88
+ _md(output, false);
89
+ _cp(file.fullpath, output);
90
+ built.push(output);
91
+ return;
92
+ }
93
+ output += ".html";
94
+ try {
95
+ const data = {
96
+ ...globalData,
97
+ ...(localData[outputName.original] ?? {}),
98
+ };
99
+
100
+ const getComponent = (component, ...args) => {
101
+ if (!component.endsWith(".ejs")) component += ".ejs";
102
+ const content = _r(`${config.components.dir}/${component}`, false);
103
+ if (!content) throw new Error("Component not found.");
104
+ const component_data = {};
105
+ for (let i in args) {
106
+ component_data[`$${i}`] = args[i];
107
+ }
108
+
109
+ return { content, component_data };
110
+ };
111
+ // NOTE - Reserved keywords
112
+ const context = (d = {}) =>
113
+ new Proxy(d, {
114
+ get(target, prop) {
115
+ if (prop in target) return target[prop];
116
+ return null;
117
+ },
118
+ });
119
+
120
+ const defaultData = {
121
+ $: (component, ...args) => {
122
+ try {
123
+ const { content, component_data } = getComponent(
124
+ component,
125
+ ...args,
126
+ );
127
+ const comp_render = ejs.render(
128
+ content,
129
+ context({
130
+ ...data,
131
+ ...component_data,
132
+ ...defaultData, // auto ref
133
+ }),
134
+ );
135
+ return comp_render;
136
+ } catch (error) {
137
+ _d(`\x1b[31mFailed to build component '${component}'`);
138
+ console.log("\x1b[31m", error.message, "\x1b[0m");
139
+ }
140
+ },
141
+ $async: async (component, ...args) => {
142
+ try {
143
+ const { content, component_data } = getComponent(
144
+ component,
145
+ ...args,
146
+ );
147
+ const comp_render = await ejs.render(
148
+ content,
149
+ context({
150
+ ...data,
151
+ ...component_data,
152
+ ...defaultData, // auto ref
153
+ }),
154
+ { async: true },
155
+ );
156
+ return comp_render;
157
+ } catch (error) {
158
+ _d(`\x1b[31mFailed to build component '${component}'`);
159
+ console.log("\x1b[31m", error.message, "\x1b[0m");
160
+ }
161
+ },
162
+ $env: (k) => process.env[k],
163
+ $upper: (v = "") => String(v).toUpperCase(),
164
+ $lower: (v = "") => String(v).toLowerCase(),
165
+ $trim: (v = "") => String(v).trim(),
166
+ $if: (b, y, n) => (Boolean(b) ? y : n),
167
+ $cls: (...classes) => classes.filter(Boolean).join(" "),
168
+ $debug: (...args) => {
169
+ console.log(...args);
170
+ return "";
171
+ },
172
+
173
+ get $route() {
174
+ return (
175
+ "/" + path.relative(_p(config.build.output), path.dirname(output))
176
+ );
177
+ },
178
+
179
+ get $css() {
180
+ let o = path
181
+ .relative(path.dirname(_p(output)), configTailwindOutput())
182
+ .replaceAll("\\", "/");
183
+
184
+ return `<link rel="stylesheet" href="${o}" />`;
185
+ },
186
+ get $date() {
187
+ return new Date();
188
+ },
189
+ };
190
+ const out = await ejs.renderFile(
191
+ file.fullpath,
192
+ context({
193
+ ...data,
194
+ ...defaultData,
195
+ }),
196
+ { async: true, beautify: false },
197
+ );
198
+ _w(
199
+ `${output}`,
200
+ (await formatHtml(out)).replace(/^\s*[\r\n]/gm, ""),
201
+ true,
202
+ );
203
+ built.push(`${output}`);
204
+ } catch (error) {
205
+ _d(`\x1b[31mFailed to build page '${file.name}.html'`);
206
+ console.log("\x1b[31m", error.message, "\x1b[0m");
207
+ }
208
+ }
209
+
210
+ await Promise.all(pages_files.map((file) => writeFile(file)));
211
+
212
+ const end = new Date();
213
+
214
+ const time = `\x1b[35m${end - start}ms\x1b[0m`;
215
+
216
+ const suffix = (arr = [], s = "file") =>
217
+ arr.length + " " + (arr.length > 1 ? `${s}s` : s);
218
+
219
+ if (rebuilt) {
220
+ _ds(`Rebuilt ${suffix(built)} in ${time}`);
221
+ } else {
222
+ if (built.length > 0) {
223
+ _ds(`Built ${suffix(built)} in ${time}`);
224
+ } else {
225
+ _d(`😢 Nothing to build. Ended in ${time}`);
226
+ }
227
+ }
228
+
229
+ const junk = _tree(config.build.output).filter(
230
+ (f) =>
231
+ f.fullpath != _p(configTailwindOutput()) &&
232
+ !built.find((b) => b.startsWith(f.fullpath)),
233
+ );
234
+ for (let file of junk) {
235
+ fs.rmSync(file.fullpath, { force: true, recursive: true });
236
+ }
237
+
238
+ const empty_folders = _tree(config.build.output).filter(
239
+ (f) => f.isDir && fs.readdirSync(f.fullpath).length == 0,
240
+ );
241
+ for (let f of empty_folders) {
242
+ fs.rmSync(f.fullpath, { force: true, recursive: true });
243
+ }
244
+ const junk_files = junk.filter((f) => !f.isDir);
245
+ const cleaned_1 =
246
+ junk_files.length > 0 ? `${suffix(junk_files, "junk file")}` : "";
247
+
248
+ const cleaned_2 =
249
+ empty_folders.length > 0
250
+ ? `${(cleaned_1 != "" ? " and " : "") + suffix(empty_folders, "empty folder")}`
251
+ : "";
252
+
253
+ const cleaned_text = `${cleaned_1}${cleaned_2}`;
254
+ if (cleaned_text != "") {
255
+ _ds(`Cleaned ${cleaned_text}.`);
256
+ }
257
+ }
258
+
259
+ module.exports = ejsbuild;
@@ -0,0 +1,12 @@
1
+ const prettier = require("prettier");
2
+
3
+ function formatHtml(html) {
4
+ return prettier.format(html, {
5
+ parser: "html",
6
+ bracketSameLine: true,
7
+ singleAttributePerLine: false,
8
+ printWidth: 180,
9
+ });
10
+ }
11
+
12
+ module.exports = formatHtml;
@@ -0,0 +1,105 @@
1
+ const fs = require("fs");
2
+ const path = require("path");
3
+ const postcss = require("postcss");
4
+ const tailwindcss = require("tailwindcss");
5
+ const autoprefixer = require("autoprefixer");
6
+ const { _p, _e, _w, _js, _ce } = require("../core");
7
+ const { config, configTailwindOutput } = require("../core/lib");
8
+
9
+ const twConfigPath = "tailwind.config.js";
10
+ function loadUserTailwindConfig() {
11
+ if (!_e(twConfigPath)) return null;
12
+
13
+ const resolved = require.resolve(_p(twConfigPath));
14
+ delete require.cache[resolved];
15
+
16
+ return require(resolved);
17
+ }
18
+
19
+ function mergeTailwindConfig(base, user) {
20
+ if (!user) return base;
21
+
22
+ return {
23
+ ...base,
24
+ ...user,
25
+
26
+ content: Array.from(
27
+ new Set([...(base.content || []), ...(user.content || [])]),
28
+ ),
29
+
30
+ theme: {
31
+ ...base.theme,
32
+ ...user.theme,
33
+
34
+ extend: {
35
+ ...(base.theme?.extend || {}),
36
+ ...(user.theme?.extend || {}),
37
+ },
38
+ },
39
+
40
+ plugins: [...(base.plugins || []), ...(user.plugins || [])],
41
+ };
42
+ }
43
+
44
+ async function buildTailwind() {
45
+ const inputCss = path.join(__dirname, "../templates/tailwind.css");
46
+ const outputCss = configTailwindOutput();
47
+
48
+ const tracker = (dir, ext = "ejs,html") => {
49
+ const oneExt = ext.split(",").length == 1;
50
+ const suffix = oneExt ? ext : `{${ext}}`;
51
+
52
+ return `./${dir}/**/*.${suffix}`;
53
+ };
54
+ const baseConfig = {
55
+ darkMode: "class",
56
+ content: [
57
+ tracker(config.pages.dir),
58
+ tracker(config.components.dir),
59
+ tracker(
60
+ config.data.dir,
61
+ ["js", "json"].includes(config.data.allow)
62
+ ? config.data.allow
63
+ : "js,json",
64
+ ),
65
+ ],
66
+ theme: {
67
+ extend: {},
68
+ },
69
+ plugins: [],
70
+ };
71
+
72
+ if (!_e(twConfigPath))
73
+ _w(
74
+ twConfigPath,
75
+ `/** @type {import('tailwindcss').Config} */
76
+ module.exports = ${_js(baseConfig)};
77
+ `,
78
+ );
79
+
80
+ const userConfig = loadUserTailwindConfig();
81
+ const finalConfig = mergeTailwindConfig(baseConfig, userConfig);
82
+
83
+ let css = fs.readFileSync(inputCss, "utf8");
84
+ const tailwindOutput = path.dirname(configTailwindOutput());
85
+ let imported = "";
86
+ for (let style of config.tailwind.imports) {
87
+ const style_path = _p(`${config.build.output}/${style}`);
88
+ const relative_path = path.relative(tailwindOutput, style_path);
89
+ imported += `@import "./${relative_path}";\n`;
90
+ }
91
+ css = `${imported}\n${css}`;
92
+
93
+ const result = await postcss([
94
+ tailwindcss(finalConfig),
95
+ autoprefixer(),
96
+ ]).process(css, {
97
+ from: inputCss,
98
+ to: outputCss,
99
+ });
100
+
101
+ fs.mkdirSync(path.dirname(outputCss), { recursive: true });
102
+ fs.writeFileSync(outputCss, result.css);
103
+ }
104
+
105
+ module.exports = buildTailwind;
@@ -0,0 +1,3 @@
1
+ @tailwind base;
2
+ @tailwind components;
3
+ @tailwind utilities;