@matthesketh/utopia-compiler 0.4.0 → 0.5.0

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/index.cjs CHANGED
@@ -32,16 +32,19 @@ __export(index_exports, {
32
32
  module.exports = __toCommonJS(index_exports);
33
33
 
34
34
  // src/parser.ts
35
+ var BLOCK_RE = /<(template|script|style|test)([\s][^>]*)?\s*>/g;
36
+ var ATTR_RE = /([a-zA-Z_][\w-]*)\s*(?:=\s*(?:"([^"]*)"|'([^']*)'|(\S+)))?/g;
35
37
  function parse(source, filename = "anonymous.utopia") {
36
38
  const descriptor = {
37
39
  template: null,
38
40
  script: null,
39
41
  style: null,
42
+ test: null,
40
43
  filename
41
44
  };
42
- const blockRe = /<(template|script|style)([\s][^>]*)?\s*>/g;
45
+ BLOCK_RE.lastIndex = 0;
43
46
  let match;
44
- while ((match = blockRe.exec(source)) !== null) {
47
+ while ((match = BLOCK_RE.exec(source)) !== null) {
45
48
  const tagName = match[1];
46
49
  const attrString = match[2] || "";
47
50
  const openTagStart = match.index;
@@ -72,11 +75,10 @@ function parse(source, filename = "anonymous.utopia") {
72
75
  );
73
76
  }
74
77
  descriptor[tagName] = block;
75
- blockRe.lastIndex = blockEnd;
78
+ BLOCK_RE.lastIndex = blockEnd;
76
79
  }
77
80
  return descriptor;
78
81
  }
79
- var ATTR_RE = /([a-zA-Z_][\w-]*)\s*(?:=\s*(?:"([^"]*)"|'([^']*)'|(\S+)))?/g;
80
82
  function parseAttributes(raw) {
81
83
  const attrs = {};
82
84
  let m;
@@ -113,6 +115,16 @@ var SFCParseError = class extends Error {
113
115
  };
114
116
 
115
117
  // src/template-compiler.ts
118
+ var TAG_NAME_CHAR_RE = /[a-zA-Z0-9\-_]/;
119
+ var ATTR_NAME_CHAR_RE = /[a-zA-Z0-9\-_:@.]/;
120
+ var ATTR_VALUE_END_RE = /[\s/>]/;
121
+ var WHITESPACE_RE = /\s/;
122
+ var TAG_START_CHAR_RE = /[a-zA-Z]/;
123
+ var U_FOR_EXPR_RE = /^\s*(?:\(\s*(\w+)\s*(?:,\s*(\w+)\s*)?\)|(\w+))\s+in\s+(.+)$/;
124
+ var COMPONENT_TAG_RE = /^[A-Z][a-zA-Z0-9_$]*$/;
125
+ var BACKSLASH_RE = /\\/g;
126
+ var SINGLE_QUOTE_RE = /'/g;
127
+ var HTML_ENTITY_RE = /&(?:#(\d+)|#x([0-9a-fA-F]+)|(\w+));/g;
116
128
  function compileTemplate(template, options = {}) {
117
129
  const ast = parseTemplate(template);
118
130
  return generate(ast, options);
@@ -294,7 +306,7 @@ var TemplateParser = class {
294
306
  // ---- Low-level helpers --------------------------------------------------
295
307
  readTagName() {
296
308
  const start = this.pos;
297
- while (this.pos < this.source.length && /[a-zA-Z0-9\-_]/.test(this.source[this.pos])) {
309
+ while (this.pos < this.source.length && TAG_NAME_CHAR_RE.test(this.source[this.pos])) {
298
310
  this.pos++;
299
311
  }
300
312
  const name = this.source.slice(start, this.pos);
@@ -303,7 +315,7 @@ var TemplateParser = class {
303
315
  }
304
316
  readAttributeName() {
305
317
  const start = this.pos;
306
- while (this.pos < this.source.length && /[a-zA-Z0-9\-_:@.]/.test(this.source[this.pos])) {
318
+ while (this.pos < this.source.length && ATTR_NAME_CHAR_RE.test(this.source[this.pos])) {
307
319
  this.pos++;
308
320
  }
309
321
  return this.source.slice(start, this.pos);
@@ -320,13 +332,13 @@ var TemplateParser = class {
320
332
  return value;
321
333
  }
322
334
  const start = this.pos;
323
- while (this.pos < this.source.length && !/[\s/>]/.test(this.source[this.pos])) {
335
+ while (this.pos < this.source.length && !ATTR_VALUE_END_RE.test(this.source[this.pos])) {
324
336
  this.pos++;
325
337
  }
326
338
  return this.source.slice(start, this.pos);
327
339
  }
328
340
  skipWhitespace() {
329
- while (this.pos < this.source.length && /\s/.test(this.source[this.pos])) {
341
+ while (this.pos < this.source.length && WHITESPACE_RE.test(this.source[this.pos])) {
330
342
  this.pos++;
331
343
  }
332
344
  }
@@ -336,7 +348,7 @@ var TemplateParser = class {
336
348
  /** Returns true when the char after `<` looks like the start of a tag name. */
337
349
  peekTagStart() {
338
350
  const next = this.source[this.pos + 1];
339
- return next !== void 0 && /[a-zA-Z]/.test(next);
351
+ return next !== void 0 && TAG_START_CHAR_RE.test(next);
340
352
  }
341
353
  expect(str) {
342
354
  if (!this.lookingAt(str)) {
@@ -657,9 +669,7 @@ ${fnBody}
657
669
  const anchorVar = this.freshVar();
658
670
  this.helpers.add("createComment");
659
671
  this.emit(`const ${anchorVar} = createComment('u-for')`);
660
- const forMatch = dir.expression.match(
661
- /^\s*(?:\(\s*(\w+)\s*(?:,\s*(\w+)\s*)?\)|(\w+))\s+in\s+(.+)$/
662
- );
672
+ const forMatch = dir.expression.match(U_FOR_EXPR_RE);
663
673
  if (!forMatch) {
664
674
  throw new Error(`Invalid u-for expression: "${dir.expression}"`);
665
675
  }
@@ -862,10 +872,10 @@ ${fnBody}
862
872
  }
863
873
  };
864
874
  function isComponentTag(tag) {
865
- return /^[A-Z][a-zA-Z0-9_$]*$/.test(tag);
875
+ return COMPONENT_TAG_RE.test(tag);
866
876
  }
867
877
  function escapeStr(s) {
868
- return s.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
878
+ return s.replace(BACKSLASH_RE, "\\\\").replace(SINGLE_QUOTE_RE, "\\'");
869
879
  }
870
880
  var ENTITY_MAP = {
871
881
  "&amp;": "&",
@@ -894,7 +904,8 @@ var ENTITY_MAP = {
894
904
  "&divide;": "\xF7"
895
905
  };
896
906
  function decodeEntities(text) {
897
- return text.replace(/&(?:#(\d+)|#x([0-9a-fA-F]+)|(\w+));/g, (match, dec, hex, named) => {
907
+ HTML_ENTITY_RE.lastIndex = 0;
908
+ return text.replace(HTML_ENTITY_RE, (match, dec, hex, named) => {
898
909
  if (dec) {
899
910
  const code = parseInt(dec, 10);
900
911
  if (code >= 0 && code <= 1114111) {
@@ -927,6 +938,9 @@ function generate(ast, options) {
927
938
  }
928
939
 
929
940
  // src/style-compiler.ts
941
+ var WHITESPACE_RE2 = /\s/;
942
+ var KEYFRAMES_RE = /^@(?:-\w+-)?keyframes\b/;
943
+ var PSEUDO_SELECTOR_RE = /(?:::?[\w-]+(?:\([^)]*\))?)+$/;
930
944
  function compileStyle(options) {
931
945
  const { source, filename, scoped, scopeId: overrideScopeId } = options;
932
946
  if (!scoped) {
@@ -947,7 +961,7 @@ function scopeSelectors(css, scopeId) {
947
961
  const result = [];
948
962
  let pos = 0;
949
963
  while (pos < css.length) {
950
- if (/\s/.test(css[pos])) {
964
+ if (WHITESPACE_RE2.test(css[pos])) {
951
965
  result.push(css[pos]);
952
966
  pos++;
953
967
  continue;
@@ -996,7 +1010,7 @@ function consumeAtRule(css, pos, scopeId) {
996
1010
  return { text: css.slice(start), end: css.length };
997
1011
  }
998
1012
  const header = css.slice(start, headerEnd);
999
- const isKeyframes = /^@(?:-\w+-)?keyframes\b/.test(header.trim());
1013
+ const isKeyframes = KEYFRAMES_RE.test(header.trim());
1000
1014
  depth = 1;
1001
1015
  let bodyStart = headerEnd + 1;
1002
1016
  let bodyEnd = headerEnd + 1;
@@ -1039,8 +1053,7 @@ function consumeRuleSet(css, pos, scopeId) {
1039
1053
  function scopeSingleSelector(selector, scopeId) {
1040
1054
  if (!selector) return selector;
1041
1055
  const attr = `[${scopeId}]`;
1042
- const pseudoRe = /(?:::?[\w-]+(?:\([^)]*\))?)+$/;
1043
- const pseudoMatch = selector.match(pseudoRe);
1056
+ const pseudoMatch = selector.match(PSEUDO_SELECTOR_RE);
1044
1057
  if (pseudoMatch) {
1045
1058
  const beforePseudo = selector.slice(0, pseudoMatch.index);
1046
1059
  return `${beforePseudo}${attr}${pseudoMatch[0]}`;
@@ -1049,6 +1062,7 @@ function scopeSingleSelector(selector, scopeId) {
1049
1062
  }
1050
1063
 
1051
1064
  // src/a11y.ts
1065
+ var HEADING_LEVEL_RE = /^h([1-6])$/;
1052
1066
  var VALID_ARIA_ROLES = /* @__PURE__ */ new Set([
1053
1067
  "alert",
1054
1068
  "alertdialog",
@@ -1218,7 +1232,7 @@ var rules = {
1218
1232
  }
1219
1233
  },
1220
1234
  "heading-order"(node, warnings, ctx) {
1221
- const match = node.tag.match(/^h([1-6])$/);
1235
+ const match = node.tag.match(HEADING_LEVEL_RE);
1222
1236
  if (!match) return;
1223
1237
  const level = parseInt(match[1], 10);
1224
1238
  if (ctx.lastHeadingLevel > 0 && level > ctx.lastHeadingLevel + 1) {
package/dist/index.d.cts CHANGED
@@ -14,6 +14,7 @@ interface SFCDescriptor {
14
14
  template: SFCBlock | null;
15
15
  script: SFCBlock | null;
16
16
  style: SFCBlock | null;
17
+ test: SFCBlock | null;
17
18
  filename: string;
18
19
  }
19
20
  /**
package/dist/index.d.ts CHANGED
@@ -14,6 +14,7 @@ interface SFCDescriptor {
14
14
  template: SFCBlock | null;
15
15
  script: SFCBlock | null;
16
16
  style: SFCBlock | null;
17
+ test: SFCBlock | null;
17
18
  filename: string;
18
19
  }
19
20
  /**
package/dist/index.js CHANGED
@@ -1,14 +1,17 @@
1
1
  // src/parser.ts
2
+ var BLOCK_RE = /<(template|script|style|test)([\s][^>]*)?\s*>/g;
3
+ var ATTR_RE = /([a-zA-Z_][\w-]*)\s*(?:=\s*(?:"([^"]*)"|'([^']*)'|(\S+)))?/g;
2
4
  function parse(source, filename = "anonymous.utopia") {
3
5
  const descriptor = {
4
6
  template: null,
5
7
  script: null,
6
8
  style: null,
9
+ test: null,
7
10
  filename
8
11
  };
9
- const blockRe = /<(template|script|style)([\s][^>]*)?\s*>/g;
12
+ BLOCK_RE.lastIndex = 0;
10
13
  let match;
11
- while ((match = blockRe.exec(source)) !== null) {
14
+ while ((match = BLOCK_RE.exec(source)) !== null) {
12
15
  const tagName = match[1];
13
16
  const attrString = match[2] || "";
14
17
  const openTagStart = match.index;
@@ -39,11 +42,10 @@ function parse(source, filename = "anonymous.utopia") {
39
42
  );
40
43
  }
41
44
  descriptor[tagName] = block;
42
- blockRe.lastIndex = blockEnd;
45
+ BLOCK_RE.lastIndex = blockEnd;
43
46
  }
44
47
  return descriptor;
45
48
  }
46
- var ATTR_RE = /([a-zA-Z_][\w-]*)\s*(?:=\s*(?:"([^"]*)"|'([^']*)'|(\S+)))?/g;
47
49
  function parseAttributes(raw) {
48
50
  const attrs = {};
49
51
  let m;
@@ -80,6 +82,16 @@ var SFCParseError = class extends Error {
80
82
  };
81
83
 
82
84
  // src/template-compiler.ts
85
+ var TAG_NAME_CHAR_RE = /[a-zA-Z0-9\-_]/;
86
+ var ATTR_NAME_CHAR_RE = /[a-zA-Z0-9\-_:@.]/;
87
+ var ATTR_VALUE_END_RE = /[\s/>]/;
88
+ var WHITESPACE_RE = /\s/;
89
+ var TAG_START_CHAR_RE = /[a-zA-Z]/;
90
+ var U_FOR_EXPR_RE = /^\s*(?:\(\s*(\w+)\s*(?:,\s*(\w+)\s*)?\)|(\w+))\s+in\s+(.+)$/;
91
+ var COMPONENT_TAG_RE = /^[A-Z][a-zA-Z0-9_$]*$/;
92
+ var BACKSLASH_RE = /\\/g;
93
+ var SINGLE_QUOTE_RE = /'/g;
94
+ var HTML_ENTITY_RE = /&(?:#(\d+)|#x([0-9a-fA-F]+)|(\w+));/g;
83
95
  function compileTemplate(template, options = {}) {
84
96
  const ast = parseTemplate(template);
85
97
  return generate(ast, options);
@@ -261,7 +273,7 @@ var TemplateParser = class {
261
273
  // ---- Low-level helpers --------------------------------------------------
262
274
  readTagName() {
263
275
  const start = this.pos;
264
- while (this.pos < this.source.length && /[a-zA-Z0-9\-_]/.test(this.source[this.pos])) {
276
+ while (this.pos < this.source.length && TAG_NAME_CHAR_RE.test(this.source[this.pos])) {
265
277
  this.pos++;
266
278
  }
267
279
  const name = this.source.slice(start, this.pos);
@@ -270,7 +282,7 @@ var TemplateParser = class {
270
282
  }
271
283
  readAttributeName() {
272
284
  const start = this.pos;
273
- while (this.pos < this.source.length && /[a-zA-Z0-9\-_:@.]/.test(this.source[this.pos])) {
285
+ while (this.pos < this.source.length && ATTR_NAME_CHAR_RE.test(this.source[this.pos])) {
274
286
  this.pos++;
275
287
  }
276
288
  return this.source.slice(start, this.pos);
@@ -287,13 +299,13 @@ var TemplateParser = class {
287
299
  return value;
288
300
  }
289
301
  const start = this.pos;
290
- while (this.pos < this.source.length && !/[\s/>]/.test(this.source[this.pos])) {
302
+ while (this.pos < this.source.length && !ATTR_VALUE_END_RE.test(this.source[this.pos])) {
291
303
  this.pos++;
292
304
  }
293
305
  return this.source.slice(start, this.pos);
294
306
  }
295
307
  skipWhitespace() {
296
- while (this.pos < this.source.length && /\s/.test(this.source[this.pos])) {
308
+ while (this.pos < this.source.length && WHITESPACE_RE.test(this.source[this.pos])) {
297
309
  this.pos++;
298
310
  }
299
311
  }
@@ -303,7 +315,7 @@ var TemplateParser = class {
303
315
  /** Returns true when the char after `<` looks like the start of a tag name. */
304
316
  peekTagStart() {
305
317
  const next = this.source[this.pos + 1];
306
- return next !== void 0 && /[a-zA-Z]/.test(next);
318
+ return next !== void 0 && TAG_START_CHAR_RE.test(next);
307
319
  }
308
320
  expect(str) {
309
321
  if (!this.lookingAt(str)) {
@@ -624,9 +636,7 @@ ${fnBody}
624
636
  const anchorVar = this.freshVar();
625
637
  this.helpers.add("createComment");
626
638
  this.emit(`const ${anchorVar} = createComment('u-for')`);
627
- const forMatch = dir.expression.match(
628
- /^\s*(?:\(\s*(\w+)\s*(?:,\s*(\w+)\s*)?\)|(\w+))\s+in\s+(.+)$/
629
- );
639
+ const forMatch = dir.expression.match(U_FOR_EXPR_RE);
630
640
  if (!forMatch) {
631
641
  throw new Error(`Invalid u-for expression: "${dir.expression}"`);
632
642
  }
@@ -829,10 +839,10 @@ ${fnBody}
829
839
  }
830
840
  };
831
841
  function isComponentTag(tag) {
832
- return /^[A-Z][a-zA-Z0-9_$]*$/.test(tag);
842
+ return COMPONENT_TAG_RE.test(tag);
833
843
  }
834
844
  function escapeStr(s) {
835
- return s.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
845
+ return s.replace(BACKSLASH_RE, "\\\\").replace(SINGLE_QUOTE_RE, "\\'");
836
846
  }
837
847
  var ENTITY_MAP = {
838
848
  "&amp;": "&",
@@ -861,7 +871,8 @@ var ENTITY_MAP = {
861
871
  "&divide;": "\xF7"
862
872
  };
863
873
  function decodeEntities(text) {
864
- return text.replace(/&(?:#(\d+)|#x([0-9a-fA-F]+)|(\w+));/g, (match, dec, hex, named) => {
874
+ HTML_ENTITY_RE.lastIndex = 0;
875
+ return text.replace(HTML_ENTITY_RE, (match, dec, hex, named) => {
865
876
  if (dec) {
866
877
  const code = parseInt(dec, 10);
867
878
  if (code >= 0 && code <= 1114111) {
@@ -894,6 +905,9 @@ function generate(ast, options) {
894
905
  }
895
906
 
896
907
  // src/style-compiler.ts
908
+ var WHITESPACE_RE2 = /\s/;
909
+ var KEYFRAMES_RE = /^@(?:-\w+-)?keyframes\b/;
910
+ var PSEUDO_SELECTOR_RE = /(?:::?[\w-]+(?:\([^)]*\))?)+$/;
897
911
  function compileStyle(options) {
898
912
  const { source, filename, scoped, scopeId: overrideScopeId } = options;
899
913
  if (!scoped) {
@@ -914,7 +928,7 @@ function scopeSelectors(css, scopeId) {
914
928
  const result = [];
915
929
  let pos = 0;
916
930
  while (pos < css.length) {
917
- if (/\s/.test(css[pos])) {
931
+ if (WHITESPACE_RE2.test(css[pos])) {
918
932
  result.push(css[pos]);
919
933
  pos++;
920
934
  continue;
@@ -963,7 +977,7 @@ function consumeAtRule(css, pos, scopeId) {
963
977
  return { text: css.slice(start), end: css.length };
964
978
  }
965
979
  const header = css.slice(start, headerEnd);
966
- const isKeyframes = /^@(?:-\w+-)?keyframes\b/.test(header.trim());
980
+ const isKeyframes = KEYFRAMES_RE.test(header.trim());
967
981
  depth = 1;
968
982
  let bodyStart = headerEnd + 1;
969
983
  let bodyEnd = headerEnd + 1;
@@ -1006,8 +1020,7 @@ function consumeRuleSet(css, pos, scopeId) {
1006
1020
  function scopeSingleSelector(selector, scopeId) {
1007
1021
  if (!selector) return selector;
1008
1022
  const attr = `[${scopeId}]`;
1009
- const pseudoRe = /(?:::?[\w-]+(?:\([^)]*\))?)+$/;
1010
- const pseudoMatch = selector.match(pseudoRe);
1023
+ const pseudoMatch = selector.match(PSEUDO_SELECTOR_RE);
1011
1024
  if (pseudoMatch) {
1012
1025
  const beforePseudo = selector.slice(0, pseudoMatch.index);
1013
1026
  return `${beforePseudo}${attr}${pseudoMatch[0]}`;
@@ -1016,6 +1029,7 @@ function scopeSingleSelector(selector, scopeId) {
1016
1029
  }
1017
1030
 
1018
1031
  // src/a11y.ts
1032
+ var HEADING_LEVEL_RE = /^h([1-6])$/;
1019
1033
  var VALID_ARIA_ROLES = /* @__PURE__ */ new Set([
1020
1034
  "alert",
1021
1035
  "alertdialog",
@@ -1185,7 +1199,7 @@ var rules = {
1185
1199
  }
1186
1200
  },
1187
1201
  "heading-order"(node, warnings, ctx) {
1188
- const match = node.tag.match(/^h([1-6])$/);
1202
+ const match = node.tag.match(HEADING_LEVEL_RE);
1189
1203
  if (!match) return;
1190
1204
  const level = parseInt(match[1], 10);
1191
1205
  if (ctx.lastHeadingLevel > 0 && level > ctx.lastHeadingLevel + 1) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@matthesketh/utopia-compiler",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "Compiler for .utopia single-file components",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -39,7 +39,7 @@
39
39
  "dist"
40
40
  ],
41
41
  "dependencies": {
42
- "@matthesketh/utopia-core": "0.4.0"
42
+ "@matthesketh/utopia-core": "0.5.0"
43
43
  },
44
44
  "scripts": {
45
45
  "build": "tsup src/index.ts --format esm,cjs --dts",