dinou 2.2.0 → 2.3.1

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/CHANGELOG.md CHANGED
@@ -5,6 +5,18 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/).
7
7
 
8
+ ## [2.3.1]
9
+
10
+ ### Fixed
11
+
12
+ - Build for ES modules (ESM).
13
+
14
+ ## [2.3.0]
15
+
16
+ ### Added
17
+
18
+ - Support for ES modules (ESM). Dinou can now import and use ESM-only packages inside React components.
19
+
8
20
  ## [2.2.0]
9
21
 
10
22
  ### Fixed
package/cli.js CHANGED
@@ -3,6 +3,7 @@
3
3
  const { program } = require("commander");
4
4
  const { execSync } = require("child_process");
5
5
  const path = require("path");
6
+ const { pathToFileURL } = require("url");
6
7
 
7
8
  const dinouPath = path.resolve(__dirname, "dinou");
8
9
  const projectRoot = process.cwd();
@@ -21,10 +22,9 @@ program
21
22
  .description("Starts")
22
23
  .action(() => {
23
24
  console.log("Starting...");
24
- const startExpress = `node --conditions react-server ${path.join(
25
- dinouPath,
26
- "server.js"
27
- )}`;
25
+ const startExpress = `node --conditions react-server --import ${
26
+ pathToFileURL(path.join(dinouPath, "register-loader.mjs")).href
27
+ } ${path.join(dinouPath, "server.js")}`;
28
28
  const startDevServer = `cross-env NODE_ENV=development rollup -c ${path.join(
29
29
  __dirname,
30
30
  "rollup.config.js"
@@ -47,10 +47,9 @@ program
47
47
  .action(() => {
48
48
  console.log("Starting the app...");
49
49
  runCommand(
50
- `cross-env NODE_ENV=production node --conditions react-server ${path.join(
51
- dinouPath,
52
- "server.js"
53
- )}`
50
+ `cross-env NODE_ENV=production node --conditions react-server --import ${
51
+ pathToFileURL(path.join(dinouPath, "register-loader.mjs")).href
52
+ } ${path.join(dinouPath, "server.js")}`
54
53
  );
55
54
  });
56
55
 
@@ -0,0 +1,31 @@
1
+ const extensions = [
2
+ "png",
3
+ "jpg",
4
+ "jpeg",
5
+ "gif",
6
+ "svg",
7
+ "webp",
8
+ "avif",
9
+ "ico",
10
+ "mp4",
11
+ "webm",
12
+ "ogg",
13
+ "mov",
14
+ "avi",
15
+ "mkv",
16
+ "mp3",
17
+ "wav",
18
+ "flac",
19
+ "m4a",
20
+ "aac",
21
+ "mjpeg",
22
+ "mjpg",
23
+ ];
24
+
25
+ // 🔹 regex útil para plugins tipo Rollup/PostCSS
26
+ const regex = new RegExp(`\\.(${extensions.join("|")})$`, "i");
27
+
28
+ // 🔹 versión con punto para comparaciones directas
29
+ const extensionsWithDot = extensions.map((ext) => `.${ext}`);
30
+
31
+ module.exports = { extensions, extensionsWithDot, regex };
@@ -0,0 +1,190 @@
1
+ const fs = require("fs");
2
+ const path = require("path");
3
+ const { transformAsync } = require("@babel/core");
4
+ const { fileURLToPath, pathToFileURL } = require("url");
5
+ const createScopedName = require("./createScopedName");
6
+ const { extensionsWithDot } = require("./asset-extensions.js");
7
+
8
+ require("css-modules-require-hook")({
9
+ generateScopedName: createScopedName,
10
+ extensions: [".css"],
11
+ });
12
+
13
+ // Lee tsconfig/jsconfig y construye un map de alias -> targetBase
14
+ function loadTsconfigAliases() {
15
+ const cwd = process.cwd();
16
+ const tsconfigPath = path.resolve(cwd, "tsconfig.json");
17
+ const jsconfigPath = path.resolve(cwd, "jsconfig.json");
18
+ const configFile = fs.existsSync(tsconfigPath)
19
+ ? tsconfigPath
20
+ : fs.existsSync(jsconfigPath)
21
+ ? jsconfigPath
22
+ : null;
23
+ if (!configFile) return new Map();
24
+
25
+ let config;
26
+ try {
27
+ config = JSON.parse(fs.readFileSync(configFile, "utf8"));
28
+ } catch (err) {
29
+ // Malformed json
30
+ return new Map();
31
+ }
32
+
33
+ const paths = (config.compilerOptions && config.compilerOptions.paths) || {};
34
+ const baseUrl =
35
+ (config.compilerOptions && config.compilerOptions.baseUrl) || ".";
36
+ const absoluteBase = path.resolve(cwd, baseUrl);
37
+
38
+ const map = new Map();
39
+
40
+ for (const key of Object.keys(paths)) {
41
+ const targets = paths[key];
42
+ if (!targets || !targets.length) continue;
43
+
44
+ // Normaliza: el primer target es el que usaremos
45
+ let target = Array.isArray(targets) ? targets[0] : targets;
46
+
47
+ // Soportar patterns con /* al final: "@/*" -> "src/*"
48
+ const keyIsWildcard = key.endsWith("/*");
49
+ const targetIsWildcard = target.endsWith("/*");
50
+
51
+ const alias = keyIsWildcard ? key.slice(0, -1) : key; // "@/"
52
+ const targetBase = targetIsWildcard ? target.slice(0, -1) : target; // "src" o "../lib"
53
+
54
+ // resolvemos el targetBase relativo a baseUrl si no es absoluto
55
+ const resolvedTargetBase = path.resolve(absoluteBase, targetBase);
56
+
57
+ map.set(alias, {
58
+ resolvedTargetBase,
59
+ keyIsWildcard,
60
+ targetIsWildcard,
61
+ });
62
+ }
63
+
64
+ return map;
65
+ }
66
+
67
+ const aliasMap = loadTsconfigAliases();
68
+
69
+ // Añadir extensiones si no existen
70
+ function tryExtensions(filePath) {
71
+ if (fs.existsSync(filePath)) return filePath;
72
+ const exts = [".js", ".ts", ".jsx", ".tsx"];
73
+ for (const ext of exts) {
74
+ const f = filePath + ext;
75
+ if (fs.existsSync(f)) return f;
76
+ }
77
+ // Si es carpeta, probar index.*
78
+ if (fs.existsSync(filePath) && fs.statSync(filePath).isDirectory()) {
79
+ for (const ext of exts) {
80
+ const f = path.join(filePath, "index" + ext);
81
+ if (fs.existsSync(f)) return f;
82
+ }
83
+ }
84
+ return null;
85
+ }
86
+
87
+ exports.resolve = async function resolve(specifier, context, defaultResolve) {
88
+ if (aliasMap.size > 0) {
89
+ for (const [alias, info] of aliasMap.entries()) {
90
+ if (specifier.startsWith(alias)) {
91
+ const absPath = path.resolve(
92
+ info.resolvedTargetBase,
93
+ specifier.slice(alias.length)
94
+ );
95
+ const found = tryExtensions(absPath);
96
+ if (found) {
97
+ const url = pathToFileURL(found).href;
98
+
99
+ return {
100
+ url,
101
+ shortCircuit: true,
102
+ };
103
+ }
104
+ }
105
+ }
106
+ }
107
+
108
+ if (specifier.startsWith("./") || specifier.startsWith("../")) {
109
+ const parentURL = context.parentURL || pathToFileURL(process.cwd()).href;
110
+ const parentDir = path.dirname(fileURLToPath(parentURL));
111
+ const absPath = path.resolve(parentDir, specifier);
112
+ const found = tryExtensions(absPath);
113
+ if (found) return { url: pathToFileURL(found).href, shortCircuit: true };
114
+ }
115
+
116
+ // Fallback to default resolver
117
+ return defaultResolve(specifier, context, defaultResolve);
118
+ };
119
+
120
+ exports.load = async function load(url, context, defaultLoad) {
121
+ // --- 🟢 Handle non-JS assets (png, jpg, etc.)
122
+ const assetExts = extensionsWithDot;
123
+ const ext = path.extname(url.split("?")[0]); // remove query params if any
124
+
125
+ if (assetExts.includes(ext)) {
126
+ // Return a tiny stub that mimics what asset-require-hook would do
127
+ const filepath = fileURLToPath(url);
128
+ const localName = path.basename(filepath, ext);
129
+ const hashedName = createScopedName(localName, filepath);
130
+ const virtualExport = `export default "/assets/${hashedName}${ext}";`;
131
+
132
+ return {
133
+ format: "module",
134
+ source: virtualExport,
135
+ shortCircuit: true,
136
+ url,
137
+ };
138
+ }
139
+
140
+ if (ext === ".css") {
141
+ const mod = require(fileURLToPath(url));
142
+ const source = `export default ${JSON.stringify(mod)};`;
143
+ return { format: "module", source, shortCircuit: true, url };
144
+ }
145
+ if (/\.(jsx|tsx|ts|js)$/.test(url)) {
146
+ let filename;
147
+ try {
148
+ filename = fileURLToPath(
149
+ url.startsWith("file://") ? url : pathToFileURL(url).href
150
+ );
151
+ } catch (e) {
152
+ throw e;
153
+ }
154
+ const rel = path.relative(process.cwd(), filename);
155
+ if (ext === ".js" && !rel.startsWith("src" + path.sep))
156
+ return defaultLoad(url, context, defaultLoad);
157
+ const source = fs.readFileSync(filename, "utf-8");
158
+ if (ext === ".js") {
159
+ return {
160
+ format: "module",
161
+ source,
162
+ shortCircuit: true,
163
+ url,
164
+ };
165
+ }
166
+
167
+ const { code } = await transformAsync(source, {
168
+ filename,
169
+ presets: [
170
+ ["@babel/preset-react", { runtime: "automatic" }],
171
+ "@babel/preset-typescript",
172
+ ],
173
+ sourceMaps: "inline",
174
+ ast: false,
175
+ });
176
+
177
+ const urlToReturn = pathToFileURL(filename).href;
178
+
179
+ return {
180
+ format: "module",
181
+ source: code,
182
+ shortCircuit: true,
183
+ url: urlToReturn,
184
+ };
185
+ }
186
+
187
+ if (url) {
188
+ return defaultLoad(url, context, defaultLoad);
189
+ }
190
+ };
@@ -12,6 +12,7 @@ const { asyncRenderJSXToClientJSX } = require("./render-jsx-to-client-jsx");
12
12
  const {
13
13
  getFilePathAndDynamicParams,
14
14
  } = require("./get-file-path-and-dynamic-params");
15
+ const importModule = require("./import-module");
15
16
 
16
17
  async function buildStaticPages() {
17
18
  const srcFolder = path.resolve(process.cwd(), "src");
@@ -26,15 +27,18 @@ async function buildStaticPages() {
26
27
  mkdirSync(distFolder, { recursive: true });
27
28
  }
28
29
 
29
- function collectPages(currentPath, segments = [], params = {}) {
30
+ async function collectPages(currentPath, segments = [], params = {}) {
30
31
  const entries = readdirSync(currentPath, { withFileTypes: true });
31
32
  const pages = [];
32
33
 
33
- for (const entry of entries) {
34
+ for await (const entry of entries) {
34
35
  if (entry.isDirectory()) {
35
36
  if (entry.name.startsWith("(") && entry.name.endsWith(")")) {
36
37
  pages.push(
37
- ...collectPages(path.join(currentPath, entry.name), segments)
38
+ ...(await collectPages(
39
+ path.join(currentPath, entry.name),
40
+ segments
41
+ ))
38
42
  );
39
43
  } else if (
40
44
  entry.name.startsWith("[[...") &&
@@ -66,7 +70,7 @@ async function buildStaticPages() {
66
70
  let dynamic;
67
71
  let getStaticPaths;
68
72
  if (pageFunctionsPath) {
69
- const module = require(pageFunctionsPath);
73
+ const module = await importModule(pageFunctionsPath);
70
74
  getStaticPaths = module.getStaticPaths;
71
75
  dynamic = module.dynamic;
72
76
  }
@@ -81,10 +85,14 @@ async function buildStaticPages() {
81
85
  const paths = getStaticPaths();
82
86
  for (const path of paths) {
83
87
  pages.push(
84
- ...collectPages(dynamicPath, [...segments, ...path], {
85
- ...params,
86
- [paramName]: path,
87
- })
88
+ ...(await collectPages(
89
+ dynamicPath,
90
+ [...segments, ...path],
91
+ {
92
+ ...params,
93
+ [paramName]: path,
94
+ }
95
+ ))
88
96
  );
89
97
  }
90
98
  }
@@ -118,7 +126,7 @@ async function buildStaticPages() {
118
126
  let dynamic;
119
127
  let getStaticPaths;
120
128
  if (pageFunctionsPath) {
121
- const module = require(pageFunctionsPath);
129
+ const module = await importModule(pageFunctionsPath);
122
130
  getStaticPaths = module.getStaticPaths;
123
131
  dynamic = module.dynamic;
124
132
  }
@@ -133,10 +141,14 @@ async function buildStaticPages() {
133
141
  const paths = getStaticPaths();
134
142
  for (const path of paths) {
135
143
  pages.push(
136
- ...collectPages(dynamicPath, [...segments, ...path], {
137
- ...params,
138
- [paramName]: path,
139
- })
144
+ ...(await collectPages(
145
+ dynamicPath,
146
+ [...segments, ...path],
147
+ {
148
+ ...params,
149
+ [paramName]: path,
150
+ }
151
+ ))
140
152
  );
141
153
  }
142
154
  }
@@ -171,7 +183,7 @@ async function buildStaticPages() {
171
183
  let dynamic;
172
184
  let getStaticPaths;
173
185
  if (pageFunctionsPath) {
174
- const module = require(pageFunctionsPath);
186
+ const module = await importModule(pageFunctionsPath);
175
187
  getStaticPaths = module.getStaticPaths;
176
188
  dynamic = module.dynamic;
177
189
  }
@@ -186,10 +198,10 @@ async function buildStaticPages() {
186
198
  const paths = getStaticPaths();
187
199
  for (const path of paths) {
188
200
  pages.push(
189
- ...collectPages(dynamicPath, [...segments, path], {
201
+ ...(await collectPages(dynamicPath, [...segments, path], {
190
202
  ...params,
191
203
  [paramName]: path,
192
- })
204
+ }))
193
205
  );
194
206
  }
195
207
  }
@@ -223,7 +235,7 @@ async function buildStaticPages() {
223
235
  let dynamic;
224
236
  let getStaticPaths;
225
237
  if (pageFunctionsPath) {
226
- const module = require(pageFunctionsPath);
238
+ const module = await importModule(pageFunctionsPath);
227
239
  getStaticPaths = module.getStaticPaths;
228
240
  dynamic = module.dynamic;
229
241
  }
@@ -236,10 +248,10 @@ async function buildStaticPages() {
236
248
  const paths = getStaticPaths();
237
249
  for (const path of paths) {
238
250
  pages.push(
239
- ...collectPages(dynamicPath, [...segments, path], {
251
+ ...(await collectPages(dynamicPath, [...segments, path], {
240
252
  ...params,
241
253
  [paramName]: path,
242
- })
254
+ }))
243
255
  );
244
256
  }
245
257
  }
@@ -249,11 +261,11 @@ async function buildStaticPages() {
249
261
  }
250
262
  } else if (!entry.name.startsWith("@")) {
251
263
  pages.push(
252
- ...collectPages(
264
+ ...(await collectPages(
253
265
  path.join(currentPath, entry.name),
254
266
  [...segments, entry.name],
255
267
  params
256
- )
268
+ ))
257
269
  );
258
270
  }
259
271
  }
@@ -282,7 +294,7 @@ async function buildStaticPages() {
282
294
  );
283
295
  let dynamic;
284
296
  if (pageFunctionsPath) {
285
- const module = require(pageFunctionsPath);
297
+ const module = await importModule(pageFunctionsPath);
286
298
  dynamic = module.dynamic;
287
299
  }
288
300
  if (pagePath && !dynamic?.()) {
@@ -293,7 +305,7 @@ async function buildStaticPages() {
293
305
  return pages;
294
306
  }
295
307
 
296
- const pages = collectPages(srcFolder);
308
+ const pages = await collectPages(srcFolder);
297
309
 
298
310
  for await (const { path: folderPath, segments, params } of pages) {
299
311
  try {
@@ -307,7 +319,7 @@ async function buildStaticPages() {
307
319
  undefined,
308
320
  segments.length
309
321
  );
310
- const pageModule = require(pagePath);
322
+ const pageModule = await importModule(pagePath);
311
323
  const Page = pageModule.default ?? pageModule;
312
324
  // Set displayName for better serialization
313
325
  // if (!Page.displayName) Page.displayName = "Page";
@@ -329,7 +341,7 @@ async function buildStaticPages() {
329
341
  let revalidate;
330
342
 
331
343
  if (pageFunctionsPath) {
332
- const pageFunctionsModule = require(pageFunctionsPath);
344
+ const pageFunctionsModule = await importModule(pageFunctionsPath);
333
345
  const getProps = pageFunctionsModule.getProps;
334
346
  revalidate = pageFunctionsModule.revalidate;
335
347
  pageFunctionsProps = await getProps?.(params, {}, {});
@@ -357,7 +369,7 @@ async function buildStaticPages() {
357
369
  if (layouts && Array.isArray(layouts)) {
358
370
  let index = 0;
359
371
  for (const [layoutPath, dParams, slots] of layouts.reverse()) {
360
- const layoutModule = require(layoutPath);
372
+ const layoutModule = await importModule(layoutPath);
361
373
  const Layout = layoutModule.default ?? layoutModule;
362
374
  // if (!Layout.displayName) Layout.displayName = "Layout";
363
375
  const updatedSlots = {};
@@ -489,7 +501,7 @@ async function buildStaticPage(reqPath) {
489
501
  );
490
502
  if (!pagePath) throw new Error(`No page found for ${reqPath}`);
491
503
 
492
- const pageModule = require(pagePath);
504
+ const pageModule = await importModule(pagePath);
493
505
  const Page = pageModule.default ?? pageModule;
494
506
 
495
507
  let props = { params: dParams, query: {} };
@@ -507,7 +519,7 @@ async function buildStaticPage(reqPath) {
507
519
  let pageFunctionsProps;
508
520
  let revalidate;
509
521
  if (pageFunctionsPath) {
510
- const pageFunctionsModule = require(pageFunctionsPath);
522
+ const pageFunctionsModule = await importModule(pageFunctionsPath);
511
523
  const getProps = pageFunctionsModule.getProps;
512
524
  revalidate = pageFunctionsModule.revalidate;
513
525
  pageFunctionsProps = await getProps?.(dParams, {}, {});
@@ -533,8 +545,8 @@ async function buildStaticPage(reqPath) {
533
545
  );
534
546
  if (layouts && Array.isArray(layouts)) {
535
547
  let index = 0;
536
- for (const [layoutPath, dParams, slots] of layouts.reverse()) {
537
- const layoutModule = require(layoutPath);
548
+ for await (const [layoutPath, dParams, slots] of layouts.reverse()) {
549
+ const layoutModule = await importModule(layoutPath);
538
550
  const Layout = layoutModule.default ?? layoutModule;
539
551
  const updatedSlots = {};
540
552
  for (const [slotName, slotElement] of Object.entries(slots)) {
@@ -12,7 +12,7 @@ async function generateStaticPage(reqPath) {
12
12
 
13
13
  try {
14
14
  // console.log("🔄 Rendering HTML for:", finalReqPath);
15
- const htmlStream = await renderAppToHtml(finalReqPath, paramsString);
15
+ const htmlStream = renderAppToHtml(finalReqPath, paramsString);
16
16
 
17
17
  mkdirSync(path.dirname(htmlPath), { recursive: true });
18
18
  const fileStream = createWriteStream(htmlPath);
@@ -13,7 +13,7 @@ async function generateStaticPages(routes) {
13
13
 
14
14
  try {
15
15
  // console.log("🔄 Rendering HTML for:", reqPath);
16
- const htmlStream = await renderAppToHtml(reqPath, paramsString);
16
+ const htmlStream = renderAppToHtml(reqPath, paramsString);
17
17
 
18
18
  mkdirSync(path.dirname(htmlPath), { recursive: true });
19
19
  const fileStream = createWriteStream(htmlPath);
@@ -4,8 +4,9 @@ const React = require("react");
4
4
  const {
5
5
  getFilePathAndDynamicParams,
6
6
  } = require("./get-file-path-and-dynamic-params");
7
+ const importModule = require("./import-module");
7
8
 
8
- function getErrorJSX(reqPath, query, error) {
9
+ async function getErrorJSX(reqPath, query, error) {
9
10
  const srcFolder = path.resolve(process.cwd(), "src");
10
11
  const reqSegments = reqPath.split("/").filter(Boolean);
11
12
  const folderPath = path.join(srcFolder, ...reqSegments);
@@ -49,7 +50,7 @@ function getErrorJSX(reqPath, query, error) {
49
50
  }
50
51
 
51
52
  if (pagePath) {
52
- const pageModule = require(pagePath);
53
+ const pageModule = await importModule(pagePath);
53
54
  const Page = pageModule.default ?? pageModule;
54
55
  jsx = React.createElement(Page, {
55
56
  params: dynamicParams ?? {},
@@ -93,7 +94,7 @@ function getErrorJSX(reqPath, query, error) {
93
94
  if (layouts && Array.isArray(layouts)) {
94
95
  let index = 0;
95
96
  for (const [layoutPath, dParams, slots] of layouts.reverse()) {
96
- const layoutModule = require(layoutPath);
97
+ const layoutModule = await importModule(layoutPath);
97
98
  const Layout = layoutModule.default ?? layoutModule;
98
99
  let props = { params: dParams, query, ...slots };
99
100
  jsx = React.createElement(Layout, props, jsx);
package/dinou/get-jsx.js CHANGED
@@ -4,6 +4,7 @@ const React = require("react");
4
4
  const {
5
5
  getFilePathAndDynamicParams,
6
6
  } = require("./get-file-path-and-dynamic-params");
7
+ const importModule = require("./import-module");
7
8
 
8
9
  async function getJSX(reqPath, query, cookies) {
9
10
  const srcFolder = path.resolve(process.cwd(), "src");
@@ -49,7 +50,7 @@ async function getJSX(reqPath, query, cookies) {
49
50
  `Page not found: no "page" file found for "${reqPath}"`
50
51
  );
51
52
  } else {
52
- const pageModule = require(notFoundPath);
53
+ const pageModule = await importModule(notFoundPath);
53
54
  const Page = pageModule.default ?? pageModule;
54
55
  jsx = React.createElement(Page, {
55
56
  params: dParams ?? {},
@@ -66,7 +67,7 @@ async function getJSX(reqPath, query, cookies) {
66
67
  }
67
68
  }
68
69
  } else {
69
- const pageModule = require(pagePath);
70
+ const pageModule = await importModule(pagePath);
70
71
  const Page = pageModule.default ?? pageModule;
71
72
 
72
73
  let props = {
@@ -86,7 +87,7 @@ async function getJSX(reqPath, query, cookies) {
86
87
  reqSegments.length
87
88
  );
88
89
  if (pageFunctionsPath) {
89
- const pageFunctionsModule = require(pageFunctionsPath);
90
+ const pageFunctionsModule = await importModule(pageFunctionsPath);
90
91
  const getProps = pageFunctionsModule.getProps;
91
92
  pageFunctionsProps = await getProps?.(dynamicParams, query, cookies);
92
93
  props = { ...props, ...(pageFunctionsProps?.page ?? {}) };
@@ -123,7 +124,7 @@ async function getJSX(reqPath, query, cookies) {
123
124
  if (layouts && Array.isArray(layouts)) {
124
125
  let index = 0;
125
126
  for (const [layoutPath, dParams, slots] of layouts.reverse()) {
126
- const layoutModule = require(layoutPath);
127
+ const layoutModule = await importModule(layoutPath);
127
128
  const layoutFolderPath = path.dirname(layoutPath);
128
129
  const resetLayoutPath = getFilePathAndDynamicParams(
129
130
  [],
@@ -10,7 +10,7 @@ async function getSSGJSXOrJSX(
10
10
  const result =
11
11
  Object.keys(query).length || isDevelopment || Object.keys(cookies).length
12
12
  ? await getJSX(reqPath, query, cookies)
13
- : getSSGJSX(reqPath) ?? (await getJSX(reqPath, query, cookies));
13
+ : (await getSSGJSX(reqPath)) ?? (await getJSX(reqPath, query, cookies));
14
14
  return result;
15
15
  }
16
16
 
@@ -1,8 +1,9 @@
1
1
  const path = require("path");
2
2
  const { existsSync, readFileSync } = require("fs");
3
3
  const React = require("react");
4
+ const importModule = require("./import-module");
4
5
 
5
- function deserializeReactElement(
6
+ async function deserializeReactElement(
6
7
  serialized,
7
8
  returnUndefined = { value: false }
8
9
  ) {
@@ -17,7 +18,9 @@ function deserializeReactElement(
17
18
  let Component;
18
19
  if (modulePath) {
19
20
  try {
20
- const module = require(path.resolve(process.cwd(), modulePath));
21
+ const module = await importModule(
22
+ path.resolve(process.cwd(), modulePath)
23
+ );
21
24
  Component = module.default ?? module;
22
25
  } catch (err) {
23
26
  console.error(`Error loading module ${modulePath}:`, err);
@@ -67,12 +70,12 @@ function deserializeReactElement(
67
70
  return returnUndefined.value ? undefined : serialized;
68
71
  }
69
72
 
70
- function getSSGJSX(reqPath) {
73
+ async function getSSGJSX(reqPath) {
71
74
  const distFolder = path.resolve(process.cwd(), "dist");
72
75
  const jsonPath = path.join(distFolder, reqPath, "index.json");
73
76
  if (existsSync(jsonPath)) {
74
77
  const { jsx } = JSON.parse(readFileSync(jsonPath, "utf8"));
75
- const deserializedJSX = deserializeReactElement(jsx);
78
+ const deserializedJSX = await deserializeReactElement(jsx);
76
79
  return deserializedJSX;
77
80
  }
78
81
  }
@@ -0,0 +1,24 @@
1
+ const { pathToFileURL } = require("url");
2
+ const path = require("path");
3
+
4
+ async function importModule(modulePath) {
5
+ try {
6
+ return require(modulePath);
7
+ } catch (err) {
8
+ if (
9
+ err.code === "ERR_REQUIRE_ESM" ||
10
+ /require\(\) of ES Module/.test(err.message)
11
+ ) {
12
+ const absPath = path.isAbsolute(modulePath)
13
+ ? modulePath
14
+ : path.resolve(process.cwd(), modulePath);
15
+
16
+ const fileUrl = pathToFileURL(absPath).href;
17
+ const mod = await import(fileUrl);
18
+ return mod;
19
+ }
20
+ throw err;
21
+ }
22
+ }
23
+
24
+ module.exports = importModule;
@@ -0,0 +1,12 @@
1
+ // dinou/register-loader.mjs
2
+ import { register } from "node:module";
3
+ import { pathToFileURL } from "node:url";
4
+ import { createRequire } from "node:module";
5
+
6
+ const require = createRequire(import.meta.url);
7
+
8
+ // Resuelve el loader dentro del propio paquete dinou
9
+ const loaderPath = require.resolve("./babel-esm-loader.js");
10
+
11
+ // Registra el loader ESM
12
+ register(pathToFileURL(loaderPath).href, pathToFileURL("./"));
@@ -1,47 +1,45 @@
1
1
  const path = require("path");
2
2
  const { spawn } = require("child_process");
3
+ const url = require("url");
3
4
 
4
- function renderAppToHtml(reqPath, paramsString, cookiesString = "{}") {
5
- return new Promise((resolve, reject) => {
6
- const child = spawn(
7
- "node",
8
- [
9
- path.resolve(__dirname, "render-html.js"),
10
- reqPath,
11
- paramsString,
12
- cookiesString,
13
- ],
14
- {
15
- env: {
16
- ...process.env,
17
- },
18
- }
19
- );
5
+ function toFileUrl(p) {
6
+ // Convierte a file://, cross-platform
7
+ return url.pathToFileURL(p).href;
8
+ }
20
9
 
21
- let errorOutput = "";
22
- child.stderr.on("data", (data) => {
23
- errorOutput += data.toString();
24
- });
10
+ const registerLoaderPath = toFileUrl(
11
+ path.join(__dirname, "register-loader.mjs")
12
+ );
13
+ const renderHtmlPath = path.resolve(__dirname, "render-html.js");
25
14
 
26
- child.on("error", (error) => {
27
- reject(new Error(`Failed to start child process: ${error.message}`));
28
- });
15
+ function renderAppToHtml(reqPath, paramsString, cookiesString = "{}") {
16
+ const child = spawn(
17
+ "node",
18
+ [
19
+ "--import",
20
+ registerLoaderPath,
21
+ renderHtmlPath,
22
+ reqPath,
23
+ paramsString,
24
+ cookiesString,
25
+ ],
26
+ {
27
+ env: { ...process.env },
28
+ stdio: ["ignore", "pipe", "pipe"], // stdin, stdout, stderr
29
+ }
30
+ );
29
31
 
30
- child.on("spawn", () => {
31
- resolve(child.stdout);
32
- });
32
+ // Capturamos errores del child
33
+ child.on("error", (err) => {
34
+ console.error("Failed to start child process:", err);
35
+ });
33
36
 
34
- child.on("close", (code) => {
35
- if (code !== 0) {
36
- try {
37
- const errorResult = JSON.parse(errorOutput);
38
- reject(new Error(errorResult.error || errorOutput));
39
- } catch {
40
- reject(new Error(`Child process failed: ${errorOutput}`));
41
- }
42
- }
43
- });
37
+ // Opcional: puedes escuchar stderr y loguear
38
+ child.stderr.on("data", (chunk) => {
39
+ console.error(chunk.toString());
44
40
  });
41
+
42
+ return child.stdout; // <-- aquí devuelves un stream legible
45
43
  }
46
44
 
47
45
  module.exports = renderAppToHtml;
@@ -10,34 +10,13 @@ babelRegister({
10
10
  extensions: [".js", ".jsx", ".ts", ".tsx"],
11
11
  });
12
12
  const addHook = require("./asset-require-hook.js");
13
+ const { extensions } = require("./asset-extensions.js");
13
14
  const createScopedName = require("./createScopedName");
14
15
  require("css-modules-require-hook")({
15
16
  generateScopedName: createScopedName,
16
17
  });
17
18
  addHook({
18
- extensions: [
19
- "png",
20
- "jpg",
21
- "jpeg",
22
- "gif",
23
- "svg",
24
- "webp",
25
- "avif",
26
- "ico",
27
- "mp4",
28
- "webm",
29
- "ogg",
30
- "mov",
31
- "avi",
32
- "mkv",
33
- "mp3",
34
- "wav",
35
- "flac",
36
- "m4a",
37
- "aac",
38
- "mjpeg",
39
- "mjpg",
40
- ],
19
+ extensions,
41
20
  name: function (localName, filepath) {
42
21
  const result = createScopedName(localName, filepath);
43
22
  return result + ".[ext]";
@@ -133,64 +112,64 @@ function formatErrorHtmlProduction(error) {
133
112
  `;
134
113
  }
135
114
 
115
+ function writeErrorOutput(error, isProd) {
116
+ process.stdout.write(
117
+ isProd ? formatErrorHtmlProduction(error) : formatErrorHtml(error)
118
+ );
119
+ process.stderr.write(
120
+ JSON.stringify({ error: error.message, stack: error.stack })
121
+ );
122
+ }
123
+
136
124
  async function renderToStream(reqPath, query, cookies = {}) {
137
125
  try {
138
126
  const jsx =
139
127
  Object.keys(query).length || isDevelopment || Object.keys(cookies).length
140
128
  ? renderJSXToClientJSX(await getJSX(reqPath, query, cookies))
141
- : getSSGJSX(reqPath) ??
129
+ : (await getSSGJSX(reqPath)) ??
142
130
  renderJSXToClientJSX(await getJSX(reqPath, query, cookies));
143
131
 
144
132
  const stream = renderToPipeableStream(jsx, {
145
133
  onError(error) {
146
- const isProd = process.env.NODE_ENV === "production";
134
+ process.nextTick(async () => {
135
+ if (stream && !stream.destroyed) {
136
+ try {
137
+ stream.unpipe(process.stdout);
138
+ stream.destroy();
139
+ } catch {}
140
+ }
141
+ const isProd = process.env.NODE_ENV === "production";
142
+
143
+ try {
144
+ const errorJSX = await getErrorJSX(reqPath, query, error);
147
145
 
148
- try {
149
- const errorJSX = getErrorJSX(reqPath, query, error);
146
+ if (errorJSX === undefined) {
147
+ writeErrorOutput(error, isProd);
148
+ process.exit(1);
149
+ }
150
150
 
151
- if (errorJSX === undefined) {
152
- process.stdout.write(
153
- isProd ? formatErrorHtmlProduction(error) : formatErrorHtml(error)
154
- );
155
- process.stderr.write(
156
- JSON.stringify({ error: error.message, stack: error.stack })
157
- );
151
+ const errorStream = renderToPipeableStream(errorJSX, {
152
+ onShellReady() {
153
+ errorStream.pipe(process.stdout);
154
+ },
155
+ onError(err) {
156
+ console.error("Error rendering error JSX:", err);
157
+ writeErrorOutput(error, isProd);
158
+ process.exit(1);
159
+ },
160
+ bootstrapModules: ["/error.js"],
161
+ bootstrapScriptContent: `window.__DINOU_ERROR_MESSAGE__=${JSON.stringify(
162
+ error.message || "Unknown error"
163
+ )};window.__DINOU_ERROR_STACK__=${JSON.stringify(
164
+ error.stack || "No stack trace available"
165
+ )};`,
166
+ });
167
+ } catch (err) {
168
+ console.error("Render error (no error.tsx?):", err);
169
+ writeErrorOutput(error, isProd);
158
170
  process.exit(1);
159
171
  }
160
-
161
- const errorStream = renderToPipeableStream(errorJSX, {
162
- onShellReady() {
163
- errorStream.pipe(process.stdout);
164
- },
165
- onError(err) {
166
- console.error("Error rendering error JSX:", err);
167
- process.stdout.write(
168
- isProd
169
- ? formatErrorHtmlProduction(error)
170
- : formatErrorHtml(error)
171
- );
172
- process.stderr.write(
173
- JSON.stringify({ error: error.message, stack: error.stack })
174
- );
175
- process.exit(1);
176
- },
177
- bootstrapModules: ["/error.js"],
178
- bootstrapScriptContent: `window.__DINOU_ERROR_MESSAGE__=${JSON.stringify(
179
- error.message || "Unknown error"
180
- )};window.__DINOU_ERROR_STACK__=${JSON.stringify(
181
- error.stack || "No stack trace available"
182
- )};`,
183
- });
184
- } catch (err) {
185
- console.error("Render error (no error.tsx?):", err);
186
- process.stdout.write(
187
- isProd ? formatErrorHtmlProduction(error) : formatErrorHtml(error)
188
- );
189
- process.stderr.write(
190
- JSON.stringify({ error: error.message, stack: error.stack })
191
- );
192
- process.exit(1);
193
- }
172
+ });
194
173
  },
195
174
  onShellReady() {
196
175
  stream.pipe(process.stdout);
package/dinou/server.js CHANGED
@@ -8,6 +8,7 @@ const express = require("express");
8
8
  const getSSGJSXOrJSX = require("./get-ssg-jsx-or-jsx.js");
9
9
  const { getErrorJSX } = require("./get-error-jsx.js");
10
10
  const addHook = require("./asset-require-hook.js");
11
+ const { extensions } = require("./asset-extensions.js");
11
12
  webpackRegister();
12
13
  const babelRegister = require("@babel/register");
13
14
  babelRegister({
@@ -24,35 +25,14 @@ require("css-modules-require-hook")({
24
25
  generateScopedName: createScopedName,
25
26
  });
26
27
  addHook({
27
- extensions: [
28
- "png",
29
- "jpg",
30
- "jpeg",
31
- "gif",
32
- "svg",
33
- "webp",
34
- "avif",
35
- "ico",
36
- "mp4",
37
- "webm",
38
- "ogg",
39
- "mov",
40
- "avi",
41
- "mkv",
42
- "mp3",
43
- "wav",
44
- "flac",
45
- "m4a",
46
- "aac",
47
- "mjpeg",
48
- "mjpg",
49
- ],
28
+ extensions,
50
29
  name: function (localName, filepath) {
51
30
  const result = createScopedName(localName, filepath);
52
31
  return result + ".[ext]";
53
32
  },
54
33
  publicPath: "/assets/",
55
34
  });
35
+ const importModule = require("./import-module");
56
36
  const generateStatic = require("./generate-static.js");
57
37
  const renderAppToHtml = require("./render-app-to-html.js");
58
38
  const revalidating = require("./revalidating.js");
@@ -117,18 +97,6 @@ if (isDevelopment) {
117
97
  }
118
98
  }
119
99
 
120
- // function clearAllUserCache() {
121
- // const srcDir = path.resolve(process.cwd(), "src");
122
- // Object.keys(require.cache).forEach((file) => {
123
- // if (file.startsWith(srcDir)) {
124
- // delete require.cache[file];
125
- // }
126
- // });
127
- // console.log(
128
- // "[Server HMR] Cleared all src/ require caches due to directive change."
129
- // );
130
- // }
131
-
132
100
  watcher.on("change", () => {
133
101
  try {
134
102
  const newManifest = JSON.parse(readFileSync(manifestPath, "utf8"));
@@ -150,12 +118,7 @@ if (isDevelopment) {
150
118
  // console.log(`Cleared cache for ${absPath} (server -> client)`);
151
119
  }
152
120
  }
153
- // if (
154
- // Object.keys(currentManifest).length !== Object.keys(newManifest).length
155
- // ) {
156
- // // Only clear if there was a change (add/remove)
157
- // clearAllUserCache();
158
- // }
121
+
159
122
  currentManifest = newManifest;
160
123
  } catch (err) {
161
124
  console.error("Error handling manifest change:", err);
@@ -261,7 +224,7 @@ app.post(/^\/____rsc_payload_error____\/.*\/?$/, async (req, res) => {
261
224
  }
262
225
  });
263
226
 
264
- app.get(/^\/.*\/?$/, async (req, res) => {
227
+ app.get(/^\/.*\/?$/, (req, res) => {
265
228
  try {
266
229
  const reqPath = req.path.endsWith("/") ? req.path : req.path + "/";
267
230
 
@@ -275,7 +238,7 @@ app.get(/^\/.*\/?$/, async (req, res) => {
275
238
  }
276
239
  }
277
240
 
278
- const appHtmlStream = await renderAppToHtml(
241
+ const appHtmlStream = renderAppToHtml(
279
242
  reqPath,
280
243
  JSON.stringify({ ...req.query }),
281
244
  JSON.stringify({ ...req.cookies })
@@ -302,7 +265,7 @@ app.post("/____server_function____", async (req, res) => {
302
265
  let relativePath = fileUrl.replace(/^file:\/\/\/?/, "");
303
266
  const absolutePath = path.resolve(process.cwd(), relativePath);
304
267
 
305
- const mod = require(absolutePath);
268
+ const mod = await importModule(absolutePath);
306
269
 
307
270
  const fn = exportName === "default" ? mod.default : mod[exportName];
308
271
 
@@ -311,9 +274,7 @@ app.post("/____server_function____", async (req, res) => {
311
274
  }
312
275
 
313
276
  const context = { req, res };
314
- if (fn.length === args.length + 1) {
315
- args.push(context);
316
- }
277
+ args.push(context);
317
278
  const result = await fn(...args);
318
279
 
319
280
  if (
package/eject.js CHANGED
@@ -34,13 +34,14 @@ if (fs.existsSync(path.join(modulePath, "postcss.config.js"))) {
34
34
  }
35
35
 
36
36
  const pkg = require(path.join(projectRoot, "package.json"));
37
- pkg.scripts["start:express"] = "node --conditions react-server dinou/server.js";
37
+ pkg.scripts["start:express"] =
38
+ "node --conditions react-server --import ./dinou/register-loader.mjs ./dinou/server.js";
38
39
  pkg.scripts["start:dev-server"] = "cross-env NODE_ENV=development rollup -c -w";
39
40
  pkg.scripts.dev =
40
41
  'concurrently "npm run start:express" "npm run start:dev-server"';
41
42
  pkg.scripts.build = "cross-env NODE_ENV=production rollup -c";
42
43
  pkg.scripts.start =
43
- "cross-env NODE_ENV=production node --conditions react-server dinou/server.js";
44
+ "cross-env NODE_ENV=production node --conditions react-server --import ./dinou/register-loader.mjs ./dinou/server.js";
44
45
  delete pkg.scripts.eject;
45
46
  fs.writeFileSync(
46
47
  path.join(projectRoot, "package.json"),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dinou",
3
- "version": "2.2.0",
3
+ "version": "2.3.1",
4
4
  "description": "Minimal React 19 Framework",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -1,10 +1,9 @@
1
1
  const fs = require("fs");
2
2
  const path = require("path");
3
3
  const createScopedName = require("../dinou/createScopedName.js");
4
+ const { regex } = require("../dinou/asset-extensions.js");
4
5
 
5
- function dinouAssetPlugin({
6
- include = /\.(png|jpe?g|gif|svg|webp|avif|ico|mp4|webm|ogg|mov|avi|mkv|mp3|wav|flac|m4a|aac|mjpeg|mjpg)$/i,
7
- } = {}) {
6
+ function dinouAssetPlugin({ include = regex } = {}) {
8
7
  return {
9
8
  name: "dinou-asset-plugin",
10
9
  async load(id) {
package/rollup.config.js CHANGED
@@ -13,6 +13,7 @@ const { esmHmrPlugin } = require("./react-refresh/rollup-plugin-esm-hmr.js");
13
13
  const dinouAssetPlugin = require("./rollup-plugins/dinou-asset-plugin.js");
14
14
  const tsconfigPaths = require("rollup-plugin-tsconfig-paths");
15
15
  const serverFunctionsPlugin = require("./rollup-plugins/rollup-plugin-server-functions");
16
+ const { regex } = require("./dinou/asset-extensions.js");
16
17
 
17
18
  const isDevelopment = process.env.NODE_ENV !== "production";
18
19
  const outputDirectory = isDevelopment ? "public" : "dist3";
@@ -76,8 +77,7 @@ module.exports = async function () {
76
77
  transformMixedEsModules: true,
77
78
  }),
78
79
  dinouAssetPlugin({
79
- include:
80
- /\.(png|jpe?g|gif|svg|webp|avif|ico|mp4|webm|ogg|mov|avi|mkv|mp3|wav|flac|m4a|aac|mjpeg|mjpg)$/i,
80
+ include: regex,
81
81
  }),
82
82
  babel({
83
83
  babelHelpers: "bundled",