@atlaskit/renderer 136.1.2 → 136.1.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
@@ -1,5 +1,19 @@
1
1
  # @atlaskit/renderer
2
2
 
3
+ ## 136.1.3
4
+
5
+ ### Patch Changes
6
+
7
+ - [`07c45974a615f`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/07c45974a615f) -
8
+ Reduce ADF validation cost on large documents, behind the new `platform_editor_adf_validator_perf`
9
+ experiment. The validator resolves the `platform_editor_fix_adf-validator_mutation_bug` gate once
10
+ per validator instead of once per node and per mark, and caches allowed-content lookups per node
11
+ type. The renderer compares `validationOverrides` by value so its validation memo stops missing on
12
+ every render, and compares ProseMirror documents with `Node.eq` rather than stringifying both. Its
13
+ memo comparator now runs the cheap checks before the document comparison. Behaviour is unchanged
14
+ in both cohorts.
15
+ - Updated dependencies
16
+
3
17
  ## 136.1.2
4
18
 
5
19
  ### Patch Changes
@@ -11,6 +11,7 @@ var _transforms = require("@atlaskit/adf-utils/transforms");
11
11
  var _analytics = require("@atlaskit/editor-common/analytics");
12
12
  var _utils = require("@atlaskit/editor-common/utils");
13
13
  var _validator = require("@atlaskit/editor-common/validator");
14
+ var _isExperimentEnabled = require("@atlaskit/platform-feature-experiments/is-experiment-enabled");
14
15
  var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
15
16
  var _expValEquals = require("@atlaskit/tmp-editor-statsig/exp-val-equals");
16
17
  var _memoizeOne = _interopRequireDefault(require("memoize-one"));
@@ -31,6 +32,9 @@ var withStopwatch = function withStopwatch(cb) {
31
32
  time: time
32
33
  };
33
34
  };
35
+
36
+ /** Schema-variant escape hatches handed to the ADF validator. */
37
+
34
38
  var _validation = function _validation(doc, schema, adfStage, useSpecBasedValidator, dispatchAnalyticsEvent, skipValidation, validationOverrides) {
35
39
  var result;
36
40
  if (useSpecBasedValidator) {
@@ -160,7 +164,14 @@ var memoValidation = (0, _memoizeOne.default)(_validation, function (newArgs, la
160
164
  oldUseSpecValidator = _lastArgs[3],
161
165
  /* ignoring dispatchAnalyticsEvent */oldSkipValidation = _lastArgs[5],
162
166
  oldValidationOverrides = _lastArgs[6];
163
- return areDocsEqual(newDoc, oldDoc) && newSchema === oldSchema && newADFStage === oldADFStage && newUseSpecValidator === oldUseSpecValidator && newSkipValidation === oldSkipValidation && newValidationOverrides === oldValidationOverrides;
167
+
168
+ // Reference-compared before, which never matched — see `areValidationOverridesEqual`.
169
+ var validationOverridesAreEqual = (0, _isExperimentEnabled.isExperimentEnabled)('platform_editor_adf_validator_perf') ? areValidationOverridesEqual(newValidationOverrides, oldValidationOverrides) : newValidationOverrides === oldValidationOverrides;
170
+
171
+ // `areDocsEqual` stringifies the whole document, so it goes last and only runs once everything
172
+ // cheaper has matched. Pure predicates, so ordering cannot change the result; the experiment is
173
+ // resolved above regardless, so exposure does not depend on reaching it.
174
+ return newSchema === oldSchema && newADFStage === oldADFStage && newUseSpecValidator === oldUseSpecValidator && newSkipValidation === oldSkipValidation && validationOverridesAreEqual && areDocsEqual(newDoc, oldDoc);
164
175
  });
165
176
 
166
177
  // Ignored via go/ees005
@@ -175,12 +186,42 @@ var areDocsEqual = function areDocsEqual(docA, docB) {
175
186
 
176
187
  // PMNode
177
188
  if (docA.type && docA.toJSON && docB.type && docB.toJSON) {
189
+ // `Node.eq` compares markup and content directly. The stringify path builds two JSON trees via
190
+ // `toJSON`, then two strings from them, and discards all four — ~14x slower on a 5k-node
191
+ // document. Same result either way; `Node.eq` is the comparison ProseMirror intends for this.
192
+ if (typeof docA.eq === 'function' && typeof docB.eq === 'function' && (0, _isExperimentEnabled.isExperimentEnabled)('platform_editor_adf_validator_perf')) {
193
+ return docA.eq(docB);
194
+ }
178
195
  return JSON.stringify(docA.toJSON()) === JSON.stringify(docB.toJSON());
179
196
  }
180
197
 
181
198
  // Object
182
199
  return JSON.stringify(docA) === JSON.stringify(docB);
183
200
  };
201
+
202
+ /**
203
+ * `Renderer` rebuilds this object in its render body, so a reference check never matches and the memo
204
+ * re-validates (and re-transforms) an unchanged document every render. A flat bag of optional
205
+ * booleans, so a shallow key/value comparison is exact and free next to what it protects.
206
+ */
207
+ var areValidationOverridesEqual = function areValidationOverridesEqual(a, b) {
208
+ if (a === b) {
209
+ return true;
210
+ }
211
+ if (!a || !b) {
212
+ return false;
213
+ }
214
+ var aEntries = Object.entries(a);
215
+ // Ignored via go/ees005
216
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
217
+ var bRecord = b;
218
+ return aEntries.length === Object.keys(b).length && aEntries.every(function (_ref) {
219
+ var _ref2 = (0, _slicedToArray2.default)(_ref, 2),
220
+ key = _ref2[0],
221
+ value = _ref2[1];
222
+ return bRecord[key] === value;
223
+ });
224
+ };
184
225
  var _serializeFragment = function _serializeFragment(serializer, doc) {
185
226
  return serializer.serializeFragment(doc.content);
186
227
  };
@@ -73,7 +73,7 @@ var DEGRADED_SEVERITY_THRESHOLD = exports.DEGRADED_SEVERITY_THRESHOLD = 3000;
73
73
  var TABLE_INFO_TIMEOUT = 10000;
74
74
  var RENDER_EVENT_SAMPLE_RATE = 0.1;
75
75
  var packageName = "@atlaskit/renderer";
76
- var packageVersion = "136.1.1";
76
+ var packageVersion = "136.1.2";
77
77
  var setAsQueryContainerStyles = (0, _react2.css)({
78
78
  containerName: 'ak-renderer-wrapper',
79
79
  containerType: 'inline-size'
@@ -6,6 +6,7 @@ import { nativeEmbedsFallbackTransform, transformContainerNodes, transformMediaL
6
6
  import { ACTION, ACTION_SUBJECT, EVENT_TYPE } from '@atlaskit/editor-common/analytics';
7
7
  import { findAndTrackUnsupportedContentNodes, validateADFEntity } from '@atlaskit/editor-common/utils';
8
8
  import { getValidDocument } from '@atlaskit/editor-common/validator';
9
+ import { isExperimentEnabled } from '@atlaskit/platform-feature-experiments/is-experiment-enabled';
9
10
  import { fg } from '@atlaskit/platform-feature-flags';
10
11
  import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
11
12
  import memoizeOne from 'memoize-one';
@@ -23,6 +24,9 @@ const withStopwatch = cb => {
23
24
  time
24
25
  };
25
26
  };
27
+
28
+ /** Schema-variant escape hatches handed to the ADF validator. */
29
+
26
30
  const _validation = (doc, schema, adfStage, useSpecBasedValidator, dispatchAnalyticsEvent, skipValidation, validationOverrides) => {
27
31
  let result;
28
32
  if (useSpecBasedValidator) {
@@ -144,7 +148,14 @@ const _validation = (doc, schema, adfStage, useSpecBasedValidator, dispatchAnaly
144
148
  const memoValidation = memoizeOne(_validation, (newArgs, lastArgs) => {
145
149
  const [newDoc, newSchema, newADFStage, newUseSpecValidator,, /* ignoring dispatchAnalyticsEvent */newSkipValidation, newValidationOverrides] = newArgs;
146
150
  const [oldDoc, oldSchema, oldADFStage, oldUseSpecValidator,, /* ignoring dispatchAnalyticsEvent */oldSkipValidation, oldValidationOverrides] = lastArgs;
147
- return areDocsEqual(newDoc, oldDoc) && newSchema === oldSchema && newADFStage === oldADFStage && newUseSpecValidator === oldUseSpecValidator && newSkipValidation === oldSkipValidation && newValidationOverrides === oldValidationOverrides;
151
+
152
+ // Reference-compared before, which never matched — see `areValidationOverridesEqual`.
153
+ const validationOverridesAreEqual = isExperimentEnabled('platform_editor_adf_validator_perf') ? areValidationOverridesEqual(newValidationOverrides, oldValidationOverrides) : newValidationOverrides === oldValidationOverrides;
154
+
155
+ // `areDocsEqual` stringifies the whole document, so it goes last and only runs once everything
156
+ // cheaper has matched. Pure predicates, so ordering cannot change the result; the experiment is
157
+ // resolved above regardless, so exposure does not depend on reaching it.
158
+ return newSchema === oldSchema && newADFStage === oldADFStage && newUseSpecValidator === oldUseSpecValidator && newSkipValidation === oldSkipValidation && validationOverridesAreEqual && areDocsEqual(newDoc, oldDoc);
148
159
  });
149
160
 
150
161
  // Ignored via go/ees005
@@ -159,12 +170,37 @@ const areDocsEqual = (docA, docB) => {
159
170
 
160
171
  // PMNode
161
172
  if (docA.type && docA.toJSON && docB.type && docB.toJSON) {
173
+ // `Node.eq` compares markup and content directly. The stringify path builds two JSON trees via
174
+ // `toJSON`, then two strings from them, and discards all four — ~14x slower on a 5k-node
175
+ // document. Same result either way; `Node.eq` is the comparison ProseMirror intends for this.
176
+ if (typeof docA.eq === 'function' && typeof docB.eq === 'function' && isExperimentEnabled('platform_editor_adf_validator_perf')) {
177
+ return docA.eq(docB);
178
+ }
162
179
  return JSON.stringify(docA.toJSON()) === JSON.stringify(docB.toJSON());
163
180
  }
164
181
 
165
182
  // Object
166
183
  return JSON.stringify(docA) === JSON.stringify(docB);
167
184
  };
185
+
186
+ /**
187
+ * `Renderer` rebuilds this object in its render body, so a reference check never matches and the memo
188
+ * re-validates (and re-transforms) an unchanged document every render. A flat bag of optional
189
+ * booleans, so a shallow key/value comparison is exact and free next to what it protects.
190
+ */
191
+ const areValidationOverridesEqual = (a, b) => {
192
+ if (a === b) {
193
+ return true;
194
+ }
195
+ if (!a || !b) {
196
+ return false;
197
+ }
198
+ const aEntries = Object.entries(a);
199
+ // Ignored via go/ees005
200
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
201
+ const bRecord = b;
202
+ return aEntries.length === Object.keys(b).length && aEntries.every(([key, value]) => bRecord[key] === value);
203
+ };
168
204
  const _serializeFragment = (serializer, doc) => {
169
205
  return serializer.serializeFragment(doc.content);
170
206
  };
@@ -59,7 +59,7 @@ export const DEGRADED_SEVERITY_THRESHOLD = 3000;
59
59
  const TABLE_INFO_TIMEOUT = 10000;
60
60
  const RENDER_EVENT_SAMPLE_RATE = 0.1;
61
61
  const packageName = "@atlaskit/renderer";
62
- const packageVersion = "136.1.1";
62
+ const packageVersion = "136.1.2";
63
63
  const setAsQueryContainerStyles = css({
64
64
  containerName: 'ak-renderer-wrapper',
65
65
  containerType: 'inline-size'
@@ -7,6 +7,7 @@ import { nativeEmbedsFallbackTransform, transformContainerNodes, transformMediaL
7
7
  import { ACTION, ACTION_SUBJECT, EVENT_TYPE } from '@atlaskit/editor-common/analytics';
8
8
  import { findAndTrackUnsupportedContentNodes, validateADFEntity } from '@atlaskit/editor-common/utils';
9
9
  import { getValidDocument } from '@atlaskit/editor-common/validator';
10
+ import { isExperimentEnabled } from '@atlaskit/platform-feature-experiments/is-experiment-enabled';
10
11
  import { fg } from '@atlaskit/platform-feature-flags';
11
12
  import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
12
13
  import memoizeOne from 'memoize-one';
@@ -24,6 +25,9 @@ var withStopwatch = function withStopwatch(cb) {
24
25
  time: time
25
26
  };
26
27
  };
28
+
29
+ /** Schema-variant escape hatches handed to the ADF validator. */
30
+
27
31
  var _validation = function _validation(doc, schema, adfStage, useSpecBasedValidator, dispatchAnalyticsEvent, skipValidation, validationOverrides) {
28
32
  var result;
29
33
  if (useSpecBasedValidator) {
@@ -153,7 +157,14 @@ var memoValidation = memoizeOne(_validation, function (newArgs, lastArgs) {
153
157
  oldUseSpecValidator = _lastArgs[3],
154
158
  /* ignoring dispatchAnalyticsEvent */oldSkipValidation = _lastArgs[5],
155
159
  oldValidationOverrides = _lastArgs[6];
156
- return areDocsEqual(newDoc, oldDoc) && newSchema === oldSchema && newADFStage === oldADFStage && newUseSpecValidator === oldUseSpecValidator && newSkipValidation === oldSkipValidation && newValidationOverrides === oldValidationOverrides;
160
+
161
+ // Reference-compared before, which never matched — see `areValidationOverridesEqual`.
162
+ var validationOverridesAreEqual = isExperimentEnabled('platform_editor_adf_validator_perf') ? areValidationOverridesEqual(newValidationOverrides, oldValidationOverrides) : newValidationOverrides === oldValidationOverrides;
163
+
164
+ // `areDocsEqual` stringifies the whole document, so it goes last and only runs once everything
165
+ // cheaper has matched. Pure predicates, so ordering cannot change the result; the experiment is
166
+ // resolved above regardless, so exposure does not depend on reaching it.
167
+ return newSchema === oldSchema && newADFStage === oldADFStage && newUseSpecValidator === oldUseSpecValidator && newSkipValidation === oldSkipValidation && validationOverridesAreEqual && areDocsEqual(newDoc, oldDoc);
157
168
  });
158
169
 
159
170
  // Ignored via go/ees005
@@ -168,12 +179,42 @@ var areDocsEqual = function areDocsEqual(docA, docB) {
168
179
 
169
180
  // PMNode
170
181
  if (docA.type && docA.toJSON && docB.type && docB.toJSON) {
182
+ // `Node.eq` compares markup and content directly. The stringify path builds two JSON trees via
183
+ // `toJSON`, then two strings from them, and discards all four — ~14x slower on a 5k-node
184
+ // document. Same result either way; `Node.eq` is the comparison ProseMirror intends for this.
185
+ if (typeof docA.eq === 'function' && typeof docB.eq === 'function' && isExperimentEnabled('platform_editor_adf_validator_perf')) {
186
+ return docA.eq(docB);
187
+ }
171
188
  return JSON.stringify(docA.toJSON()) === JSON.stringify(docB.toJSON());
172
189
  }
173
190
 
174
191
  // Object
175
192
  return JSON.stringify(docA) === JSON.stringify(docB);
176
193
  };
194
+
195
+ /**
196
+ * `Renderer` rebuilds this object in its render body, so a reference check never matches and the memo
197
+ * re-validates (and re-transforms) an unchanged document every render. A flat bag of optional
198
+ * booleans, so a shallow key/value comparison is exact and free next to what it protects.
199
+ */
200
+ var areValidationOverridesEqual = function areValidationOverridesEqual(a, b) {
201
+ if (a === b) {
202
+ return true;
203
+ }
204
+ if (!a || !b) {
205
+ return false;
206
+ }
207
+ var aEntries = Object.entries(a);
208
+ // Ignored via go/ees005
209
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
210
+ var bRecord = b;
211
+ return aEntries.length === Object.keys(b).length && aEntries.every(function (_ref) {
212
+ var _ref2 = _slicedToArray(_ref, 2),
213
+ key = _ref2[0],
214
+ value = _ref2[1];
215
+ return bRecord[key] === value;
216
+ });
217
+ };
177
218
  var _serializeFragment = function _serializeFragment(serializer, doc) {
178
219
  return serializer.serializeFragment(doc.content);
179
220
  };
@@ -64,7 +64,7 @@ export var DEGRADED_SEVERITY_THRESHOLD = 3000;
64
64
  var TABLE_INFO_TIMEOUT = 10000;
65
65
  var RENDER_EVENT_SAMPLE_RATE = 0.1;
66
66
  var packageName = "@atlaskit/renderer";
67
- var packageVersion = "136.1.1";
67
+ var packageVersion = "136.1.2";
68
68
  var setAsQueryContainerStyles = css({
69
69
  containerName: 'ak-renderer-wrapper',
70
70
  containerType: 'inline-size'
@@ -20,8 +20,10 @@ export interface ResultWithTime<T> {
20
20
  time: number;
21
21
  }
22
22
  type DispatchAnalyticsEvent = (event: AnalyticsEventPayload) => void;
23
- export declare const renderDocument: <T>(doc: any, serializer: Serializer<T>, schema?: Schema, adfStage?: ADFStage, useSpecBasedValidator?: boolean, rendererId?: string, dispatchAnalyticsEvent?: DispatchAnalyticsEvent, unsupportedContentLevelsTracking?: UnsupportedContentLevelsTracking, appearance?: RendererAppearance, includeNodesCountInStats?: boolean, skipValidation?: boolean, validationOverrides?: {
23
+ /** Schema-variant escape hatches handed to the ADF validator. */
24
+ type ValidationOverrides = {
24
25
  allowNestedTables?: boolean;
25
26
  allowTableInPanel?: boolean;
26
- }) => RenderOutput<T | null>;
27
+ };
28
+ export declare const renderDocument: <T>(doc: any, serializer: Serializer<T>, schema?: Schema, adfStage?: ADFStage, useSpecBasedValidator?: boolean, rendererId?: string, dispatchAnalyticsEvent?: DispatchAnalyticsEvent, unsupportedContentLevelsTracking?: UnsupportedContentLevelsTracking, appearance?: RendererAppearance, includeNodesCountInStats?: boolean, skipValidation?: boolean, validationOverrides?: ValidationOverrides) => RenderOutput<T | null>;
27
29
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/renderer",
3
- "version": "136.1.2",
3
+ "version": "136.1.3",
4
4
  "description": "Renderer component",
5
5
  "publishConfig": {
6
6
  "registry": "https://registry.npmjs.org/"