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 ADDED
@@ -0,0 +1,401 @@
1
+ # Fast EJS Builder
2
+
3
+ ![Fast EJS](logo.png)
4
+
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-purple.svg)](https://opensource.org/licenses/MIT)
6
+ [![npm version](https://img.shields.io/npm/v/fast-ejs-builder.svg)](https://www.npmjs.com/package/fast-ejs-builder)
7
+
8
+ A fast and lightweight static site generator that combines EJS templating with Tailwind CSS for rapid web development.
9
+
10
+ ## Features
11
+
12
+ - **Fast Builds**: Optimized build process with configurable intervals
13
+ - **Tailwind CSS Integration**: Automatic CSS generation and processing
14
+ - **Component System**: Reusable EJS components for modular development
15
+ - **Data Injection**: Support for JavaScript and JSON data files
16
+ - **Development Mode**: Live reloading with watch functionality
17
+ - **Flexible Configuration**: JSON-based configuration with schema validation
18
+ - **Index Routing**: Automatic `index.html` generation for clean URLs
19
+
20
+ ## Installation
21
+
22
+ ### Global Installation (Recommended)
23
+
24
+ ```bash
25
+ npm install -g fast-ejs-builder
26
+ ```
27
+
28
+ ### Local Installation
29
+
30
+ ```bash
31
+ npm install fast-ejs-builder --save-dev
32
+ ```
33
+
34
+ ## Quick Start
35
+
36
+ 1. **Initialize your project:**
37
+
38
+ ```bash
39
+ mkdir my-site && cd my-site
40
+ ```
41
+
42
+ 2. **Create your project structure:**
43
+ This is the recommended structure. You can use your own.
44
+
45
+ ```txt
46
+ my-site/
47
+ ├── app/
48
+ │ ├── components/ # Reusable EJS components
49
+ │ ├── data/ # Global and local data files
50
+ │ └── pages/ # EJS templates
51
+ │ └── public/ # Static assets
52
+ └── fast.ejs.json # Configuration file
53
+ ```
54
+
55
+ 3. **Configure your site** in `fast.ejs.json`:
56
+
57
+ ```json
58
+ {
59
+ "build": {
60
+ "output": "build",
61
+ "interval": 100,
62
+ "useIndexRouting": true
63
+ },
64
+ "components": {
65
+ "dir": "app/components"
66
+ },
67
+ "data": {
68
+ "dir": "app/data",
69
+ "allow": "all"
70
+ },
71
+ "pages": {
72
+ "dir": "app/pages"
73
+ },
74
+ "tailwind": {
75
+ "output": "public/app.css",
76
+ "imports": []
77
+ }
78
+ }
79
+ ```
80
+
81
+ 4. **Start development:**
82
+
83
+ ```bash
84
+ fast-ejs dev
85
+ ```
86
+
87
+ 5. **Build for production:**
88
+
89
+ ```bash
90
+ fast-ejs build
91
+ ```
92
+
93
+ ### With local Installation
94
+
95
+ - Add fast-ejs-builder to your dev dependencies
96
+
97
+ ```bash
98
+ npm install fast-ejs-builder --save-dev
99
+ ```
100
+
101
+ - Add the build and dev scripts in your `package.json`
102
+
103
+ ```json
104
+ {
105
+ "scripts:": {
106
+ "dev": "fast-ejs dev",
107
+ "build": "fast-ejs build"
108
+ }
109
+ }
110
+ ```
111
+
112
+ - Run your package
113
+
114
+ ```bash
115
+ npm run dev
116
+ npm run build
117
+ ```
118
+
119
+ ## Configuration
120
+
121
+ The `fast.ejs.json` (occasionnally called _the FEJ_) file controls all aspects of your site generation. Here's a complete configuration example:
122
+
123
+ ```json
124
+ {
125
+ "build": {
126
+ "output": "build",
127
+ "interval": 100,
128
+ "useIndexRouting": true
129
+ },
130
+ "components": {
131
+ "dir": "app/components"
132
+ },
133
+ "data": {
134
+ "dir": "app/data",
135
+ "allow": "all"
136
+ },
137
+ "pages": {
138
+ "dir": "app/pages"
139
+ },
140
+ "tailwind": {
141
+ "output": "public/app.css",
142
+ "imports": [
143
+ "https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap"
144
+ ]
145
+ }
146
+ }
147
+ ```
148
+
149
+ ### Configuration Options
150
+
151
+ - **`build.output`**: Directory where generated HTML files are saved
152
+ - **`build.interval`**: Milliseconds between rebuilds in development mode
153
+ - **`build.useIndexRouting`**: Generate `route/index.html` instead of `route.html`
154
+ - **`components.dir`**: Directory containing reusable EJS components
155
+ - **`data.dir`**: Directory for global and page-specific data files
156
+ - **`data.allow`**: Data file format (`"js"`, `"json"`, or `"all"`)
157
+ - **`pages.dir`**: Directory containing your EJS page templates. **Here is where you should mainly work**.
158
+ - **`tailwind.output`**: Path to generated Tailwind CSS file
159
+ - **`tailwind.imports`**: Array of external CSS URLs to include
160
+
161
+ ## Usage Examples
162
+
163
+ ### Creating Pages
164
+
165
+ Create EJS templates in your `pages.dir` directory:
166
+
167
+ ```ejs
168
+ <!-- app/pages/index.ejs -->
169
+ <!DOCTYPE html>
170
+ <html lang="en">
171
+ <head>
172
+ <meta charset="UTF-8" />
173
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
174
+ <title><%= title %></title>
175
+ <link rel="stylesheet" href="<%= tailwindCss %>" />
176
+ </head>
177
+ <body class="bg-gray-100">
178
+ <div class="container mx-auto px-4 py-8">
179
+ <%- include('../components/header') %>
180
+ <!-- or <%- $('header') %> -->
181
+ <main>
182
+ <h1 class="text-4xl font-bold text-gray-900 mb-4"><%= title %></h1>
183
+ <p class="text-lg text-gray-700"><%= description %></p>
184
+ </main>
185
+ <%- $('footer') %>
186
+ </div>
187
+ </body>
188
+ </html>
189
+ ```
190
+
191
+ ### Using Components
192
+
193
+ Create reusable components in your `components.dir`:
194
+
195
+ ```ejs
196
+ <!-- app/components/header.ejs -->
197
+ <header class="bg-white shadow-sm">
198
+ <nav class="container mx-auto px-4 py-4">
199
+ <div class="flex items-center justify-between">
200
+ <a href="/" class="text-xl font-bold text-gray-900"><%= siteName %> | <%= $0 %> </a>
201
+ <ul class="flex space-x-4">
202
+ <% navItems.forEach(item => { %>
203
+ <li>
204
+ <a
205
+ href="<%= item.url %>"
206
+ class="text-gray-600 hover:text-gray-900 <%= $cls($route==item.url && 'underline decoration-2 underline-offset-4') %>"
207
+ >
208
+ <%= $if($route === item.url,"»") %>
209
+ <%= item.label %>
210
+ </a>
211
+ </li>
212
+ <% }) %>
213
+ </ul>
214
+ </div>
215
+ </nav>
216
+ </header>
217
+ ```
218
+
219
+ And use it inside a page or another component by calling:
220
+
221
+ ```ejs
222
+ <%- $("header","Some arg") %>
223
+ ```
224
+
225
+ Here `$0` has the value of `"Some arg"`. You can pass many args and access them through `$1`,`$2`,...\
226
+ Remember that only components can access args data `$x`.
227
+
228
+ ### Passing data to pages
229
+
230
+ #### 1. With base data
231
+
232
+ Fast EJS comes with default data that can't be overrided.
233
+
234
+ - `$` : _function_
235
+ Imports a component by its name. Like a 'super include'.
236
+
237
+ ```ejs
238
+ <%- $("users/avatar","https://placehold.co/400") %>
239
+ ```
240
+
241
+ - `$0`,`$1`,... :
242
+ Return the args passed to a component. **Can be accessed only inside a component, not a page**.\
243
+ In the previous example, we can access `"https://placehold.co/400"` by using `$0` (`0` for the first arg).
244
+
245
+ ```ejs
246
+ <!--app/components/users/avatar.ejs-->
247
+ <img src="<%= $0 %>" class="w-10 aspect-square rounded-full"/>
248
+ ```
249
+
250
+ - `$async` : _Promise function_
251
+ Asynchronously imports a component.
252
+
253
+ ```ejs
254
+ <%- await $async("dashboard") %>
255
+ ```
256
+
257
+ - `$route` :
258
+ Returns the current route relative to the `pages.dir`. In this example, it will return `/home`
259
+
260
+ ```ejs
261
+ <!--app/pages/home.ejs-->
262
+ <%= $route %>
263
+ ```
264
+
265
+ - `$css` :
266
+ Automatically imports the relative path of generated tailwind css from `tailwind.output` inside a page. No need to manually write the css path and change everytime.\
267
+ \
268
+ For example, inside `app/pages/users/profile.ejs`, it can return something like `../../public/app.css`\
269
+ while inside `app/pages/index.ejs`, it will return something like `./public/app.css`
270
+
271
+ ```ejs
272
+ <%- $css %>
273
+ ```
274
+
275
+ - `$date` :
276
+ Returns a new Date object.\
277
+ Stop writing year manually.
278
+
279
+ ```ejs
280
+ <%= $date.getFullYear() %>
281
+ ```
282
+
283
+ - `$env` : _function_
284
+ Get a env variable from `process.env`. Useful to build env based pages.
285
+
286
+ ```ejs
287
+ <%= $env("NODE_ENV") %>
288
+ ```
289
+
290
+ - `$cls` : _function_
291
+ Same behavior as `tailwind clsx`. Useful to write conditions based classes.
292
+
293
+ ```ejs
294
+ <%= $cls(isActive && "text-primary", "bg-gray-100") %>
295
+ ```
296
+
297
+ - `$if` : _function_
298
+ Returns a value based on a condition or a default value if set. Can also works with components :=)
299
+
300
+ ```ejs
301
+ <%- $if(isActive, $("active-bar")) %>
302
+ <%= $if($env("NODE_ENV")=="prod", "Hello","World") %>
303
+ ```
304
+
305
+ - `$debug` : _function_
306
+ Prints anything in the console during build. Use it to debug your pages or components
307
+
308
+ ```ejs
309
+ <!--app/components/header.ejs-->
310
+ <%- $debug("Header component says hi !") %>
311
+ ```
312
+
313
+ - `$upper`,`$lower` and `$trim` : _functions_
314
+ Utils for strings.
315
+
316
+ ```ejs
317
+ <%- $trim($upper(user.name)) %>
318
+ ```
319
+
320
+ Create data files in `data.dir`:
321
+
322
+ - **Global data** : Can be accessed inside every pages and components
323
+
324
+ If `data.allow` is `all` or `js`
325
+
326
+ ```javascript
327
+ // app/data/global.data.js
328
+ module.exports = {
329
+ siteName: "My Awesome Site",
330
+ navItems: [
331
+ { label: "Home", url: "/" },
332
+ { label: "About", url: "/about" },
333
+ { label: "Contact", url: "/contact" },
334
+ ],
335
+ add: (a, b) => a + b,
336
+ getUsers: async () => await db.getUsers(),
337
+ };
338
+ ```
339
+
340
+ If `data.allow` is `all` or `json`
341
+
342
+ ```json
343
+ // app/data/global.data.json
344
+ {
345
+ "siteName": "My Awesome Site",
346
+ "navItems": [
347
+ { "label": "Home", "url": "/" },
348
+ { "label": "About", "url": "/about" },
349
+ { "label": "Contact", "url": "/contact" },
350
+ ],
351
+ };
352
+ ```
353
+
354
+ - **Local data**
355
+
356
+ ```javascript
357
+ // app/pages/index.data.js
358
+ module.exports = {
359
+ title: "Welcome to My Site",
360
+ description: "This is a fast-ejs powered website with Tailwind CSS.",
361
+ };
362
+ ```
363
+
364
+ ## Commands
365
+
366
+ - **`fast-ejs dev`**: Start development server with live reloading
367
+ - **`fast-ejs build`**: Build static files for production
368
+
369
+ ## Usage tips
370
+
371
+ - **Don't misuse EJS tags**
372
+
373
+ ```ejs
374
+ <!-- Avoid this ❌ -->
375
+ <% $("header")%>
376
+ <%= $css %>
377
+ <%- user.name %>
378
+ ```
379
+
380
+ Using `<%` means you're not expecting an output but `$("header")` should return a component.
381
+ Using `<%=` means you're expecting an escaped value but `$css` requires to be unescaped.
382
+ Using `<%-` means you're expecting an unescaped but `user.name` may return a string.
383
+
384
+ - **Fast EJS is a builder / generator. Not a framework**
385
+
386
+ You're basiclally coding ejs templates with 'super powers', and something is generating the html and css files for you. You're not using a big framework that will run your SaaS.\
387
+ Please consider using this for small projects like static portfolios or landing pages.
388
+
389
+ ## Contributing
390
+
391
+ Contributions are welcome! Please feel free to submit a Pull Request.
392
+
393
+ 1. Fork the repository
394
+ 2. Create your feature branch (`git checkout -b feature/amazing-feature`)
395
+ 3. Commit your changes (`git commit -m 'Add some amazing feature'`)
396
+ 4. Push to the branch (`git push origin feature/amazing-feature`)
397
+ 5. Open a Pull Request
398
+
399
+ ## Support
400
+
401
+ If you have any questions or need help, please open an issue on [GitHub](https://github.com/D3R50N/fast-ejs/issues).
package/bin/index.js ADDED
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { args, _d, _p } = require("../core");
4
+ const ejsbuild = require("../src/build");
5
+ const ejsbuild_dev = require("../src/build-dev");
6
+ const command = args[0];
7
+
8
+ (async function () {
9
+ switch (command) {
10
+ case "dev":
11
+ ejsbuild_dev();
12
+ break;
13
+ case "build":
14
+ ejsbuild();
15
+ break;
16
+
17
+ default:
18
+ _d(`\x1b[31mCommmand '${command}' not found.`);
19
+ break;
20
+ }
21
+ })();
package/core/index.js ADDED
@@ -0,0 +1,112 @@
1
+ const fs = require("fs");
2
+ const path = require("path");
3
+
4
+ const root = process.cwd();
5
+ const args = process.argv.slice(2);
6
+
7
+ const _p = (pathname) => path.join(root, path.relative(root, pathname ?? ""));
8
+ const _e = (pathname) => fs.existsSync(_p(pathname));
9
+ const _ext = (pathname) => path.extname(pathname).slice(1);
10
+ const _name = (pathname) => path.parse(pathname).name;
11
+ const _rd = (dirname) => {
12
+ try {
13
+ dirname ??= "";
14
+ const f = fs.readdirSync(_p(dirname));
15
+
16
+ return f.map((s) => {
17
+ const fullpath = _p(`${dirname}/${s}`);
18
+ const _ = path.join(dirname, s);
19
+ const stats = fs.statSync(_);
20
+ return {
21
+ name: _name(s),
22
+ fullpath,
23
+ path: _,
24
+ isDir: stats.isDirectory(),
25
+ ext: _ext(s),
26
+ };
27
+ });
28
+ } catch (e) {
29
+ console.log(e);
30
+ return [];
31
+ }
32
+ };
33
+ const _tree = (dirname) => {
34
+ const files = _rd(dirname);
35
+ if (files.length == 0) return [];
36
+
37
+ for (let dir of files.filter((f) => f.isDir)) {
38
+ files.push(..._tree(dir.path));
39
+ }
40
+
41
+ return files;
42
+ };
43
+ const _ce = (pathname, message = "") => {
44
+ if (!_e(pathname)) {
45
+ _d(`\x1b[33m${message} '${pathname}' not found`);
46
+ return false;
47
+ }
48
+ return true;
49
+ };
50
+
51
+ const _js = (data) => JSON.stringify(data, null, 2);
52
+
53
+ const _md = (pathname, isDir = true) => {
54
+ const p = _p(pathname);
55
+ const dir = isDir ? p : path.dirname(p);
56
+ fs.mkdirSync(dir, { recursive: true });
57
+ };
58
+
59
+ const _w = (pathname, data, force = false) => {
60
+ if (!force && _e(pathname)) return false;
61
+ try {
62
+ const parsed = typeof data == "object" ? _js(data) : data;
63
+ _md(pathname, false);
64
+ fs.writeFileSync(_p(pathname), parsed, { encoding: "utf8" });
65
+ return true;
66
+ } catch (error) {
67
+ console.log(error);
68
+ return false;
69
+ }
70
+ };
71
+
72
+ const _r = (pathname, json = true) => {
73
+ if (_e(pathname)) {
74
+ const data = fs.readFileSync(_p(pathname), { encoding: "utf8" });
75
+ return json ? JSON.parse(data) : data;
76
+ }
77
+ };
78
+ const _d = (...messages) => {
79
+ let obj = "";
80
+ for (let message of messages) {
81
+ obj += typeof message == "object" ? _js(message) : message;
82
+ obj += " ";
83
+ }
84
+ obj = obj.trim();
85
+ console.log(`\x1b[1m\x1b[35m[Fast EJS]\x1b[0m \x1b[1m${obj}\x1b[0m`);
86
+ };
87
+
88
+ const _ds = (...messages) => _d("\x1b[32m✔\x1b[0m\x1b[1m", ...messages);
89
+
90
+ const _cp = (i, o) => fs.copyFileSync(_p(i), _p(o));
91
+
92
+ const _has = (obj, k) => Object.hasOwn(obj, k);
93
+
94
+ module.exports = {
95
+ _ce,
96
+ _cp,
97
+ _d,
98
+ _ds,
99
+ _e,
100
+ _ext,
101
+ _has,
102
+ _js,
103
+ _md,
104
+ _p,
105
+ _r,
106
+ _rd,
107
+ _tree,
108
+ _name,
109
+ _w,
110
+ args,
111
+ root,
112
+ };