~repos /atoms-element

#js

git clone https://pyrossh.dev/repos/atoms-element.git

A simple web component library for defining your custom elements. It works on both client and server.


7dc696c6 Peter John

4 years ago
implement easy html parsing
example/app-counter.js CHANGED
@@ -1,4 +1,4 @@
1
- import { createElement, html, object, number, string, unsafeHTML } from '../element.js';
1
+ import { createElement, html, object, number, string, unsafeHTML } from '../index.js';
2
2
 
3
3
  const name = () => 'app-counter';
4
4
 
example/index.js CHANGED
@@ -1,5 +1,4 @@
1
- import { html, css } from '../element.js';
2
- import { createPage } from '../page.js';
1
+ import { createPage, html, css } from '../index.js';
3
2
  import { pageStyles } from './styles.js';
4
3
  import './app-counter.js';
5
4
 
example/server.js CHANGED
@@ -9,7 +9,7 @@ const __dirname = dirname(__filename);
9
9
 
10
10
  const port = process.argv[2] || 3000;
11
11
  const srcMap = {
12
- '/element.js': `${__dirname}/../element.js`,
12
+ '/index.js': `${__dirname}/../index.js`,
13
13
  '/lit-html.js': `${__dirname}/../lit-html.js`,
14
14
  '/styles.js': `${__dirname}/styles.js`,
15
15
  '/app-counter.js': `${__dirname}/app-counter.js`,
element.d.ts → index.d.ts RENAMED
@@ -1,4 +1,17 @@
1
+ export declare type Config = {
2
+ version: string
3
+ url: string
4
+ image: string
5
+ author: string
6
+ languages: Array<string>
7
+ title: string
8
+ description: string
9
+ keywords: string
1
- import { Config } from './page';
10
+ categories: Array<any>
11
+ tags: Array<any>
12
+ strings: {[key: string]: any}
13
+ themes: {[key: string]: any}
14
+ }
2
15
 
3
16
  export declare type Location = {
4
17
  readonly ancestorOrigins: DOMStringList;
@@ -17,6 +30,9 @@ export declare type Location = {
17
30
  toString: () => string;
18
31
  }
19
32
 
33
+ export declare type Data = any;
34
+ export declare type Item = any;
35
+
20
36
  export class AtomsElement {
21
37
  static register(): () => void;
22
38
  static observedAttributes: Array<string>;
@@ -37,3 +53,27 @@ export type CreateElementProps = {
37
53
  }
38
54
 
39
55
  export const createElement = (props: CreateElementProps) => CreateElementProps;
56
+
57
+ export type HandlerProps = {
58
+ config: Config;
59
+ data: Data;
60
+ item: Item;
61
+ }
62
+ export type Handler = (props: HandlerProps) => string;
63
+
64
+ export type CreatePageProps = {
65
+ route: Handler;
66
+ datapaths: Handler;
67
+ head: Handler;
68
+ body: Handler;
69
+ }
70
+
71
+ export type PageRenderProps = {
72
+ config: Config;
73
+ data: Data;
74
+ item: Item;
75
+ headScript: string;
76
+ bodyScript: string;
77
+ }
78
+
79
+ export const createPage = (props: CreatePageProps) => (props: PageRenderProps) => string;
element.js → index.js RENAMED
@@ -375,7 +375,7 @@ const classLookup = {
375
375
  export const getTWStyleSheet = (template) => {
376
376
  const classList = template.strings
377
377
  .reduce((acc, item) => {
378
- const matches = item.match(/(?:class|className)=(?:["']\W+\s*(?:\w+)\()?["']([^'"]+)['"]/gim);
378
+ const matches = item.match(/class=(?:["']\W+\s*(?:\w+)\()?["']([^'"]+)['"]/gim);
379
379
  if (matches) {
380
380
  matches.forEach((matched) => {
381
381
  acc += matched.replace('class="', '').replace('"', '') + ' ';
@@ -417,6 +417,239 @@ const wrapAttribute = (attrName, suffix, text, v) => {
417
417
  return buffer;
418
418
  };
419
419
 
420
+ const tagRE = /<[a-zA-Z0-9\-\!\/](?:"[^"]*"|'[^']*'|[^'">])*>/g;
421
+ const whitespaceRE = /^\s*$/;
422
+ const attrRE = /\s([^'"/\s><]+?)[\s/>]|([^\s=]+)=\s?(".*?"|'.*?')/g;
423
+ const voidElements = {
424
+ area: true,
425
+ base: true,
426
+ br: true,
427
+ col: true,
428
+ embed: true,
429
+ hr: true,
430
+ img: true,
431
+ input: true,
432
+ link: true,
433
+ meta: true,
434
+ param: true,
435
+ source: true,
436
+ track: true,
437
+ wbr: true,
438
+ };
439
+ const empty = Object.create(null);
440
+
441
+ const parseTag = (tag) => {
442
+ const res = {
443
+ type: 'tag',
444
+ name: '',
445
+ voidElement: false,
446
+ attrs: {},
447
+ children: [],
448
+ };
449
+
450
+ const tagMatch = tag.match(/<\/?([^\s]+?)[/\s>]/);
451
+ if (tagMatch) {
452
+ res.name = tagMatch[1];
453
+ if (voidElements[tagMatch[1]] || tag.charAt(tag.length - 2) === '/') {
454
+ res.voidElement = true;
455
+ }
456
+
457
+ // handle comment tag
458
+ if (res.name.startsWith('!--')) {
459
+ const endIndex = tag.indexOf('-->');
460
+ return {
461
+ type: 'comment',
462
+ comment: endIndex !== -1 ? tag.slice(4, endIndex) : '',
463
+ };
464
+ }
465
+ }
466
+
467
+ const reg = new RegExp(attrRE);
468
+ let result = null;
469
+ for (;;) {
470
+ result = reg.exec(tag);
471
+
472
+ if (result === null) {
473
+ break;
474
+ }
475
+
476
+ if (!result[0].trim()) {
477
+ continue;
478
+ }
479
+
480
+ if (result[1]) {
481
+ const attr = result[1].trim();
482
+ let arr = [attr, ''];
483
+
484
+ if (attr.indexOf('=') > -1) {
485
+ arr = attr.split('=');
486
+ }
487
+
488
+ res.attrs[arr[0]] = arr[1];
489
+ reg.lastIndex--;
490
+ } else if (result[2]) {
491
+ res.attrs[result[2]] = result[3].trim().substring(1, result[3].length - 1);
492
+ }
493
+ }
494
+
495
+ return res;
496
+ };
497
+ const parseHtml = (html, options) => {
498
+ options || (options = {});
499
+ options.components || (options.components = empty);
500
+ const result = [];
501
+ const arr = [];
502
+ let current;
503
+ let level = -1;
504
+ let inComponent = false;
505
+
506
+ // handle text at top level
507
+ if (html.indexOf('<') !== 0) {
508
+ var end = html.indexOf('<');
509
+ result.push({
510
+ type: 'text',
511
+ content: end === -1 ? html : html.substring(0, end),
512
+ });
513
+ }
514
+
515
+ html.replace(tagRE, function (tag, index) {
516
+ if (inComponent) {
517
+ if (tag !== '</' + current.name + '>') {
518
+ return;
519
+ } else {
520
+ inComponent = false;
521
+ }
522
+ }
523
+ const isOpen = tag.charAt(1) !== '/';
524
+ const isComment = tag.startsWith('<!--');
525
+ const start = index + tag.length;
526
+ const nextChar = html.charAt(start);
527
+ let parent;
528
+
529
+ if (isComment) {
530
+ const comment = parseTag(tag);
531
+
532
+ // if we're at root, push new base node
533
+ if (level < 0) {
534
+ result.push(comment);
535
+ return result;
536
+ }
537
+ parent = arr[level];
538
+ parent.children.push(comment);
539
+ return result;
540
+ }
541
+
542
+ if (isOpen) {
543
+ level++;
544
+
545
+ current = parseTag(tag);
546
+ if (current.type === 'tag' && options.components[current.name]) {
547
+ current.type = 'component';
548
+ inComponent = true;
549
+ }
550
+
551
+ if (!current.voidElement && !inComponent && nextChar && nextChar !== '<') {
552
+ current.children.push({
553
+ type: 'text',
554
+ content: html.slice(start, html.indexOf('<', start)),
555
+ });
556
+ }
557
+
558
+ // if we're at root, push new base node
559
+ if (level === 0) {
560
+ result.push(current);
561
+ }
562
+
563
+ parent = arr[level - 1];
564
+
565
+ if (parent) {
566
+ parent.children.push(current);
567
+ }
568
+
569
+ arr[level] = current;
570
+ }
571
+
572
+ if (!isOpen || current.voidElement) {
573
+ if (level > -1 && (current.voidElement || current.name === tag.slice(2, -1))) {
574
+ level--;
575
+ // move current up a level to match the end tag
576
+ current = level === -1 ? result : arr[level];
577
+ }
578
+ if (!inComponent && nextChar !== '<' && nextChar) {
579
+ // trailing text node
580
+ // if we're at the root, push a base text node. otherwise add as
581
+ // a child to the current node.
582
+ parent = level === -1 ? result : arr[level].children;
583
+
584
+ // calculate correct end of the content slice in case there's
585
+ // no tag after the text node.
586
+ const end = html.indexOf('<', start);
587
+ let content = html.slice(start, end === -1 ? undefined : end);
588
+ // if a node is nothing but whitespace, collapse it as the spec states:
589
+ // https://www.w3.org/TR/html4/struct/text.html#h-9.1
590
+ if (whitespaceRE.test(content)) {
591
+ content = ' ';
592
+ }
593
+ // don't add whitespace-only text nodes if they would be trailing text nodes
594
+ // or if they would be leading whitespace-only text nodes:
595
+ // * end > -1 indicates this is not a trailing text node
596
+ // * leading node is when level is -1 and parent has length 0
597
+ if ((end > -1 && level + parent.length >= 0) || content !== ' ') {
598
+ parent.push({
599
+ type: 'text',
600
+ content: content,
601
+ });
602
+ }
603
+ }
604
+ }
605
+ });
606
+
607
+ return result;
608
+ };
609
+
610
+ const stringifyAttrs = (attrs) => {
611
+ const buff = [];
612
+ for (let key in attrs) {
613
+ buff.push(key + '="' + attrs[key] + '"');
614
+ }
615
+ if (!buff.length) {
616
+ return '';
617
+ }
618
+ return ' ' + buff.join(' ');
619
+ };
620
+
621
+ const stringifyHtml = (buff, doc) => {
622
+ switch (doc.type) {
623
+ case 'text':
624
+ return buff + doc.content;
625
+ case 'tag':
626
+ buff += '<' + doc.name + (doc.attrs ? stringifyAttrs(doc.attrs) : '') + (doc.voidElement ? '/>' : '>');
627
+ if (doc.voidElement) {
628
+ return buff;
629
+ }
630
+ return buff + doc.children.reduce(stringifyHtml, '') + '</' + doc.name + '>';
631
+ case 'comment':
632
+ buff += '<!--' + doc.comment + '-->';
633
+ return buff;
634
+ }
635
+ };
636
+
637
+ const hydrate = (node) => {
638
+ if (node.children) {
639
+ for (const child of node.children) {
640
+ const Clazz = AtomsElement.getElement(child.name);
641
+ if (Clazz) {
642
+ const instance = new Clazz(child.attrs);
643
+ const res = instance.renderTemplate();
644
+ child.children.push(...parseHtml(res));
645
+ }
646
+ if (child.children) {
647
+ hydrate(child);
648
+ }
649
+ }
650
+ }
651
+ };
652
+
420
653
  export const render = isBrowser
421
654
  ? litRender
422
655
  : (template) => {
@@ -460,7 +693,14 @@ export const render = isBrowser
460
693
  // console.log('value', value);
461
694
  }
462
695
  });
696
+ const nodes = parseHtml(js);
697
+ for (const node of nodes) {
698
+ hydrate(node);
699
+ }
700
+ const html = nodes.reduce((acc, node) => {
701
+ return acc + stringifyHtml('', node);
702
+ }, '');
463
- return js;
703
+ return html;
464
704
  };
465
705
 
466
706
  const previousValues = new WeakMap();
@@ -705,7 +945,7 @@ export class AtomsElement extends BaseElement {
705
945
  get attrs() {
706
946
  return Object.keys(this.constructor.attrTypes).reduceRight((acc, key) => {
707
947
  const attrType = this.constructor.attrTypes[key];
708
- const newValue = isBrowser ? this.getAttribute(key.toLowerCase()) : this.ssrAttributes.find((item) => item.name === key.toLowerCase())?.value;
948
+ const newValue = isBrowser ? this.getAttribute(key.toLowerCase()) : this.ssrAttributes[key.toLowerCase()];
709
949
  const data = attrType.parse(newValue);
710
950
  attrType.validate(`<${this.constructor.name}> ${key}`, data);
711
951
  acc[key] = data;
@@ -791,3 +1031,41 @@ export const createElement = ({ name, attrTypes, stateTypes, computedTypes, rend
791
1031
  Element.register();
792
1032
  return { name, attrTypes, stateTypes, computedTypes, render };
793
1033
  };
1034
+
1035
+ export const createPage = ({ route, datapaths, head, body }) => {
1036
+ return ({ config, data, item, headScript, bodyScript }) => {
1037
+ const isProd = process.env.NODE_ENV === 'production';
1038
+ const props = { config, data, item };
1039
+ const headHtml = render(head(props));
1040
+ const bodyTemplate = body(props);
1041
+ const bodyHtml = render(bodyTemplate);
1042
+ return `
1043
+ <!DOCTYPE html>
1044
+ <html lang="${config.lang}">
1045
+ <head>
1046
+ <meta charset="utf-8" />
1047
+ <meta http-equiv="x-ua-compatible" content="ie=edge" />
1048
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
1049
+ <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=5.0, shrink-to-fit=no">
1050
+ <link rel="sitemap" type="application/xml" href="/sitemap.xml" />
1051
+ <link rel="icon" type="image/png" href="/assets/icon.png" />
1052
+ ${headHtml}
1053
+ <style>
1054
+ ${getTWStyleSheet(bodyTemplate)}
1055
+ </style>
1056
+ ${headScript}
1057
+ </head>
1058
+ <body>
1059
+ ${bodyHtml}
1060
+ <script>
1061
+ window.__DEV__ = ${!isProd};
1062
+ window.config = ${JSON.stringify(config)};
1063
+ window.data = ${JSON.stringify(data)};
1064
+ window.item = ${JSON.stringify(item)};
1065
+ </script>
1066
+ ${bodyScript}
1067
+ </body>
1068
+ </html>
1069
+ `;
1070
+ };
1071
+ };
element.test.js → index.test.js RENAMED
@@ -1,5 +1,5 @@
1
1
  import { expect, test, jest } from '@jest/globals';
2
- import { AtomsElement, html, render, number, boolean, string, array, object, unsafeHTML, css, classMap, createElement } from './element.js';
2
+ import { AtomsElement, createElement, createPage, html, render, number, boolean, string, array, object, unsafeHTML, css, classMap } from './index.js';
3
3
 
4
4
  global.__DEV = true;
5
5
 
@@ -389,3 +389,78 @@ test('createElement ', async () => {
389
389
  const res = instance.renderTemplate();
390
390
  expect(res).toEqual(` <div></div> `);
391
391
  });
392
+
393
+ test('Page', () => {
394
+ const route = () => {
395
+ const langPart = this.config.lang === 'en' ? '' : `/${this.config.lang}`;
396
+ return `${langPart}`;
397
+ };
398
+ const head = ({ config }) => {
399
+ return html`
400
+ <title>${config.title}</title>
401
+ <meta name="title" content=${config.title} />
402
+ <meta name="description" content=${config.title} />
403
+ `;
404
+ };
405
+
406
+ const body = ({ config }) => {
407
+ return html`
408
+ <div>
409
+ <app-header></app-header>
410
+ <main class="flex flex-1 flex-col mt-20 items-center">
411
+ <h1 class="text-5xl">${config.title}</h1>
412
+ </main>
413
+ </div>
414
+ `;
415
+ };
416
+ const renderPage = createPage({
417
+ route,
418
+ head,
419
+ body,
420
+ });
421
+ const scripts = '<script type="module"><script>';
422
+ const res = renderPage({ config: { lang: 'en', title: '123' }, headScript: scripts, bodyScript: scripts });
423
+ expect(res).toEqual(`
424
+ <!DOCTYPE html>
425
+ <html lang="en">
426
+ <head>
427
+ <meta charset="utf-8" />
428
+ <meta http-equiv="x-ua-compatible" content="ie=edge" />
429
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
430
+ <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=5.0, shrink-to-fit=no">
431
+ <link rel="sitemap" type="application/xml" href="/sitemap.xml" />
432
+ <link rel="icon" type="image/png" href="/assets/icon.png" />
433
+
434
+ <title>123</title>
435
+ <meta name="title" content="123">
436
+ <meta name="description" content="123">
437
+
438
+ <style>
439
+ .div-1gao8uk {
440
+ color: red;
441
+
442
+ }
443
+
444
+ </style>
445
+ <script type="module"><script>
446
+ </head>
447
+ <body>
448
+
449
+ <div>
450
+ <app-header></app-header>
451
+ <main class="flex flex-1 flex-col mt-20 items-center">
452
+ <h1 class="text-5xl">123</h1>
453
+ </main>
454
+ </div>
455
+
456
+ <script>
457
+ window.__DEV__ = true;
458
+ window.config = {"lang":"en","title":"123"};
459
+ window.data = undefined;
460
+ window.item = undefined;
461
+ </script>
462
+ <script type="module"><script>
463
+ </body>
464
+ </html>
465
+ `);
466
+ });
package-lock.json CHANGED
@@ -7,9 +7,6 @@
7
7
  "": {
8
8
  "version": "2.3.0",
9
9
  "license": "MIT",
10
- "dependencies": {
11
- "parse5": "^6.0.1"
12
- },
13
10
  "devDependencies": {
14
11
  "jest": "^27.0.5"
15
12
  },
@@ -3198,7 +3195,8 @@
3198
3195
  "node_modules/parse5": {
3199
3196
  "version": "6.0.1",
3200
3197
  "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz",
3201
- "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw=="
3198
+ "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==",
3199
+ "dev": true
3202
3200
  },
3203
3201
  "node_modules/path-exists": {
3204
3202
  "version": "4.0.0",
@@ -6426,7 +6424,8 @@
6426
6424
  "parse5": {
6427
6425
  "version": "6.0.1",
6428
6426
  "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz",
6429
- "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw=="
6427
+ "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==",
6428
+ "dev": true
6430
6429
  },
6431
6430
  "path-exists": {
6432
6431
  "version": "4.0.0",
package.json CHANGED
@@ -41,8 +41,5 @@
41
41
  "singleQuote": true,
42
42
  "arrowParens": "always",
43
43
  "trailingComma": "all"
44
- },
45
- "dependencies": {
46
- "parse5": "^6.0.1"
47
44
  }
48
45
  }
page.d.ts DELETED
@@ -1,40 +0,0 @@
1
- export declare type Config = {
2
- version: string
3
- url: string
4
- image: string
5
- author: string
6
- languages: Array<string>
7
- title: string
8
- description: string
9
- keywords: string
10
- categories: Array<any>
11
- tags: Array<any>
12
- strings: {[key: string]: any}
13
- themes: {[key: string]: any}
14
- }
15
- export declare type Data = any;
16
- export declare type Item = any;
17
-
18
- export type find = (node: any) => void;
19
- export type ssr = (template: any) => string;
20
-
21
- export type Props = {
22
- config: Config;
23
- data: Data;
24
- item: Item;
25
- }
26
- export type Handler = (props: Props) => string;
27
- export type CreatePageProps = {
28
- route: Handler;
29
- datapaths: Handler;
30
- head: Handler;
31
- body: Handler;
32
- }
33
- export type PageRenderProps = {
34
- config: Config;
35
- data: Data;
36
- item: Item;
37
- headScript: string;
38
- bodyScript: string;
39
- }
40
- export const createPage = (props: CreatePageProps) => (props: PageRenderProps) => string;
page.js DELETED
@@ -1,62 +0,0 @@
1
- import parse5 from 'parse5';
2
- import { AtomsElement, render, getTWStyleSheet } from './element.js';
3
-
4
- export const find = (node) => {
5
- for (const child of node.childNodes) {
6
- if (AtomsElement.getElement(child.tagName)) {
7
- const Clazz = AtomsElement.getElement(child.tagName);
8
- const instance = new Clazz(child.attrs);
9
- const res = instance.renderTemplate();
10
- const frag = parse5.parseFragment(res);
11
- child.childNodes.push(...frag.childNodes);
12
- }
13
- if (child.childNodes) {
14
- find(child);
15
- }
16
- }
17
- };
18
-
19
- export const ssr = (template) => {
20
- const text = render(template);
21
- const h = parse5.parseFragment(text);
22
- find(h);
23
- return parse5.serialize(h);
24
- };
25
-
26
- export const createPage = ({ route, datapaths, head, body, styles }) => {
27
- return ({ config, data, item, headScript, bodyScript }) => {
28
- const isProd = process.env.NODE_ENV === 'production';
29
- const props = { config, data, item };
30
- const headHtml = ssr(head(props));
31
- const bodyTemplate = body(props);
32
- const bodyHtml = ssr(bodyTemplate);
33
- return `
34
- <!DOCTYPE html>
35
- <html lang="${config.lang}">
36
- <head>
37
- <meta charset="utf-8" />
38
- <meta http-equiv="x-ua-compatible" content="ie=edge" />
39
- <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
40
- <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=5.0, shrink-to-fit=no">
41
- <link rel="sitemap" type="application/xml" href="/sitemap.xml" />
42
- <link rel="icon" type="image/png" href="/assets/icon.png" />
43
- ${headHtml}
44
- <style>
45
- ${getTWStyleSheet(bodyTemplate)}
46
- </style>
47
- ${headScript}
48
- </head>
49
- <body>
50
- ${bodyHtml}
51
- <script>
52
- window.__DEV__ = ${!isProd};
53
- window.config = ${JSON.stringify(config)};
54
- window.data = ${JSON.stringify(data)};
55
- window.item = ${JSON.stringify(item)};
56
- </script>
57
- ${bodyScript}
58
- </body>
59
- </html>
60
- `;
61
- };
62
- };
page.test.js DELETED
@@ -1,85 +0,0 @@
1
- import { expect, test } from '@jest/globals';
2
- import { html, css } from './element.js';
3
- import { createPage } from './page.js';
4
-
5
- test('Page', () => {
6
- const route = () => {
7
- const langPart = this.config.lang === 'en' ? '' : `/${this.config.lang}`;
8
- return `${langPart}`;
9
- };
10
- const styles = css({
11
- div: {
12
- color: 'red',
13
- },
14
- });
15
- const head = ({ config }) => {
16
- return html`
17
- <title>${config.title}</title>
18
- <meta name="title" content=${config.title} />
19
- <meta name="description" content=${config.title} />
20
- `;
21
- };
22
-
23
- const body = ({ config }) => {
24
- return html`
25
- <div>
26
- <app-header></app-header>
27
- <main class="flex flex-1 flex-col mt-20 items-center">
28
- <h1 class="text-5xl">${config.title}</h1>
29
- </main>
30
- </div>
31
- `;
32
- };
33
- const renderPage = createPage({
34
- route,
35
- head,
36
- body,
37
- styles,
38
- });
39
- const scripts = '<script type="module"><script>';
40
- const res = renderPage({ config: { lang: 'en', title: '123' }, headScript: scripts, bodyScript: scripts });
41
- expect(res).toEqual(`
42
- <!DOCTYPE html>
43
- <html lang="en">
44
- <head>
45
- <meta charset="utf-8" />
46
- <meta http-equiv="x-ua-compatible" content="ie=edge" />
47
- <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
48
- <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=5.0, shrink-to-fit=no">
49
- <link rel="sitemap" type="application/xml" href="/sitemap.xml" />
50
- <link rel="icon" type="image/png" href="/assets/icon.png" />
51
-
52
- <title>123</title>
53
- <meta name="title" content="123">
54
- <meta name="description" content="123">
55
-
56
- <style>
57
- .div-1gao8uk {
58
- color: red;
59
-
60
- }
61
-
62
-
63
- </style>
64
- <script type="module"><script>
65
- </head>
66
- <body>
67
-
68
- <div>
69
- <app-header></app-header>
70
- <main class="flex flex-1 flex-col mt-20 items-center">
71
- <h1 class="text-5xl">123</h1>
72
- </main>
73
- </div>
74
-
75
- <script>
76
- window.__DEV__ = true;
77
- window.config = {"lang":"en","title":"123"};
78
- window.data = undefined;
79
- window.item = undefined;
80
- </script>
81
- <script type="module"><script>
82
- </body>
83
- </html>
84
- `);
85
- });