@nordcraft/search 1.0.86 → 1.0.87
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/dist/rules/issues/miscellaneous/createStaticSizeConstraintRule.d.ts +5 -0
- package/dist/rules/issues/miscellaneous/createStaticSizeConstraintRule.js +74 -0
- package/dist/rules/issues/miscellaneous/createStaticSizeConstraintRule.js.map +1 -0
- package/dist/rules/issues/miscellaneous/miscRules.index.d.ts +3 -0
- package/dist/rules/issues/miscellaneous/miscRules.index.js +10 -1
- package/dist/rules/issues/miscellaneous/miscRules.index.js.map +1 -1
- package/dist/rules/issues/slots/unknownComponentSlotRule.js +1 -1
- package/dist/rules/issues/slots/unknownComponentSlotRule.js.map +1 -1
- package/dist/types.d.ts +1 -1
- package/package.json +3 -3
- package/src/rules/issues/miscellaneous/createStaticSizeConstraintRule.test.ts +153 -0
- package/src/rules/issues/miscellaneous/createStaticSizeConstraintRule.ts +90 -0
- package/src/rules/issues/miscellaneous/miscRules.index.ts +10 -1
- package/src/rules/issues/slots/unknownComponentSlotRule.ts +1 -1
- package/src/types.ts +10 -9
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { isDefined } from '@nordcraft/core/dist/utils/util';
|
|
2
|
+
import { VOID_HTML_ELEMENTS } from '@nordcraft/ssr/dist/const';
|
|
3
|
+
export function createStaticSizeConstraintRule(tag, maxSize, level = 'info') {
|
|
4
|
+
return {
|
|
5
|
+
code: 'size constraint',
|
|
6
|
+
category: 'Performance',
|
|
7
|
+
level: level,
|
|
8
|
+
visit: (report, args) => {
|
|
9
|
+
if (args.nodeType === 'component-node' &&
|
|
10
|
+
args.value.type === 'element' &&
|
|
11
|
+
args.value.tag === tag) {
|
|
12
|
+
let size = 0;
|
|
13
|
+
const component = args.component;
|
|
14
|
+
const evaluateElement = (element) => {
|
|
15
|
+
if (!element ||
|
|
16
|
+
['element', 'text', 'slot', 'component'].includes(element.type) ===
|
|
17
|
+
false) {
|
|
18
|
+
return '';
|
|
19
|
+
}
|
|
20
|
+
const children = (element.children ?? []).map((child) => evaluateElement(component.nodes?.[child]));
|
|
21
|
+
const tag = element.type === 'element' ? element.tag : 'span';
|
|
22
|
+
const attributes = {};
|
|
23
|
+
if (element.type === 'element' || element.type === 'component') {
|
|
24
|
+
Object.entries(element.attrs ?? {}).forEach(([key, value]) => {
|
|
25
|
+
if (value?.type === 'value' && isDefined(value.value)) {
|
|
26
|
+
attributes[key] = String(value.value);
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
const attributeString = Object.entries(attributes)
|
|
31
|
+
.map(([key, value]) => `${key}="${value}"`)
|
|
32
|
+
.join(' ');
|
|
33
|
+
if (VOID_HTML_ELEMENTS.includes(tag)) {
|
|
34
|
+
return attributeString
|
|
35
|
+
? `<${tag} ${attributeString} />`
|
|
36
|
+
: `<${tag} />`;
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
const openingTag = attributeString
|
|
40
|
+
? `<${tag} ${attributeString}>`
|
|
41
|
+
: `<${tag}>`;
|
|
42
|
+
return `${openingTag}${children.join('')}</${tag}>`;
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
const staticElement = evaluateElement(args.value);
|
|
46
|
+
size = new Blob([staticElement]).size;
|
|
47
|
+
if (size > maxSize) {
|
|
48
|
+
report({
|
|
49
|
+
path: args.path,
|
|
50
|
+
info: {
|
|
51
|
+
title: `Element size exceeds the suggested maximum allowed size of ${sizeFormatter(maxSize)}`,
|
|
52
|
+
description: `The <${tag}> element has a size of ${sizeFormatter(size)}, which exceeds the suggested limit of ${sizeFormatter(maxSize)}. Consider simplifying the content or structure of this element to reduce its size and improve performance.`,
|
|
53
|
+
},
|
|
54
|
+
details: { tag, size },
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
const sizeFormatter = (sizeInBytes) => {
|
|
62
|
+
const units = ['byte', 'KB', 'MB', 'GB', 'TB'];
|
|
63
|
+
let unitIndex = 0;
|
|
64
|
+
let size = sizeInBytes;
|
|
65
|
+
while (size >= 1024 && unitIndex < units.length - 1) {
|
|
66
|
+
size /= 1024;
|
|
67
|
+
unitIndex++;
|
|
68
|
+
}
|
|
69
|
+
const unit = unitIndex === 0 && size !== 1 ? 'bytes' : units[unitIndex];
|
|
70
|
+
return `${new Intl.NumberFormat(undefined, {
|
|
71
|
+
maximumFractionDigits: unitIndex === 0 ? 0 : 2,
|
|
72
|
+
}).format(size)} ${unit}`;
|
|
73
|
+
};
|
|
74
|
+
//# sourceMappingURL=createStaticSizeConstraintRule.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createStaticSizeConstraintRule.js","sourceRoot":"","sources":["../../../../src/rules/issues/miscellaneous/createStaticSizeConstraintRule.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
declare const _default: (import("../../../types").Rule<{
|
|
2
2
|
node: string;
|
|
3
|
+
}, import("../../../types").NodeType, import("../../../types").NodeType> | import("../../../types").Rule<{
|
|
4
|
+
tag: string;
|
|
5
|
+
size: number;
|
|
3
6
|
}, import("../../../types").NodeType, import("../../../types").NodeType> | import("../../../types").Rule<{
|
|
4
7
|
name: string;
|
|
5
8
|
}, import("../../../types").NodeType, import("../../../types").NodeType>)[];
|
|
@@ -1,5 +1,14 @@
|
|
|
1
|
+
import { createStaticSizeConstraintRule } from './createStaticSizeConstraintRule';
|
|
1
2
|
import { noReferenceNodeRule } from './noReferenceNodeRule';
|
|
2
3
|
import { requireExtensionRule } from './requireExtensionRule';
|
|
3
4
|
import { unknownCookieRule } from './unknownCookieRule';
|
|
4
|
-
export default [
|
|
5
|
+
export default [
|
|
6
|
+
noReferenceNodeRule,
|
|
7
|
+
requireExtensionRule,
|
|
8
|
+
unknownCookieRule,
|
|
9
|
+
// 100 KB is a large SVG
|
|
10
|
+
createStaticSizeConstraintRule('svg', 100 * 1024),
|
|
11
|
+
// 50 KB is a large img element (with potential base64 encoded image)
|
|
12
|
+
createStaticSizeConstraintRule('img', 50 * 1024),
|
|
13
|
+
];
|
|
5
14
|
//# sourceMappingURL=miscRules.index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"miscRules.index.js","sourceRoot":"","sources":["../../../../src/rules/issues/miscellaneous/miscRules.index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAA;AAC3D,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAA;AAC7D,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAA;AAEvD,eAAe,CAAC,
|
|
1
|
+
{"version":3,"file":"miscRules.index.js","sourceRoot":"","sources":["../../../../src/rules/issues/miscellaneous/miscRules.index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,8BAA8B,EAAE,MAAM,kCAAkC,CAAA;AACjF,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAA;AAC3D,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAA;AAC7D,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAA;AAEvD,eAAe;IACb,mBAAmB;IACnB,oBAAoB;IACpB,iBAAiB;IACjB,wBAAwB;IACxB,8BAA8B,CAAC,KAAK,EAAE,GAAG,GAAG,IAAI,CAAC;IACjD,qEAAqE;IACrE,8BAA8B,CAAC,KAAK,EAAE,EAAE,GAAG,IAAI,CAAC;CACjD,CAAA"}
|
|
@@ -8,7 +8,7 @@ export const unknownComponentSlotRule = {
|
|
|
8
8
|
return;
|
|
9
9
|
}
|
|
10
10
|
// We only want to check the immediate children of a "sub component"
|
|
11
|
-
if (value.type !== 'component' || value
|
|
11
|
+
if (value.type !== 'component' || (value?.children ?? []).length === 0) {
|
|
12
12
|
return;
|
|
13
13
|
}
|
|
14
14
|
const [, currentComponentName, ,] = path;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"unknownComponentSlotRule.js","sourceRoot":"","sources":["../../../../src/rules/issues/slots/unknownComponentSlotRule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,gDAAgD,CAAA;AAIhF,MAAM,CAAC,MAAM,wBAAwB,GAA+B;IAClE,IAAI,EAAE,wBAAwB;IAC9B,KAAK,EAAE,OAAO;IACd,QAAQ,EAAE,mBAAmB;IAC7B,KAAK,EAAE,CAAC,MAAM,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC;QACnD,IAAI,QAAQ,KAAK,gBAAgB,EAAE,CAAC;YAClC,OAAM;QACR,CAAC;QAED,oEAAoE;QACpE,IAAI,KAAK,CAAC,IAAI,KAAK,WAAW,IAAI,KAAK,
|
|
1
|
+
{"version":3,"file":"unknownComponentSlotRule.js","sourceRoot":"","sources":["../../../../src/rules/issues/slots/unknownComponentSlotRule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,gDAAgD,CAAA;AAIhF,MAAM,CAAC,MAAM,wBAAwB,GAA+B;IAClE,IAAI,EAAE,wBAAwB;IAC9B,KAAK,EAAE,OAAO;IACd,QAAQ,EAAE,mBAAmB;IAC7B,KAAK,EAAE,CAAC,MAAM,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC;QACnD,IAAI,QAAQ,KAAK,gBAAgB,EAAE,CAAC;YAClC,OAAM;QACR,CAAC;QAED,oEAAoE;QACpE,IAAI,KAAK,CAAC,IAAI,KAAK,WAAW,IAAI,CAAC,KAAK,EAAE,QAAQ,IAAI,EAAE,CAAC,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACvE,OAAM;QACR,CAAC;QAED,MAAM,CAAC,EAAE,oBAAoB,EAAE,AAAD,EAAG,GAAG,IAAI,CAAA;QAExC,MAAM,SAAS,GAAG,KAAK,CAAC,UAAU,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;QAC9C,IAAI,CAAC,SAAS,EAAE,CAAC;YACf,OAAM;QACR,CAAC;QACD,sCAAsC;QACtC,MAAM,YAAY,GAAG,IAAI,eAAe,CAAC;YACvC,SAAS;YACT,YAAY,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC;YAC9C,WAAW,EAAE,SAAS;YACtB,cAAc,EAAE;gBACd,QAAQ,EAAE,KAAK,CAAC,QAAQ;gBACxB,QAAQ,EAAE,KAAK,CAAC,QAAQ;aACzB;SACF,CAAC,CAAA;QAEF,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,CAAC,YAAY,CAAC,KAAK,IAAI,EAAE,CAAC;aACxD,MAAM,CAAC,CAAC,IAAI,EAAyB,EAAE,CAAC,IAAI,CAAC,IAAI,KAAK,MAAM,CAAC;aAC7D,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,IAAI,SAAS,CAAC,CAAA;QAExC,0EAA0E;QAC1E,KAAK,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC;YAChC,MAAM,SAAS,GAAG,KAAK,CAAC,UAAU,CAAC,oBAAoB,CAAC,EAAE,KAAK,EAAE,CAAC,KAAK,CAAC,CAAA;YACxE,MAAM,QAAQ,GAAG,SAAS,EAAE,IAAI,IAAI,SAAS,CAAA;YAE7C,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;gBACpC,MAAM,CAAC;oBACL,IAAI,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC;oBACxC,IAAI,EAAE;wBACJ,KAAK,EAAE,wBAAwB;wBAC/B,WAAW,EAAE,KAAK,QAAQ,gEAAgE;qBAC3F;oBACD,OAAO,EAAE,EAAE,QAAQ,EAAE,QAAQ,EAAE;iBAChC,CAAC,CAAA;YACJ,CAAC;QAAA,CACF,CAAC,CAAA;IAAA,CACH;CACF,CAAA"}
|
package/dist/types.d.ts
CHANGED
|
@@ -31,7 +31,7 @@ import type { InvalidStyleSyntaxRuleFix } from './rules/issues/style/invalidStyl
|
|
|
31
31
|
import type { LegacyStyleVariableRuleFix } from './rules/issues/style/legacyStyleVariableRule';
|
|
32
32
|
import type { NoReferenceVariableRuleFix } from './rules/issues/variables/noReferenceVariableRule';
|
|
33
33
|
import type { NoPostNavigateActionRuleFix } from './rules/issues/workflows/noPostNavigateAction';
|
|
34
|
-
export type Code = 'duplicate action argument name' | 'duplicate event trigger' | 'duplicate formula argument name' | 'duplicate url parameter' | 'duplicate workflow parameter' | '
|
|
34
|
+
export type Code = 'duplicate action argument name' | 'duplicate event trigger' | 'duplicate formula argument name' | 'duplicate route' | 'duplicate url parameter' | 'duplicate workflow parameter' | 'image without dimension' | 'invalid api parser mode' | 'invalid api proxy body setting' | 'invalid api proxy cookie setting' | 'invalid component structure' | 'invalid element child' | 'invalid style syntax' | 'legacy action' | 'legacy api' | 'legacy formula' | 'legacy style variable' | 'legacy theme' | 'no context consumers' | 'no post navigate action' | 'no-console' | 'no-reference api input' | 'no-reference api service' | 'no-reference api' | 'no-reference attribute in instance' | 'no-reference attribute' | 'no-reference component formula' | 'no-reference component workflow' | 'no-reference component' | 'no-reference event' | 'no-reference global css variable' | 'no-reference node' | 'no-reference project action' | 'no-reference project formula' | 'no-reference variable' | 'no-static-node-condition' | 'no-unnecessary-condition-falsy' | 'no-unnecessary-condition-truthy' | 'non-empty void element' | 'required api autofetch' | 'required direct child' | 'required direct parent' | 'required element attribute' | 'required extension' | 'required meta tag' | 'size constraint' | 'unknown action argument' | 'unknown action event' | 'unknown api input' | 'unknown api service' | 'unknown api' | 'unknown attribute' | 'unknown classname' | 'unknown component attribute' | 'unknown component formula input' | 'unknown component slot' | 'unknown component' | 'unknown context formula' | 'unknown context provider formula' | 'unknown context provider workflow' | 'unknown context provider' | 'unknown context workflow' | 'unknown cookie' | 'unknown css variable' | 'unknown event' | 'unknown fetch input' | 'unknown formula' | 'unknown project action' | 'unknown project formula input' | 'unknown project formula' | 'unknown repeat index formula' | 'unknown repeat item formula' | 'unknown set url parameter' | 'unknown set url parameters' | 'unknown trigger event' | 'unknown trigger workflow parameter' | 'unknown trigger workflow' | 'unknown url parameter' | 'unknown variable setter' | 'unknown variable' | 'unknown workflow parameter';
|
|
35
35
|
export type Category = 'Unknown Reference' | 'No References' | 'SEO' | 'Accessibility' | 'Deprecation' | 'Performance' | 'Security' | 'Quality' | 'Other';
|
|
36
36
|
export type Level = 'error' | 'warning' | 'info';
|
|
37
37
|
export type Result = {
|
package/package.json
CHANGED
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
"directory": "packages/search"
|
|
11
11
|
},
|
|
12
12
|
"dependencies": {
|
|
13
|
-
"@nordcraft/ssr": "1.0.
|
|
14
|
-
"@nordcraft/core": "1.0.
|
|
13
|
+
"@nordcraft/ssr": "1.0.87",
|
|
14
|
+
"@nordcraft/core": "1.0.87",
|
|
15
15
|
"jsondiffpatch": "0.7.3",
|
|
16
16
|
"postcss": "8.5.6",
|
|
17
17
|
"zod": "4.2.1"
|
|
@@ -26,5 +26,5 @@
|
|
|
26
26
|
"test:watch:only": "bun test --watch --only"
|
|
27
27
|
},
|
|
28
28
|
"files": ["dist", "src"],
|
|
29
|
-
"version": "1.0.
|
|
29
|
+
"version": "1.0.87"
|
|
30
30
|
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { valueFormula } from '@nordcraft/core/dist/formula/formulaUtils'
|
|
2
|
+
import { describe, expect, test } from 'bun:test'
|
|
3
|
+
import { searchProject } from '../../../searchProject'
|
|
4
|
+
import { createStaticSizeConstraintRule } from './createStaticSizeConstraintRule'
|
|
5
|
+
|
|
6
|
+
describe('createStaticSizeConstraintRule', () => {
|
|
7
|
+
test('should calculate element size correctly for SVGs', () => {
|
|
8
|
+
const problems = Array.from(
|
|
9
|
+
searchProject({
|
|
10
|
+
files: {
|
|
11
|
+
formulas: {},
|
|
12
|
+
components: {
|
|
13
|
+
test: {
|
|
14
|
+
name: 'test',
|
|
15
|
+
nodes: {
|
|
16
|
+
root: {
|
|
17
|
+
type: 'element',
|
|
18
|
+
attrs: {},
|
|
19
|
+
classes: {},
|
|
20
|
+
events: {},
|
|
21
|
+
tag: 'img',
|
|
22
|
+
children: ['svg'],
|
|
23
|
+
style: {},
|
|
24
|
+
},
|
|
25
|
+
svg: {
|
|
26
|
+
type: 'element',
|
|
27
|
+
attrs: {},
|
|
28
|
+
classes: {},
|
|
29
|
+
events: {},
|
|
30
|
+
tag: 'svg',
|
|
31
|
+
children: ['path'],
|
|
32
|
+
style: {},
|
|
33
|
+
},
|
|
34
|
+
path: {
|
|
35
|
+
type: 'element',
|
|
36
|
+
attrs: {},
|
|
37
|
+
classes: {},
|
|
38
|
+
events: {},
|
|
39
|
+
tag: 'path',
|
|
40
|
+
children: [],
|
|
41
|
+
style: {},
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
formulas: {},
|
|
45
|
+
apis: {},
|
|
46
|
+
attributes: {},
|
|
47
|
+
variables: {},
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
rules: [createStaticSizeConstraintRule('svg', 1)],
|
|
52
|
+
}),
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
expect(problems).toHaveLength(1)
|
|
56
|
+
expect(problems[0].code).toBe('size constraint')
|
|
57
|
+
expect(problems[0].path).toEqual(['components', 'test', 'nodes', 'svg'])
|
|
58
|
+
const expectedSize = new Blob(['<svg><path></path></svg>']).size
|
|
59
|
+
expect(problems[0].details.size).toEqual(expectedSize)
|
|
60
|
+
})
|
|
61
|
+
test('should calculate element size correctly for imgs', () => {
|
|
62
|
+
const problems = Array.from(
|
|
63
|
+
searchProject({
|
|
64
|
+
files: {
|
|
65
|
+
formulas: {},
|
|
66
|
+
components: {
|
|
67
|
+
test: {
|
|
68
|
+
name: 'test',
|
|
69
|
+
nodes: {
|
|
70
|
+
root: {
|
|
71
|
+
type: 'element',
|
|
72
|
+
attrs: {
|
|
73
|
+
src: valueFormula(
|
|
74
|
+
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAAEACAIAAADTED8xAAADMElEQVR4nOzVwQnAIBQFQYXff81RUkQCOyDj1YOPnbXWPmeTRef+/3O/OyBjzh3CD95BfqICMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMO0TAAD//2Anhf4QtqobAAAAAElFTkSuQmCC',
|
|
75
|
+
),
|
|
76
|
+
},
|
|
77
|
+
classes: {},
|
|
78
|
+
events: {},
|
|
79
|
+
tag: 'img',
|
|
80
|
+
children: [],
|
|
81
|
+
style: {},
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
formulas: {},
|
|
85
|
+
apis: {},
|
|
86
|
+
attributes: {},
|
|
87
|
+
variables: {},
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
rules: [createStaticSizeConstraintRule('img', 1)],
|
|
92
|
+
}),
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
expect(problems).toHaveLength(1)
|
|
96
|
+
expect(problems[0].code).toBe('size constraint')
|
|
97
|
+
expect(problems[0].path).toEqual(['components', 'test', 'nodes', 'root'])
|
|
98
|
+
const expectedSize = new Blob([
|
|
99
|
+
'<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAAEACAIAAADTED8xAAADMElEQVR4nOzVwQnAIBQFQYXff81RUkQCOyDj1YOPnbXWPmeTRef+/3O/OyBjzh3CD95BfqICMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMO0TAAD//2Anhf4QtqobAAAAAElFTkSuQmCC" />',
|
|
100
|
+
]).size
|
|
101
|
+
expect(problems[0].details.size).toEqual(expectedSize)
|
|
102
|
+
})
|
|
103
|
+
test('should ignore elements that are not too large', () => {
|
|
104
|
+
const problems = Array.from(
|
|
105
|
+
searchProject({
|
|
106
|
+
files: {
|
|
107
|
+
formulas: {},
|
|
108
|
+
components: {
|
|
109
|
+
test: {
|
|
110
|
+
name: 'test',
|
|
111
|
+
nodes: {
|
|
112
|
+
root: {
|
|
113
|
+
type: 'element',
|
|
114
|
+
attrs: {},
|
|
115
|
+
classes: {},
|
|
116
|
+
events: {},
|
|
117
|
+
tag: 'img',
|
|
118
|
+
children: ['svg'],
|
|
119
|
+
style: {},
|
|
120
|
+
},
|
|
121
|
+
svg: {
|
|
122
|
+
type: 'element',
|
|
123
|
+
attrs: {},
|
|
124
|
+
classes: {},
|
|
125
|
+
events: {},
|
|
126
|
+
tag: 'svg',
|
|
127
|
+
children: ['path'],
|
|
128
|
+
style: {},
|
|
129
|
+
},
|
|
130
|
+
path: {
|
|
131
|
+
type: 'element',
|
|
132
|
+
attrs: {},
|
|
133
|
+
classes: {},
|
|
134
|
+
events: {},
|
|
135
|
+
tag: 'path',
|
|
136
|
+
children: [],
|
|
137
|
+
style: {},
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
formulas: {},
|
|
141
|
+
apis: {},
|
|
142
|
+
attributes: {},
|
|
143
|
+
variables: {},
|
|
144
|
+
},
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
rules: [createStaticSizeConstraintRule('svg', 100)],
|
|
148
|
+
}),
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
expect(problems).toHaveLength(0)
|
|
152
|
+
})
|
|
153
|
+
})
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import type { NodeModel } from '@nordcraft/core/dist/component/component.types'
|
|
2
|
+
import { isDefined } from '@nordcraft/core/dist/utils/util'
|
|
3
|
+
import { VOID_HTML_ELEMENTS } from '@nordcraft/ssr/dist/const'
|
|
4
|
+
import type { Level, Rule } from '../../../types'
|
|
5
|
+
|
|
6
|
+
export function createStaticSizeConstraintRule(
|
|
7
|
+
tag: string,
|
|
8
|
+
maxSize: number,
|
|
9
|
+
level: Level = 'info',
|
|
10
|
+
): Rule<{
|
|
11
|
+
tag: string
|
|
12
|
+
size: number
|
|
13
|
+
}> {
|
|
14
|
+
return {
|
|
15
|
+
code: 'size constraint',
|
|
16
|
+
category: 'Performance',
|
|
17
|
+
level: level,
|
|
18
|
+
visit: (report, args) => {
|
|
19
|
+
if (
|
|
20
|
+
args.nodeType === 'component-node' &&
|
|
21
|
+
args.value.type === 'element' &&
|
|
22
|
+
args.value.tag === tag
|
|
23
|
+
) {
|
|
24
|
+
let size = 0
|
|
25
|
+
const component = args.component
|
|
26
|
+
const evaluateElement = (element?: NodeModel): string => {
|
|
27
|
+
if (
|
|
28
|
+
!element ||
|
|
29
|
+
['element', 'text', 'slot', 'component'].includes(element.type) ===
|
|
30
|
+
false
|
|
31
|
+
) {
|
|
32
|
+
return ''
|
|
33
|
+
}
|
|
34
|
+
const children = (element.children ?? []).map((child) =>
|
|
35
|
+
evaluateElement(component.nodes?.[child]),
|
|
36
|
+
)
|
|
37
|
+
const tag = element.type === 'element' ? element.tag : 'span'
|
|
38
|
+
const attributes: Record<string, string> = {}
|
|
39
|
+
if (element.type === 'element' || element.type === 'component') {
|
|
40
|
+
Object.entries(element.attrs ?? {}).forEach(([key, value]) => {
|
|
41
|
+
if (value?.type === 'value' && isDefined(value.value)) {
|
|
42
|
+
attributes[key] = String(value.value)
|
|
43
|
+
}
|
|
44
|
+
})
|
|
45
|
+
}
|
|
46
|
+
const attributeString = Object.entries(attributes)
|
|
47
|
+
.map(([key, value]) => `${key}="${value}"`)
|
|
48
|
+
.join(' ')
|
|
49
|
+
if (VOID_HTML_ELEMENTS.includes(tag)) {
|
|
50
|
+
return attributeString
|
|
51
|
+
? `<${tag} ${attributeString} />`
|
|
52
|
+
: `<${tag} />`
|
|
53
|
+
} else {
|
|
54
|
+
const openingTag = attributeString
|
|
55
|
+
? `<${tag} ${attributeString}>`
|
|
56
|
+
: `<${tag}>`
|
|
57
|
+
return `${openingTag}${children.join('')}</${tag}>`
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
const staticElement = evaluateElement(args.value)
|
|
61
|
+
size = new Blob([staticElement]).size
|
|
62
|
+
if (size > maxSize) {
|
|
63
|
+
report({
|
|
64
|
+
path: args.path,
|
|
65
|
+
info: {
|
|
66
|
+
title: `Element size exceeds the suggested maximum allowed size of ${sizeFormatter(maxSize)}`,
|
|
67
|
+
description: `The <${tag}> element has a size of ${sizeFormatter(size)}, which exceeds the suggested limit of ${sizeFormatter(maxSize)}. Consider simplifying the content or structure of this element to reduce its size and improve performance.`,
|
|
68
|
+
},
|
|
69
|
+
details: { tag, size },
|
|
70
|
+
})
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const sizeFormatter = (sizeInBytes: number) => {
|
|
78
|
+
const units = ['byte', 'KB', 'MB', 'GB', 'TB']
|
|
79
|
+
let unitIndex = 0
|
|
80
|
+
let size = sizeInBytes
|
|
81
|
+
|
|
82
|
+
while (size >= 1024 && unitIndex < units.length - 1) {
|
|
83
|
+
size /= 1024
|
|
84
|
+
unitIndex++
|
|
85
|
+
}
|
|
86
|
+
const unit = unitIndex === 0 && size !== 1 ? 'bytes' : units[unitIndex]
|
|
87
|
+
return `${new Intl.NumberFormat(undefined, {
|
|
88
|
+
maximumFractionDigits: unitIndex === 0 ? 0 : 2,
|
|
89
|
+
}).format(size)} ${unit}`
|
|
90
|
+
}
|
|
@@ -1,5 +1,14 @@
|
|
|
1
|
+
import { createStaticSizeConstraintRule } from './createStaticSizeConstraintRule'
|
|
1
2
|
import { noReferenceNodeRule } from './noReferenceNodeRule'
|
|
2
3
|
import { requireExtensionRule } from './requireExtensionRule'
|
|
3
4
|
import { unknownCookieRule } from './unknownCookieRule'
|
|
4
5
|
|
|
5
|
-
export default [
|
|
6
|
+
export default [
|
|
7
|
+
noReferenceNodeRule,
|
|
8
|
+
requireExtensionRule,
|
|
9
|
+
unknownCookieRule,
|
|
10
|
+
// 100 KB is a large SVG
|
|
11
|
+
createStaticSizeConstraintRule('svg', 100 * 1024),
|
|
12
|
+
// 50 KB is a large img element (with potential base64 encoded image)
|
|
13
|
+
createStaticSizeConstraintRule('img', 50 * 1024),
|
|
14
|
+
]
|
|
@@ -12,7 +12,7 @@ export const unknownComponentSlotRule: Rule<{ slotName: string }> = {
|
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
// We only want to check the immediate children of a "sub component"
|
|
15
|
-
if (value.type !== 'component' || value
|
|
15
|
+
if (value.type !== 'component' || (value?.children ?? []).length === 0) {
|
|
16
16
|
return
|
|
17
17
|
}
|
|
18
18
|
|
package/src/types.ts
CHANGED
|
@@ -59,15 +59,16 @@ export type Code =
|
|
|
59
59
|
| 'duplicate action argument name'
|
|
60
60
|
| 'duplicate event trigger'
|
|
61
61
|
| 'duplicate formula argument name'
|
|
62
|
+
| 'duplicate route'
|
|
62
63
|
| 'duplicate url parameter'
|
|
63
64
|
| 'duplicate workflow parameter'
|
|
64
|
-
| '
|
|
65
|
+
| 'image without dimension'
|
|
65
66
|
| 'invalid api parser mode'
|
|
66
67
|
| 'invalid api proxy body setting'
|
|
67
68
|
| 'invalid api proxy cookie setting'
|
|
69
|
+
| 'invalid component structure'
|
|
68
70
|
| 'invalid element child'
|
|
69
71
|
| 'invalid style syntax'
|
|
70
|
-
| 'invalid component structure'
|
|
71
72
|
| 'legacy action'
|
|
72
73
|
| 'legacy api'
|
|
73
74
|
| 'legacy formula'
|
|
@@ -77,10 +78,10 @@ export type Code =
|
|
|
77
78
|
| 'no post navigate action'
|
|
78
79
|
| 'no-console'
|
|
79
80
|
| 'no-reference api input'
|
|
80
|
-
| 'no-reference api'
|
|
81
81
|
| 'no-reference api service'
|
|
82
|
-
| 'no-reference
|
|
82
|
+
| 'no-reference api'
|
|
83
83
|
| 'no-reference attribute in instance'
|
|
84
|
+
| 'no-reference attribute'
|
|
84
85
|
| 'no-reference component formula'
|
|
85
86
|
| 'no-reference component workflow'
|
|
86
87
|
| 'no-reference component'
|
|
@@ -100,24 +101,25 @@ export type Code =
|
|
|
100
101
|
| 'required element attribute'
|
|
101
102
|
| 'required extension'
|
|
102
103
|
| 'required meta tag'
|
|
103
|
-
| '
|
|
104
|
+
| 'size constraint'
|
|
104
105
|
| 'unknown action argument'
|
|
105
106
|
| 'unknown action event'
|
|
106
107
|
| 'unknown api input'
|
|
107
|
-
| 'unknown api'
|
|
108
108
|
| 'unknown api service'
|
|
109
|
+
| 'unknown api'
|
|
109
110
|
| 'unknown attribute'
|
|
110
111
|
| 'unknown classname'
|
|
111
112
|
| 'unknown component attribute'
|
|
112
113
|
| 'unknown component formula input'
|
|
113
114
|
| 'unknown component slot'
|
|
115
|
+
| 'unknown component'
|
|
114
116
|
| 'unknown context formula'
|
|
115
117
|
| 'unknown context provider formula'
|
|
116
118
|
| 'unknown context provider workflow'
|
|
117
119
|
| 'unknown context provider'
|
|
118
120
|
| 'unknown context workflow'
|
|
119
121
|
| 'unknown cookie'
|
|
120
|
-
| 'unknown
|
|
122
|
+
| 'unknown css variable'
|
|
121
123
|
| 'unknown event'
|
|
122
124
|
| 'unknown fetch input'
|
|
123
125
|
| 'unknown formula'
|
|
@@ -129,12 +131,11 @@ export type Code =
|
|
|
129
131
|
| 'unknown set url parameter'
|
|
130
132
|
| 'unknown set url parameters'
|
|
131
133
|
| 'unknown trigger event'
|
|
134
|
+
| 'unknown trigger workflow parameter'
|
|
132
135
|
| 'unknown trigger workflow'
|
|
133
136
|
| 'unknown url parameter'
|
|
134
137
|
| 'unknown variable setter'
|
|
135
138
|
| 'unknown variable'
|
|
136
|
-
| 'unknown css variable'
|
|
137
|
-
| 'unknown trigger workflow parameter'
|
|
138
139
|
| 'unknown workflow parameter'
|
|
139
140
|
|
|
140
141
|
export type Category =
|