WordPress / gutenberg

The Block Editor project for WordPress and beyond. Plugin is available from the official repository.
https://wordpress.org/gutenberg/
Other
10.52k stars 4.21k forks source link

Block editor cannot be rendered in a server-side context #49288

Open mrmurphy opened 1 year ago

mrmurphy commented 1 year ago

Description

When I try to import modules from various block-related gutenberg components in non-standard browser environments, I get a bundle failure because the window object is not defined. Ideally, I'd like to pre-render block-editor content on the server. Minimally, I'd like to be able to have imports from block-editor related files without breaking my bundler.

Here's a list of patches that I found in my experimentation that contain possible fixes:

@wordpress/block-editor

diff --git a/node_modules/@wordpress/block-editor/build/components/block-toolbar/utils.js b/node_modules/@wordpress/block-editor/build/components/block-toolbar/utils.js
index ff21367..be3b5e5 100644
--- a/node_modules/@wordpress/block-editor/build/components/block-toolbar/utils.js
+++ b/node_modules/@wordpress/block-editor/build/components/block-toolbar/utils.js
@@ -14,7 +14,7 @@ var _element = require("@wordpress/element");
 const {
   clearTimeout,
   setTimeout
-} = window;
+} = globalThis;

 const noop = () => {};

diff --git a/node_modules/@wordpress/block-editor/build/components/typewriter/index.js b/node_modules/@wordpress/block-editor/build/components/typewriter/index.js
index 826c512..35be029 100644
--- a/node_modules/@wordpress/block-editor/build/components/typewriter/index.js
+++ b/node_modules/@wordpress/block-editor/build/components/typewriter/index.js
@@ -25,7 +25,10 @@ var _store = require("../../store");
 /**
  * Internal dependencies
  */
-const isIE = window.navigator.userAgent.indexOf('Trident') !== -1;
+let isIE = false;
+if (typeof window !== 'undefined') {
+  isIE = window.navigator.userAgent.indexOf('Trident') !== -1;
+}
 const arrowKeyCodes = new Set([_keycodes.UP, _keycodes.DOWN, _keycodes.LEFT, _keycodes.RIGHT]);
 const initialTriggerPercentage = 0.75;

@wordpress/blocks

diff --git a/node_modules/@wordpress/blocks/build/api/raw-handling/image-corrector.js b/node_modules/@wordpress/blocks/build/api/raw-handling/image-corrector.js
index 77df21e..2d64494 100644
--- a/node_modules/@wordpress/blocks/build/api/raw-handling/image-corrector.js
+++ b/node_modules/@wordpress/blocks/build/api/raw-handling/image-corrector.js
@@ -17,7 +17,7 @@ var _blob = require("@wordpress/blob");
 const {
   atob,
   File
-} = window;
+} = globalThis;

 function imageCorrector(node) {
   if (node.nodeName !== 'IMG') {
diff --git a/node_modules/@wordpress/blocks/build/api/raw-handling/ms-list-converter.js b/node_modules/@wordpress/blocks/build/api/raw-handling/ms-list-converter.js
index 59b270a..39242a9 100644
--- a/node_modules/@wordpress/blocks/build/api/raw-handling/ms-list-converter.js
+++ b/node_modules/@wordpress/blocks/build/api/raw-handling/ms-list-converter.js
@@ -8,9 +8,9 @@ exports.default = msListConverter;
 /**
  * Browser dependencies
  */
-const {
+let {
   parseInt
-} = window;
+} = globalThis;

 function isList(node) {
   return node.nodeName === 'OL' || node.nodeName === 'UL';
diff --git a/node_modules/@wordpress/blocks/build/api/raw-handling/paste-handler.js b/node_modules/@wordpress/blocks/build/api/raw-handling/paste-handler.js
index 75ac1f0..1622366 100644
--- a/node_modules/@wordpress/blocks/build/api/raw-handling/paste-handler.js
+++ b/node_modules/@wordpress/blocks/build/api/raw-handling/paste-handler.js
@@ -72,7 +72,7 @@ var _slackParagraphCorrector = _interopRequireDefault(require("./slack-paragraph
  */
 const {
   console
-} = window;
+} = globalThis;
 /**
  * Filters HTML to only contain phrasing content.
  *
diff --git a/node_modules/@wordpress/blocks/build/store/actions.js b/node_modules/@wordpress/blocks/build/store/actions.js
index ebbcc4b..af7f52a 100644
--- a/node_modules/@wordpress/blocks/build/store/actions.js
+++ b/node_modules/@wordpress/blocks/build/store/actions.js
@@ -51,7 +51,7 @@ var _constants = require("../api/constants");
 const {
   error,
   warn
-} = window.console;
+} = globalThis.console;
 /**
  * Mapping of legacy category slugs to their latest normal values, used to
  * accommodate updates of the default set of block categories.

I'm not sure those are the only places, because I'm running into plenty of other problems from my own code 😄 , but I know at least those are problematic.

This isn't only a problem for SSR, but for Web Workers as well. We currently have to be careful not to import any files that import block-related code into our worker process, or we'll get a runtime error there on initial load.

Step-by-step reproduction instructions

Try to set up a basic block editor in Next.js, or write code in a web worker that uses functions from the @wordpress/blocks library, specifically these imports are an example of what we're using:

import {
  BlockInstance,
  createBlock,
  findTransform,
  getBlockAttributes,
  getBlockTransforms,
} from "@wordpress/blocks";

Screenshots, screen recording, code snippet

No response

Environment info

    "@wordpress/blocks": "12.5.0",
    "@wordpress/block-editor": "11.5.0",
    ...

Please confirm that you have searched existing issues in the repo.

Yes

Please confirm that you have tested with all plugins deactivated except Gutenberg.

Yes

ellatrix commented 1 year ago

To be able to use the blocks package on the server, you'll also need JSDOM:

https://github.com/ellatrix/patterns/blob/main/data.js#L1-L21

mrmurphy commented 1 year ago

So it seems like this ticket may just apply to the usage of window instead of globalThis, so that the code can be imported in non-window contexts, whether or not it will be run. And so that any functions that don't have DOM dependencies can be used.

Outside of that, perhaps there could be a guide in the blocks or block-editor README about compatibility with SSR?

kevin940726 commented 1 year ago

I don't think these packages support SSR and that it's ever a priority. Given that react now recommends starting with a server side framework, I think we ultimately should support SSR or at least add notes in the READMEs. If we agree on that, a tracking issue might help!