blinkk / rootjs

Root.js – A full-featured web development tool with a built-in CMS.
https://rootjs.dev
MIT License
8 stars 0 forks source link

Runtime error with RootCMSClient in Next.js-based project #426

Closed cjwang18 closed 1 month ago

cjwang18 commented 1 month ago

Describe the bug Runtime error occurs when trying to use RootCMSClient in a Next.js-based (and probably any webpack-based) project.

 ⨯ ../../../node_modules/fsevents/fsevents.node
Module parse failed: Unexpected character '�' (1:0)
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
(Source code omitted for this binary file)

Import trace for requested module:
../../../node_modules/fsevents/fsevents.node
../../../node_modules/fsevents/fsevents.js
./node_modules/rollup/dist/es/shared/node-entry.js
./node_modules/rollup/dist/es/rollup.js
./node_modules/vite/dist/node/index.js
./node_modules/@blinkk/root/dist/chunk-G5ALYSEQ.js
./node_modules/@blinkk/root/dist/node.js
./node_modules/@blinkk/root-cms/dist/plugin.js
./src/app/page.tsx

To Reproduce Steps to reproduce the behavior:

  1. Install @blinkk/root and @blinkk/root-cms into a Next.js project
  2. Create an App Router page (src/app/page.tsx) that attempts to instantiate RootCMSClient with the cmsPlugin to communicate with Firebase
  3. Call cmsClient.getDoc
  4. See error

Expected behavior The runtime error should not happen

Screenshots Stacktrace above. Screenshot: image

Additional context Following the stacktrace, I was able to get past the runtime error by commenting out import { loadRootConfig, viteSsrLoadModule } from "@blinkk/root/node"; in node_modules/@blinkk/root-cms/dist/plugin.js as well as any code that depended on it.

For reference, the entire patch is as follows:

diff --git a/node_modules/@blinkk/root-cms/.DS_Store b/node_modules/@blinkk/root-cms/.DS_Store
new file mode 100644
index 0000000..a35e947
Binary files /dev/null and b/node_modules/@blinkk/root-cms/.DS_Store differ
diff --git a/node_modules/@blinkk/root-cms/dist/plugin.js b/node_modules/@blinkk/root-cms/dist/plugin.js
index 86a864b..3b4420f 100644
--- a/node_modules/@blinkk/root-cms/dist/plugin.js
+++ b/node_modules/@blinkk/root-cms/dist/plugin.js
@@ -17,22 +17,22 @@ import sirv from "sirv";
 import { promises as fs } from "node:fs";
 import path from "node:path";
 import { fileURLToPath } from "node:url";
-import { loadRootConfig, viteSsrLoadModule } from "@blinkk/root/node";
+// import { loadRootConfig, viteSsrLoadModule } from "@blinkk/root/node";
 import * as dom from "dts-dom";
 var __dirname = path.dirname(fileURLToPath(import.meta.url));
-async function generateTypes() {
-  const rootDir = process.cwd();
-  const rootConfig = await loadRootConfig(rootDir, { command: "root-cms" });
-  const modulePath = path.resolve(__dirname, "./project.js");
-  const project = await viteSsrLoadModule(
-    rootConfig,
-    modulePath
-  );
-  const schemas = project.getProjectSchemas();
-  const outputPath = path.resolve(rootDir, "root-cms.d.ts");
-  await generateSchemaDts(outputPath, schemas);
-  console.log("saved root-cms.d.ts!");
-}
+// async function generateTypes() {
+//   const rootDir = process.cwd();
+//   const rootConfig = await loadRootConfig(rootDir, { command: "root-cms" });
+//   const modulePath = path.resolve(__dirname, "./project.js");
+//   const project = await viteSsrLoadModule(
+//     rootConfig,
+//     modulePath
+//   );
+//   const schemas = project.getProjectSchemas();
+//   const outputPath = path.resolve(rootDir, "root-cms.d.ts");
+//   await generateSchemaDts(outputPath, schemas);
+//   console.log("saved root-cms.d.ts!");
+// }
 var TEMPLATE = `/** Root.js CMS types. This file is autogenerated. */

 export interface RootCMSImage {
@@ -1592,9 +1592,9 @@ function cmsPlugin(options) {
      * Watches for .schema.ts file changes and generates a root-cms.d.ts file.
      */
     onFileChange: (eventName, filepath) => {
-      if (filepath.endsWith(".schema.ts")) {
-        generateTypes();
-      }
+      // if (filepath.endsWith(".schema.ts")) {
+      //   generateTypes();
+      // }
     },
     /**
      * Attaches CMS-specific middleware to the Root.js server.
stevenle commented 1 month ago

@cjwang18 Can you try one workaround:

import rootConfig from '@/root.config.js';
const cmsClient = new RootCMSClient(rootConfig);

The loadRootConfig() function bundles the root config through vite/esbuild or loads it from a pre-bundled file in dist/root.config.js (which is generated by root build). Doesn't sound like you need that here, so doing a direct import will avoid having it go through the bundler step.

stevenle commented 1 month ago

In the meantime I'll add a configuration option to disable the file watcher, which I think is the main cause of the issue you're seeing.

stevenle commented 1 month ago

(Another thing to try: https://stackoverflow.com/questions/64103792/fsevents-causes-module-parse-failed-unexpected-character)

cjwang18 commented 1 month ago

Thank you @stevenle for your prompt responses!

Regarding the config, I'm actually inlining it (without an import even).

const cmsClient = new RootCMSClient({
  plugins: [
    cmsPlugin({
      ...
    }),
  ],
})

It's not ideal for now but our use case is a bit different as we're prototyping some functionality; we really only need the ability to read CMS content from Firestore.

It's been a while since I initially encountered this issue and I'm pretty sure I already tried the node-loader without success. I can give it another shot and report back.

stevenle commented 1 month ago

@cjwang18 I think https://github.com/blinkk/rootjs/pull/427 should fix the issue. I'll cut a release soon so you can test.

A note: I think this module should only be transpiled through webpack if it's going to be used on the client side, it's been a while since I've used Next.js but I was under the impression that the getStaticProps() (or other SSR methods) would be compiled through a Node.js based loader. Without access to your code, I would recommend double-checking to make sure that the RootCMSClient isn't being used in any client-side paths.

stevenle commented 1 month ago

@cjwang18 @blinkk/root-cms@1.3.11 should have the fix. You'll just need to pass cmsPlugin({..., watch: false}) which should disable the generate-types.js module (and subsequent deps) from being loaded altogether (it was moved to a dynamic import). Going to close out this bug but if it's still and issue feel free to re-open.

cjwang18 commented 1 month ago

Hey @stevenle thanks for the quick turnaround on this but unfortunately the issue still persists.

While I see the addition of the watch option, image

and have configured the cmsPlugin accordingly, image

I'm still getting the same stacktrace.

I feel like it's because the import is still inline in the plugin.js file and until that is dynamically imported, we would continue to see the issue image

stevenle commented 1 month ago

I see, so this is more of a compile-time error than a runtime error. I think what you want to do in that case is to mark @blinkk/root-cms as "external" to prevent bundling: https://webpack.js.org/configuration/externals/.

The client isn't supposed to be included in any client-side code anyway, so there may be something on your end you may need to configure to prevent this from being used except on the server-side.

cjwang18 commented 1 month ago

Ah sorry, my bad on classifying it as runtime. Indeed it's compile-time and even says "Failed to compile" in the error screenshot 😅 .

To clarify, the 1st and 3rd screenshots in the above message are taken from node_modules/@blinkk/root-cms/dist/plugin.js. Maybe I'm misunderstanding something, but I don't think it's our project that's compiling the import statement into executable code.

Also, I'm defining the RootCMSClient within a React Server Component (Next.js's App Router page component), so I don't think this is executed client-side.

stevenle commented 1 month ago

From the error message what I assume is happening is your react component is being bundled through webpack, and webpack is attempting to include @blinkk/root-cms in this bundle. If you mark this package as external, the server would load the package from node_modules as opposed to including in your bundle. Give that a try, that should solve your compile-time issues.

cjwang18 commented 1 month ago

Thanks Steven, but unfortunately, that doesn't seem to change anything. I'm overriding the externals config like so

config.externals = [
      ...config.externals,
      {
        '@blinkk/root': '@blinkk/root',
        '@blinkk/root-cms': '@blinkk/root-cms',
      },
    ]

I also tried your earlier suggestion with the node-loader but that ended up with additional errors.

I also looked in the dev-compiled page.js file and can't find any references to "blinkk" or "rootcms"/"root-cms", but I'm not sure if I should expect that to be there if it failed to compile. I was looking in the wrong directory (static instead of the server), but at least that seems to confirm that it's indeed running server-side.

stevenle commented 1 month ago

Can you try adding '@blinkk/root-cms/client' as another test? The externals map may expect to match 1:1 with the import statement. If none of this works, I can send you an example of how to load data from firestore directly which would bypass any of the bundling issues that you're seeing.

stevenle commented 1 month ago

This may also be the relevant config that you should be modifying: https://nextjs.org/docs/app/api-reference/next-config-js/serverExternalPackages

cjwang18 commented 1 month ago

This may also be the relevant config that you should be modifying: https://nextjs.org/docs/app/api-reference/next-config-js/serverExternalPackages

Aha, this is indeed the config that resolved the issue.

experimental: {
    serverComponentsExternalPackages: ['@blinkk/root-cms'],
  },

Thank you so much for looking into this with me!