@atlaskit/editor-ssr-renderer 2.1.6 → 2.2.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
@@ -1,5 +1,29 @@
1
1
  # @atlaskit/editor-ssr-renderer
2
2
 
3
+ ## 2.2.1
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies
8
+
9
+ ## 2.2.0
10
+
11
+ ### Minor Changes
12
+
13
+ - [`cbc1403b3cae1`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/cbc1403b3cae1) -
14
+ [EDITOR-5094](https://hello.jira.atlassian.cloud/browse/EDITOR-5094) - add PerfPortal segments for
15
+ editor SSR logic
16
+
17
+ ### Patch Changes
18
+
19
+ - Updated dependencies
20
+
21
+ ## 2.1.7
22
+
23
+ ### Patch Changes
24
+
25
+ - Updated dependencies
26
+
3
27
  ## 2.1.6
4
28
 
5
29
  ### Patch Changes
@@ -21,12 +21,16 @@ var _model = require("@atlaskit/editor-prosemirror/model");
21
21
  var _eventDispatcher = require("@atlaskit/editor-common/event-dispatcher");
22
22
  var _providerFactory = require("@atlaskit/editor-common/provider-factory");
23
23
  var _expValEquals = require("@atlaskit/tmp-editor-statsig/exp-val-equals");
24
- var _excluded = ["plugins", "schema", "doc", "portalProviderAPI", "intl", "onEditorStateChanged"];
24
+ var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
25
+ var _ssrMeasures = require("@atlaskit/editor-common/performance/ssr-measures");
26
+ var _excluded = ["plugins", "schema", "doc", "portalProviderAPI", "intl", "onSSRMeasure", "onEditorStateChanged"];
25
27
  function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function _interopRequireWildcard(e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != _typeof(e) && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (var _t in e) "default" !== _t && {}.hasOwnProperty.call(e, _t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, _t)) && (i.get || i.set) ? o(f, _t, i) : f[_t] = e[_t]); return f; })(e, t); }
26
28
  function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
27
29
  function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { (0, _defineProperty2.default)(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
28
30
  function _callSuper(t, o, e) { return o = (0, _getPrototypeOf2.default)(o), (0, _possibleConstructorReturn2.default)(t, _isNativeReflectConstruct() ? Reflect.construct(o, e || [], (0, _getPrototypeOf2.default)(t).constructor) : o.apply(t, e)); }
29
31
  function _isNativeReflectConstruct() { try { var t = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); } catch (t) {} return (_isNativeReflectConstruct = function _isNativeReflectConstruct() { return !!t; })(); }
32
+ var SSR_TRACE_SEGMENT_NAME = 'reactEditorView/editorSSRRenderer';
33
+
30
34
  // The copy of type from prosemirror-view.
31
35
  // Probably, we need to fix this package exports and add `NodeViewConstructor` and `MarkViewConstructor` types here.
32
36
  /**
@@ -179,43 +183,50 @@ function EditorSSRRenderer(_ref) {
179
183
  doc = _ref.doc,
180
184
  portalProviderAPI = _ref.portalProviderAPI,
181
185
  intl = _ref.intl,
186
+ onSSRMeasure = _ref.onSSRMeasure,
182
187
  onEditorStateChanged = _ref.onEditorStateChanged,
183
188
  divProps = (0, _objectWithoutProperties2.default)(_ref, _excluded);
189
+ // Should be always the first statement in the component
190
+ var firstRenderStartTimestampRef = (0, _react.useRef)(performance.now());
191
+
184
192
  // PMPluginFactoryParams use `getIntl` function to get current intl instance,
185
193
  // so we don't need to add `intl` as a dependency to `useMemo`.
186
194
  // We will store intl in ref and access to it dynamically in `getIntl` function call.
187
195
  var intlRef = (0, _react.useRef)(intl);
188
196
  intlRef.current = intl;
189
197
  var pmPlugins = (0, _react.useMemo)(function () {
190
- var eventDispatcher = new SSREventDispatcher();
191
- var providerFactory = new _providerFactory.ProviderFactory();
192
- var pmPluginFactoryParams = {
193
- dispatch: (0, _eventDispatcher.createDispatch)(eventDispatcher),
194
- dispatchAnalyticsEvent: function dispatchAnalyticsEvent() {},
195
- eventDispatcher: eventDispatcher,
196
- featureFlags: {},
197
- getIntl: function getIntl() {
198
- return intlRef.current;
199
- },
200
- nodeViewPortalProviderAPI: portalProviderAPI,
201
- portalProviderAPI: portalProviderAPI,
202
- providerFactory: providerFactory,
203
- schema: schema
198
+ var createPMPlugins = function createPMPlugins() {
199
+ var eventDispatcher = new SSREventDispatcher();
200
+ var providerFactory = new _providerFactory.ProviderFactory();
201
+ var pmPluginFactoryParams = {
202
+ dispatch: (0, _eventDispatcher.createDispatch)(eventDispatcher),
203
+ dispatchAnalyticsEvent: function dispatchAnalyticsEvent() {},
204
+ eventDispatcher: eventDispatcher,
205
+ featureFlags: {},
206
+ getIntl: function getIntl() {
207
+ return intlRef.current;
208
+ },
209
+ nodeViewPortalProviderAPI: portalProviderAPI,
210
+ portalProviderAPI: portalProviderAPI,
211
+ providerFactory: providerFactory,
212
+ schema: schema
213
+ };
214
+ return plugins.reduce(function (acc, editorPlugin) {
215
+ var _editorPlugin$pmPlugi;
216
+ (_editorPlugin$pmPlugi = editorPlugin.pmPlugins) === null || _editorPlugin$pmPlugi === void 0 || _editorPlugin$pmPlugi.call(editorPlugin).forEach(function (_ref2) {
217
+ var plugin = _ref2.plugin;
218
+ try {
219
+ var pmPlugin = plugin(pmPluginFactoryParams);
220
+ if (pmPlugin) {
221
+ acc.push(pmPlugin);
222
+ }
223
+ } catch (_unused) {}
224
+ });
225
+ return acc;
226
+ }, []);
204
227
  };
205
- return plugins.reduce(function (acc, editorPlugin) {
206
- var _editorPlugin$pmPlugi;
207
- (_editorPlugin$pmPlugi = editorPlugin.pmPlugins) === null || _editorPlugin$pmPlugi === void 0 || _editorPlugin$pmPlugi.call(editorPlugin).forEach(function (_ref2) {
208
- var plugin = _ref2.plugin;
209
- try {
210
- var pmPlugin = plugin(pmPluginFactoryParams);
211
- if (pmPlugin) {
212
- acc.push(pmPlugin);
213
- }
214
- } catch (_unused) {}
215
- });
216
- return acc;
217
- }, []);
218
- }, [plugins, portalProviderAPI, schema]);
228
+ return (0, _platformFeatureFlags.fg)('platform_editor_better_editor_ssr_spans') ? (0, _ssrMeasures.profileSSROperation)("".concat(SSR_TRACE_SEGMENT_NAME, "/createPMPlugins"), createPMPlugins, onSSRMeasure) : createPMPlugins();
229
+ }, [plugins, portalProviderAPI, schema, onSSRMeasure]);
219
230
  var nodeViews = (0, _react.useMemo)(function () {
220
231
  return pmPlugins.reduce(function (acc, plugin) {
221
232
  return Object.assign(acc, plugin.props.nodeViews);
@@ -227,12 +238,15 @@ function EditorSSRRenderer(_ref) {
227
238
  }, {});
228
239
  }, [pmPlugins]);
229
240
  var editorState = (0, _react.useMemo)(function () {
230
- return _state.EditorState.create({
231
- doc: doc,
232
- schema: schema,
233
- plugins: pmPlugins
234
- });
235
- }, [doc, pmPlugins, schema]);
241
+ var createEditorState = function createEditorState() {
242
+ return _state.EditorState.create({
243
+ doc: doc,
244
+ schema: schema,
245
+ plugins: pmPlugins
246
+ });
247
+ };
248
+ return (0, _platformFeatureFlags.fg)('platform_editor_better_editor_ssr_spans') ? (0, _ssrMeasures.profileSSROperation)("".concat(SSR_TRACE_SEGMENT_NAME, "/createEditorState"), createEditorState, onSSRMeasure) : createEditorState();
249
+ }, [doc, pmPlugins, schema, onSSRMeasure]);
236
250
 
237
251
  // In React 19 could be replaced by `useEffectEvent` hook.
238
252
  var onEditorStateChangedRef = (0, _react.useRef)(onEditorStateChanged);
@@ -247,127 +261,133 @@ function EditorSSRRenderer(_ref) {
247
261
  });
248
262
  }, [editorState]);
249
263
  var _useMemo = (0, _react.useMemo)(function () {
250
- var nodePositions = new WeakMap();
264
+ var createSerializerAndNodePositions = function createSerializerAndNodePositions() {
265
+ var nodePositions = new WeakMap();
251
266
 
252
- // ProseMirror View adds <br class="ProseMirror-trailingBreak" /> to empty nodes. Because we are using
253
- // DOMSerializer, we should simulate the same behaviour to get the same HTML document.
254
- //
255
- // There are a lot of conditions that check for adding `<br />` but we could implement only the case when we
256
- // are adding `<br />` to empty texblock, because if we add `<br />` in other cases it will change order of DOM nodes inside
257
- // this node (`<br />`) will be the first, after will be other nodes. It's because we are adding `<br />` to root node before
258
- // we are rendering child node.
259
- //
260
- // See: https://discuss.prosemirror.net/t/where-can-i-read-about-prosemirror-trailingbreak/6665
261
- // See: https://github.com/ProseMirror/prosemirror-view/blob/76c7c47f03730b18397b94bd269ece8a9cb7f486/src/viewdesc.ts#L803
262
- // See: https://github.com/ProseMirror/prosemirror-view/blob/76c7c47f03730b18397b94bd269ece8a9cb7f486/src/viewdesc.ts#L1365
263
- var addTrailingBreakIfNeeded = function addTrailingBreakIfNeeded(node, contentDOM) {
264
- if (contentDOM && node.isTextblock && !node.lastChild) {
265
- var br = document.createElement('br');
266
- br.classList.add('ProseMirror-trailingBreak');
267
- contentDOM.appendChild(br);
268
- }
269
- };
270
- var toDomNodeRenderers = Object.fromEntries(Object.entries(schema.nodes).map(function (_ref3) {
271
- var _ref4 = (0, _slicedToArray2.default)(_ref3, 2),
272
- nodeName = _ref4[0],
273
- nodeType = _ref4[1];
274
- return [nodeName, nodeType.spec.toDOM];
275
- }).filter(function (_ref5) {
276
- var _ref6 = (0, _slicedToArray2.default)(_ref5, 2),
277
- toDOM = _ref6[1];
278
- return !!toDOM;
279
- }));
280
- var toDomMarkRenderers = Object.fromEntries(Object.entries(schema.marks).map(function (_ref7) {
281
- var _ref8 = (0, _slicedToArray2.default)(_ref7, 2),
282
- markName = _ref8[0],
283
- markType = _ref8[1];
284
- return [markName, markType.spec.toDOM];
285
- }).filter(function (_ref9) {
286
- var _ref0 = (0, _slicedToArray2.default)(_ref9, 2),
287
- toDOM = _ref0[1];
288
- return !!toDOM;
289
- }));
290
- var nodeViewRenderers = Object.fromEntries(Object.entries(nodeViews).map(function (_ref1) {
291
- var _ref10 = (0, _slicedToArray2.default)(_ref1, 2),
292
- nodeName = _ref10[0],
293
- nodeViewFactory = _ref10[1];
294
- return [nodeName, function (node) {
295
- var nodeViewInstance = nodeViewFactory(node, editorView, function () {
296
- var _nodePositions$get;
297
- return (_nodePositions$get = nodePositions.get(node)) !== null && _nodePositions$get !== void 0 ? _nodePositions$get : 0;
298
- }, [], _view.DecorationSet.create(node, []));
299
- addTrailingBreakIfNeeded(node, nodeViewInstance.contentDOM);
300
- return {
301
- dom: nodeViewInstance.dom,
302
- // Leaf nodes have no content, ProseMirror will throw an error if we pass contentDOM
303
- contentDOM: node.isLeaf ? undefined : nodeViewInstance.contentDOM
304
- };
305
- }];
306
- }));
267
+ // ProseMirror View adds <br class="ProseMirror-trailingBreak" /> to empty nodes. Because we are using
268
+ // DOMSerializer, we should simulate the same behaviour to get the same HTML document.
269
+ //
270
+ // There are a lot of conditions that check for adding `<br />` but we could implement only the case when we
271
+ // are adding `<br />` to empty texblock, because if we add `<br />` in other cases it will change order of DOM nodes inside
272
+ // this node (`<br />`) will be the first, after will be other nodes. It's because we are adding `<br />` to root node before
273
+ // we are rendering child node.
274
+ //
275
+ // See: https://discuss.prosemirror.net/t/where-can-i-read-about-prosemirror-trailingbreak/6665
276
+ // See: https://github.com/ProseMirror/prosemirror-view/blob/76c7c47f03730b18397b94bd269ece8a9cb7f486/src/viewdesc.ts#L803
277
+ // See: https://github.com/ProseMirror/prosemirror-view/blob/76c7c47f03730b18397b94bd269ece8a9cb7f486/src/viewdesc.ts#L1365
278
+ var addTrailingBreakIfNeeded = function addTrailingBreakIfNeeded(node, contentDOM) {
279
+ if (contentDOM && node.isTextblock && !node.lastChild) {
280
+ var br = document.createElement('br');
281
+ br.classList.add('ProseMirror-trailingBreak');
282
+ contentDOM.appendChild(br);
283
+ }
284
+ };
285
+ var toDomNodeRenderers = Object.fromEntries(Object.entries(schema.nodes).map(function (_ref3) {
286
+ var _ref4 = (0, _slicedToArray2.default)(_ref3, 2),
287
+ nodeName = _ref4[0],
288
+ nodeType = _ref4[1];
289
+ return [nodeName, nodeType.spec.toDOM];
290
+ }).filter(function (_ref5) {
291
+ var _ref6 = (0, _slicedToArray2.default)(_ref5, 2),
292
+ toDOM = _ref6[1];
293
+ return !!toDOM;
294
+ }));
295
+ var toDomMarkRenderers = Object.fromEntries(Object.entries(schema.marks).map(function (_ref7) {
296
+ var _ref8 = (0, _slicedToArray2.default)(_ref7, 2),
297
+ markName = _ref8[0],
298
+ markType = _ref8[1];
299
+ return [markName, markType.spec.toDOM];
300
+ }).filter(function (_ref9) {
301
+ var _ref0 = (0, _slicedToArray2.default)(_ref9, 2),
302
+ toDOM = _ref0[1];
303
+ return !!toDOM;
304
+ }));
305
+ var nodeViewRenderers = Object.fromEntries(Object.entries(nodeViews).map(function (_ref1) {
306
+ var _ref10 = (0, _slicedToArray2.default)(_ref1, 2),
307
+ nodeName = _ref10[0],
308
+ nodeViewFactory = _ref10[1];
309
+ return [nodeName, function (node) {
310
+ var nodeViewInstance = nodeViewFactory(node, editorView, function () {
311
+ var _nodePositions$get;
312
+ return (_nodePositions$get = nodePositions.get(node)) !== null && _nodePositions$get !== void 0 ? _nodePositions$get : 0;
313
+ }, [], _view.DecorationSet.create(node, []));
314
+ addTrailingBreakIfNeeded(node, nodeViewInstance.contentDOM);
315
+ return {
316
+ dom: nodeViewInstance.dom,
317
+ // Leaf nodes have no content, ProseMirror will throw an error if we pass contentDOM
318
+ contentDOM: node.isLeaf ? undefined : nodeViewInstance.contentDOM
319
+ };
320
+ }];
321
+ }));
307
322
 
308
- // Create renderers for textblock nodes that don't have custom NodeViews (e.g. paragraph, heading)
309
- var textblockRenderers = Object.fromEntries(Object.entries(schema.nodes).filter(function (_ref11) {
310
- var _ref12 = (0, _slicedToArray2.default)(_ref11, 2),
311
- nodeName = _ref12[0],
312
- nodeType = _ref12[1];
313
- // Only handle textblock nodes
314
- return nodeType.spec.toDOM && nodeType.isTextblock && !nodeViews[nodeName];
315
- }).map(function (_ref13) {
316
- var _ref14 = (0, _slicedToArray2.default)(_ref13, 2),
317
- nodeName = _ref14[0],
318
- nodeType = _ref14[1];
319
- var toDOM = nodeType.spec.toDOM;
320
- if (!toDOM) {
321
- return [nodeName, undefined];
322
- }
323
- return [nodeName, function (node) {
324
- if (!node.lastChild) {
325
- var result = _model.DOMSerializer.renderSpec(document, toDOM(node));
326
- addTrailingBreakIfNeeded(node, result.contentDOM);
327
- return result;
323
+ // Create renderers for textblock nodes that don't have custom NodeViews (e.g. paragraph, heading)
324
+ var textblockRenderers = Object.fromEntries(Object.entries(schema.nodes).filter(function (_ref11) {
325
+ var _ref12 = (0, _slicedToArray2.default)(_ref11, 2),
326
+ nodeName = _ref12[0],
327
+ nodeType = _ref12[1];
328
+ // Only handle textblock nodes
329
+ return nodeType.spec.toDOM && nodeType.isTextblock && !nodeViews[nodeName];
330
+ }).map(function (_ref13) {
331
+ var _ref14 = (0, _slicedToArray2.default)(_ref13, 2),
332
+ nodeName = _ref14[0],
333
+ nodeType = _ref14[1];
334
+ var toDOM = nodeType.spec.toDOM;
335
+ if (!toDOM) {
336
+ return [nodeName, undefined];
328
337
  }
329
- return toDOM(node);
330
- }];
331
- }).filter(function (_ref15) {
332
- var _ref16 = (0, _slicedToArray2.default)(_ref15, 2),
333
- renderer = _ref16[1];
334
- return !!renderer;
335
- }));
336
- var markViewRenderers = Object.fromEntries(Object.entries(markViews).map(function (_ref17) {
337
- var _ref18 = (0, _slicedToArray2.default)(_ref17, 2),
338
- markName = _ref18[0],
339
- markViewFactory = _ref18[1];
340
- return [markName, function (mark) {
341
- var markViewInstance = markViewFactory(mark, editorView, false);
342
- return {
343
- dom: markViewInstance.dom,
344
- contentDOM: markViewInstance.contentDOM
345
- };
346
- }];
347
- }));
348
- var serializer = new _model.DOMSerializer(_objectSpread(_objectSpread(_objectSpread(_objectSpread({}, toDomNodeRenderers), textblockRenderers), nodeViewRenderers), {}, {
349
- text: renderText
350
- }), _objectSpread(_objectSpread({}, toDomMarkRenderers), markViewRenderers));
351
- return {
352
- serializer: serializer,
353
- nodePositions: nodePositions
338
+ return [nodeName, function (node) {
339
+ if (!node.lastChild) {
340
+ var result = _model.DOMSerializer.renderSpec(document, toDOM(node));
341
+ addTrailingBreakIfNeeded(node, result.contentDOM);
342
+ return result;
343
+ }
344
+ return toDOM(node);
345
+ }];
346
+ }).filter(function (_ref15) {
347
+ var _ref16 = (0, _slicedToArray2.default)(_ref15, 2),
348
+ renderer = _ref16[1];
349
+ return !!renderer;
350
+ }));
351
+ var markViewRenderers = Object.fromEntries(Object.entries(markViews).map(function (_ref17) {
352
+ var _ref18 = (0, _slicedToArray2.default)(_ref17, 2),
353
+ markName = _ref18[0],
354
+ markViewFactory = _ref18[1];
355
+ return [markName, function (mark) {
356
+ var markViewInstance = markViewFactory(mark, editorView, false);
357
+ return {
358
+ dom: markViewInstance.dom,
359
+ contentDOM: markViewInstance.contentDOM
360
+ };
361
+ }];
362
+ }));
363
+ var serializer = new _model.DOMSerializer(_objectSpread(_objectSpread(_objectSpread(_objectSpread({}, toDomNodeRenderers), textblockRenderers), nodeViewRenderers), {}, {
364
+ text: renderText
365
+ }), _objectSpread(_objectSpread({}, toDomMarkRenderers), markViewRenderers));
366
+ return {
367
+ serializer: serializer,
368
+ nodePositions: nodePositions
369
+ };
354
370
  };
355
- }, [editorView, markViews, nodeViews, schema.marks, schema.nodes]),
371
+ return (0, _platformFeatureFlags.fg)('platform_editor_better_editor_ssr_spans') ? (0, _ssrMeasures.profileSSROperation)("".concat(SSR_TRACE_SEGMENT_NAME, "/createSerializerAndNodePositions"), createSerializerAndNodePositions, onSSRMeasure) : createSerializerAndNodePositions();
372
+ }, [editorView, markViews, nodeViews, schema.marks, schema.nodes, onSSRMeasure]),
356
373
  serializer = _useMemo.serializer,
357
374
  nodePositions = _useMemo.nodePositions;
358
375
  var editorHTML = (0, _react.useMemo)(function () {
359
- if (!doc) {
360
- return undefined;
361
- }
362
- try {
376
+ var serializeFragment = function serializeFragment() {
377
+ if (!doc) {
378
+ return undefined;
379
+ }
363
380
  doc.descendants(function (node, pos) {
364
381
  nodePositions.set(node, pos);
365
382
  });
366
383
  return serializer.serializeFragment(doc.content);
384
+ };
385
+ try {
386
+ return (0, _platformFeatureFlags.fg)('platform_editor_better_editor_ssr_spans') ? (0, _ssrMeasures.profileSSROperation)("".concat(SSR_TRACE_SEGMENT_NAME, "/serializeFragment"), serializeFragment, onSSRMeasure) : serializeFragment();
367
387
  } catch (_unused2) {
368
388
  return undefined;
369
389
  }
370
- }, [doc, serializer, nodePositions]);
390
+ }, [doc, serializer, nodePositions, onSSRMeasure]);
371
391
  var containerRef = (0, _react.useRef)(null);
372
392
  (0, _react.useLayoutEffect)(function () {
373
393
  if (containerRef.current && editorHTML) {
@@ -375,7 +395,11 @@ function EditorSSRRenderer(_ref) {
375
395
  containerRef.current.appendChild(editorHTML);
376
396
  }
377
397
  }, [editorHTML]);
378
- return /*#__PURE__*/_react.default.createElement("div", {
398
+ return /*#__PURE__*/_react.default.createElement(_ssrMeasures.SSRRenderMeasure, {
399
+ segmentName: SSR_TRACE_SEGMENT_NAME,
400
+ startTimestampRef: firstRenderStartTimestampRef,
401
+ onSSRMeasure: (0, _platformFeatureFlags.fg)('platform_editor_better_editor_ssr_spans') ? onSSRMeasure : undefined
402
+ }, /*#__PURE__*/_react.default.createElement("div", {
379
403
  ref: containerRef,
380
404
  id: divProps.id
381
405
  // For some reason on SSR, the result `class` has a trailing space, that broke UFO,
@@ -396,7 +420,7 @@ function EditorSSRRenderer(_ref) {
396
420
  contenteditable: "true",
397
421
  "data-gramm": "false",
398
422
  translate: "no"
399
- });
423
+ }));
400
424
  }
401
425
  function renderText(node) {
402
426
  return node.text || '';
@@ -5,6 +5,9 @@ import { DOMSerializer } from '@atlaskit/editor-prosemirror/model';
5
5
  import { EventDispatcher, createDispatch } from '@atlaskit/editor-common/event-dispatcher';
6
6
  import { ProviderFactory } from '@atlaskit/editor-common/provider-factory';
7
7
  import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
8
+ import { fg } from '@atlaskit/platform-feature-flags';
9
+ import { profileSSROperation, SSRRenderMeasure } from '@atlaskit/editor-common/performance/ssr-measures';
10
+ const SSR_TRACE_SEGMENT_NAME = 'reactEditorView/editorSSRRenderer';
8
11
 
9
12
  // The copy of type from prosemirror-view.
10
13
  // Probably, we need to fix this package exports and add `NodeViewConstructor` and `MarkViewConstructor` types here.
@@ -111,43 +114,50 @@ export function EditorSSRRenderer({
111
114
  doc,
112
115
  portalProviderAPI,
113
116
  intl,
117
+ onSSRMeasure,
114
118
  onEditorStateChanged,
115
119
  ...divProps
116
120
  }) {
121
+ // Should be always the first statement in the component
122
+ const firstRenderStartTimestampRef = useRef(performance.now());
123
+
117
124
  // PMPluginFactoryParams use `getIntl` function to get current intl instance,
118
125
  // so we don't need to add `intl` as a dependency to `useMemo`.
119
126
  // We will store intl in ref and access to it dynamically in `getIntl` function call.
120
127
  const intlRef = useRef(intl);
121
128
  intlRef.current = intl;
122
129
  const pmPlugins = useMemo(() => {
123
- const eventDispatcher = new SSREventDispatcher();
124
- const providerFactory = new ProviderFactory();
125
- const pmPluginFactoryParams = {
126
- dispatch: createDispatch(eventDispatcher),
127
- dispatchAnalyticsEvent: () => {},
128
- eventDispatcher,
129
- featureFlags: {},
130
- getIntl: () => intlRef.current,
131
- nodeViewPortalProviderAPI: portalProviderAPI,
132
- portalProviderAPI: portalProviderAPI,
133
- providerFactory,
134
- schema
130
+ const createPMPlugins = () => {
131
+ const eventDispatcher = new SSREventDispatcher();
132
+ const providerFactory = new ProviderFactory();
133
+ const pmPluginFactoryParams = {
134
+ dispatch: createDispatch(eventDispatcher),
135
+ dispatchAnalyticsEvent: () => {},
136
+ eventDispatcher,
137
+ featureFlags: {},
138
+ getIntl: () => intlRef.current,
139
+ nodeViewPortalProviderAPI: portalProviderAPI,
140
+ portalProviderAPI: portalProviderAPI,
141
+ providerFactory,
142
+ schema
143
+ };
144
+ return plugins.reduce((acc, editorPlugin) => {
145
+ var _editorPlugin$pmPlugi;
146
+ (_editorPlugin$pmPlugi = editorPlugin.pmPlugins) === null || _editorPlugin$pmPlugi === void 0 ? void 0 : _editorPlugin$pmPlugi.call(editorPlugin).forEach(({
147
+ plugin
148
+ }) => {
149
+ try {
150
+ const pmPlugin = plugin(pmPluginFactoryParams);
151
+ if (pmPlugin) {
152
+ acc.push(pmPlugin);
153
+ }
154
+ } catch {}
155
+ });
156
+ return acc;
157
+ }, []);
135
158
  };
136
- return plugins.reduce((acc, editorPlugin) => {
137
- var _editorPlugin$pmPlugi;
138
- (_editorPlugin$pmPlugi = editorPlugin.pmPlugins) === null || _editorPlugin$pmPlugi === void 0 ? void 0 : _editorPlugin$pmPlugi.call(editorPlugin).forEach(({
139
- plugin
140
- }) => {
141
- try {
142
- const pmPlugin = plugin(pmPluginFactoryParams);
143
- if (pmPlugin) {
144
- acc.push(pmPlugin);
145
- }
146
- } catch {}
147
- });
148
- return acc;
149
- }, []);
150
- }, [plugins, portalProviderAPI, schema]);
159
+ return fg('platform_editor_better_editor_ssr_spans') ? profileSSROperation(`${SSR_TRACE_SEGMENT_NAME}/createPMPlugins`, createPMPlugins, onSSRMeasure) : createPMPlugins();
160
+ }, [plugins, portalProviderAPI, schema, onSSRMeasure]);
151
161
  const nodeViews = useMemo(() => {
152
162
  return pmPlugins.reduce((acc, plugin) => {
153
163
  return Object.assign(acc, plugin.props.nodeViews);
@@ -159,12 +169,15 @@ export function EditorSSRRenderer({
159
169
  }, {});
160
170
  }, [pmPlugins]);
161
171
  const editorState = useMemo(() => {
162
- return EditorState.create({
163
- doc,
164
- schema,
165
- plugins: pmPlugins
166
- });
167
- }, [doc, pmPlugins, schema]);
172
+ const createEditorState = () => {
173
+ return EditorState.create({
174
+ doc,
175
+ schema,
176
+ plugins: pmPlugins
177
+ });
178
+ };
179
+ return fg('platform_editor_better_editor_ssr_spans') ? profileSSROperation(`${SSR_TRACE_SEGMENT_NAME}/createEditorState`, createEditorState, onSSRMeasure) : createEditorState();
180
+ }, [doc, pmPlugins, schema, onSSRMeasure]);
168
181
 
169
182
  // In React 19 could be replaced by `useEffectEvent` hook.
170
183
  const onEditorStateChangedRef = useRef(onEditorStateChanged);
@@ -182,101 +195,107 @@ export function EditorSSRRenderer({
182
195
  serializer,
183
196
  nodePositions
184
197
  } = useMemo(() => {
185
- const nodePositions = new WeakMap();
198
+ const createSerializerAndNodePositions = () => {
199
+ const nodePositions = new WeakMap();
186
200
 
187
- // ProseMirror View adds <br class="ProseMirror-trailingBreak" /> to empty nodes. Because we are using
188
- // DOMSerializer, we should simulate the same behaviour to get the same HTML document.
189
- //
190
- // There are a lot of conditions that check for adding `<br />` but we could implement only the case when we
191
- // are adding `<br />` to empty texblock, because if we add `<br />` in other cases it will change order of DOM nodes inside
192
- // this node (`<br />`) will be the first, after will be other nodes. It's because we are adding `<br />` to root node before
193
- // we are rendering child node.
194
- //
195
- // See: https://discuss.prosemirror.net/t/where-can-i-read-about-prosemirror-trailingbreak/6665
196
- // See: https://github.com/ProseMirror/prosemirror-view/blob/76c7c47f03730b18397b94bd269ece8a9cb7f486/src/viewdesc.ts#L803
197
- // See: https://github.com/ProseMirror/prosemirror-view/blob/76c7c47f03730b18397b94bd269ece8a9cb7f486/src/viewdesc.ts#L1365
198
- const addTrailingBreakIfNeeded = (node, contentDOM) => {
199
- if (contentDOM && node.isTextblock && !node.lastChild) {
200
- const br = document.createElement('br');
201
- br.classList.add('ProseMirror-trailingBreak');
202
- contentDOM.appendChild(br);
203
- }
204
- };
205
- const toDomNodeRenderers = Object.fromEntries(Object.entries(schema.nodes).map(([nodeName, nodeType]) => {
206
- return [nodeName, nodeType.spec.toDOM];
207
- }).filter(([, toDOM]) => !!toDOM));
208
- const toDomMarkRenderers = Object.fromEntries(Object.entries(schema.marks).map(([markName, markType]) => {
209
- return [markName, markType.spec.toDOM];
210
- }).filter(([, toDOM]) => !!toDOM));
211
- const nodeViewRenderers = Object.fromEntries(Object.entries(nodeViews).map(([nodeName, nodeViewFactory]) => {
212
- return [nodeName, node => {
213
- const nodeViewInstance = nodeViewFactory(node, editorView, () => {
214
- var _nodePositions$get;
215
- return (_nodePositions$get = nodePositions.get(node)) !== null && _nodePositions$get !== void 0 ? _nodePositions$get : 0;
216
- }, [], DecorationSet.create(node, []));
217
- addTrailingBreakIfNeeded(node, nodeViewInstance.contentDOM);
218
- return {
219
- dom: nodeViewInstance.dom,
220
- // Leaf nodes have no content, ProseMirror will throw an error if we pass contentDOM
221
- contentDOM: node.isLeaf ? undefined : nodeViewInstance.contentDOM
222
- };
223
- }];
224
- }));
201
+ // ProseMirror View adds <br class="ProseMirror-trailingBreak" /> to empty nodes. Because we are using
202
+ // DOMSerializer, we should simulate the same behaviour to get the same HTML document.
203
+ //
204
+ // There are a lot of conditions that check for adding `<br />` but we could implement only the case when we
205
+ // are adding `<br />` to empty texblock, because if we add `<br />` in other cases it will change order of DOM nodes inside
206
+ // this node (`<br />`) will be the first, after will be other nodes. It's because we are adding `<br />` to root node before
207
+ // we are rendering child node.
208
+ //
209
+ // See: https://discuss.prosemirror.net/t/where-can-i-read-about-prosemirror-trailingbreak/6665
210
+ // See: https://github.com/ProseMirror/prosemirror-view/blob/76c7c47f03730b18397b94bd269ece8a9cb7f486/src/viewdesc.ts#L803
211
+ // See: https://github.com/ProseMirror/prosemirror-view/blob/76c7c47f03730b18397b94bd269ece8a9cb7f486/src/viewdesc.ts#L1365
212
+ const addTrailingBreakIfNeeded = (node, contentDOM) => {
213
+ if (contentDOM && node.isTextblock && !node.lastChild) {
214
+ const br = document.createElement('br');
215
+ br.classList.add('ProseMirror-trailingBreak');
216
+ contentDOM.appendChild(br);
217
+ }
218
+ };
219
+ const toDomNodeRenderers = Object.fromEntries(Object.entries(schema.nodes).map(([nodeName, nodeType]) => {
220
+ return [nodeName, nodeType.spec.toDOM];
221
+ }).filter(([, toDOM]) => !!toDOM));
222
+ const toDomMarkRenderers = Object.fromEntries(Object.entries(schema.marks).map(([markName, markType]) => {
223
+ return [markName, markType.spec.toDOM];
224
+ }).filter(([, toDOM]) => !!toDOM));
225
+ const nodeViewRenderers = Object.fromEntries(Object.entries(nodeViews).map(([nodeName, nodeViewFactory]) => {
226
+ return [nodeName, node => {
227
+ const nodeViewInstance = nodeViewFactory(node, editorView, () => {
228
+ var _nodePositions$get;
229
+ return (_nodePositions$get = nodePositions.get(node)) !== null && _nodePositions$get !== void 0 ? _nodePositions$get : 0;
230
+ }, [], DecorationSet.create(node, []));
231
+ addTrailingBreakIfNeeded(node, nodeViewInstance.contentDOM);
232
+ return {
233
+ dom: nodeViewInstance.dom,
234
+ // Leaf nodes have no content, ProseMirror will throw an error if we pass contentDOM
235
+ contentDOM: node.isLeaf ? undefined : nodeViewInstance.contentDOM
236
+ };
237
+ }];
238
+ }));
225
239
 
226
- // Create renderers for textblock nodes that don't have custom NodeViews (e.g. paragraph, heading)
227
- const textblockRenderers = Object.fromEntries(Object.entries(schema.nodes).filter(([nodeName, nodeType]) => {
228
- // Only handle textblock nodes
229
- return nodeType.spec.toDOM && nodeType.isTextblock && !nodeViews[nodeName];
230
- }).map(([nodeName, nodeType]) => {
231
- const toDOM = nodeType.spec.toDOM;
232
- if (!toDOM) {
233
- return [nodeName, undefined];
234
- }
235
- return [nodeName, node => {
236
- if (!node.lastChild) {
237
- const result = DOMSerializer.renderSpec(document, toDOM(node));
238
- addTrailingBreakIfNeeded(node, result.contentDOM);
239
- return result;
240
+ // Create renderers for textblock nodes that don't have custom NodeViews (e.g. paragraph, heading)
241
+ const textblockRenderers = Object.fromEntries(Object.entries(schema.nodes).filter(([nodeName, nodeType]) => {
242
+ // Only handle textblock nodes
243
+ return nodeType.spec.toDOM && nodeType.isTextblock && !nodeViews[nodeName];
244
+ }).map(([nodeName, nodeType]) => {
245
+ const toDOM = nodeType.spec.toDOM;
246
+ if (!toDOM) {
247
+ return [nodeName, undefined];
240
248
  }
241
- return toDOM(node);
242
- }];
243
- }).filter(([, renderer]) => !!renderer));
244
- const markViewRenderers = Object.fromEntries(Object.entries(markViews).map(([markName, markViewFactory]) => {
245
- return [markName, mark => {
246
- const markViewInstance = markViewFactory(mark, editorView, false);
247
- return {
248
- dom: markViewInstance.dom,
249
- contentDOM: markViewInstance.contentDOM
250
- };
251
- }];
252
- }));
253
- const serializer = new DOMSerializer({
254
- ...toDomNodeRenderers,
255
- ...textblockRenderers,
256
- ...nodeViewRenderers,
257
- text: renderText
258
- }, {
259
- ...toDomMarkRenderers,
260
- ...markViewRenderers
261
- });
262
- return {
263
- serializer,
264
- nodePositions
249
+ return [nodeName, node => {
250
+ if (!node.lastChild) {
251
+ const result = DOMSerializer.renderSpec(document, toDOM(node));
252
+ addTrailingBreakIfNeeded(node, result.contentDOM);
253
+ return result;
254
+ }
255
+ return toDOM(node);
256
+ }];
257
+ }).filter(([, renderer]) => !!renderer));
258
+ const markViewRenderers = Object.fromEntries(Object.entries(markViews).map(([markName, markViewFactory]) => {
259
+ return [markName, mark => {
260
+ const markViewInstance = markViewFactory(mark, editorView, false);
261
+ return {
262
+ dom: markViewInstance.dom,
263
+ contentDOM: markViewInstance.contentDOM
264
+ };
265
+ }];
266
+ }));
267
+ const serializer = new DOMSerializer({
268
+ ...toDomNodeRenderers,
269
+ ...textblockRenderers,
270
+ ...nodeViewRenderers,
271
+ text: renderText
272
+ }, {
273
+ ...toDomMarkRenderers,
274
+ ...markViewRenderers
275
+ });
276
+ return {
277
+ serializer,
278
+ nodePositions
279
+ };
265
280
  };
266
- }, [editorView, markViews, nodeViews, schema.marks, schema.nodes]);
281
+ return fg('platform_editor_better_editor_ssr_spans') ? profileSSROperation(`${SSR_TRACE_SEGMENT_NAME}/createSerializerAndNodePositions`, createSerializerAndNodePositions, onSSRMeasure) : createSerializerAndNodePositions();
282
+ }, [editorView, markViews, nodeViews, schema.marks, schema.nodes, onSSRMeasure]);
267
283
  const editorHTML = useMemo(() => {
268
- if (!doc) {
269
- return undefined;
270
- }
271
- try {
284
+ const serializeFragment = () => {
285
+ if (!doc) {
286
+ return undefined;
287
+ }
272
288
  doc.descendants((node, pos) => {
273
289
  nodePositions.set(node, pos);
274
290
  });
275
291
  return serializer.serializeFragment(doc.content);
292
+ };
293
+ try {
294
+ return fg('platform_editor_better_editor_ssr_spans') ? profileSSROperation(`${SSR_TRACE_SEGMENT_NAME}/serializeFragment`, serializeFragment, onSSRMeasure) : serializeFragment();
276
295
  } catch {
277
296
  return undefined;
278
297
  }
279
- }, [doc, serializer, nodePositions]);
298
+ }, [doc, serializer, nodePositions, onSSRMeasure]);
280
299
  const containerRef = useRef(null);
281
300
  useLayoutEffect(() => {
282
301
  if (containerRef.current && editorHTML) {
@@ -284,7 +303,11 @@ export function EditorSSRRenderer({
284
303
  containerRef.current.appendChild(editorHTML);
285
304
  }
286
305
  }, [editorHTML]);
287
- return /*#__PURE__*/React.createElement("div", {
306
+ return /*#__PURE__*/React.createElement(SSRRenderMeasure, {
307
+ segmentName: SSR_TRACE_SEGMENT_NAME,
308
+ startTimestampRef: firstRenderStartTimestampRef,
309
+ onSSRMeasure: fg('platform_editor_better_editor_ssr_spans') ? onSSRMeasure : undefined
310
+ }, /*#__PURE__*/React.createElement("div", {
288
311
  ref: containerRef,
289
312
  id: divProps.id
290
313
  // For some reason on SSR, the result `class` has a trailing space, that broke UFO,
@@ -305,7 +328,7 @@ export function EditorSSRRenderer({
305
328
  contenteditable: "true",
306
329
  "data-gramm": "false",
307
330
  translate: "no"
308
- });
331
+ }));
309
332
  }
310
333
  function renderText(node) {
311
334
  return node.text || '';
@@ -6,7 +6,7 @@ import _getPrototypeOf from "@babel/runtime/helpers/getPrototypeOf";
6
6
  import _inherits from "@babel/runtime/helpers/inherits";
7
7
  import _classCallCheck from "@babel/runtime/helpers/classCallCheck";
8
8
  import _createClass from "@babel/runtime/helpers/createClass";
9
- var _excluded = ["plugins", "schema", "doc", "portalProviderAPI", "intl", "onEditorStateChanged"];
9
+ var _excluded = ["plugins", "schema", "doc", "portalProviderAPI", "intl", "onSSRMeasure", "onEditorStateChanged"];
10
10
  function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
11
11
  function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
12
12
  function _callSuper(t, o, e) { return o = _getPrototypeOf(o), _possibleConstructorReturn(t, _isNativeReflectConstruct() ? Reflect.construct(o, e || [], _getPrototypeOf(t).constructor) : o.apply(t, e)); }
@@ -18,6 +18,9 @@ import { DOMSerializer } from '@atlaskit/editor-prosemirror/model';
18
18
  import { EventDispatcher, createDispatch } from '@atlaskit/editor-common/event-dispatcher';
19
19
  import { ProviderFactory } from '@atlaskit/editor-common/provider-factory';
20
20
  import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
21
+ import { fg } from '@atlaskit/platform-feature-flags';
22
+ import { profileSSROperation, SSRRenderMeasure } from '@atlaskit/editor-common/performance/ssr-measures';
23
+ var SSR_TRACE_SEGMENT_NAME = 'reactEditorView/editorSSRRenderer';
21
24
 
22
25
  // The copy of type from prosemirror-view.
23
26
  // Probably, we need to fix this package exports and add `NodeViewConstructor` and `MarkViewConstructor` types here.
@@ -171,43 +174,50 @@ export function EditorSSRRenderer(_ref) {
171
174
  doc = _ref.doc,
172
175
  portalProviderAPI = _ref.portalProviderAPI,
173
176
  intl = _ref.intl,
177
+ onSSRMeasure = _ref.onSSRMeasure,
174
178
  onEditorStateChanged = _ref.onEditorStateChanged,
175
179
  divProps = _objectWithoutProperties(_ref, _excluded);
180
+ // Should be always the first statement in the component
181
+ var firstRenderStartTimestampRef = useRef(performance.now());
182
+
176
183
  // PMPluginFactoryParams use `getIntl` function to get current intl instance,
177
184
  // so we don't need to add `intl` as a dependency to `useMemo`.
178
185
  // We will store intl in ref and access to it dynamically in `getIntl` function call.
179
186
  var intlRef = useRef(intl);
180
187
  intlRef.current = intl;
181
188
  var pmPlugins = useMemo(function () {
182
- var eventDispatcher = new SSREventDispatcher();
183
- var providerFactory = new ProviderFactory();
184
- var pmPluginFactoryParams = {
185
- dispatch: createDispatch(eventDispatcher),
186
- dispatchAnalyticsEvent: function dispatchAnalyticsEvent() {},
187
- eventDispatcher: eventDispatcher,
188
- featureFlags: {},
189
- getIntl: function getIntl() {
190
- return intlRef.current;
191
- },
192
- nodeViewPortalProviderAPI: portalProviderAPI,
193
- portalProviderAPI: portalProviderAPI,
194
- providerFactory: providerFactory,
195
- schema: schema
189
+ var createPMPlugins = function createPMPlugins() {
190
+ var eventDispatcher = new SSREventDispatcher();
191
+ var providerFactory = new ProviderFactory();
192
+ var pmPluginFactoryParams = {
193
+ dispatch: createDispatch(eventDispatcher),
194
+ dispatchAnalyticsEvent: function dispatchAnalyticsEvent() {},
195
+ eventDispatcher: eventDispatcher,
196
+ featureFlags: {},
197
+ getIntl: function getIntl() {
198
+ return intlRef.current;
199
+ },
200
+ nodeViewPortalProviderAPI: portalProviderAPI,
201
+ portalProviderAPI: portalProviderAPI,
202
+ providerFactory: providerFactory,
203
+ schema: schema
204
+ };
205
+ return plugins.reduce(function (acc, editorPlugin) {
206
+ var _editorPlugin$pmPlugi;
207
+ (_editorPlugin$pmPlugi = editorPlugin.pmPlugins) === null || _editorPlugin$pmPlugi === void 0 || _editorPlugin$pmPlugi.call(editorPlugin).forEach(function (_ref2) {
208
+ var plugin = _ref2.plugin;
209
+ try {
210
+ var pmPlugin = plugin(pmPluginFactoryParams);
211
+ if (pmPlugin) {
212
+ acc.push(pmPlugin);
213
+ }
214
+ } catch (_unused) {}
215
+ });
216
+ return acc;
217
+ }, []);
196
218
  };
197
- return plugins.reduce(function (acc, editorPlugin) {
198
- var _editorPlugin$pmPlugi;
199
- (_editorPlugin$pmPlugi = editorPlugin.pmPlugins) === null || _editorPlugin$pmPlugi === void 0 || _editorPlugin$pmPlugi.call(editorPlugin).forEach(function (_ref2) {
200
- var plugin = _ref2.plugin;
201
- try {
202
- var pmPlugin = plugin(pmPluginFactoryParams);
203
- if (pmPlugin) {
204
- acc.push(pmPlugin);
205
- }
206
- } catch (_unused) {}
207
- });
208
- return acc;
209
- }, []);
210
- }, [plugins, portalProviderAPI, schema]);
219
+ return fg('platform_editor_better_editor_ssr_spans') ? profileSSROperation("".concat(SSR_TRACE_SEGMENT_NAME, "/createPMPlugins"), createPMPlugins, onSSRMeasure) : createPMPlugins();
220
+ }, [plugins, portalProviderAPI, schema, onSSRMeasure]);
211
221
  var nodeViews = useMemo(function () {
212
222
  return pmPlugins.reduce(function (acc, plugin) {
213
223
  return Object.assign(acc, plugin.props.nodeViews);
@@ -219,12 +229,15 @@ export function EditorSSRRenderer(_ref) {
219
229
  }, {});
220
230
  }, [pmPlugins]);
221
231
  var editorState = useMemo(function () {
222
- return EditorState.create({
223
- doc: doc,
224
- schema: schema,
225
- plugins: pmPlugins
226
- });
227
- }, [doc, pmPlugins, schema]);
232
+ var createEditorState = function createEditorState() {
233
+ return EditorState.create({
234
+ doc: doc,
235
+ schema: schema,
236
+ plugins: pmPlugins
237
+ });
238
+ };
239
+ return fg('platform_editor_better_editor_ssr_spans') ? profileSSROperation("".concat(SSR_TRACE_SEGMENT_NAME, "/createEditorState"), createEditorState, onSSRMeasure) : createEditorState();
240
+ }, [doc, pmPlugins, schema, onSSRMeasure]);
228
241
 
229
242
  // In React 19 could be replaced by `useEffectEvent` hook.
230
243
  var onEditorStateChangedRef = useRef(onEditorStateChanged);
@@ -239,127 +252,133 @@ export function EditorSSRRenderer(_ref) {
239
252
  });
240
253
  }, [editorState]);
241
254
  var _useMemo = useMemo(function () {
242
- var nodePositions = new WeakMap();
255
+ var createSerializerAndNodePositions = function createSerializerAndNodePositions() {
256
+ var nodePositions = new WeakMap();
243
257
 
244
- // ProseMirror View adds <br class="ProseMirror-trailingBreak" /> to empty nodes. Because we are using
245
- // DOMSerializer, we should simulate the same behaviour to get the same HTML document.
246
- //
247
- // There are a lot of conditions that check for adding `<br />` but we could implement only the case when we
248
- // are adding `<br />` to empty texblock, because if we add `<br />` in other cases it will change order of DOM nodes inside
249
- // this node (`<br />`) will be the first, after will be other nodes. It's because we are adding `<br />` to root node before
250
- // we are rendering child node.
251
- //
252
- // See: https://discuss.prosemirror.net/t/where-can-i-read-about-prosemirror-trailingbreak/6665
253
- // See: https://github.com/ProseMirror/prosemirror-view/blob/76c7c47f03730b18397b94bd269ece8a9cb7f486/src/viewdesc.ts#L803
254
- // See: https://github.com/ProseMirror/prosemirror-view/blob/76c7c47f03730b18397b94bd269ece8a9cb7f486/src/viewdesc.ts#L1365
255
- var addTrailingBreakIfNeeded = function addTrailingBreakIfNeeded(node, contentDOM) {
256
- if (contentDOM && node.isTextblock && !node.lastChild) {
257
- var br = document.createElement('br');
258
- br.classList.add('ProseMirror-trailingBreak');
259
- contentDOM.appendChild(br);
260
- }
261
- };
262
- var toDomNodeRenderers = Object.fromEntries(Object.entries(schema.nodes).map(function (_ref3) {
263
- var _ref4 = _slicedToArray(_ref3, 2),
264
- nodeName = _ref4[0],
265
- nodeType = _ref4[1];
266
- return [nodeName, nodeType.spec.toDOM];
267
- }).filter(function (_ref5) {
268
- var _ref6 = _slicedToArray(_ref5, 2),
269
- toDOM = _ref6[1];
270
- return !!toDOM;
271
- }));
272
- var toDomMarkRenderers = Object.fromEntries(Object.entries(schema.marks).map(function (_ref7) {
273
- var _ref8 = _slicedToArray(_ref7, 2),
274
- markName = _ref8[0],
275
- markType = _ref8[1];
276
- return [markName, markType.spec.toDOM];
277
- }).filter(function (_ref9) {
278
- var _ref0 = _slicedToArray(_ref9, 2),
279
- toDOM = _ref0[1];
280
- return !!toDOM;
281
- }));
282
- var nodeViewRenderers = Object.fromEntries(Object.entries(nodeViews).map(function (_ref1) {
283
- var _ref10 = _slicedToArray(_ref1, 2),
284
- nodeName = _ref10[0],
285
- nodeViewFactory = _ref10[1];
286
- return [nodeName, function (node) {
287
- var nodeViewInstance = nodeViewFactory(node, editorView, function () {
288
- var _nodePositions$get;
289
- return (_nodePositions$get = nodePositions.get(node)) !== null && _nodePositions$get !== void 0 ? _nodePositions$get : 0;
290
- }, [], DecorationSet.create(node, []));
291
- addTrailingBreakIfNeeded(node, nodeViewInstance.contentDOM);
292
- return {
293
- dom: nodeViewInstance.dom,
294
- // Leaf nodes have no content, ProseMirror will throw an error if we pass contentDOM
295
- contentDOM: node.isLeaf ? undefined : nodeViewInstance.contentDOM
296
- };
297
- }];
298
- }));
258
+ // ProseMirror View adds <br class="ProseMirror-trailingBreak" /> to empty nodes. Because we are using
259
+ // DOMSerializer, we should simulate the same behaviour to get the same HTML document.
260
+ //
261
+ // There are a lot of conditions that check for adding `<br />` but we could implement only the case when we
262
+ // are adding `<br />` to empty texblock, because if we add `<br />` in other cases it will change order of DOM nodes inside
263
+ // this node (`<br />`) will be the first, after will be other nodes. It's because we are adding `<br />` to root node before
264
+ // we are rendering child node.
265
+ //
266
+ // See: https://discuss.prosemirror.net/t/where-can-i-read-about-prosemirror-trailingbreak/6665
267
+ // See: https://github.com/ProseMirror/prosemirror-view/blob/76c7c47f03730b18397b94bd269ece8a9cb7f486/src/viewdesc.ts#L803
268
+ // See: https://github.com/ProseMirror/prosemirror-view/blob/76c7c47f03730b18397b94bd269ece8a9cb7f486/src/viewdesc.ts#L1365
269
+ var addTrailingBreakIfNeeded = function addTrailingBreakIfNeeded(node, contentDOM) {
270
+ if (contentDOM && node.isTextblock && !node.lastChild) {
271
+ var br = document.createElement('br');
272
+ br.classList.add('ProseMirror-trailingBreak');
273
+ contentDOM.appendChild(br);
274
+ }
275
+ };
276
+ var toDomNodeRenderers = Object.fromEntries(Object.entries(schema.nodes).map(function (_ref3) {
277
+ var _ref4 = _slicedToArray(_ref3, 2),
278
+ nodeName = _ref4[0],
279
+ nodeType = _ref4[1];
280
+ return [nodeName, nodeType.spec.toDOM];
281
+ }).filter(function (_ref5) {
282
+ var _ref6 = _slicedToArray(_ref5, 2),
283
+ toDOM = _ref6[1];
284
+ return !!toDOM;
285
+ }));
286
+ var toDomMarkRenderers = Object.fromEntries(Object.entries(schema.marks).map(function (_ref7) {
287
+ var _ref8 = _slicedToArray(_ref7, 2),
288
+ markName = _ref8[0],
289
+ markType = _ref8[1];
290
+ return [markName, markType.spec.toDOM];
291
+ }).filter(function (_ref9) {
292
+ var _ref0 = _slicedToArray(_ref9, 2),
293
+ toDOM = _ref0[1];
294
+ return !!toDOM;
295
+ }));
296
+ var nodeViewRenderers = Object.fromEntries(Object.entries(nodeViews).map(function (_ref1) {
297
+ var _ref10 = _slicedToArray(_ref1, 2),
298
+ nodeName = _ref10[0],
299
+ nodeViewFactory = _ref10[1];
300
+ return [nodeName, function (node) {
301
+ var nodeViewInstance = nodeViewFactory(node, editorView, function () {
302
+ var _nodePositions$get;
303
+ return (_nodePositions$get = nodePositions.get(node)) !== null && _nodePositions$get !== void 0 ? _nodePositions$get : 0;
304
+ }, [], DecorationSet.create(node, []));
305
+ addTrailingBreakIfNeeded(node, nodeViewInstance.contentDOM);
306
+ return {
307
+ dom: nodeViewInstance.dom,
308
+ // Leaf nodes have no content, ProseMirror will throw an error if we pass contentDOM
309
+ contentDOM: node.isLeaf ? undefined : nodeViewInstance.contentDOM
310
+ };
311
+ }];
312
+ }));
299
313
 
300
- // Create renderers for textblock nodes that don't have custom NodeViews (e.g. paragraph, heading)
301
- var textblockRenderers = Object.fromEntries(Object.entries(schema.nodes).filter(function (_ref11) {
302
- var _ref12 = _slicedToArray(_ref11, 2),
303
- nodeName = _ref12[0],
304
- nodeType = _ref12[1];
305
- // Only handle textblock nodes
306
- return nodeType.spec.toDOM && nodeType.isTextblock && !nodeViews[nodeName];
307
- }).map(function (_ref13) {
308
- var _ref14 = _slicedToArray(_ref13, 2),
309
- nodeName = _ref14[0],
310
- nodeType = _ref14[1];
311
- var toDOM = nodeType.spec.toDOM;
312
- if (!toDOM) {
313
- return [nodeName, undefined];
314
- }
315
- return [nodeName, function (node) {
316
- if (!node.lastChild) {
317
- var result = DOMSerializer.renderSpec(document, toDOM(node));
318
- addTrailingBreakIfNeeded(node, result.contentDOM);
319
- return result;
314
+ // Create renderers for textblock nodes that don't have custom NodeViews (e.g. paragraph, heading)
315
+ var textblockRenderers = Object.fromEntries(Object.entries(schema.nodes).filter(function (_ref11) {
316
+ var _ref12 = _slicedToArray(_ref11, 2),
317
+ nodeName = _ref12[0],
318
+ nodeType = _ref12[1];
319
+ // Only handle textblock nodes
320
+ return nodeType.spec.toDOM && nodeType.isTextblock && !nodeViews[nodeName];
321
+ }).map(function (_ref13) {
322
+ var _ref14 = _slicedToArray(_ref13, 2),
323
+ nodeName = _ref14[0],
324
+ nodeType = _ref14[1];
325
+ var toDOM = nodeType.spec.toDOM;
326
+ if (!toDOM) {
327
+ return [nodeName, undefined];
320
328
  }
321
- return toDOM(node);
322
- }];
323
- }).filter(function (_ref15) {
324
- var _ref16 = _slicedToArray(_ref15, 2),
325
- renderer = _ref16[1];
326
- return !!renderer;
327
- }));
328
- var markViewRenderers = Object.fromEntries(Object.entries(markViews).map(function (_ref17) {
329
- var _ref18 = _slicedToArray(_ref17, 2),
330
- markName = _ref18[0],
331
- markViewFactory = _ref18[1];
332
- return [markName, function (mark) {
333
- var markViewInstance = markViewFactory(mark, editorView, false);
334
- return {
335
- dom: markViewInstance.dom,
336
- contentDOM: markViewInstance.contentDOM
337
- };
338
- }];
339
- }));
340
- var serializer = new DOMSerializer(_objectSpread(_objectSpread(_objectSpread(_objectSpread({}, toDomNodeRenderers), textblockRenderers), nodeViewRenderers), {}, {
341
- text: renderText
342
- }), _objectSpread(_objectSpread({}, toDomMarkRenderers), markViewRenderers));
343
- return {
344
- serializer: serializer,
345
- nodePositions: nodePositions
329
+ return [nodeName, function (node) {
330
+ if (!node.lastChild) {
331
+ var result = DOMSerializer.renderSpec(document, toDOM(node));
332
+ addTrailingBreakIfNeeded(node, result.contentDOM);
333
+ return result;
334
+ }
335
+ return toDOM(node);
336
+ }];
337
+ }).filter(function (_ref15) {
338
+ var _ref16 = _slicedToArray(_ref15, 2),
339
+ renderer = _ref16[1];
340
+ return !!renderer;
341
+ }));
342
+ var markViewRenderers = Object.fromEntries(Object.entries(markViews).map(function (_ref17) {
343
+ var _ref18 = _slicedToArray(_ref17, 2),
344
+ markName = _ref18[0],
345
+ markViewFactory = _ref18[1];
346
+ return [markName, function (mark) {
347
+ var markViewInstance = markViewFactory(mark, editorView, false);
348
+ return {
349
+ dom: markViewInstance.dom,
350
+ contentDOM: markViewInstance.contentDOM
351
+ };
352
+ }];
353
+ }));
354
+ var serializer = new DOMSerializer(_objectSpread(_objectSpread(_objectSpread(_objectSpread({}, toDomNodeRenderers), textblockRenderers), nodeViewRenderers), {}, {
355
+ text: renderText
356
+ }), _objectSpread(_objectSpread({}, toDomMarkRenderers), markViewRenderers));
357
+ return {
358
+ serializer: serializer,
359
+ nodePositions: nodePositions
360
+ };
346
361
  };
347
- }, [editorView, markViews, nodeViews, schema.marks, schema.nodes]),
362
+ return fg('platform_editor_better_editor_ssr_spans') ? profileSSROperation("".concat(SSR_TRACE_SEGMENT_NAME, "/createSerializerAndNodePositions"), createSerializerAndNodePositions, onSSRMeasure) : createSerializerAndNodePositions();
363
+ }, [editorView, markViews, nodeViews, schema.marks, schema.nodes, onSSRMeasure]),
348
364
  serializer = _useMemo.serializer,
349
365
  nodePositions = _useMemo.nodePositions;
350
366
  var editorHTML = useMemo(function () {
351
- if (!doc) {
352
- return undefined;
353
- }
354
- try {
367
+ var serializeFragment = function serializeFragment() {
368
+ if (!doc) {
369
+ return undefined;
370
+ }
355
371
  doc.descendants(function (node, pos) {
356
372
  nodePositions.set(node, pos);
357
373
  });
358
374
  return serializer.serializeFragment(doc.content);
375
+ };
376
+ try {
377
+ return fg('platform_editor_better_editor_ssr_spans') ? profileSSROperation("".concat(SSR_TRACE_SEGMENT_NAME, "/serializeFragment"), serializeFragment, onSSRMeasure) : serializeFragment();
359
378
  } catch (_unused2) {
360
379
  return undefined;
361
380
  }
362
- }, [doc, serializer, nodePositions]);
381
+ }, [doc, serializer, nodePositions, onSSRMeasure]);
363
382
  var containerRef = useRef(null);
364
383
  useLayoutEffect(function () {
365
384
  if (containerRef.current && editorHTML) {
@@ -367,7 +386,11 @@ export function EditorSSRRenderer(_ref) {
367
386
  containerRef.current.appendChild(editorHTML);
368
387
  }
369
388
  }, [editorHTML]);
370
- return /*#__PURE__*/React.createElement("div", {
389
+ return /*#__PURE__*/React.createElement(SSRRenderMeasure, {
390
+ segmentName: SSR_TRACE_SEGMENT_NAME,
391
+ startTimestampRef: firstRenderStartTimestampRef,
392
+ onSSRMeasure: fg('platform_editor_better_editor_ssr_spans') ? onSSRMeasure : undefined
393
+ }, /*#__PURE__*/React.createElement("div", {
371
394
  ref: containerRef,
372
395
  id: divProps.id
373
396
  // For some reason on SSR, the result `class` has a trailing space, that broke UFO,
@@ -388,7 +411,7 @@ export function EditorSSRRenderer(_ref) {
388
411
  contenteditable: "true",
389
412
  "data-gramm": "false",
390
413
  translate: "no"
391
- });
414
+ }));
392
415
  }
393
416
  function renderText(node) {
394
417
  return node.text || '';
@@ -14,9 +14,14 @@ interface Props {
14
14
  id: string;
15
15
  intl: IntlShape;
16
16
  onEditorStateChanged?: (state: EditorState) => void;
17
+ onSSRMeasure?: (measure: {
18
+ endTimestamp: number;
19
+ segmentName: string;
20
+ startTimestamp: number;
21
+ }) => void;
17
22
  plugins: EditorPlugin[];
18
23
  portalProviderAPI: PortalProviderAPI;
19
24
  schema: Schema;
20
25
  }
21
- export declare function EditorSSRRenderer({ plugins, schema, doc, portalProviderAPI, intl, onEditorStateChanged, ...divProps }: Props): React.JSX.Element;
26
+ export declare function EditorSSRRenderer({ plugins, schema, doc, portalProviderAPI, intl, onSSRMeasure, onEditorStateChanged, ...divProps }: Props): React.JSX.Element;
22
27
  export {};
@@ -14,9 +14,14 @@ interface Props {
14
14
  id: string;
15
15
  intl: IntlShape;
16
16
  onEditorStateChanged?: (state: EditorState) => void;
17
+ onSSRMeasure?: (measure: {
18
+ endTimestamp: number;
19
+ segmentName: string;
20
+ startTimestamp: number;
21
+ }) => void;
17
22
  plugins: EditorPlugin[];
18
23
  portalProviderAPI: PortalProviderAPI;
19
24
  schema: Schema;
20
25
  }
21
- export declare function EditorSSRRenderer({ plugins, schema, doc, portalProviderAPI, intl, onEditorStateChanged, ...divProps }: Props): React.JSX.Element;
26
+ export declare function EditorSSRRenderer({ plugins, schema, doc, portalProviderAPI, intl, onSSRMeasure, onEditorStateChanged, ...divProps }: Props): React.JSX.Element;
22
27
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/editor-ssr-renderer",
3
- "version": "2.1.6",
3
+ "version": "2.2.1",
4
4
  "description": "SSR Renderer based on Editor",
5
5
  "author": "Atlassian Pty Ltd",
6
6
  "atlassian": {
@@ -29,12 +29,18 @@
29
29
  "dependencies": {
30
30
  "@atlaskit/adf-schema": "^51.5.0",
31
31
  "@atlaskit/editor-prosemirror": "^7.3.0",
32
- "@atlaskit/tmp-editor-statsig": "^25.0.0",
32
+ "@atlaskit/platform-feature-flags": "^1.1.0",
33
+ "@atlaskit/tmp-editor-statsig": "^27.1.0",
33
34
  "@babel/runtime": "^7.0.0",
34
35
  "react-intl-next": "npm:react-intl@^5.18.1"
35
36
  },
36
37
  "peerDependencies": {
37
- "@atlaskit/editor-common": "^111.12.0",
38
+ "@atlaskit/editor-common": "^111.16.0",
38
39
  "react": "^18.2.0"
40
+ },
41
+ "platform-feature-flags": {
42
+ "platform_editor_better_editor_ssr_spans": {
43
+ "type": "boolean"
44
+ }
39
45
  }
40
46
  }