@pi-r/chrome 0.3.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/index.js CHANGED
@@ -12,6 +12,8 @@ const transform_1 = require("@e-mc/document/transform");
12
12
  const Document = require('@e-mc/document');
13
13
  const CACHE_DBRESULT = new Map();
14
14
  const CACHE_LOCALFILE = new Map();
15
+ const CACHE_QUERY = new Map();
16
+ const CACHE_SELECTOR = new Map();
15
17
  const CACHE_DATASET = {};
16
18
  const CACHE_ATRULES = {};
17
19
  let CACHE_TOTAL = 0;
@@ -19,11 +21,11 @@ const REGEXP_SRCSETSIZE = /~\s*([\d.]+)\s*([wx])/i;
19
21
  const REGEXP_TEMPLATECONDITIONAL = /(\n\s+)?\{\{if\s+((?:[^}]|}(?!}))+?)\}\}(\s*)([\S\s]*?)(?:\s*\{\{else(?:\s+if\s+((?:[^}]|}(?!}))+?))?\}\}(\s*)([\S\s]*?)\s*)?\s*\{\{end\}\}/g;
20
22
  const REGEXP_TEMPLATECOMPARISON = /\s*\(\s*(?:(eq|ne|lt|le|gt|ge)\s+)?("[^"]+"|'[^']+'|[^\s)]+)(?:\s+("[^"]+"|'[^']+'|[^\s)]+))?\s*\)/g;
21
23
  const REGEXP_TEMPLATEMATCH = new RegExp(`^(?:(and|or|not)\\s+)?((?:${REGEXP_TEMPLATECOMPARISON.source})+)|(!|not\\s+)?([^\\s][\\S\\s]*)$`);
22
- const REGEXP_IMPORTMODULE = /(?:(?:^|[\n;]+)\s*import\s+(?:("(?:[^"]|(?<=\\)")+"|'(?:[^']|(?<=\\)')+')|.+?from\s+("(?:[^"]|(?<=\\)")+"|'(?:[^']|(?<=\\)')+')(?:[ \t]+assert[ \t]+\{((?:"(?:[^"]|(?<=\\)")+"|'(?:[^']|(?<=\\)')+'|`(?:[^`]|(?<=\\)`)+`|{[^{]+{|{[^}]+}|}[^}]+}|(?![{}])[^"'`])+)\})?)|\bimport\(\s*("(?:[^"]|(?<=\\)")+"|'(?:[^']|(?<=\\)')+')\s*(?:,\s*\{((?:"(?:[^"]|(?<=\\)")+"|'(?:[^']|(?<=\\)')+'|`(?:[^`]|(?<=\\)`)+`|{[^{]+{|{[^}]+}|}[^}]+}|(?![{}])[^"'`])+)\}\s*)?\))/g;
24
+ const REGEXP_IMPORTMODULE = /(?:(?:^|[\n;]+)\s*import\s+(?:("(?:[^"]|(?<=\\)")+"|'(?:[^']|(?<=\\)')+')|.+?from\s+("(?:[^"]|(?<=\\)")+"|'(?:[^']|(?<=\\)')+')(?:[ \t]+assert[ \t]+\{((?:"(?:[^"]|(?<=\\)")+"|'(?:[^']|(?<=\\)')+'|`(?:[^`]|(?<=\\)`)+`|{[^{]*{|{[^}]*}|}[^}]*}|(?![{}])[^"'`])+)\})?)|\bimport\(\s*("(?:[^"]|(?<=\\)")+"|'(?:[^']|(?<=\\)')+')\s*(?:,\s*\{((?:"(?:[^"]|(?<=\\)")+"|'(?:[^']|(?<=\\)')+'|`(?:[^`]|(?<=\\)`)+`|{[^{]*{|{[^}]*}|}[^}]*}|(?![{}])[^"'`])+)\}\s*)?\))/g;
23
25
  const REGEXP_BLOCKEXCLUDE = new RegExp(`(\\s*)<!--\\s*exclude\\s*:\\s*(?:start|end)\\s*-->` + dom_1.DomWriter.PATTERN_TRAILINGSPACE, 'gi');
24
26
  const [PATTERN_STRING_SOME, PATTERN_STRING_MANY] = (function () {
25
- const pattern = '(?:\\s|' + dom_1.DomWriter.PATTERN_COMMENT + '|`[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}`)';
26
- return [pattern + '*', pattern + '+'];
27
+ const grouping = '(?:\\s|' + dom_1.DomWriter.PATTERN_COMMENT + '|`[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}`)';
28
+ return [grouping + '*', grouping + '+'];
27
29
  })();
28
30
  const REGEXP_COMMENT = new RegExp(dom_1.DomWriter.PATTERN_COMMENT, 'g');
29
31
  const REGEXP_CONTENT = new RegExp(`\\bcontent${PATTERN_STRING_SOME}:${PATTERN_STRING_SOME}(?:"[^"]*"|'[^']*')`, 'gi');
@@ -32,16 +34,24 @@ const REGEXP_VARIABLES = new RegExp(`(\\s*)(--[^\\s:]*)${PATTERN_STRING_SOME}:[^
32
34
  const REGEXP_FONTFACE = new RegExp(`(\\s*)@font-face${PATTERN_STRING_SOME}{([^}]+)}` + dom_1.DomWriter.PATTERN_TRAILINGSPACE, 'gi');
33
35
  const REGEXP_FONTFAMILY = new RegExp(`font-family${PATTERN_STRING_SOME}:([^;}]+)`, 'i');
34
36
  const REGEXP_KEYFRAMES = new RegExp(`(\\s*)@keyframes${PATTERN_STRING_MANY}([^{]+){`, 'gi');
37
+ const REGEXP_PROPERTY = new RegExp(`(\\s*)@property${PATTERN_STRING_MANY}(--[^\\s]+)${PATTERN_STRING_SOME}{`, 'gi');
35
38
  const REGEXP_NTHCHILD = new RegExp(`\\(${PATTERN_STRING_SOME}([+-])?(?:(\\d*)[nN]|(0))${PATTERN_STRING_SOME}([+-])?${PATTERN_STRING_SOME}(\\d*)${PATTERN_STRING_SOME}\\)`, 'g');
36
39
  const REGEXP_VARIABLES_UNSAFE = new RegExp(`(\\s*)(--[^\\s:]*)\\s*:[^;}]*([;}])` + dom_1.DomWriter.PATTERN_TRAILINGSPACE, 'g');
37
40
  const REGEXP_FONTFACE_UNSAFE = new RegExp(`(\\s*)@font-face\\s*{([^}]+)}` + dom_1.DomWriter.PATTERN_TRAILINGSPACE, 'gi');
38
41
  const REGEXP_FONTFAMILY_UNSAFE = /font-family\s*:([^;}]+)/i;
39
42
  const REGEXP_KEYFRAMES_UNSAFE = /(\s*)@keyframes\s+([^{]+){/gi;
43
+ const REGEXP_PROPERTY_UNSAFE = /(\s*)@property\s+(--[^\s]+)\s*{/gi;
40
44
  const REGEXP_NTHCHILD_UNSAFE = /\(\s*([+-])?(?:(\d*)[nN]|(0))\s*([+-])?\s*(\d*)\s*\)/g;
41
45
  const REGEXP_OBJECT_NAME = /([^[.\s]+)((?:\s*\[[^\]]+\]\s*)+)?\s*\.?\s*/g;
42
46
  const REGEXP_OBJECT_SUBSCRIPT = /\[\s*(["'])?(.+?)\1\s*\]/g;
43
47
  const REGEXP_TEMPLATE_LEADING = /\s*\{\{- \s*((?:[^}]|}(?!}))+?)\}\}/g;
44
48
  const REGEXP_TEMPLATE_TRAILING = /\{\{((?:[^}]|}(?!}))+?)\s*? -\}\}\s*/g;
49
+ const REGEXP_URL = /(?:\b[Uu][Rr][Ll]\(|@import\s+(["'])|\b([Hh][Rr][Ee][Ff])\s*=)/g;
50
+ const REGEXP_URL_JS = /\bimport\(/g;
51
+ const REGEXP_URL_SVG = /(?:\b[Uu][Rr][Ll]\(|@import\s+(["'])|\b([Hh][Rr][Ee][Ff])\s*=)/g;
52
+ const REGEXP_IMAGESET = /(?:-webkit-)?image-set\(/gi;
53
+ const REGEXP_IMAGESET_URL = /(?:^|[^(])\s*("(\s*[^)][^"]*)"|'(\s*[^)][^"]*)')\s*(?:,|$|[^)][^,]*,?)/g;
54
+ const REGEXP_REPLACECSS = /^@import\s+(?:url\((?:[^)]|(?<=\\)\))+\)|"(?:[^"]|(?<=\\)")+"|'(?:[^']|(?<=\\)')+')(\s*layer(?:\(([^)]*)\)|\b))?(?:\s*supports\(((?:\([^(]+\(|\([^)]+\)|\)[^)]+\)|(?:(?!\))[^(])+|(?:(?!\()[^)])+)+)\))?(.*?);?$/;
45
55
  function removeNamespace(name, source, newline, comments) {
46
56
  if (source.indexOf('-' + name) !== -1) {
47
57
  const [a, b, c, d, e] = CACHE_DATASET[name] || (CACHE_DATASET[name] = [
@@ -106,15 +116,18 @@ function trimQuote(value) {
106
116
  const last = value[value.length - 1];
107
117
  return first === last && (first === '"' || first === "'") ? value.substring(1, value.length - 1).trim() : value;
108
118
  }
109
- function findClosingIndex(source, lastIndex) {
110
- const pattern = /[{}]/g;
119
+ function findClosingIndex(source, lastIndex, char) {
120
+ const pattern = char === '{' ? /[{}]/g : /[()]/g;
111
121
  pattern.lastIndex = lastIndex;
112
122
  let opened = 1, closed = 0, endIndex = -1, trailing = '', match;
113
123
  while (match = pattern.exec(source)) {
114
- if (match[0] === '{') {
124
+ if (match[0] === char) {
115
125
  ++opened;
116
126
  }
117
127
  else if (++closed === opened) {
128
+ if (char === '(') {
129
+ return match.index;
130
+ }
118
131
  endIndex = match.index + 1;
119
132
  let ch;
120
133
  while (isSpace(ch = source[endIndex])) {
@@ -261,22 +274,21 @@ function isTruthy(data, attr, falsey) {
261
274
  }
262
275
  function hasSameOrigin(value, ...other) {
263
276
  for (let url of other) {
264
- if (!url) {
265
- continue;
266
- }
267
- try {
268
- if (typeof value === 'string') {
269
- value = new URL(value);
270
- }
271
- if (typeof url === 'string') {
272
- url = new URL(url);
277
+ if (url) {
278
+ try {
279
+ if (typeof value === 'string') {
280
+ value = new URL(value);
281
+ }
282
+ if (typeof url === 'string') {
283
+ url = new URL(url);
284
+ }
285
+ if (value.origin === url.origin) {
286
+ return true;
287
+ }
273
288
  }
274
- if (value.origin === url.origin) {
275
- return true;
289
+ catch {
276
290
  }
277
291
  }
278
- catch {
279
- }
280
292
  }
281
293
  return false;
282
294
  }
@@ -316,14 +328,10 @@ function getBaseURLs(asset, baseUrl) {
316
328
  }
317
329
  return result;
318
330
  }
319
- function createHash(host, file, bufferMap) {
320
- const hash = file.hash;
321
- if (!hash) {
322
- return;
323
- }
331
+ function createHash(host, file, hash, bufferMap) {
324
332
  const { filename, localUri } = file;
325
333
  try {
326
- const buffer = localUri && bufferMap[localUri] || getBuffer(file);
334
+ const buffer = localUri && bufferMap[localUri] || getBuffer(file, localUri);
327
335
  if (buffer) {
328
336
  const [algorithm, length] = (0, util_1.getHashData)(hash);
329
337
  let value;
@@ -377,8 +385,29 @@ function transformURL(host, parent, type, source, importFile) {
377
385
  const baseHref = getBaseURLs(importFile || parent, this.baseUrl);
378
386
  const isHtml = type === 'html';
379
387
  const isJs = type === 'js';
380
- const pattern = isJs ? /\bimport\(/g : type === 'svg' ? /(?:\b[Uu][Rr][Ll]\(|@import\s+(["'])|\b([Hh][Rr][Ee][Ff])\s*=)/g : /\b[Uu][Rr][Ll]\(|@import\s+(["'])/g;
388
+ const pattern = isJs ? REGEXP_URL_JS : type === 'svg' ? REGEXP_URL_SVG : REGEXP_URL;
389
+ pattern.lastIndex = 0;
390
+ REGEXP_IMAGESET.lastIndex = 0;
381
391
  let length = source.length, modified, match;
392
+ while (match = REGEXP_IMAGESET.exec(source)) {
393
+ const startIndex = match.index + match[0].length;
394
+ const endIndex = findClosingIndex(source, startIndex, '(');
395
+ if (endIndex === -1) {
396
+ continue;
397
+ }
398
+ REGEXP_IMAGESET_URL.lastIndex = 0;
399
+ const original = source.substring(startIndex, endIndex);
400
+ let content = original, url;
401
+ while (url = REGEXP_IMAGESET_URL.exec(original)) {
402
+ const quote = url[2] ? '"' : "'";
403
+ content = content.replace(url[1], `url(${quote + (url[2] || url[3]).trim() + quote})`);
404
+ modified = true;
405
+ }
406
+ if (modified) {
407
+ source = spliceString(source, startIndex, REGEXP_IMAGESET.lastIndex = endIndex, content);
408
+ modified = false;
409
+ }
410
+ }
382
411
  while (match = pattern.exec(source)) {
383
412
  const isText = !!match[1];
384
413
  const isHref = !!match[2];
@@ -735,7 +764,7 @@ function transformJs(host, file, output) {
735
764
  });
736
765
  }
737
766
  else {
738
- this.writeFail(['Unable to create element entry point', path.basename(chunkUri)], (0, types_1.errorMessage)('js', 'Element not found', chunkUri), 4 /* LOG_TYPE.PROCESS */);
767
+ this.writeFail(['Unable to create script entry point', path.basename(chunkUri)], (0, types_1.errorMessage)('js', 'Element not found', chunkUri), 4 /* LOG_TYPE.PROCESS */);
739
768
  }
740
769
  }
741
770
  }
@@ -766,11 +795,16 @@ function replaceCss(file) {
766
795
  }
767
796
  }
768
797
  function replaceHtml(source, { productionRelease, contentMap }, cssMap) {
769
- for (const data of [cssMap, contentMap]) {
798
+ const finalize = (value, data) => {
770
799
  for (const id in data) {
771
- source = source.replace(new RegExp((0, types_1.escapePattern)(id), 'g'), data[id]);
800
+ value = value.replace(new RegExp((0, types_1.escapePattern)(id), 'g'), data[id]);
772
801
  }
802
+ return value;
803
+ };
804
+ if (cssMap) {
805
+ source = finalize(source, cssMap);
773
806
  }
807
+ source = finalize(source, contentMap);
774
808
  return productionRelease ? this.removeServerRoot(source) : source;
775
809
  }
776
810
  function isWatched(file) {
@@ -839,10 +873,6 @@ async function checkData(item, name, result, validate) {
839
873
  }
840
874
  }
841
875
  }
842
- function getBuffer(item) {
843
- let localUri;
844
- return item.sourceUTF8 || item.buffer || ((localUri = item.localUri) && isPath(localUri) ? fs.readFileSync(localUri, item.encoding) : null);
845
- }
846
876
  const getNewlineString = dom_1.DomWriter.getNewlineString.bind(dom_1.DomWriter);
847
877
  const isSpace = dom_1.DomWriter.isSpace.bind(dom_1.DomWriter);
848
878
  const replaceMatch = dom_1.DomWriter.replaceMatch.bind(dom_1.DomWriter);
@@ -853,6 +883,7 @@ const asString = Document.asString.bind(Document);
853
883
  const resolvePath = Document.resolvePath.bind(Document);
854
884
  const toPosix = Document.toPosix.bind(Document);
855
885
  const isDir = Document.isDir.bind(Document);
886
+ const getBuffer = (item, localUri) => item.sourceUTF8 || item.buffer || ((localUri || (localUri = item.localUri)) && isPath(localUri) ? fs.readFileSync(localUri, item.encoding) : null);
856
887
  const hasSourceMap = (metadata, value) => (0, types_1.isObject)(metadata) && metadata.__sourcemap__ === value;
857
888
  const isBundled = (value, main) => typeof value === 'number' && (value > 0 || main && value === 0);
858
889
  const isFont = (value) => value === 'truetype' || value === 'opentype' || value === 'woff' || value === 'woff2';
@@ -882,6 +913,7 @@ class ChromeDocument extends Document {
882
913
  useOriginalHtmlPage: undefined,
883
914
  useUnsafeHtmlReplace: undefined,
884
915
  useUnsafeCssReplace: undefined,
916
+ useSessionCache: undefined,
885
917
  stripCommentsAndCDATA: undefined,
886
918
  normalizeHtmlOutput: undefined,
887
919
  escapeReservedCharacters: undefined,
@@ -904,7 +936,8 @@ class ChromeDocument extends Document {
904
936
  this._elements = null;
905
937
  this._idMap = Object.create(null);
906
938
  this._editing = [];
907
- this._selectorMap = {};
939
+ this._queryMap = new Map();
940
+ this._selectorMap = new Map();
908
941
  }
909
942
  static async purgeMemory(percent = 1, limit = 0, parent) {
910
943
  if (typeof limit === 'boolean') {
@@ -912,14 +945,14 @@ class ChromeDocument extends Document {
912
945
  limit = 0;
913
946
  }
914
947
  let result = 0;
915
- if (limit === 0 || CACHE_TOTAL >= limit) {
948
+ if ((limit === 0 || CACHE_TOTAL >= limit) && percent > 0) {
916
949
  if (percent >= 1) {
917
950
  result += CACHE_DBRESULT.size + CACHE_LOCALFILE.size;
918
951
  CACHE_DBRESULT.clear();
919
952
  CACHE_LOCALFILE.clear();
920
953
  CACHE_TOTAL = 0;
921
954
  }
922
- else if (percent > 0) {
955
+ else {
923
956
  const stored = [];
924
957
  for (const map of [CACHE_DBRESULT, CACHE_LOCALFILE]) {
925
958
  for (const [key, value] of map) {
@@ -934,6 +967,8 @@ class ChromeDocument extends Document {
934
967
  }
935
968
  CACHE_TOTAL = stored.length - result;
936
969
  }
970
+ CACHE_QUERY.clear();
971
+ CACHE_SELECTOR.clear();
937
972
  }
938
973
  return result + (parent && result > 0 ? await super.purgeMemory(typeof parent === 'number' && parent > 0 ? parent : percent, limit, true) : 0);
939
974
  }
@@ -1027,16 +1062,15 @@ class ChromeDocument extends Document {
1027
1062
  const items = new Set((asset.watch.assets || []));
1028
1063
  (function recurse(children) {
1029
1064
  for (const item of children) {
1030
- if (items.has(item)) {
1031
- continue;
1032
- }
1033
- if (isWatched.call(instance, item)) {
1034
- item.flags |= 8 /* ASSET_FLAG.WATCH */;
1035
- }
1036
- items.add(item);
1037
- const next = related.get(item);
1038
- if (next) {
1039
- recurse(next);
1065
+ if (!items.has(item)) {
1066
+ if (isWatched.call(instance, item)) {
1067
+ item.flags |= 8 /* ASSET_FLAG.WATCH */;
1068
+ }
1069
+ items.add(item);
1070
+ const next = related.get(item);
1071
+ if (next) {
1072
+ recurse(next);
1073
+ }
1040
1074
  }
1041
1075
  }
1042
1076
  })(includes);
@@ -1052,20 +1086,18 @@ class ChromeDocument extends Document {
1052
1086
  if (this.canWrite(productionRelease)) {
1053
1087
  const serverRoot = ChromeDocument.INTERNAL_SERVERROOT;
1054
1088
  const srcDir = path.join(this.baseDirectory, serverRoot);
1055
- if (!isDir(srcDir)) {
1056
- return;
1057
- }
1058
- const result = await Document.copyDir(srcDir, productionRelease, this.incremental !== 'staging');
1059
- const failed = result.failed.map(value => this.removeCwd(value));
1060
- for (const value of this.files) {
1061
- if (!value.startsWith(serverRoot)) {
1062
- continue;
1063
- }
1064
- if (!failed.includes(value)) {
1065
- this.delete(value);
1066
- }
1067
- else {
1068
- instance.writeFail(["Unable to move file" /* ERR_MESSAGE.MOVE_FILE */, instance.moduleName], (0, types_1.errorMessage)('production', path.join(this.baseDirectory, value)), { fatal: true });
1089
+ if (isDir(srcDir)) {
1090
+ const result = await Document.copyDir(srcDir, productionRelease, this.incremental !== 'staging');
1091
+ const failed = result.failed.map(value => this.removeCwd(value));
1092
+ for (const value of this.files) {
1093
+ if (value.startsWith(serverRoot)) {
1094
+ if (!failed.includes(value)) {
1095
+ this.delete(value);
1096
+ }
1097
+ else {
1098
+ instance.writeFail(["Unable to move file" /* ERR_MESSAGE.MOVE_FILE */, instance.moduleName], (0, types_1.errorMessage)('production', path.join(this.baseDirectory, value)), { fatal: true });
1099
+ }
1100
+ }
1069
1101
  }
1070
1102
  }
1071
1103
  }
@@ -1602,7 +1634,7 @@ class ChromeDocument extends Document {
1602
1634
  replaceContent(source, statement, mimeType) {
1603
1635
  switch (mimeType) {
1604
1636
  case 'text/css': {
1605
- const match = /^@import\s+(?:url\((?:[^)]|(?<=\\)\))+\)|"(?:[^"]|(?<=\\)")+"|'(?:[^']|(?<=\\)')+')(\s*layer(?:\(([^)]*)\)|\b))?(?:\s*supports\(((?:\([^(]+\(|\([^)]+\)|\)[^)]+\)|(?:(?!\))[^(])+|(?:(?!\()[^)])+)+)\))?(.*?);?$/.exec((typeof statement === 'string' ? statement : statement[0]).trim());
1637
+ const match = REGEXP_REPLACECSS.exec((typeof statement === 'string' ? statement : statement[0]).trim());
1606
1638
  if (match) {
1607
1639
  const layer = match[1];
1608
1640
  const supports = match[3]?.trim();
@@ -1937,12 +1969,12 @@ class ChromeDocument extends Document {
1937
1969
  }
1938
1970
  }
1939
1971
  if (productionRelease) {
1940
- const { imported, inlineUrl: inlineUrl, inlineUrlCloud: inlineUrlCloud } = item;
1972
+ const { inlineUrl, inlineUrlCloud, imported, hash } = item;
1941
1973
  if (inlineUrl || inlineUrlCloud) {
1942
- const filename = createHash(host, item, bufferMap);
1974
+ const filename = hash && createHash(host, item, hash, bufferMap);
1943
1975
  if (filename) {
1944
- for (const hash of this.assets) {
1945
- const url = inlineUrl ? hash.inlineUrlMap?.[inlineUrl] : hash.inlineUrlCloudMap?.[inlineUrlCloud];
1976
+ for (const asset of this.assets) {
1977
+ const url = inlineUrl ? asset.inlineUrlMap?.[inlineUrl] : asset.inlineUrlCloudMap?.[inlineUrlCloud];
1946
1978
  if (url) {
1947
1979
  const search = url.indexOf('?');
1948
1980
  const index = (search !== -1 ? url.substring(0, search) : url).lastIndexOf('/');
@@ -1955,17 +1987,17 @@ class ChromeDocument extends Document {
1955
1987
  result += url.substring(search);
1956
1988
  }
1957
1989
  if (inlineUrl) {
1958
- hash.inlineUrlMap[inlineUrl] = result;
1990
+ asset.inlineUrlMap[inlineUrl] = result;
1959
1991
  }
1960
1992
  else {
1961
- hash.inlineUrlCloudMap[inlineUrlCloud] = result;
1993
+ asset.inlineUrlCloudMap[inlineUrlCloud] = result;
1962
1994
  }
1963
1995
  }
1964
1996
  }
1965
1997
  }
1966
1998
  hashed.add(item);
1967
1999
  }
1968
- else if ((!item.editing || imported) && createHash(host, item, bufferMap)) {
2000
+ else if (hash && (!item.editing || imported) && createHash(host, item, hash, bufferMap)) {
1969
2001
  hashed.add(item);
1970
2002
  }
1971
2003
  }
@@ -2021,7 +2053,7 @@ class ChromeDocument extends Document {
2021
2053
  }
2022
2054
  }
2023
2055
  item.sourceUTF8 = source;
2024
- if (productionRelease && !hashed.has(item) && createHash(host, item, bufferMap)) {
2056
+ if (productionRelease && item.hash && !hashed.has(item) && createHash(host, item, item.hash, bufferMap)) {
2025
2057
  hashed.add(item);
2026
2058
  }
2027
2059
  }
@@ -2438,13 +2470,13 @@ class ChromeDocument extends Document {
2438
2470
  const length = batch.length;
2439
2471
  const current = batch[0];
2440
2472
  const type = current.source;
2441
- let result = new Array(length);
2473
+ let result = new Array(length), errors;
2442
2474
  switch (type) {
2443
2475
  case 'cloud':
2444
2476
  result = await cloud.getDatabaseBatchRows(batch, true, sessionKey).catch(err => {
2445
2477
  this.abort(type);
2446
2478
  if (err instanceof Error) {
2447
- cloud.addLog(this.aborted ? types_1.STATUS_TYPE.ERROR : types_1.STATUS_TYPE.WARN, err);
2479
+ cloud.addLog(this.aborted ? types_1.STATUS_TYPE.ERROR : types_1.STATUS_TYPE.WARN, errors = err);
2448
2480
  }
2449
2481
  return [];
2450
2482
  });
@@ -2610,9 +2642,7 @@ class ChromeDocument extends Document {
2610
2642
  }
2611
2643
  if (!getData) {
2612
2644
  const { settings, encoding, persist = true } = current;
2613
- if (!target) {
2614
- target = settings && this.settings.export?.[settings] || pathname;
2615
- }
2645
+ target || (target = settings && this.settings.export?.[settings] || pathname);
2616
2646
  if (typeof target === 'function') {
2617
2647
  getData = target;
2618
2648
  hint = target.name;
@@ -2700,7 +2730,7 @@ class ChromeDocument extends Document {
2700
2730
  search.schema = loadLocalFile.call(this, current, pathname, cacheData, cmd.encoding);
2701
2731
  }
2702
2732
  catch (err) {
2703
- this.writeFail(['Unable to parse document', path.basename(pathname)], err, { startTime });
2733
+ this.writeFail(['Unable to parse document', path.basename(pathname)], err instanceof Error ? errors = err : err, { startTime });
2704
2734
  search.schema = undefined;
2705
2735
  }
2706
2736
  }
@@ -2786,7 +2816,7 @@ class ChromeDocument extends Document {
2786
2816
  this.abort(type);
2787
2817
  }
2788
2818
  if (err) {
2789
- this.writeFail(["Unable to execute query" /* ERR_DB.EXEC_QUERY */, message || "Unknown" /* ERR_MESSAGE.UNKNOWN */], err, { type: 65536 /* LOG_TYPE.DB */, startTime });
2819
+ this.writeFail(["Unable to execute query" /* ERR_DB.EXEC_QUERY */, message || "Unknown" /* ERR_MESSAGE.UNKNOWN */], err instanceof Error ? errors = err : err, { type: 65536 /* LOG_TYPE.DB */, startTime });
2790
2820
  }
2791
2821
  return this.aborted;
2792
2822
  };
@@ -2839,7 +2869,7 @@ class ChromeDocument extends Document {
2839
2869
  if (items.length && !item.transactionFail) {
2840
2870
  const { element, preRender } = item;
2841
2871
  const domElement = new dom_1.HtmlElement(moduleName, element);
2842
- let errors, value = '';
2872
+ let value = '';
2843
2873
  const checkValue = async (name) => {
2844
2874
  const data = await checkData.call(this, item, name, value, row => typeof row === 'string');
2845
2875
  if (data) {
@@ -2928,7 +2958,7 @@ class ChromeDocument extends Document {
2928
2958
  else {
2929
2959
  const pattern = /\$\{\s*([^\s}]+)\s*\}/g;
2930
2960
  while (match = pattern.exec(segment)) {
2931
- segment = replaceMatch(match, segment, match[1] === '__index__' && !(0, types_1.isObject)(row) ? (j + 1).toString() : valueAsString(getObjectValue(row, match[1]), ', '), pattern);
2961
+ segment = replaceMatch(match, segment, match[1] === "__index__" /* PROPERTIES.INDEX */ && !(0, types_1.isObject)(row) ? (j + 1).toString() : valueAsString(getObjectValue(row, match[1]), ', '), pattern);
2932
2962
  }
2933
2963
  }
2934
2964
  value += trimTemplate(segment);
@@ -3135,7 +3165,10 @@ class ChromeDocument extends Document {
3135
3165
  }
3136
3166
  else {
3137
3167
  if (!empty && item.type !== 'display' || item.transactionFail) {
3138
- const emptyResponse = (value, service = item.source) => errorDataSource(service + ' -> Empty', value || "Unknown" /* ERR_MESSAGE.UNKNOWN */);
3168
+ const emptyResponse = (value, service = item.source) => {
3169
+ value || (value = "Unknown" /* ERR_MESSAGE.UNKNOWN */);
3170
+ return errors instanceof Error ? (0, types_1.errorMessage)(service, value, errors.message) : errorDataSource(service + ' -> Empty', value);
3171
+ };
3139
3172
  switch (item.source) {
3140
3173
  case 'cloud': {
3141
3174
  const { service, table, id, query } = item;
@@ -3183,8 +3216,8 @@ class ChromeDocument extends Document {
3183
3216
  if (isIgnored(item, true)) {
3184
3217
  continue;
3185
3218
  }
3186
- if (!hashed.has(item)) {
3187
- createHash(host, item, bufferMap);
3219
+ if (item.hash && !hashed.has(item)) {
3220
+ createHash(host, item, item.hash, bufferMap);
3188
3221
  }
3189
3222
  switch (item.element?.tagName.toLowerCase()) {
3190
3223
  case 'style':
@@ -3225,12 +3258,13 @@ class ChromeDocument extends Document {
3225
3258
  }
3226
3259
  }
3227
3260
  }
3228
- removeUnusedStyles(source, { useUnsafeCssReplace, usedVariables, usedFontFace, usedKeyframes, unusedStyles, unusedMedia, unusedContainer, unusedSupports } = this.config) {
3261
+ removeUnusedStyles(source, { useUnsafeCssReplace, usedVariables, usedFontFace, usedKeyframes, unusedStyles, unusedMedia, unusedContainer, unusedSupports, useSessionCache } = this.config) {
3229
3262
  var _a;
3230
3263
  if (!usedVariables && !usedFontFace && !usedKeyframes && !unusedStyles && !unusedMedia && !unusedContainer && !unusedSupports) {
3231
3264
  return;
3232
3265
  }
3233
- const selectorMap = this._selectorMap;
3266
+ const queryMap = useSessionCache ? CACHE_QUERY : this._queryMap;
3267
+ const selectorMap = useSessionCache ? CACHE_SELECTOR : this._selectorMap;
3234
3268
  const replaceMap = [];
3235
3269
  const getStringSome = () => useUnsafeCssReplace ? '\\s*' : PATTERN_STRING_SOME;
3236
3270
  const getStringMany = () => useUnsafeCssReplace ? '\\s+' : PATTERN_STRING_MANY;
@@ -3327,17 +3361,17 @@ class ChromeDocument extends Document {
3327
3361
  return `[${capture[1]}]`;
3328
3362
  }
3329
3363
  });
3330
- for (let i = 0; i < items.length; ++i) {
3364
+ for (let i = 0, length = revised.length; i < length; ++i) {
3331
3365
  selector = selector.replace('`%' + i + '`', items[i]);
3332
3366
  }
3333
- return selector.replace(/[ ]+/g, getStringMany()) + getStringSome();
3367
+ return selector.replace(/ +/g, getStringMany()) + getStringSome();
3334
3368
  };
3335
3369
  const replaceUnunsed = (name, items) => {
3336
3370
  for (let value of items) {
3337
3371
  const key = name + '_' + (value = value.trim()) + (useUnsafeCssReplace ? '_1' : '');
3338
- let [pattern] = selectorMap[key] || [], selector = '', rule;
3372
+ let pattern = queryMap.get(key), rule;
3339
3373
  if (!pattern) {
3340
- let flags = 'gi', revised;
3374
+ let flags = 'gi', selector = '', revised;
3341
3375
  if (name === 'container') {
3342
3376
  for (let i = 0, length = value.length, casing = true; i < length; i++) {
3343
3377
  let ch = value[i];
@@ -3373,27 +3407,25 @@ class ChromeDocument extends Document {
3373
3407
  .replace(/\s*(\\[()])\s*/g, (...capture) => getStringSome() + capture[1] + getStringSome())
3374
3408
  .replace(/:\s+/g, getStringSome() + ':' + getStringSome())
3375
3409
  .replace(/\s+([<>/]|<=|>=|\\\*)\s+/g, (...capture) => getStringSome() + capture[1] + getStringSome())
3376
- .replace(/[ ]+/g, getStringMany());
3410
+ .replace(/ +/g, getStringMany());
3377
3411
  if (flags === 'g') {
3378
3412
  selector = selector.replace(/`([a-z][A-Z])`/g, (...capture) => `[${capture[1]}]`);
3379
3413
  }
3380
3414
  }
3381
3415
  pattern = new RegExp('(\\s*)@' + (flags === 'g' ? name.split('').map(ch => `[${ch + ch.toUpperCase()}]`).join('') : name) + getStringMany() + selector + getStringSome() + '{', flags);
3416
+ queryMap.set(key, pattern);
3382
3417
  }
3383
3418
  else {
3384
3419
  pattern.lastIndex = 0;
3385
3420
  }
3386
3421
  while (rule = pattern.exec(current)) {
3387
- const [endIndex, trailing] = findClosingIndex(current, getEndIndex(rule));
3422
+ const [endIndex, trailing] = findClosingIndex(current, getEndIndex(rule), '{');
3388
3423
  if (endIndex !== -1) {
3389
3424
  current = spliceSource(current, rule.index, endIndex, pattern, rule[1], trailing);
3390
3425
  (checkEmpty || (checkEmpty = {}))[name] = true;
3391
3426
  modified = true;
3392
3427
  }
3393
3428
  }
3394
- if (selector) {
3395
- selectorMap[key] = [pattern];
3396
- }
3397
3429
  }
3398
3430
  };
3399
3431
  if (!useUnsafeCssReplace) {
@@ -3432,41 +3464,110 @@ class ChromeDocument extends Document {
3432
3464
  replaceUnunsed('container', unusedContainer);
3433
3465
  }
3434
3466
  if (unusedStyles) {
3435
- for (const value of unusedStyles) {
3436
- const key = value + (useUnsafeCssReplace ? '_1' : '');
3437
- let [single, group] = selectorMap[key] || [], selector;
3438
- if (!single || !group) {
3439
- single = new RegExp(`(}|^)(${getStringSome()})(?<!@[^}]*)${selector = parseSelector(value)}\\{([^}]*)\\}` + dom_1.DomWriter.PATTERN_TRAILINGSPACE, 'g');
3440
- group = new RegExp(`((?:}|^)[^{]*?)((?:,${getStringSome()}|(?<!@[^}]*))${selector})(,[^{]*)?\\{`, 'g');
3441
- }
3442
- else {
3443
- single.lastIndex = 0;
3444
- group.lastIndex = 0;
3445
- }
3446
- while ((match = single.exec(current)) && match[3].indexOf('{') === -1) {
3447
- current = spliceSource(current, match.index, getEndIndex(match), single, match[2], match[4], match[1] || '');
3448
- modified = true;
3449
- }
3450
- while (match = group.exec(current)) {
3451
- const opening = match.index + match[0].length;
3452
- const closing = current.indexOf('}', opening);
3453
- if (closing !== -1 && current.substring(opening, closing).indexOf('{') === -1) {
3454
- const middle = match[2][0] === ',';
3455
- const leading = middle ? match[1].trimEnd() : match[1];
3456
- const trailing = match[3];
3457
- const segment = trailing ? (!middle ? (!isSpace(leading[leading.length - 1]) ? ' ' : '') + trailing.substring(1).trimStart() : trailing).trimEnd() : '';
3458
- current = replaceMatch(match, current, leading + segment + ' {');
3459
- group.lastIndex = match.index;
3460
- modified = true;
3467
+ current = (function recurse(target, child) {
3468
+ let output = '', index = 0, pendingStart = 0, pendingEnd = 0, found = -1;
3469
+ while ((found = target.indexOf('{', index)) !== -1) {
3470
+ ++found;
3471
+ const closing = findClosingIndex(target, found, '{')[0];
3472
+ if (closing === -1) {
3473
+ break;
3474
+ }
3475
+ let header = target.substring(index, found), invalid = 0, content;
3476
+ for (const value of unusedStyles) {
3477
+ const key = value + (child ? '_1' : '') + (useUnsafeCssReplace ? '_2' : '');
3478
+ const pattern = selectorMap.get(key);
3479
+ let single, group;
3480
+ if (!pattern) {
3481
+ const selector = parseSelector(value) + (child ? `(?:${getStringSome()}&${getStringSome()})*` : '');
3482
+ single = new RegExp(`(^|;)(${getStringSome()})(?<!${child ? '&|' : ''}@[^};]*)${selector}\\{$`);
3483
+ group = new RegExp(`((?:^|;)[^{]*?${getStringSome()}(,${getStringSome()}|(?<!@[^};]*)))(${(child ? '(?:&\\s+|(?<!&))' : '') + selector})(,[^{]*)?\\{$`, 'g');
3484
+ selectorMap.set(key, [single, group]);
3485
+ }
3486
+ else {
3487
+ [single, group] = pattern;
3488
+ }
3489
+ if (match = single.exec(header)) {
3490
+ content = match[1] === ';' ? header.substring(0, match.index + 1) + match[2].replace(/[^\n]+$/, '') : undefined;
3491
+ invalid = 1;
3492
+ break;
3493
+ }
3494
+ let leading = -1, first = true;
3495
+ while (match = group.exec(header)) {
3496
+ const start = match.index + match[1].length;
3497
+ if (!child && first) {
3498
+ if (match[1].trimEnd().endsWith(';')) {
3499
+ leading = start;
3500
+ }
3501
+ first = false;
3502
+ }
3503
+ const sibling = match[2][0] === ',';
3504
+ const trailing = match[4];
3505
+ const offset = trailing && !sibling ? /^,\s*/.exec(trailing)[0].length : 1;
3506
+ header = header.substring(0, start - (sibling ? match[2].length : 0)) + header.substring(start + match[3].length + (trailing ? sibling ? 0 : offset : /\s$/.test(match[3]) ? -1 : 0));
3507
+ group.lastIndex = match.index;
3508
+ invalid = 2;
3509
+ }
3510
+ if (invalid) {
3511
+ group.lastIndex = 0;
3512
+ const empty = /(.)\s*{$/.exec(header);
3513
+ if (empty) {
3514
+ if (header.trim() === '{') {
3515
+ content = undefined;
3516
+ invalid = 1;
3517
+ break;
3518
+ }
3519
+ else if (leading !== -1 && empty[1] === ';') {
3520
+ content = target.substring(index, found).substring(0, leading).replace(/[^\n;]+$/, '');
3521
+ invalid = 1;
3522
+ break;
3523
+ }
3524
+ }
3525
+ }
3526
+ }
3527
+ if (invalid === 0 || invalid === 2) {
3528
+ const trailing = target.substring(found, closing);
3529
+ if (content = recurse(trailing, true)) {
3530
+ content = header + content;
3531
+ }
3532
+ else if (invalid === 2) {
3533
+ content = header + trailing;
3534
+ }
3535
+ }
3536
+ if (content) {
3537
+ if (pendingEnd) {
3538
+ output = target.substring(pendingStart, pendingEnd) + content;
3539
+ pendingEnd = 0;
3540
+ }
3541
+ else {
3542
+ output += content;
3543
+ }
3544
+ }
3545
+ else if (!invalid) {
3546
+ if (output) {
3547
+ output += target.substring(index, closing);
3548
+ }
3549
+ else {
3550
+ pendingEnd = closing;
3551
+ }
3552
+ }
3553
+ else if (pendingEnd) {
3554
+ output = target.substring(pendingStart, pendingEnd);
3555
+ pendingEnd = 0;
3461
3556
  }
3557
+ else {
3558
+ pendingStart = closing;
3559
+ }
3560
+ index = closing;
3462
3561
  }
3463
- if (selector) {
3464
- selectorMap[key] = [single, group];
3562
+ if (output) {
3563
+ modified = true;
3564
+ return output + target.substring(index);
3465
3565
  }
3466
- }
3566
+ return !child ? target : '';
3567
+ })(current, false);
3467
3568
  }
3468
3569
  if (usedVariables) {
3469
- const pattern = useUnsafeCssReplace ? REGEXP_VARIABLES_UNSAFE : REGEXP_VARIABLES;
3570
+ let pattern = useUnsafeCssReplace ? REGEXP_VARIABLES_UNSAFE : REGEXP_VARIABLES;
3470
3571
  while (match = pattern.exec(current)) {
3471
3572
  if (!usedVariables.includes(match[2])) {
3472
3573
  current = replaceMatch(match, current, match[3] === ';' ? getNewlineString(match[1], match[4]) : '', pattern);
@@ -3474,24 +3575,35 @@ class ChromeDocument extends Document {
3474
3575
  }
3475
3576
  }
3476
3577
  pattern.lastIndex = 0;
3578
+ pattern = useUnsafeCssReplace ? REGEXP_PROPERTY_UNSAFE : REGEXP_PROPERTY;
3579
+ while (match = pattern.exec(current)) {
3580
+ if (!usedVariables.includes(match[2])) {
3581
+ const [endIndex, trailing] = findClosingIndex(current, getEndIndex(match), '{');
3582
+ if (endIndex !== -1) {
3583
+ current = spliceSource(current, match.index, endIndex, pattern, match[1], trailing);
3584
+ modified = true;
3585
+ }
3586
+ }
3587
+ }
3588
+ pattern.lastIndex = 0;
3477
3589
  }
3478
3590
  if (usedFontFace) {
3479
- const fontFace = useUnsafeCssReplace ? REGEXP_FONTFACE_UNSAFE : REGEXP_FONTFACE;
3591
+ const pattern = useUnsafeCssReplace ? REGEXP_FONTFACE_UNSAFE : REGEXP_FONTFACE;
3480
3592
  const fontFamily = useUnsafeCssReplace ? REGEXP_FONTFAMILY_UNSAFE : REGEXP_FONTFAMILY;
3481
- while (match = fontFace.exec(current)) {
3593
+ while (match = pattern.exec(current)) {
3482
3594
  const font = fontFamily.exec(match[0]);
3483
3595
  if (font && !usedFontFace.includes(font[1].trim().replace(/^(["'])([\S\s]+)\1$/, (...content) => content[2]))) {
3484
- current = spliceSource(current, match.index, getEndIndex(match), fontFace, match[1], match[3]);
3596
+ current = spliceSource(current, match.index, getEndIndex(match), pattern, match[1], match[3]);
3485
3597
  modified = true;
3486
3598
  }
3487
3599
  }
3488
- fontFace.lastIndex = 0;
3600
+ pattern.lastIndex = 0;
3489
3601
  }
3490
3602
  if (usedKeyframes) {
3491
3603
  const pattern = useUnsafeCssReplace ? REGEXP_KEYFRAMES_UNSAFE : REGEXP_KEYFRAMES;
3492
3604
  while (match = pattern.exec(current)) {
3493
3605
  if (!usedKeyframes.includes(match[2].trim())) {
3494
- const [endIndex, trailing] = findClosingIndex(current, getEndIndex(match));
3606
+ const [endIndex, trailing] = findClosingIndex(current, getEndIndex(match), '{');
3495
3607
  if (endIndex !== -1) {
3496
3608
  current = spliceSource(current, match.index, endIndex, pattern, match[1], trailing);
3497
3609
  modified = true;
@@ -3546,7 +3658,9 @@ class ChromeDocument extends Document {
3546
3658
  return this._editing;
3547
3659
  }
3548
3660
  set dataSource(value) {
3549
- this._dataSource = value;
3661
+ if (Array.isArray(value)) {
3662
+ this._dataSource = value;
3663
+ }
3550
3664
  }
3551
3665
  get dataSource() {
3552
3666
  const result = this._dataSource;
@@ -3569,7 +3683,7 @@ class ChromeDocument extends Document {
3569
3683
  return (_a = this.module).settings || (_a.settings = {});
3570
3684
  }
3571
3685
  }
3572
- ChromeDocument.INTERNAL_ASSIGNUUID = '__assign__';
3573
- ChromeDocument.INTERNAL_SERVERROOT = '__serverroot__';
3686
+ ChromeDocument.INTERNAL_ASSIGNUUID = "__assign__" /* PROPERTIES.ASSIGN */;
3687
+ ChromeDocument.INTERNAL_SERVERROOT = "__serverroot__" /* PROPERTIES.SERVERROOT */;
3574
3688
 
3575
3689
  module.exports = ChromeDocument;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-r/chrome",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "description": "Chrome document constructor for E-mc.",
5
5
  "main": "index.js",
6
6
  "publishConfig": {
@@ -20,9 +20,9 @@
20
20
  "license": "MIT",
21
21
  "homepage": "https://github.com/anpham6/pi-r#readme",
22
22
  "dependencies": {
23
- "@e-mc/cloud": "^0.6.0",
24
- "@e-mc/core": "^0.6.0",
25
- "@e-mc/document": "^0.6.0",
26
- "@e-mc/types": "^0.6.0"
23
+ "@e-mc/cloud": "^0.7.0",
24
+ "@e-mc/core": "^0.7.0",
25
+ "@e-mc/document": "^0.7.0",
26
+ "@e-mc/types": "^0.7.0"
27
27
  }
28
28
  }
package/types/index.d.ts CHANGED
@@ -10,6 +10,13 @@ import type { IDomWriter, IXmlElement } from '@e-mc/document/parse/types';
10
10
 
11
11
  import type { ChromeAsset, CssRuleData, DataSource, DocumentOutput } from './squared';
12
12
 
13
+ export const enum PROPERTIES {
14
+ INDEX = '__index__',
15
+ FROM_HTML = '__fromhtml__',
16
+ ASSIGN = '__assign__',
17
+ SERVERROOT = '__serverroot__'
18
+ }
19
+
13
20
  interface DocumentDirectory extends IDocumentDirectory {
14
21
  template?: string;
15
22
  data?: string;
@@ -29,6 +29,7 @@ export interface DocumentOutput {
29
29
  productionIncremental?: boolean;
30
30
  useOriginalHtmlPage?: boolean | string;
31
31
  useUnsafeReplace?: ArrayOf<"html" | "css"> | boolean;
32
+ useSessionCache?: boolean;
32
33
  stripCommentsAndCDATA?: boolean | string;
33
34
  normalizeHtmlOutput?: boolean | string;
34
35
  escapeReservedCharacters?: boolean;