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/README.md +401 -0
- package/bin/index.js +21 -0
- package/core/index.js +112 -0
- package/core/lib.js +231 -0
- package/core/prompter.js +99 -0
- package/fast.ejs.json +22 -0
- package/fast.ejs.schema.json +93 -0
- package/logo.png +0 -0
- package/package.json +39 -0
- package/src/build-dev.js +34 -0
- package/src/build.js +259 -0
- package/src/prettier.js +12 -0
- package/src/tailwind.js +105 -0
- package/templates/tailwind.css +3 -0
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;
|
package/src/prettier.js
ADDED
package/src/tailwind.js
ADDED
|
@@ -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;
|