dinou 2.0.1 → 2.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dinou",
3
- "version": "2.0.1",
3
+ "version": "2.0.2",
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);