html.cjs 16 KB

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