dinou 2.0.1 → 2.0.3

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,25 @@ 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.0.3]
9
+
10
+ ### Fixed
11
+
12
+ - Fixed cross-platform path handling using Node.js `path` module for macOS/Linux compatibility in `get-jsx.js`, `get-error-jsx.js`, and `build-static-pages.js`.
13
+ - Added `awaitWriteFinish` to `chokidar` in `server.js` to avoid parsing incomplete manifest files on macOS.
14
+
15
+ ## [2.0.2]
16
+
17
+ ### Fixed
18
+
19
+ - Watch server components in react manifest plugin.
20
+
21
+ ## [2.0.1]
22
+
23
+ ### Fixed
24
+
25
+ - Use createFromFetch from react-server-dom-esm in client-error.jsx.
26
+
8
27
  ## [2.0.0]
9
28
 
10
29
  ### Changed
@@ -357,7 +357,7 @@ async function buildStaticPages() {
357
357
  const updatedSlots = {};
358
358
  for (const [slotName, slotElement] of Object.entries(slots)) {
359
359
  const slotFolder = path.join(
360
- layoutPath.split("\\").slice(0, -1).join("\\"),
360
+ path.dirname(layoutPath),
361
361
  `@${slotName}`
362
362
  );
363
363
  const [slotPath] = getFilePathAndDynamicParams(
@@ -58,10 +58,10 @@ function getErrorJSX(reqPath, query, error) {
58
58
  });
59
59
 
60
60
  const noLayoutErrorPath = path.join(
61
- pagePath.split("\\").slice(0, -1).join("\\"),
61
+ path.dirname(pagePath),
62
62
  `no_layout_error`
63
63
  );
64
- if (existsSync(path.resolve(process.cwd(), `${noLayoutErrorPath}`))) {
64
+ if (existsSync(noLayoutErrorPath)) {
65
65
  return jsx;
66
66
  }
67
67
 
package/dinou/get-jsx.js CHANGED
@@ -55,11 +55,13 @@ async function getJSX(reqPath, query) {
55
55
  params: dParams ?? {},
56
56
  query,
57
57
  });
58
+
59
+ const notFoundDir = path.dirname(notFoundPath);
58
60
  const noLayoutNotFoundPath = path.join(
59
- notFoundPath.split("\\").slice(0, -1).join("\\"),
61
+ notFoundDir,
60
62
  `no_layout_not_found`
61
63
  );
62
- if (existsSync(path.resolve(process.cwd(), `${noLayoutNotFoundPath}`))) {
64
+ if (existsSync(noLayoutNotFoundPath)) {
63
65
  return jsx;
64
66
  }
65
67
  }
@@ -71,7 +73,8 @@ async function getJSX(reqPath, query) {
71
73
  params: dynamicParams,
72
74
  query,
73
75
  };
74
- const pageFolder = pagePath.split("\\").slice(0, -1).join("\\");
76
+
77
+ const pageFolder = path.dirname(pagePath);
75
78
  const [pageFunctionsPath] = getFilePathAndDynamicParams(
76
79
  reqSegments,
77
80
  query,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dinou",
3
- "version": "2.0.1",
3
+ "version": "2.0.3",
4
4
  "description": "Minimal React 19 Framework",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -12,6 +12,7 @@ function reactClientManifestPlugin({
12
12
  } = {}) {
13
13
  const manifest = {};
14
14
  const clientModules = new Set();
15
+ const serverModules = new Set();
15
16
 
16
17
  function parseExports(code) {
17
18
  const ast = parser.parse(code, {
@@ -77,6 +78,102 @@ function reactClientManifestPlugin({
77
78
  }
78
79
  }
79
80
 
81
+ function getImports(code, baseFilePath, visited = new Set()) {
82
+ if (visited.has(baseFilePath)) {
83
+ return [];
84
+ }
85
+ visited.add(baseFilePath);
86
+
87
+ const ast = parser.parse(code, {
88
+ sourceType: "module",
89
+ plugins: ["jsx", "typescript"],
90
+ });
91
+ const imports = new Set();
92
+
93
+ traverse(ast, {
94
+ ImportDeclaration(nodePath) {
95
+ const source = nodePath.node.source.value;
96
+ if (source.startsWith(".")) {
97
+ let absImportPath = path.resolve(path.dirname(baseFilePath), source);
98
+ const extensions = [".js", ".jsx", ".ts", ".tsx"];
99
+ if (!extensions.some((ext) => absImportPath.endsWith(ext))) {
100
+ for (const ext of extensions) {
101
+ const potentialPath = absImportPath + ext;
102
+ if (existsSync(potentialPath)) {
103
+ absImportPath = potentialPath;
104
+ break;
105
+ }
106
+ }
107
+ }
108
+ if (existsSync(absImportPath)) {
109
+ imports.add(absImportPath);
110
+ try {
111
+ const importCode = readFileSync(absImportPath, "utf8");
112
+ const nestedImports = getImports(
113
+ importCode,
114
+ absImportPath,
115
+ visited
116
+ );
117
+ nestedImports.forEach((nestedPath) => imports.add(nestedPath));
118
+ } catch (err) {
119
+ console.warn(
120
+ `[react-client-manifest] Could not read import: ${absImportPath}`,
121
+ err.message
122
+ );
123
+ }
124
+ } else {
125
+ console.warn(
126
+ `[react-client-manifest] Import path not found: ${absImportPath}`
127
+ );
128
+ }
129
+ }
130
+ },
131
+ });
132
+
133
+ return Array.from(imports);
134
+ }
135
+
136
+ function isPageOrLayout(absPath) {
137
+ const fileName = path.basename(absPath);
138
+ return fileName.startsWith("page.") || fileName.startsWith("layout.");
139
+ }
140
+
141
+ function isAsyncDefaultExport(code) {
142
+ const ast = parser.parse(code, {
143
+ sourceType: "module",
144
+ plugins: ["jsx", "typescript"],
145
+ });
146
+
147
+ let isAsync = false;
148
+
149
+ traverse(ast, {
150
+ ExportDefaultDeclaration(path) {
151
+ let decl = path.node.declaration;
152
+
153
+ if (decl.type === "Identifier") {
154
+ const binding = path.scope.getBinding(decl.name);
155
+ if (binding && binding.path) {
156
+ decl = binding.path.node;
157
+ if (decl.type === "VariableDeclarator") {
158
+ decl = decl.init;
159
+ }
160
+ }
161
+ }
162
+
163
+ if (
164
+ decl &&
165
+ (decl.type === "FunctionDeclaration" ||
166
+ decl.type === "ArrowFunctionExpression" ||
167
+ decl.type === "FunctionExpression")
168
+ ) {
169
+ isAsync = decl.async;
170
+ }
171
+ },
172
+ });
173
+
174
+ return isAsync;
175
+ }
176
+
80
177
  return {
81
178
  name: "react-client-manifest",
82
179
  async buildStart() {
@@ -93,12 +190,23 @@ function reactClientManifestPlugin({
93
190
  if (isClientModule) {
94
191
  clientModules.add(normalizedPath);
95
192
  updateManifestForModule(absPath, code, true);
96
-
97
193
  this.emitFile({
98
194
  type: "chunk",
99
195
  id: absPath,
100
196
  name: path.basename(absPath, path.extname(absPath)),
101
197
  });
198
+ } else if (isPageOrLayout(absPath)) {
199
+ if (!isAsyncDefaultExport(code)) {
200
+ this.warn(
201
+ `[react-client-manifest] The file ${normalizedPath} is a page or layout without "use client" directive, but its default export is not an async function. Add "use client" if it's a client component, or make the default export async if it's a server component.`
202
+ );
203
+ }
204
+ serverModules.add(normalizedPath);
205
+ this.addWatchFile(absPath);
206
+ const imports = getImports(code, absPath);
207
+ for (const importPath of imports) {
208
+ this.addWatchFile(importPath);
209
+ }
102
210
  }
103
211
  }
104
212
  },
@@ -119,6 +227,7 @@ function reactClientManifestPlugin({
119
227
  }
120
228
  }
121
229
  clientModules.delete(normalizedId);
230
+ serverModules.delete(normalizedId);
122
231
  return;
123
232
  }
124
233
  const code = readFileSync(id, "utf8");
@@ -128,19 +237,33 @@ function reactClientManifestPlugin({
128
237
 
129
238
  if (isClientModule) {
130
239
  clientModules.add(normalizedId);
240
+ serverModules.delete(normalizedId);
131
241
  this.addWatchFile(id);
132
242
  } else {
133
243
  clientModules.delete(normalizedId);
244
+ if (isPageOrLayout(id)) {
245
+ if (!isAsyncDefaultExport(code)) {
246
+ this.warn(
247
+ `[react-client-manifest] The file ${normalizedId} is a page or layout without "use client" directive, but its default export is not an async function. Add "use client" if it's a client component, or make the default export async if it's a server component.`
248
+ );
249
+ }
250
+ serverModules.add(normalizedId);
251
+ this.addWatchFile(id);
252
+ const imports = getImports(code, id);
253
+ for (const importPath of imports) {
254
+ this.addWatchFile(importPath);
255
+ }
256
+ } else {
257
+ serverModules.delete(normalizedId);
258
+ }
134
259
  }
135
260
  },
136
261
  generateBundle(outputOptions, bundle) {
137
262
  for (const [fileName, chunk] of Object.entries(bundle)) {
138
263
  if (chunk.type !== "chunk") continue;
139
-
140
264
  for (const modulePath of Object.keys(chunk.modules)) {
141
265
  const absModulePath = path.resolve(modulePath);
142
266
  const baseFileUrl = pathToFileURL(absModulePath).href;
143
-
144
267
  for (const manifestKey in manifest) {
145
268
  if (manifestKey.startsWith(baseFileUrl)) {
146
269
  manifest[manifestKey].id = "/" + fileName;
@@ -148,7 +271,6 @@ function reactClientManifestPlugin({
148
271
  }
149
272
  }
150
273
  }
151
-
152
274
  const serialized = JSON.stringify(manifest, null, 2);
153
275
  mkdirSync(dirname(manifestPath), { recursive: true });
154
276
  writeFileSync(manifestPath, serialized);