fast-ejs-builder 1.0.0 → 1.0.2

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 CHANGED
@@ -44,10 +44,9 @@ npm install fast-ejs-builder --save-dev
44
44
 
45
45
  ```txt
46
46
  my-site/
47
- ├── app/
48
- ├── components/ # Reusable EJS components
49
- │ ├── data/ # Global and local data files
50
- │ └── pages/ # EJS templates
47
+ ├── components/ # Reusable EJS components
48
+ ├── data/ # Global and local data files
49
+ └── pages/ # EJS templates
51
50
  │ └── public/ # Static assets
52
51
  └── fast.ejs.json # Configuration file
53
52
  ```
@@ -62,14 +61,15 @@ npm install fast-ejs-builder --save-dev
62
61
  "useIndexRouting": true
63
62
  },
64
63
  "components": {
65
- "dir": "app/components"
64
+ "dir": "components",
65
+ "autoGenerate": false
66
66
  },
67
67
  "data": {
68
- "dir": "app/data",
68
+ "dir": "data",
69
69
  "allow": "all"
70
70
  },
71
71
  "pages": {
72
- "dir": "app/pages"
72
+ "dir": "pages"
73
73
  },
74
74
  "tailwind": {
75
75
  "output": "public/app.css",
@@ -128,18 +128,20 @@ The `fast.ejs.json` (occasionnally called _the FEJ_) file controls all aspects o
128
128
  "useIndexRouting": true
129
129
  },
130
130
  "components": {
131
- "dir": "app/components"
131
+ "dir": "components",
132
+ "autoGenerate": false
132
133
  },
133
134
  "data": {
134
- "dir": "app/data",
135
+ "dir": "data",
135
136
  "allow": "all"
136
137
  },
137
138
  "pages": {
138
- "dir": "app/pages"
139
+ "dir": "pages"
139
140
  },
140
141
  "tailwind": {
141
142
  "output": "public/app.css",
142
143
  "imports": [
144
+ "public/style.css",
143
145
  "https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap"
144
146
  ]
145
147
  }
@@ -152,11 +154,12 @@ The `fast.ejs.json` (occasionnally called _the FEJ_) file controls all aspects o
152
154
  - **`build.interval`**: Milliseconds between rebuilds in development mode
153
155
  - **`build.useIndexRouting`**: Generate `route/index.html` instead of `route.html`
154
156
  - **`components.dir`**: Directory containing reusable EJS components
157
+ - **`components.autoGenerate`**: Generate missing components. Default is `false`
155
158
  - **`data.dir`**: Directory for global and page-specific data files
156
159
  - **`data.allow`**: Data file format (`"js"`, `"json"`, or `"all"`)
157
160
  - **`pages.dir`**: Directory containing your EJS page templates. **Here is where you should mainly work**.
158
161
  - **`tailwind.output`**: Path to generated Tailwind CSS file
159
- - **`tailwind.imports`**: Array of external CSS URLs to include
162
+ - **`tailwind.imports`**: Array of external CSS URLs to include. Each `@layer` will be detected if specified.
160
163
 
161
164
  ## Usage Examples
162
165
 
@@ -165,7 +168,7 @@ The `fast.ejs.json` (occasionnally called _the FEJ_) file controls all aspects o
165
168
  Create EJS templates in your `pages.dir` directory:
166
169
 
167
170
  ```ejs
168
- <!-- app/pages/index.ejs -->
171
+ <!-- pages/index.ejs -->
169
172
  <!DOCTYPE html>
170
173
  <html lang="en">
171
174
  <head>
@@ -193,7 +196,7 @@ Create EJS templates in your `pages.dir` directory:
193
196
  Create reusable components in your `components.dir`:
194
197
 
195
198
  ```ejs
196
- <!-- app/components/header.ejs -->
199
+ <!-- components/header.ejs -->
197
200
  <header class="bg-white shadow-sm">
198
201
  <nav class="container mx-auto px-4 py-4">
199
202
  <div class="flex items-center justify-between">
@@ -243,7 +246,7 @@ Fast EJS comes with default data that can't be overrided.
243
246
  In the previous example, we can access `"https://placehold.co/400"` by using `$0` (`0` for the first arg).
244
247
 
245
248
  ```ejs
246
- <!--app/components/users/avatar.ejs-->
249
+ <!--components/users/avatar.ejs-->
247
250
  <img src="<%= $0 %>" class="w-10 aspect-square rounded-full"/>
248
251
  ```
249
252
 
@@ -258,15 +261,15 @@ Fast EJS comes with default data that can't be overrided.
258
261
  Returns the current route relative to the `pages.dir`. In this example, it will return `/home`
259
262
 
260
263
  ```ejs
261
- <!--app/pages/home.ejs-->
264
+ <!--pages/home.ejs-->
262
265
  <%= $route %>
263
266
  ```
264
267
 
265
268
  - `$css` :\
266
269
  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
270
  \
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`
271
+ For example, inside `pages/users/profile.ejs`, it can return something like `../../public/app.css`\
272
+ while inside `pages/index.ejs`, it will return something like `./public/app.css`
270
273
 
271
274
  ```ejs
272
275
  <%- $css %>
@@ -306,7 +309,7 @@ Fast EJS comes with default data that can't be overrided.
306
309
  Prints anything in the console during build. Use it to debug your pages or components
307
310
 
308
311
  ```ejs
309
- <!--app/components/header.ejs-->
312
+ <!--components/header.ejs-->
310
313
  <%- $debug("Header component says hi !") %>
311
314
  ```
312
315
 
@@ -319,14 +322,15 @@ Fast EJS comes with default data that can't be overrided.
319
322
 
320
323
  #### 2. Using your own data
321
324
 
322
- Fill data files in `data.dir` (generated automatically if missing).
325
+ Fill data files in `data.dir` (generated automatically if missing).\
326
+ Remember that using js data allow you to use function, async function and getter as data.
323
327
 
324
328
  - **Global data** : Can be accessed inside every pages and components
325
329
 
326
330
  If `data.allow` is `all` or `js` (recommended)
327
331
 
328
332
  ```javascript
329
- // app/data/global.data.js
333
+ // data/global.js
330
334
  module.exports = {
331
335
  siteName: "My Awesome Site",
332
336
  navItems: [
@@ -336,13 +340,16 @@ module.exports = {
336
340
  ],
337
341
  add: (a, b) => a + b,
338
342
  getUsers: async () => await db.getUsers(),
343
+ get randomBool() {
344
+ return Math.random() > 0.5;
345
+ },
339
346
  };
340
347
  ```
341
348
 
342
349
  If `data.allow` is `all` or `json`
343
350
 
344
351
  ```js
345
- // app/data/global.data.json
352
+ // data/global.json
346
353
  {
347
354
  "siteName": "My Awesome Site",
348
355
  "navItems": [
@@ -356,18 +363,79 @@ If `data.allow` is `all` or `json`
356
363
  - **Local data** : Can be accessed only in the target page
357
364
 
358
365
  ```javascript
359
- // app/data/local.data.js
366
+ // data/local.js
360
367
  module.exports = {
361
- // for page "app/pages/users/profile.ejs"
368
+ // for page "pages/users/profile.ejs"
362
369
  "users/profile": {
363
370
  title: "Welcome to My Site",
364
371
  description: "This is a fast-ejs powered website with Tailwind CSS.",
365
372
  },
373
+
374
+ // dynamic routes
375
+ "user/$id": (params) => ({
376
+ id: params.id,
377
+ username: params.name,
378
+ }),
379
+ "blog/$id": ({ title, id }) => ({
380
+ id,
381
+ blogTitle: title,
382
+ }),
383
+ };
384
+ ```
385
+
386
+ - **Route params** : Params sent to dynamic routes. Should return an array of object
387
+
388
+ ```javascript
389
+ // data/route.js
390
+ module.exports = {
391
+ "user/$id": [{ id: "u1", name: "John Doe" }],
392
+ "blog/$id": () => {
393
+ const arr = [];
394
+ for (let i = 0; i < 5; i++) {
395
+ arr.push({ title: "Article " + i, id: i, isPremium: i % 2 == 0 });
396
+ }
397
+ return arr;
398
+ },
399
+ // or getter
400
+ get "blog/$id"() {
401
+ // ...
402
+ },
366
403
  };
367
404
  ```
368
405
 
369
406
  **Note that all JS data can be asynchronous, and each asynchronous data will affect the build time.**
370
407
 
408
+ ## Use cases
409
+
410
+ You can use fast-ejs to build any static website. Just keep in mind that all pages are pre-built, not in runtime like front-end frameworks.\
411
+ For example, if you want to create a blog, you have to use dynamic routes (ex: `articles/$id.ejs`) in order to generate each article page at once:
412
+
413
+ ```js
414
+ // - Create "articles/$id.ejs" with the logic
415
+ // - Inside "data/route.js", add all articles with their data
416
+ module.exports = {
417
+ // ...,
418
+ "articles/$id": async () => {
419
+ const list = await db.getArticles();
420
+ return list.map((a) => ({ ...a, id: a._id })); // ensure "id" key
421
+ },
422
+ };
423
+
424
+ // - Add your view data inside "data/local.js"
425
+ module.exports = {
426
+ // ...,
427
+ "articles/$id": (article /*represents the data from route.js*/) => {
428
+ const { name, date } = article;
429
+ return { name, date }; // if only name and date are used in the view
430
+ },
431
+ };
432
+
433
+ /** Then, when the build is done, route.js will generate files based on your list. And each files built with the corresponding data.
434
+ * /articles/1 (retrieve data "articles/$id" but with the article with id=1 )
435
+ * etc..
436
+ */
437
+ ```
438
+
371
439
  ## Commands
372
440
 
373
441
  - **`fast-ejs dev`**: Start development server with live reloading
@@ -380,7 +448,10 @@ Here is the real building flow :
380
448
  - Get the data inside `data.dir` that match the `data.allow`.
381
449
  - Scan the specified `pages.dir`.
382
450
  - All empty folder will be ignored
383
- - If any `.ejs` file is found, render the html with the corresponding data and generate the right file inside `build.output` depending on `build.useIndexRouting`.
451
+ - If any `.ejs` file is found, search for used component inside `components.dir`.
452
+ - If a component is missing, it will be generated if `components.autoGenerate` is `true`
453
+ - If the file it is dynamic file (ex: $id.ejs), search for corresponding routes inside `data.dir/route.js(json)`
454
+ - Render the html with the corresponding data and generate the right file inside `build.output` depending on `build.useIndexRouting`.
384
455
  - For any other file, just copy it inside the `build.output`.
385
456
  - Generate the css with tailwind at `tailwind.output` along with `tailwind.imports` if specified.
386
457
  - Scan the `build.output` and clean all junk files and folders (files from previous build and empty folders).
package/core/index.js CHANGED
@@ -72,6 +72,7 @@ const _r = (pathname, json = true) => {
72
72
  const data = fs.readFileSync(_p(pathname), { encoding: "utf8" });
73
73
  return json ? JSON.parse(data) : data;
74
74
  }
75
+ return null;
75
76
  };
76
77
  const _d = (...messages) => {
77
78
  let obj = "";
@@ -89,9 +90,13 @@ const _cp = (i, o) => fs.copyFileSync(_p(i), _p(o));
89
90
 
90
91
  const _has = (obj, k) => Object.hasOwn(obj, k);
91
92
 
93
+ const _v = async (v, ...args) =>
94
+ typeof v === "function" ? await v(...args) : v;
95
+
92
96
  module.exports = {
93
97
  _ce,
94
98
  _cp,
99
+
95
100
  _d,
96
101
  _ds,
97
102
  _e,
@@ -104,6 +109,7 @@ module.exports = {
104
109
  _rd,
105
110
  _tree,
106
111
  _name,
112
+ _v,
107
113
  _w,
108
114
  args,
109
115
  root,
package/core/lib.js CHANGED
@@ -1,4 +1,4 @@
1
- const { args, _r, _has, _ce, _d, _w, _md, _e, _p, _ds } = require(".");
1
+ const { args, _r, _has, _ce, _d, _w, _md, _e, _p, _ds, _v } = require(".");
2
2
  const { $confirm, $input, $select, $number } = require("./prompter");
3
3
  const default_fej = require("../fast.ejs.json");
4
4
  const config = {
@@ -14,6 +14,7 @@ function getConfigFile() {
14
14
  if (v) return v;
15
15
  }
16
16
  }
17
+ return "fast.ejs.json";
17
18
  }
18
19
 
19
20
  function smartMerge(target = {}, source = {}) {
@@ -45,7 +46,7 @@ function smartMerge(target = {}, source = {}) {
45
46
  }
46
47
 
47
48
  async function getConfig() {
48
- const file = getConfigFile() || "fast.ejs.json";
49
+ const file = getConfigFile();
49
50
 
50
51
  if (!_ce(file, "Config file")) {
51
52
  const auto = await $confirm("Do you want to use default settings ?", true);
@@ -62,6 +63,10 @@ async function getConfig() {
62
63
  "In which folder are your components ?",
63
64
  config.components.dir,
64
65
  );
66
+ config.components.autoGenerate = await $input(
67
+ "Auto generate missing components ?",
68
+ config.components.autoGenerate,
69
+ );
65
70
 
66
71
  config.build.output = await $input(
67
72
  "Where do you want to output ?",
@@ -116,8 +121,9 @@ function generateBaseDirs() {
116
121
  }
117
122
 
118
123
  async function getDatas() {
119
- const globalDataPath = (type) => `${config.data.dir}/global.data.${type}`;
120
- const localDataPath = (type) => `${config.data.dir}/local.data.${type}`;
124
+ const globalDataPath = (type) => `${config.data.dir}/global.${type}`;
125
+ const localDataPath = (type) => `${config.data.dir}/local.${type}`;
126
+ const routeDataPath = (type) => `${config.data.dir}/route.${type}`;
121
127
 
122
128
  const globalJs = globalDataPath("js");
123
129
  const globalJson = globalDataPath("json");
@@ -125,6 +131,9 @@ async function getDatas() {
125
131
  const localJs = localDataPath("js");
126
132
  const localJson = localDataPath("json");
127
133
 
134
+ const routeJs = routeDataPath("js");
135
+ const routeJson = routeDataPath("json");
136
+
128
137
  const templates = {
129
138
  globalJS: `
130
139
  module.exports = async () => ({
@@ -138,9 +147,24 @@ module.exports = () => ({
138
147
  index: {
139
148
  title: process.env.APP_NAME,
140
149
  },
141
- about: {}, // about.ejs,
142
150
  "contact/menu": {}, // contact/menu.ejs
151
+
152
+ "blog/$id": (params) => ({
153
+ blogId: params.id,
154
+ }), // blog/$id.ejs
155
+
156
+ "article/$name": ({ name }) => ({
157
+ name,
158
+ }), // blog/$id.ejs
143
159
  });
160
+ `,
161
+ routeJS: `
162
+ module.exports = {
163
+ "blog/$id": [
164
+ { id: 123, title: "Article 1" },
165
+ { id: 456, title: "Article 2", isPremium: true },
166
+ ],
167
+ };
144
168
  `,
145
169
  };
146
170
 
@@ -150,14 +174,13 @@ module.exports = () => ({
150
174
  const resolved = require.resolve(_p(path));
151
175
  delete require.cache[resolved];
152
176
  const v = require(resolved);
153
- return typeof v == "function" ? await v(default_data_args) : v;
177
+ return await _v(v, default_data_args);
154
178
  };
155
179
 
156
180
  const getGlobalJSData = async () => {
157
181
  if (_e(globalJs)) {
158
182
  return await parseJSDate(globalJs);
159
183
  } else {
160
- _d(`Global file '${globalJs}' not found. It will be generated`);
161
184
  _w(globalJs, templates.globalJS.trim());
162
185
  return {};
163
186
  }
@@ -167,18 +190,24 @@ module.exports = () => ({
167
190
  if (_e(localJs)) {
168
191
  return await parseJSDate(localJs);
169
192
  } else {
170
- _d(`Local file '${localJs}' not found. It will be generated`);
171
193
  _w(localJs, templates.localJS.trim());
172
194
  return {};
173
195
  }
174
196
  };
175
197
 
198
+ const getRouteJSData = async () => {
199
+ if (_e(routeJs)) {
200
+ return await parseJSDate(routeJs);
201
+ } else {
202
+ _w(routeJs, templates.routeJS.trim());
203
+ return {};
204
+ }
205
+ };
206
+
176
207
  const getGlobalJSONData = () => {
177
208
  if (_e(globalJson)) {
178
209
  return _r(globalJson);
179
210
  } else {
180
- _d(`Global file '${globalJson}' not found. It will be generated`);
181
- _w(globalJson, {});
182
211
  return {};
183
212
  }
184
213
  };
@@ -187,31 +216,40 @@ module.exports = () => ({
187
216
  if (_e(localJson)) {
188
217
  return _r(localJson);
189
218
  } else {
190
- _d(`Local file '${localJson}' not found. It will be generated`);
191
- _w(localJson, { index: { title: "HomePage" }, about: {} });
192
219
  return {};
193
220
  }
194
221
  };
195
222
 
196
- let globalData = {},
197
- localData = {};
223
+ const getRouteJSONData = () => {
224
+ if (_e(routeJson)) {
225
+ return _r(routeJson);
226
+ } else {
227
+ return {};
228
+ }
229
+ };
198
230
 
231
+ let globalData = {},
232
+ localData = {},
233
+ routeData = {};
199
234
  switch (config.data.allow) {
200
235
  case "js":
201
236
  globalData = await getGlobalJSData();
202
237
  localData = await getLocalJSData();
238
+ routeData = await getRouteJSData();
203
239
  break;
204
240
  case "json":
205
241
  globalData = getGlobalJSONData();
206
242
  localData = getLocalJSONData();
243
+ routeData = getRouteJSONData();
207
244
  break;
208
245
  default:
209
246
  globalData = { ...getGlobalJSONData(), ...(await getGlobalJSData()) };
210
247
  localData = { ...getLocalJSONData(), ...(await getLocalJSData()) };
248
+ routeData = { ...getRouteJSONData(), ...(await getRouteJSData()) };
211
249
  break;
212
250
  }
213
251
 
214
- return { globalData, localData };
252
+ return { globalData, localData, routeData };
215
253
  }
216
254
 
217
255
  const configTailwindOutput = () => {
package/fast.ejs.json CHANGED
@@ -6,14 +6,15 @@
6
6
  "useIndexRouting": true
7
7
  },
8
8
  "components": {
9
- "dir": "app/components"
9
+ "dir": "components",
10
+ "autoGenerate": false
10
11
  },
11
12
  "data": {
12
13
  "allow": "all",
13
- "dir": "app/data"
14
+ "dir": "data"
14
15
  },
15
16
  "pages": {
16
- "dir": "app/pages"
17
+ "dir": "pages"
17
18
  },
18
19
  "tailwind": {
19
20
  "imports": [],
@@ -31,9 +31,14 @@
31
31
  "components": {
32
32
  "properties": {
33
33
  "dir": {
34
- "default": "app/components",
34
+ "default": "components",
35
35
  "description": "The folder containing your EJS components",
36
36
  "type": "string"
37
+ },
38
+ "autoGenerate": {
39
+ "default": false,
40
+ "description": "Automatically generate missing components when they are referenced",
41
+ "type": "boolean"
37
42
  }
38
43
  },
39
44
  "required": ["dir"],
@@ -42,8 +47,8 @@
42
47
  "data": {
43
48
  "properties": {
44
49
  "dir": {
45
- "default": "app/data",
46
- "description": "The folder containing global and local data files (global.data.js/json, local.data.js/json)",
50
+ "default": "data",
51
+ "description": "The folder containing global and local data files (global.js/json, local.js/json, route.js/json)",
47
52
  "type": "string"
48
53
  },
49
54
  "allow": {
@@ -56,11 +61,10 @@
56
61
  "required": ["dir"],
57
62
  "additionalProperties": false
58
63
  },
59
-
60
64
  "pages": {
61
65
  "properties": {
62
66
  "dir": {
63
- "default": "app/pages",
67
+ "default": "pages",
64
68
  "description": "The folder containing your EJS templates",
65
69
  "type": "string"
66
70
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fast-ejs-builder",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "Fast-EJS is a simple and fast tool to pre-render EJS templates into static HTML files with clean conventions and flexible data handling.",
5
5
  "keywords": [
6
6
  "tailwind",
package/src/build-dev.js CHANGED
@@ -1,7 +1,7 @@
1
1
  const { args, _d, _p } = require("../core");
2
2
  const ejsbuild = require("../src/build");
3
3
  const nodemon = require("nodemon");
4
- const { config, getConfig } = require("../core/lib");
4
+ const { config, getConfig, getConfigFile } = require("../core/lib");
5
5
 
6
6
  async function ejsbuild_dev() {
7
7
  await getConfig();
@@ -10,7 +10,9 @@ async function ejsbuild_dev() {
10
10
  config.pages.dir,
11
11
  config.components.dir,
12
12
  config.data.dir,
13
+ getConfigFile(),
13
14
  ".env",
15
+ "tailwind.config.js",
14
16
  ];
15
17
 
16
18
  _d("Watching :", toWatch.map((d) => `'${d}'`).join(", "));
package/src/build.js CHANGED
@@ -18,6 +18,7 @@ const {
18
18
  _w,
19
19
  _ds,
20
20
  _cp,
21
+ _v,
21
22
  } = require("../core");
22
23
  const {
23
24
  getConfig,
@@ -40,7 +41,7 @@ async function ejsbuild(code = 0) {
40
41
  const start = new Date();
41
42
 
42
43
  generateBaseDirs();
43
- const { globalData, localData } = await getDatas();
44
+ const { globalData, localData, routeData } = await getDatas();
44
45
 
45
46
  /** @param {File } file */
46
47
  function getOutputName(file) {
@@ -61,6 +62,13 @@ async function ejsbuild(code = 0) {
61
62
  return { normalized, original };
62
63
  }
63
64
 
65
+ const params_regex = /\$([a-zA-Z_]\w*)\b/g;
66
+ function fillParams(s = "", p = {}) {
67
+ return s.replace(params_regex, (_, key) => {
68
+ return p[key] ?? `$${key}`;
69
+ });
70
+ }
71
+
64
72
  // generate tailwind file only if detected any view
65
73
  if (
66
74
  _tree(config.pages.dir).filter((f) => !f.isDir && f.ext == "ejs").length > 0
@@ -74,7 +82,7 @@ async function ejsbuild(code = 0) {
74
82
  const built = [];
75
83
 
76
84
  /** @param {File } file */
77
- async function writeFile(file) {
85
+ async function writeFile(file, templatePath, templateData) {
78
86
  const outputName = getOutputName(file);
79
87
  const hasConflict = !!allOutputNames.find(
80
88
  (f) => f.original == outputName.normalized,
@@ -91,16 +99,57 @@ async function ejsbuild(code = 0) {
91
99
  return;
92
100
  }
93
101
  output += ".html";
102
+ const routeParams = outputName.original.match(params_regex) ?? [];
103
+
104
+ const hasParams = routeParams.length > 0;
105
+
106
+ const local =
107
+ localData[outputName.original] ?? localData[`/${outputName.original}`];
108
+
109
+ if (hasParams) {
110
+ const routes = await _v(
111
+ routeData[outputName.original] ?? routeData[`/${outputName.original}`],
112
+ );
113
+ if (typeof routes == "object") {
114
+ const params = Array.isArray(routes) ? routes : [routes];
115
+ for (let p of params) {
116
+ const fill = (s) => fillParams(s, p);
117
+ const pf = {
118
+ name: fill(file.name),
119
+ fullpath: fill(file.fullpath),
120
+ path: fill(file.path),
121
+ isDir: file.isDir,
122
+ ext: file.ext,
123
+ };
124
+
125
+ const notFoundParams = [
126
+ ...(pf.fullpath.matchAll(params_regex) ?? []),
127
+ ].map((m) => m[1]);
128
+ if (notFoundParams.length > 0) {
129
+ _d(
130
+ `\x1b[31m${notFoundParams.map((p) => `'${p}'`).join(", ")} not found for route '${outputName.original}'`,
131
+ );
132
+ continue;
133
+ }
134
+
135
+ writeFile(pf, file.fullpath, await _v(local, p));
136
+ }
137
+ }
138
+
139
+ return;
140
+ }
141
+
94
142
  try {
95
143
  const data = {
96
144
  ...globalData,
97
- ...(localData[outputName.original] ?? {}),
145
+ ...((await _v(local, {})) ?? {}),
146
+ ...(templateData ?? {}),
98
147
  };
99
148
 
100
149
  const getComponent = (component, ...args) => {
101
150
  if (!component.endsWith(".ejs")) component += ".ejs";
102
151
  const content = _r(`${config.components.dir}/${component}`, false);
103
- if (!content) throw new Error("Component not found.");
152
+ if (content == null) throw new Error("Component not found.");
104
153
  const component_data = {};
105
154
  for (let i in args) {
106
155
  component_data[`$${i}`] = args[i];
@@ -108,6 +157,18 @@ async function ejsbuild(code = 0) {
108
157
 
109
158
  return { content, component_data };
110
159
  };
160
+
161
+ const handleComponentError = (component, error) => {
162
+ if (config.components.autoGenerate) {
163
+ _md(config.components.dir + `/${component}`, false);
164
+ _w(config.components.dir + `/${component}.ejs`, "", false);
165
+ _ds(`Missing component '${component}' generated.`);
166
+ } else {
167
+ _d(`\x1b[31mFailed to build component '${component}'`);
168
+ console.log("\x1b[31m", error.message, "\x1b[0m");
169
+ }
170
+ };
171
+
111
172
  // NOTE - Reserved keywords
112
173
  const context = (d = {}) =>
113
174
  new Proxy(d, {
@@ -134,8 +195,7 @@ async function ejsbuild(code = 0) {
134
195
  );
135
196
  return comp_render;
136
197
  } catch (error) {
137
- _d(`\x1b[31mFailed to build component '${component}'`);
138
- console.log("\x1b[31m", error.message, "\x1b[0m");
198
+ handleComponentError(component, error);
139
199
  }
140
200
  },
141
201
  $async: async (component, ...args) => {
@@ -155,8 +215,7 @@ async function ejsbuild(code = 0) {
155
215
  );
156
216
  return comp_render;
157
217
  } catch (error) {
158
- _d(`\x1b[31mFailed to build component '${component}'`);
159
- console.log("\x1b[31m", error.message, "\x1b[0m");
218
+ handleComponentError(component, error);
160
219
  }
161
220
  },
162
221
  $env: (k) => process.env[k],
@@ -187,8 +246,9 @@ async function ejsbuild(code = 0) {
187
246
  return new Date();
188
247
  },
189
248
  };
249
+ // const fileContent = _r(file.fullpath, false);
190
250
  const out = await ejs.renderFile(
191
- file.fullpath,
251
+ templatePath ?? file.fullpath,
192
252
  context({
193
253
  ...data,
194
254
  ...defaultData,
package/src/tailwind.js CHANGED
@@ -3,7 +3,7 @@ const path = require("path");
3
3
  const postcss = require("postcss");
4
4
  const tailwindcss = require("tailwindcss");
5
5
  const autoprefixer = require("autoprefixer");
6
- const { _p, _e, _w, _js, _ce } = require("../core");
6
+ const { _p, _e, _w, _js, _ce, _r } = require("../core");
7
7
  const { config, configTailwindOutput } = require("../core/lib");
8
8
 
9
9
  const twConfigPath = "tailwind.config.js";
@@ -41,6 +41,24 @@ function mergeTailwindConfig(base, user) {
41
41
  };
42
42
  }
43
43
 
44
+ function extractLayers(path) {
45
+ const css = _r(path, false);
46
+ if (!css) return {};
47
+ const regex = /@layer\s+([a-zA-Z0-9_-]+)\s*\{([\s\S]*?\})\s*\}/g;
48
+ const result = {};
49
+ let match;
50
+
51
+ while ((match = regex.exec(css)) !== null) {
52
+ const name = match[1];
53
+ const content = match[2].trim();
54
+
55
+ result[name] ??= [];
56
+ result[name].push(content);
57
+ }
58
+
59
+ return result;
60
+ }
61
+
44
62
  async function buildTailwind() {
45
63
  const inputCss = path.join(__dirname, "../templates/tailwind.css");
46
64
  const outputCss = configTailwindOutput();
@@ -83,13 +101,34 @@ module.exports = ${_js(baseConfig)};
83
101
  let css = fs.readFileSync(inputCss, "utf8");
84
102
  const tailwindOutput = path.dirname(configTailwindOutput());
85
103
  let imported = "";
104
+ const layers = {};
86
105
  for (let style of config.tailwind.imports) {
106
+ const uri_regex = /^https?:\/\/[^\s/$.?#].[^\s]*$/i;
107
+ if (uri_regex.test(style)) {
108
+ imported += `@import "${style}";\n`;
109
+ continue;
110
+ }
87
111
  const style_path = _p(`${config.build.output}/${style}`);
88
112
  const relative_path = path.relative(tailwindOutput, style_path);
89
113
  imported += `@import "./${relative_path}";\n`;
114
+
115
+ const lay = extractLayers(`${config.pages.dir}/${style}`);
116
+ for (let k in lay) {
117
+ layers[k] ??= "";
118
+ layers[k] += `${lay[k]}\n`;
119
+ }
90
120
  }
121
+
91
122
  css = `${imported}\n${css}`;
92
123
 
124
+ for (let k in layers) {
125
+ css += `
126
+ @layer ${k} {
127
+ ${layers[k]?.trim()}
128
+ }
129
+ `;
130
+ }
131
+
93
132
  const result = await postcss([
94
133
  tailwindcss(finalConfig),
95
134
  autoprefixer(),