html.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486
  1. import { effect, style, insert, createComponent, delegateEvents, classList, dynamicProperty, setAttribute, setAttributeNS, addEventListener, Aliases, Properties, ChildProperties, DelegatedEvents, SVGElements, SVGNamespace } from '/lib/ui/solid-js/web/dist/web.js';
  2. var attrRE, lookup, parseTag, pushCommentNode, pushTextNode, tagRE;
  3. tagRE = /(?:<!--[\S\s]*?-->|<(?:"[^"]*"['"]*|'[^']*'['"]*|[^'">])+>)/g;
  4. attrRE = /\s([^'"/\s><]+?)[\s/>]|([^\s=]+)=\s?(".*?"|'.*?')/g;
  5. lookup = {
  6. area: true,
  7. base: true,
  8. br: true,
  9. col: true,
  10. embed: true,
  11. hr: true,
  12. img: true,
  13. input: true,
  14. keygen: true,
  15. link: true,
  16. menuitem: true,
  17. meta: true,
  18. param: true,
  19. source: true,
  20. track: true,
  21. wbr: true
  22. };
  23. parseTag = function (tag) {
  24. var res;
  25. res = {
  26. type: 'tag',
  27. name: '',
  28. voidElement: false,
  29. attrs: {},
  30. children: []
  31. };
  32. const tagMatch = tag.match(/<\/?([^\s]+?)[/\s>]/);
  33. if (tagMatch) {
  34. res.name = tagMatch[1];
  35. if (lookup[tagMatch[1].toLowerCase()] || tag.charAt(tag.length - 2) === '/') {
  36. res.voidElement = true;
  37. }
  38. if (res.name.startsWith('!--')) {
  39. const endIndex = tag.indexOf('-->');
  40. return {
  41. type: 'comment',
  42. comment: endIndex !== -1 ? tag.slice(4, endIndex) : ''
  43. };
  44. }
  45. }
  46. const reg = new RegExp(attrRE);
  47. let result = null;
  48. for (;;) {
  49. result = reg.exec(tag);
  50. if (result === null) {
  51. break;
  52. }
  53. if (!result[0].trim()) {
  54. continue;
  55. }
  56. if (result[1]) {
  57. const attr = result[1].trim();
  58. let arr = [attr, ''];
  59. if (attr.indexOf('=') > -1) {
  60. arr = attr.split('=');
  61. }
  62. res.attrs[arr[0]] = arr[1];
  63. reg.lastIndex--;
  64. } else if (result[2]) {
  65. res.attrs[result[2]] = result[3].trim().substring(1, result[3].length - 1);
  66. }
  67. }
  68. return res;
  69. };
  70. pushTextNode = function (list, html, start) {
  71. var content, end;
  72. end = html.indexOf('<', start);
  73. content = html.slice(start, end === -1 ? void 0 : end);
  74. if (!/^\s*$/.test(content)) {
  75. list.push({
  76. type: 'text',
  77. content: content
  78. });
  79. }
  80. };
  81. pushCommentNode = function (list, tag) {
  82. var content;
  83. content = tag.replace('<!--', '').replace('-->', '');
  84. if (!/^\s*$/.test(content)) {
  85. list.push({
  86. type: 'comment',
  87. content: content
  88. });
  89. }
  90. };
  91. function parse(html) {
  92. var arr, byTag, current, level, result;
  93. result = [];
  94. current = void 0;
  95. level = -1;
  96. arr = [];
  97. byTag = {};
  98. html.replace(tagRE, function (tag, index) {
  99. var isComment, isOpen, nextChar, parent, start;
  100. isOpen = tag.charAt(1) !== '/';
  101. isComment = tag.slice(0, 4) === '<!--';
  102. start = index + tag.length;
  103. nextChar = html.charAt(start);
  104. parent = void 0;
  105. if (isOpen && !isComment) {
  106. level++;
  107. current = parseTag(tag);
  108. if (!current.voidElement && nextChar && nextChar !== '<') {
  109. pushTextNode(current.children, html, start);
  110. }
  111. byTag[current.tagName] = current;
  112. if (level === 0) {
  113. result.push(current);
  114. }
  115. parent = arr[level - 1];
  116. if (parent) {
  117. parent.children.push(current);
  118. }
  119. arr[level] = current;
  120. }
  121. if (isComment) {
  122. if (level < 0) {
  123. pushCommentNode(result, tag);
  124. } else {
  125. pushCommentNode(arr[level].children, tag);
  126. }
  127. }
  128. if (isComment || !isOpen || current.voidElement) {
  129. if (!isComment) {
  130. level--;
  131. }
  132. if (nextChar !== '<' && nextChar) {
  133. parent = level === -1 ? result : arr[level].children;
  134. pushTextNode(parent, html, start);
  135. }
  136. }
  137. });
  138. return result;
  139. }
  140. var attrString, stringifier;
  141. attrString = function (attrs) {
  142. var buff, key;
  143. buff = [];
  144. for (key in attrs) {
  145. buff.push(key + '="' + attrs[key] + '"');
  146. }
  147. if (!buff.length) {
  148. return '';
  149. }
  150. return ' ' + buff.join(' ');
  151. };
  152. stringifier = function (buff, doc) {
  153. switch (doc.type) {
  154. case 'text':
  155. return buff + doc.content;
  156. case 'tag':
  157. buff += '<' + doc.name + (doc.attrs ? attrString(doc.attrs) : '') + (doc.voidElement ? '/>' : '>');
  158. if (doc.voidElement) {
  159. return buff;
  160. }
  161. return buff + doc.children.reduce(stringifier, '') + '</' + doc.name + '>';
  162. case 'comment':
  163. return buff += '<!--' + doc.content + '-->';
  164. }
  165. };
  166. function stringify(doc) {
  167. return doc.reduce(function (token, rootEl) {
  168. return token + stringifier('', rootEl);
  169. }, '');
  170. }
  171. const cache = new Map();
  172. const VOID_ELEMENTS = /^(?:area|base|br|col|embed|hr|img|input|keygen|link|menuitem|meta|param|source|track|wbr)$/i;
  173. const spaces = " \\f\\n\\r\\t";
  174. const almostEverything = "[^ " + spaces + "\\/>\"'=]+";
  175. const attrName = "[ " + spaces + "]+" + almostEverything;
  176. const tagName = "<([A-Za-z$#]+[A-Za-z0-9:_-]*)((?:";
  177. const attrPartials = "(?:\\s*=\\s*(?:'[^']*?'|\"[^\"]*?\"|\\([^)]*?\\)|<[^>]*?>|" + almostEverything + "))?)";
  178. const attrSeeker = new RegExp(tagName + attrName + attrPartials + "+)([ " + spaces + "]*/?>)", "g");
  179. const findAttributes = new RegExp("(" + attrName + "\\s*=\\s*)(['\"(]?)" + "<!--#-->" + "(['\")]?)", "gi");
  180. const selfClosing = new RegExp(tagName + attrName + attrPartials + "*)([ " + spaces + "]*/>)", "g");
  181. const marker = "<!--#-->";
  182. const reservedNameSpaces = new Set(["class", "on", "oncapture", "style", "use", "prop", "attr"]);
  183. function attrReplacer($0, $1, $2, $3) {
  184. return "<" + $1 + $2.replace(findAttributes, replaceAttributes) + $3;
  185. }
  186. function replaceAttributes($0, $1, $2, $3) {
  187. return $1 + ($2 || '"') + "###" + ($3 || '"');
  188. }
  189. function fullClosing($0, $1, $2) {
  190. return VOID_ELEMENTS.test($1) ? $0 : "<" + $1 + $2 + "></" + $1 + ">";
  191. }
  192. function toPropertyName(name) {
  193. return name.toLowerCase().replace(/-([a-z])/g, (_, w) => w.toUpperCase());
  194. }
  195. function createHTML(r, {
  196. delegateEvents = true
  197. } = {}) {
  198. let uuid = 1;
  199. r.wrapProps = props => {
  200. const d = Object.getOwnPropertyDescriptors(props);
  201. for (const k in d) {
  202. if (typeof d[k].value === "function" && !d[k].value.length) r.dynamicProperty(props, k);
  203. }
  204. return props;
  205. };
  206. function createTemplate(statics) {
  207. let i = 0,
  208. markup = "";
  209. for (; i < statics.length - 1; i++) {
  210. markup = markup + statics[i] + "<!--#-->";
  211. }
  212. markup = markup + statics[i];
  213. markup = markup.replace(selfClosing, fullClosing).replace(/<(<!--#-->)/g, "<###").replace(attrSeeker, attrReplacer).replace(/>\n+\s*/g, ">").replace(/\n+\s*</g, "<").replace(/\s+</g, " <").replace(/>\s+/g, "> ");
  214. const [html, code] = parseTemplate(parse(markup)),
  215. templates = [];
  216. for (let i = 0; i < html.length; i++) {
  217. templates.push(document.createElement("template"));
  218. templates[i].innerHTML = html[i];
  219. const nomarkers = templates[i].content.querySelectorAll("script,style");
  220. for (let j = 0; j < nomarkers.length; j++) {
  221. const d = nomarkers[j].firstChild.data || "";
  222. if (d.indexOf(marker) > -1) {
  223. const parts = d.split(marker).reduce((memo, p, i) => {
  224. i && memo.push("");
  225. memo.push(p);
  226. return memo;
  227. }, []);
  228. nomarkers[i].firstChild.replaceWith(...parts);
  229. }
  230. }
  231. }
  232. templates[0].create = code;
  233. cache.set(statics, templates);
  234. return templates;
  235. }
  236. function parseKeyValue(tag, name, isSVG, isCE, options) {
  237. let count = options.counter++,
  238. expr = `!doNotWrap ? exprs[${count}]() : exprs[${count}]`,
  239. parts,
  240. namespace;
  241. if ((parts = name.split(":")) && parts[1] && reservedNameSpaces.has(parts[0])) {
  242. name = parts[1];
  243. namespace = parts[0];
  244. }
  245. const isChildProp = r.ChildProperties.has(name);
  246. const isProp = r.Properties.has(name);
  247. if (name === "style") {
  248. const prev = `_$v${uuid++}`;
  249. options.decl.push(`${prev}={}`);
  250. options.exprs.push(`r.style(${tag},${expr},${prev})`);
  251. } else if (name === "classList") {
  252. const prev = `_$v${uuid++}`;
  253. options.decl.push(`${prev}={}`);
  254. options.exprs.push(`r.classList(${tag},${expr},${prev})`);
  255. } else if (namespace !== "attr" && (isChildProp || !isSVG && isProp || isCE || namespace === "prop")) {
  256. if (isCE && !isChildProp && !isProp && namespace !== "prop") name = toPropertyName(name);
  257. options.exprs.push(`${tag}.${name} = ${expr}`);
  258. } else {
  259. const ns = isSVG && name.indexOf(":") > -1 && r.SVGNamespace[name.split(":")[0]];
  260. if (ns) options.exprs.push(`r.setAttributeNS(${tag},"${ns}","${name}",${expr})`);else options.exprs.push(`r.setAttribute(${tag},"${r.Aliases[name] || name}",${expr})`);
  261. }
  262. }
  263. function parseAttribute(tag, name, isSVG, isCE, options) {
  264. if (name.slice(0, 2) === "on") {
  265. if (!name.includes(":")) {
  266. const lc = name.slice(2).toLowerCase();
  267. const delegate = delegateEvents && r.DelegatedEvents.has(lc);
  268. options.exprs.push(`r.addEventListener(${tag},"${lc}",exprs[${options.counter++}],${delegate})`);
  269. delegate && options.delegatedEvents.add(lc);
  270. } else {
  271. let capture = name.startsWith("oncapture:");
  272. options.exprs.push(`${tag}.addEventListener("${name.slice(capture ? 10 : 3)}",exprs[${options.counter++}]${capture ? ",true" : ""})`);
  273. }
  274. } else if (name === "ref") {
  275. options.exprs.push(`exprs[${options.counter++}](${tag})`);
  276. } else {
  277. const childOptions = Object.assign({}, options, {
  278. exprs: []
  279. }),
  280. count = options.counter;
  281. parseKeyValue(tag, name, isSVG, isCE, childOptions);
  282. options.decl.push(`_fn${count} = doNotWrap => {\n${childOptions.exprs.join(";\n")};\n}`);
  283. options.exprs.push(`typeof exprs[${count}] === "function" ? r.effect(_fn${count}) : _fn${count}(true)`);
  284. options.counter = childOptions.counter;
  285. options.wrap = false;
  286. }
  287. }
  288. function processChildren(node, options) {
  289. const childOptions = Object.assign({}, options, {
  290. first: true,
  291. multi: false,
  292. parent: options.path
  293. });
  294. if (node.children.length > 1) {
  295. for (let i = 0; i < node.children.length; i++) {
  296. const child = node.children[i];
  297. if (child.type === "comment" && child.content === "#" || child.type === "tag" && child.name === "###") {
  298. childOptions.multi = true;
  299. break;
  300. }
  301. }
  302. }
  303. let i = 0;
  304. while (i < node.children.length) {
  305. const child = node.children[i];
  306. if (child.name === "###") {
  307. if (childOptions.multi) {
  308. node.children[i] = {
  309. type: "comment",
  310. content: "#"
  311. };
  312. i++;
  313. } else node.children.splice(i, 1);
  314. processComponent(child, childOptions);
  315. continue;
  316. }
  317. parseNode(child, childOptions);
  318. i++;
  319. }
  320. options.counter = childOptions.counter;
  321. options.templateId = childOptions.templateId;
  322. }
  323. function processComponent(node, options) {
  324. const keys = Object.keys(node.attrs),
  325. props = [],
  326. componentIdentifier = options.counter++;
  327. for (let i = 0; i < keys.length; i++) {
  328. const name = keys[i],
  329. value = node.attrs[name];
  330. if (value === "###") {
  331. let count = options.counter++;
  332. props.push(`${name}: exprs[${count}]`);
  333. } else props.push(`${name}: "${value}"`);
  334. }
  335. if (node.children.length === 1 && node.children[0].type === "comment" && node.children[0].content === "#") {
  336. props.push(`children: () => exprs[${options.counter++}]`);
  337. } else if (node.children.length) {
  338. const children = {
  339. type: "fragment",
  340. children: node.children
  341. },
  342. childOptions = Object.assign({}, options, {
  343. first: true,
  344. decl: [],
  345. exprs: []
  346. });
  347. parseNode(children, childOptions);
  348. props.push(`children: () => { ${childOptions.exprs.join(";\n")}}`);
  349. options.templateId = childOptions.templateId;
  350. options.counter = childOptions.counter;
  351. }
  352. let tag;
  353. if (options.multi) {
  354. tag = `_$el${uuid++}`;
  355. options.decl.push(`${tag} = ${options.path}.${options.first ? "firstChild" : "nextSibling"}`);
  356. }
  357. if (options.parent) options.exprs.push(`r.insert(${options.parent}, r.createComponent(exprs[${componentIdentifier}], r.wrapProps({${props.join(", ") || ""}}))${tag ? `, ${tag}` : ""})`);else options.exprs.push(`${options.fragment ? "" : "return "}r.createComponent(exprs[${componentIdentifier}], r.wrapProps({${props.join(", ") || ""}}))`);
  358. options.path = tag;
  359. options.first = false;
  360. }
  361. function parseNode(node, options) {
  362. if (node.type === "fragment") {
  363. const parts = [];
  364. node.children.forEach(child => {
  365. if (child.type === "tag") {
  366. if (child.name === "###") {
  367. const childOptions = Object.assign({}, options, {
  368. first: true,
  369. fragment: true,
  370. decl: [],
  371. exprs: []
  372. });
  373. processComponent(child, childOptions);
  374. parts.push(childOptions.exprs[0]);
  375. options.counter = childOptions.counter;
  376. options.templateId = childOptions.templateId;
  377. return;
  378. }
  379. options.templateId++;
  380. const id = uuid;
  381. const childOptions = Object.assign({}, options, {
  382. first: true,
  383. decl: [],
  384. exprs: []
  385. });
  386. parseNode(child, childOptions);
  387. options.templateNodes.push([child]);
  388. parts.push(`function() { ${childOptions.decl.join(",\n") + ";\n" + childOptions.exprs.join(";\n") + `;\nreturn _$el${id};\n`}}()`);
  389. options.counter = childOptions.counter;
  390. options.templateId = childOptions.templateId;
  391. } else if (child.type === "text") {
  392. parts.push(`"${child.content}"`);
  393. } else if (child.type === "comment" && child.content === "#") {
  394. parts.push(`exprs[${options.counter++}]`);
  395. }
  396. });
  397. options.exprs.push(`return [${parts.join(", \n")}]`);
  398. } else if (node.type === "tag") {
  399. const tag = `_$el${uuid++}`;
  400. options.decl.push(!options.decl.length ? `const ${tag} = tmpls[${options.templateId}].content.firstChild.cloneNode(true)` : `${tag} = ${options.path}.${options.first ? "firstChild" : "nextSibling"}`);
  401. const keys = Object.keys(node.attrs);
  402. const isSVG = r.SVGElements.has(node.name);
  403. const isCE = node.name.includes("-");
  404. for (let i = 0; i < keys.length; i++) {
  405. const name = keys[i],
  406. value = node.attrs[name];
  407. if (value === "###") {
  408. delete node.attrs[name];
  409. parseAttribute(tag, name, isSVG, isCE, options);
  410. }
  411. }
  412. options.path = tag;
  413. options.first = false;
  414. processChildren(node, options);
  415. } else if (node.type === "text") {
  416. const tag = `_$el${uuid++}`;
  417. options.decl.push(`${tag} = ${options.path}.${options.first ? "firstChild" : "nextSibling"}`);
  418. options.path = tag;
  419. options.first = false;
  420. } else if (node.type === "comment" && node.content === "#") {
  421. const tag = `_$el${uuid++}`;
  422. options.decl.push(`${tag} = ${options.path}.${options.first ? "firstChild" : "nextSibling"}`);
  423. if (options.multi) {
  424. options.exprs.push(`r.insert(${options.parent}, exprs[${options.counter++}], ${tag})`);
  425. } else options.exprs.push(`r.insert(${options.parent}, exprs[${options.counter++}])`);
  426. options.path = tag;
  427. options.first = false;
  428. }
  429. }
  430. function parseTemplate(nodes) {
  431. const options = {
  432. path: "",
  433. decl: [],
  434. exprs: [],
  435. delegatedEvents: new Set(),
  436. counter: 0,
  437. first: true,
  438. multi: false,
  439. templateId: 0,
  440. templateNodes: []
  441. },
  442. id = uuid,
  443. origNodes = nodes;
  444. let toplevel;
  445. if (nodes.length > 1) {
  446. nodes = [{
  447. type: "fragment",
  448. children: nodes
  449. }];
  450. }
  451. if (nodes[0].name === "###") {
  452. toplevel = true;
  453. processComponent(nodes[0], options);
  454. } else parseNode(nodes[0], options);
  455. r.delegateEvents(Array.from(options.delegatedEvents));
  456. const templateNodes = [origNodes].concat(options.templateNodes);
  457. return [templateNodes.map(t => stringify(t)), new Function("tmpls", "exprs", "r", options.decl.join(",\n") + ";\n" + options.exprs.join(";\n") + (toplevel ? "" : `;\nreturn _$el${id};\n`))];
  458. }
  459. function html(statics, ...args) {
  460. const templates = cache.get(statics) || createTemplate(statics);
  461. return templates[0].create(templates, args, r);
  462. }
  463. return html;
  464. }
  465. var index = createHTML({
  466. effect,
  467. style,
  468. insert,
  469. createComponent,
  470. delegateEvents,
  471. classList,
  472. dynamicProperty,
  473. setAttribute,
  474. setAttributeNS,
  475. addEventListener,
  476. Aliases,
  477. Properties,
  478. ChildProperties,
  479. DelegatedEvents,
  480. SVGElements,
  481. SVGNamespace
  482. });
  483. export default index;