~repos /atoms-element
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.
a3cc1df0
—
Peter John 4 years ago
improve element and page
- element.d.ts +19 -24
- element.js +81 -63
- element.test.js +66 -11
- example/app-counter.js +80 -46
- example/index.html +0 -10
- example/index.js +24 -25
- example/main.js +45 -0
- example/modern-normalize.css +271 -0
- package-lock.json +2 -2
- package.json +1 -1
- page.js +36 -53
- page.test.js +52 -53
element.d.ts
CHANGED
|
@@ -1,19 +1,5 @@
|
|
|
1
1
|
import { Config } from './page';
|
|
2
2
|
|
|
3
|
-
export declare type Destructor = () => void | undefined;
|
|
4
|
-
export declare type EffectCallback = () => (void | Destructor);
|
|
5
|
-
export declare type SetStateAction<S> = S | ((prevState: S) => S);
|
|
6
|
-
export declare type Dispatch<A> = (value: A) => void;
|
|
7
|
-
export declare type DispatchWithoutAction = () => void;
|
|
8
|
-
export declare type DependencyList = ReadonlyArray<any>;
|
|
9
|
-
export declare type Reducer<S, A> = (prevState: S, action: A) => S;
|
|
10
|
-
export declare type ReducerWithoutAction<S> = (prevState: S) => S;
|
|
11
|
-
export declare type ReducerState<R extends Reducer<any, any>> = R extends Reducer<infer S, any> ? S : never;
|
|
12
|
-
export declare type ReducerAction<R extends Reducer<any, any>> = R extends Reducer<any, infer A> ? A : never;
|
|
13
|
-
export declare type ReducerStateWithoutAction<R extends ReducerWithoutAction<any>> = R extends ReducerWithoutAction<infer S> ? S : never;
|
|
14
|
-
export declare interface MutableRefObject<T> {
|
|
15
|
-
current: T | null | undefined;
|
|
16
|
-
}
|
|
17
3
|
export declare type Location = {
|
|
18
4
|
readonly ancestorOrigins: DOMStringList;
|
|
19
5
|
hash: string;
|
|
@@ -38,15 +24,24 @@ export default class AtomsElement {
|
|
|
38
24
|
config: Config;
|
|
39
25
|
location: Location;
|
|
40
26
|
attrs: {[key: string]: any};
|
|
27
|
+
state: {[key: string]: any};
|
|
28
|
+
computed: {[key: string]: any};
|
|
41
29
|
styles: () => string;
|
|
42
|
-
useState: <S>(initialState: S | (() => S)) => [S, Dispatch<SetStateAction<S>>];
|
|
43
|
-
useEffect: (effect: EffectCallback, deps?: DependencyList) => void;
|
|
44
|
-
useLayoutEffect: (effect: EffectCallback, deps?: DependencyList) => void;
|
|
45
|
-
useReducer: <R extends ReducerWithoutAction<any>, I>(
|
|
46
|
-
reducer: R,
|
|
47
|
-
initializerArg: I,
|
|
48
|
-
initializer: (arg: I) => ReducerStateWithoutAction<R>
|
|
49
|
-
) => [ReducerStateWithoutAction<R>, DispatchWithoutAction];
|
|
50
|
-
useCallback: <T extends (...args: any[]) => any>(callback: T, deps: DependencyList) => T;
|
|
51
|
-
useMemo: <T>(factory: () => T, deps: DependencyList | undefined) => T;
|
|
52
30
|
}
|
|
31
|
+
|
|
32
|
+
// declare type Colors = 'red' | 'purple' | 'blue' | 'green';
|
|
33
|
+
// declare type Luminance = 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900;
|
|
34
|
+
// declare type BgColor = `bg-${Colors}-${Luminance}`;
|
|
35
|
+
// declare type Distance = 0.5 | 1 | 1.5 | 2 | 2.5 | 3 | 3.5 | 4 | 5 | 6 | 7 | 8 | 9 | 10;
|
|
36
|
+
// declare type Breakpoints = 'xs:' | 'sm:' | 'md:' | 'lg:' | 'xl:' | '';
|
|
37
|
+
// declare type Space = `${Breakpoints}space-${'x' | 'y'}-${Distance}`;
|
|
38
|
+
// declare type ValidClass = Space | BgColor;
|
|
39
|
+
// declare type Tailwind<S> = S extends `${infer Class} ${infer Rest}` ? Class extends ValidClass ? `${Class} ${Tailwind<Rest>}` : never : S extends `${infer Class}` ? Class extends ValidClass ? S : never : never;
|
|
40
|
+
// declare function doSomethingWithTwClass<S>(cls: Tailwind<S>): Tailwind<S>;
|
|
41
|
+
// declare const bad: never;
|
|
42
|
+
// declare const bad2: never;
|
|
43
|
+
// declare const bad3: never;
|
|
44
|
+
// declare const bad4: never;
|
|
45
|
+
// declare const good: "bg-red-400 space-x-4 md:space-x-8";
|
|
46
|
+
// declare const good2: "bg-red-400 space-x-4";
|
|
47
|
+
// declare const good3: "space-x-1.5 bg-blue-200";
|
element.js
CHANGED
|
@@ -3,72 +3,63 @@ import { html, render as litRender, directive, NodePart, AttributePart, Property
|
|
|
3
3
|
const isBrowser = typeof window !== 'undefined';
|
|
4
4
|
export { html, isBrowser };
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
// Taken from emotion
|
|
7
|
-
|
|
8
|
-
class CSSResult {
|
|
9
|
-
|
|
7
|
+
const murmur2 = (str) => {
|
|
8
|
+
var h = 0;
|
|
9
|
+
var k,
|
|
10
|
+
i = 0,
|
|
11
|
+
len = str.length;
|
|
10
|
-
|
|
12
|
+
for (; len >= 4; ++i, len -= 4) {
|
|
13
|
+
k = (str.charCodeAt(i) & 0xff) | ((str.charCodeAt(++i) & 0xff) << 8) | ((str.charCodeAt(++i) & 0xff) << 16) | ((str.charCodeAt(++i) & 0xff) << 24);
|
|
11
|
-
|
|
14
|
+
k = (k & 0xffff) * 0x5bd1e995 + (((k >>> 16) * 0xe995) << 16);
|
|
12
|
-
}
|
|
13
|
-
|
|
15
|
+
k ^= k >>> 24;
|
|
16
|
+
h = ((k & 0xffff) * 0x5bd1e995 + (((k >>> 16) * 0xe995) << 16)) ^ ((h & 0xffff) * 0x5bd1e995 + (((h >>> 16) * 0xe995) << 16));
|
|
14
17
|
}
|
|
15
|
-
|
|
16
|
-
|
|
18
|
+
switch (len) {
|
|
19
|
+
case 3:
|
|
20
|
+
h ^= (str.charCodeAt(i + 2) & 0xff) << 16;
|
|
21
|
+
case 2:
|
|
22
|
+
h ^= (str.charCodeAt(i + 1) & 0xff) << 8;
|
|
23
|
+
case 1:
|
|
17
|
-
|
|
24
|
+
h ^= str.charCodeAt(i) & 0xff;
|
|
25
|
+
h = (h & 0xffff) * 0x5bd1e995 + (((h >>> 16) * 0xe995) << 16);
|
|
18
26
|
}
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Wrap a value for interpolation in a [[`css`]] tagged template literal.
|
|
23
|
-
*
|
|
24
|
-
* This is unsafe because untrusted CSS text can be used to phone home
|
|
25
|
-
* or exfiltrate data to an attacker controlled site. Take care to only use
|
|
26
|
-
|
|
27
|
+
h ^= h >>> 13;
|
|
27
|
-
*/
|
|
28
|
-
|
|
28
|
+
h = (h & 0xffff) * 0x5bd1e995 + (((h >>> 16) * 0xe995) << 16);
|
|
29
|
-
return
|
|
29
|
+
return ((h ^ (h >>> 15)) >>> 0).toString(36);
|
|
30
30
|
};
|
|
31
31
|
|
|
32
|
-
const textFromCSSResult = (value) => {
|
|
33
|
-
if (value instanceof CSSResult) {
|
|
34
|
-
return value.cssText;
|
|
35
|
-
} else if (typeof value === 'number') {
|
|
36
|
-
return value;
|
|
37
|
-
} else {
|
|
38
|
-
throw new Error(
|
|
39
|
-
|
|
32
|
+
const hyphenate = (s) => s.replace(/[A-Z]|^ms/g, '-$&').toLowerCase();
|
|
40
|
-
take care to ensure page security.`,
|
|
41
|
-
);
|
|
42
|
-
}
|
|
43
|
-
};
|
|
44
33
|
|
|
45
|
-
/**
|
|
46
|
-
* Template tag which which can be used with LitElement's [[LitElement.styles |
|
|
47
|
-
* `styles`]] property to set element styles. For security reasons, only literal
|
|
48
|
-
* string values may be used. To incorporate non-literal values [[`unsafeCSS`]]
|
|
49
|
-
* may be used inside a template string part.
|
|
50
|
-
*/
|
|
51
|
-
export const
|
|
34
|
+
export const convertStyles = (prefix, obj, parentClassName, indent = '') => {
|
|
52
|
-
const
|
|
35
|
+
const className = parentClassName || prefix + '-' + murmur2(JSON.stringify(obj)).toString(36);
|
|
36
|
+
const cssText = Object.keys(obj).reduce((acc, key) => {
|
|
37
|
+
const value = obj[key];
|
|
38
|
+
if (typeof value === 'object') {
|
|
39
|
+
acc += '\n ' + indent + convertStyles(prefix, value, key, indent + ' ').cssText;
|
|
40
|
+
} else {
|
|
41
|
+
acc += ' ' + indent + hyphenate(key) + ': ' + value + ';\n';
|
|
42
|
+
}
|
|
43
|
+
return acc;
|
|
44
|
+
}, `${parentClassName ? '' : '.'}${className} {\n`);
|
|
53
|
-
return
|
|
45
|
+
return { className, cssText: cssText + `\n${indent}}` };
|
|
54
46
|
};
|
|
55
47
|
|
|
56
|
-
|
|
48
|
+
export const css = (obj) => {
|
|
49
|
+
Object.keys(obj).forEach((key) => {
|
|
50
|
+
const value = obj[key];
|
|
57
|
-
|
|
51
|
+
const { className, cssText } = convertStyles(key, value);
|
|
58
|
-
// return value && (value as HasCSSSymbol)[cssSymbol] != null;
|
|
59
|
-
|
|
52
|
+
obj[key] = className;
|
|
53
|
+
obj[className] = cssText;
|
|
54
|
+
});
|
|
55
|
+
obj.toString = () => {
|
|
60
|
-
|
|
56
|
+
return Object.keys(obj).reduce((acc, key) => {
|
|
61
|
-
// if (typeof value === "number") return String(value);
|
|
62
|
-
// if (hasCSSSymbol(value)) return value[cssSymbol];
|
|
63
|
-
// throw new TypeError(`${value} is not supported type.`);
|
|
64
|
-
// };
|
|
65
|
-
// export const css = (strings: readonly string[], ...values: unknown[]) => ({
|
|
66
|
-
// [cssSymbol]: strings
|
|
67
|
-
// .slice(1)
|
|
68
|
-
|
|
57
|
+
acc += key.includes('-') ? obj[key] + '\n\n' : '';
|
|
58
|
+
return acc;
|
|
69
|
-
|
|
59
|
+
}, '');
|
|
60
|
+
};
|
|
70
|
-
|
|
61
|
+
return obj;
|
|
71
|
-
|
|
62
|
+
};
|
|
72
63
|
|
|
73
64
|
const lastAttributeNameRegex =
|
|
74
65
|
/([ \x09\x0a\x0c\x0d])([^\0-\x1F\x7F-\x9F "'>=/]+)([ \x09\x0a\x0c\x0d]*=[ \x09\x0a\x0c\x0d]*(?:[^ \x09\x0a\x0c\x0d"'`<>=]*|"[^"]*|'[^']*))$/;
|
|
@@ -275,7 +266,6 @@ export const array = validator('array', (innerType, context, data) => {
|
|
|
275
266
|
}
|
|
276
267
|
});
|
|
277
268
|
|
|
278
|
-
const normalizeCss = `html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}main{display:block}h1{font-size:2em;margin:.67em 0}hr{box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace,monospace;font-size:1em}a{background-color:transparent}abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}img{border-style:none}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:.35em .75em .625em}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details{display:block}summary{display:list-item}template{display:none}[hidden]{display:none}`;
|
|
279
269
|
const fifo = (q) => q.shift();
|
|
280
270
|
const filo = (q) => q.pop();
|
|
281
271
|
const microtask = (flush) => () => queueMicrotask(flush);
|
|
@@ -292,7 +282,7 @@ const task = (flush) => {
|
|
|
292
282
|
const registry = {};
|
|
293
283
|
const BaseElement = isBrowser ? window.HTMLElement : class {};
|
|
294
284
|
|
|
295
|
-
export
|
|
285
|
+
export class AtomsElement extends BaseElement {
|
|
296
286
|
static register() {
|
|
297
287
|
registry[this.name] = this;
|
|
298
288
|
if (isBrowser) {
|
|
@@ -419,11 +409,10 @@ export default class AtomsElement extends BaseElement {
|
|
|
419
409
|
const result = render(template, this);
|
|
420
410
|
if (isBrowser) {
|
|
421
411
|
if (!this.stylesMounted) {
|
|
422
|
-
this.appendChild(document.createElement('style')).textContent =
|
|
412
|
+
this.appendChild(document.createElement('style')).textContent = this.constructor.styles.toString();
|
|
423
413
|
this.stylesMounted = true;
|
|
424
414
|
}
|
|
425
415
|
} else {
|
|
426
|
-
// ${normalizeCss}
|
|
427
416
|
return `
|
|
428
417
|
${result}
|
|
429
418
|
<style>
|
|
@@ -433,3 +422,32 @@ export default class AtomsElement extends BaseElement {
|
|
|
433
422
|
}
|
|
434
423
|
}
|
|
435
424
|
}
|
|
425
|
+
|
|
426
|
+
const createElement = ({ name, attrTypes, stateTypes, computedTypes, styles, render }) => {
|
|
427
|
+
const Element = class extends AtomsElement {
|
|
428
|
+
static name = name();
|
|
429
|
+
|
|
430
|
+
static attrTypes = attrTypes();
|
|
431
|
+
|
|
432
|
+
static stateTypes = stateTypes();
|
|
433
|
+
|
|
434
|
+
static computedTypes = computedTypes();
|
|
435
|
+
|
|
436
|
+
static styles = styles;
|
|
437
|
+
|
|
438
|
+
constructor(ssrAttributes) {
|
|
439
|
+
super(ssrAttributes);
|
|
440
|
+
}
|
|
441
|
+
render() {
|
|
442
|
+
return render({
|
|
443
|
+
attrs: this.attrs,
|
|
444
|
+
state: this.state,
|
|
445
|
+
computed: this.computed,
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
};
|
|
449
|
+
Element.register();
|
|
450
|
+
return { name, attrTypes, stateTypes, computedTypes, styles, render };
|
|
451
|
+
};
|
|
452
|
+
|
|
453
|
+
export default createElement;
|
element.test.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { expect, test, jest } from '@jest/globals';
|
|
2
|
-
import AtomsElement,
|
|
2
|
+
import { AtomsElement, html, render, number, boolean, string, array, object, unsafeHTML, css, classMap } from './element.js';
|
|
3
3
|
|
|
4
4
|
global.__DEV = true;
|
|
5
5
|
|
|
@@ -130,6 +130,60 @@ test('array', () => {
|
|
|
130
130
|
spy.mockRestore();
|
|
131
131
|
});
|
|
132
132
|
|
|
133
|
+
test('css', () => {
|
|
134
|
+
const styles = css({
|
|
135
|
+
button: {
|
|
136
|
+
color: 'magenta',
|
|
137
|
+
fontSize: '10px',
|
|
138
|
+
'@media screen and (min-width:40em)': {
|
|
139
|
+
fontSize: '64px',
|
|
140
|
+
},
|
|
141
|
+
':hover': {
|
|
142
|
+
color: 'black',
|
|
143
|
+
},
|
|
144
|
+
'@media screen and (min-width:56em)': {
|
|
145
|
+
':hover': {
|
|
146
|
+
color: 'navy',
|
|
147
|
+
},
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
container: {
|
|
151
|
+
flex: 1,
|
|
152
|
+
alignItems: 'center',
|
|
153
|
+
justifyContent: 'center',
|
|
154
|
+
},
|
|
155
|
+
});
|
|
156
|
+
expect(styles.toString()).toEqual(`.button-1t9ijgh {
|
|
157
|
+
color: magenta;
|
|
158
|
+
font-size: 10px;
|
|
159
|
+
|
|
160
|
+
@media screen and (min-width:40em) {
|
|
161
|
+
font-size: 64px;
|
|
162
|
+
|
|
163
|
+
}
|
|
164
|
+
:hover {
|
|
165
|
+
color: black;
|
|
166
|
+
|
|
167
|
+
}
|
|
168
|
+
@media screen and (min-width:56em) {
|
|
169
|
+
|
|
170
|
+
:hover {
|
|
171
|
+
color: navy;
|
|
172
|
+
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
.container-1dvem0h {
|
|
178
|
+
flex: 1;
|
|
179
|
+
align-items: center;
|
|
180
|
+
justify-content: center;
|
|
181
|
+
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
`);
|
|
185
|
+
});
|
|
186
|
+
|
|
133
187
|
test('render', async () => {
|
|
134
188
|
const age = 1;
|
|
135
189
|
const data = { name: '123', address: { street: '1' } };
|
|
@@ -258,11 +312,11 @@ test('AtomsElement', async () => {
|
|
|
258
312
|
}),
|
|
259
313
|
};
|
|
260
314
|
|
|
261
|
-
static styles = css
|
|
315
|
+
static styles = css({
|
|
262
|
-
div {
|
|
316
|
+
div: {
|
|
263
|
-
color: red
|
|
317
|
+
color: 'red',
|
|
264
|
-
}
|
|
318
|
+
},
|
|
265
|
-
|
|
319
|
+
});
|
|
266
320
|
|
|
267
321
|
render() {
|
|
268
322
|
const {
|
|
@@ -301,11 +355,12 @@ test('AtomsElement', async () => {
|
|
|
301
355
|
</div>
|
|
302
356
|
|
|
303
357
|
<style>
|
|
304
|
-
|
|
305
|
-
|
|
358
|
+
.div-1gao8uk {
|
|
306
|
-
|
|
359
|
+
color: red;
|
|
360
|
+
|
|
307
|
-
|
|
361
|
+
}
|
|
308
|
-
|
|
362
|
+
|
|
363
|
+
|
|
309
364
|
</style>
|
|
310
365
|
`);
|
|
311
366
|
});
|
example/app-counter.js
CHANGED
|
@@ -1,53 +1,87 @@
|
|
|
1
|
-
import
|
|
1
|
+
import createElement, { html, css, object, number, string } from '../element.js';
|
|
2
2
|
|
|
3
|
-
class Counter extends AtomsElement {
|
|
4
|
-
|
|
3
|
+
const name = () => 'app-counter';
|
|
5
4
|
|
|
6
|
-
|
|
5
|
+
const attrTypes = () => ({
|
|
7
|
-
|
|
6
|
+
name: string().required(),
|
|
8
|
-
|
|
7
|
+
meta: object({
|
|
9
|
-
|
|
8
|
+
start: number(),
|
|
9
|
+
}),
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
const stateTypes = () => ({
|
|
13
|
+
count: number()
|
|
14
|
+
.required()
|
|
15
|
+
.default((attrs) => attrs.meta?.start || 0),
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
const computedTypes = () => ({
|
|
19
|
+
sum: number()
|
|
20
|
+
.required()
|
|
21
|
+
.compute('count', (count) => {
|
|
22
|
+
return count + 10;
|
|
10
23
|
}),
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
static stateTypes = {
|
|
14
|
-
count: number()
|
|
15
|
-
.required()
|
|
16
|
-
.default((attrs) => attrs.meta?.start || 0),
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
static computedTypes = {
|
|
20
|
-
sum: number()
|
|
21
|
-
.required()
|
|
22
|
-
.compute('count', (count) => {
|
|
23
|
-
return count + 10;
|
|
24
|
-
|
|
24
|
+
});
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
static styles = css`
|
|
28
|
-
.container {
|
|
29
|
-
}
|
|
30
|
-
`;
|
|
31
25
|
|
|
26
|
+
const styles = css({
|
|
27
|
+
title: {
|
|
28
|
+
fontSize: '24px',
|
|
29
|
+
marginBottom: '0.5rem',
|
|
30
|
+
},
|
|
32
|
-
|
|
31
|
+
container: {
|
|
32
|
+
display: 'flex',
|
|
33
|
+
flex: 1,
|
|
34
|
+
flexDirection: 'row',
|
|
35
|
+
fontSize: '32px',
|
|
36
|
+
color: 'gray',
|
|
37
|
+
},
|
|
38
|
+
mx: {
|
|
39
|
+
marginLeft: '40px',
|
|
40
|
+
marginRight: '40px',
|
|
41
|
+
},
|
|
42
|
+
button: {
|
|
43
|
+
// margin: 0,
|
|
44
|
+
// padding: 0,
|
|
45
|
+
// cursor: 'pointer',
|
|
46
|
+
// backgroundImage: 'none',
|
|
47
|
+
// '-webkitAppearance': 'button',
|
|
48
|
+
// textTransform: 'none',
|
|
49
|
+
// fontSize: '100%',
|
|
50
|
+
paddingTop: '0.5rem',
|
|
51
|
+
paddingBottom: '0.5rem',
|
|
52
|
+
paddingLeft: '1rem',
|
|
53
|
+
paddingRight: '1rem',
|
|
54
|
+
color: 'rgba(55, 65, 81, 1)',
|
|
55
|
+
borderRadius: '0.25rem',
|
|
56
|
+
backgroundColor: 'rgba(209, 213, 219, 1)',
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
const render = ({ attrs, state, computed }) => {
|
|
33
|
-
|
|
61
|
+
const { name } = attrs;
|
|
34
|
-
|
|
62
|
+
const { count, setCount } = state;
|
|
35
|
-
|
|
63
|
+
const { sum } = computed;
|
|
36
|
-
|
|
64
|
+
|
|
37
|
-
|
|
65
|
+
return html`
|
|
38
|
-
|
|
66
|
+
<div>
|
|
39
|
-
|
|
67
|
+
<div class=${styles.title}>Counter: ${name}</div>
|
|
40
|
-
|
|
68
|
+
<div class=${styles.container}>
|
|
41
|
-
|
|
69
|
+
<button class=${styles.button} @click=${() => setCount((v) => v - 1)}>-</button>
|
|
42
|
-
|
|
70
|
+
<div class=${styles.mx}>
|
|
43
|
-
|
|
71
|
+
<h1>${count}</h1>
|
|
44
|
-
|
|
72
|
+
<h1>${sum}</h1>
|
|
45
|
-
</div>
|
|
46
|
-
<button @click=${() => setCount((v) => v + 1)}>+</button>
|
|
47
73
|
</div>
|
|
74
|
+
<button class=${styles.button} @click=${() => setCount((v) => v + 1)}>+</button>
|
|
48
75
|
</div>
|
|
76
|
+
</div>
|
|
49
|
-
|
|
77
|
+
`;
|
|
50
|
-
|
|
78
|
+
};
|
|
51
|
-
}
|
|
52
79
|
|
|
80
|
+
export default createElement({
|
|
81
|
+
name,
|
|
82
|
+
attrTypes,
|
|
83
|
+
stateTypes,
|
|
53
|
-
|
|
84
|
+
computedTypes,
|
|
85
|
+
styles,
|
|
86
|
+
render,
|
|
87
|
+
});
|
example/index.html
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
<html>
|
|
2
|
-
<body>
|
|
3
|
-
<div>
|
|
4
|
-
<app-counter name="1"></app-counter>
|
|
5
|
-
</div>
|
|
6
|
-
<script type="module">
|
|
7
|
-
import './app-counter.js';
|
|
8
|
-
</script>
|
|
9
|
-
</body>
|
|
10
|
-
</html>
|
example/index.js
CHANGED
|
@@ -1,32 +1,31 @@
|
|
|
1
1
|
import { html, css } from '../element.js';
|
|
2
|
-
import
|
|
2
|
+
import createPage from '../page.js';
|
|
3
3
|
import './app-counter.js';
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
const route = () => {
|
|
6
|
-
route() {
|
|
7
|
-
|
|
6
|
+
return '/counter';
|
|
8
|
-
|
|
7
|
+
};
|
|
9
8
|
|
|
10
|
-
datapaths() {
|
|
11
|
-
|
|
9
|
+
const styles = () => css({});
|
|
12
|
-
}
|
|
13
10
|
|
|
14
|
-
|
|
11
|
+
const head = ({ config }) => {
|
|
15
|
-
|
|
12
|
+
return html`
|
|
13
|
+
<title>${config.title}</title>
|
|
14
|
+
<link href="/modern-normalize.css" rel="stylesheet" as="style" />
|
|
15
|
+
`;
|
|
16
|
-
|
|
16
|
+
};
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
const body = () => {
|
|
19
|
+
return html`
|
|
20
|
+
<div>
|
|
19
|
-
|
|
21
|
+
<app-counter name="1" meta="{'start': 5}"></app-counter>
|
|
22
|
+
</div>
|
|
23
|
+
`;
|
|
20
|
-
|
|
24
|
+
};
|
|
21
25
|
|
|
26
|
+
export default createPage({
|
|
27
|
+
route,
|
|
28
|
+
styles,
|
|
29
|
+
head,
|
|
22
|
-
body
|
|
30
|
+
body,
|
|
23
|
-
return html`
|
|
24
|
-
<div>
|
|
25
|
-
<app-counter name="1"></app-counter>
|
|
26
|
-
</div>
|
|
27
|
-
|
|
31
|
+
});
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const counterPage = new CounterPage({ config: { lang: 'en', title: 'Counter App' } });
|
|
32
|
-
console.log(counterPage.render());
|
example/main.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import http from 'http';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
import path, { dirname } from 'path';
|
|
5
|
+
import renderIndex from './index.js';
|
|
6
|
+
|
|
7
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
8
|
+
const __dirname = dirname(__filename);
|
|
9
|
+
|
|
10
|
+
const port = process.argv[2] || 3000;
|
|
11
|
+
const map = {
|
|
12
|
+
'/element.js': `${__dirname}/../element.js`,
|
|
13
|
+
'/lit-html.js': `${__dirname}/../lit-html.js`,
|
|
14
|
+
'/modern-normalize.css': `${__dirname}/modern-normalize.css`,
|
|
15
|
+
'/app-counter.js': `${__dirname}/app-counter.js`,
|
|
16
|
+
'.js': 'text/javascript',
|
|
17
|
+
'.css': 'text/css',
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
http
|
|
21
|
+
.createServer(function (req, res) {
|
|
22
|
+
if (req.url === '/') {
|
|
23
|
+
res.statusCode = 200;
|
|
24
|
+
res.setHeader('Content-type', 'text/html');
|
|
25
|
+
const html = renderIndex({
|
|
26
|
+
config: { lang: 'en', title: 'Counter App' },
|
|
27
|
+
headScript: '',
|
|
28
|
+
bodyScript: `
|
|
29
|
+
<script type="module">
|
|
30
|
+
import './app-counter.js';
|
|
31
|
+
</script>
|
|
32
|
+
`,
|
|
33
|
+
});
|
|
34
|
+
res.end(html);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
const filename = map[req.url];
|
|
38
|
+
const data = fs.readFileSync(filename);
|
|
39
|
+
const ext = path.parse(filename).ext;
|
|
40
|
+
res.setHeader('Content-type', map[ext] || 'text/plain');
|
|
41
|
+
res.end(data);
|
|
42
|
+
})
|
|
43
|
+
.listen(parseInt(port));
|
|
44
|
+
|
|
45
|
+
console.log(`Server listening on port ${port}`);
|
example/modern-normalize.css
ADDED
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
/*! modern-normalize v1.1.0 | MIT License | https://github.com/sindresorhus/modern-normalize */
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
Document
|
|
5
|
+
========
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
Use a better box model (opinionated).
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
*,
|
|
13
|
+
::before,
|
|
14
|
+
::after {
|
|
15
|
+
box-sizing: border-box;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
1. Correct the line height in all browsers.
|
|
20
|
+
2. Prevent adjustments of font size after orientation changes in iOS.
|
|
21
|
+
3. Use a more readable tab size (opinionated).
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
html {
|
|
25
|
+
line-height: 1.15; /* 1 */
|
|
26
|
+
-webkit-text-size-adjust: 100%; /* 2 */
|
|
27
|
+
-moz-tab-size: 4; /* 3 */
|
|
28
|
+
tab-size: 4; /* 3 */
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/*
|
|
32
|
+
Sections
|
|
33
|
+
========
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
1. Remove the margin in all browsers.
|
|
38
|
+
2. Improve consistency of default fonts in all browsers. (https://github.com/sindresorhus/modern-normalize/issues/3)
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
body {
|
|
42
|
+
margin: 0; /* 1 */
|
|
43
|
+
font-family: system-ui, -apple-system, /* Firefox supports this but not yet system-ui */ 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji',
|
|
44
|
+
'Segoe UI Emoji'; /* 2 */
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/*
|
|
48
|
+
Grouping content
|
|
49
|
+
================
|
|
50
|
+
*/
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
1. Add the correct height in Firefox.
|
|
54
|
+
2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655)
|
|
55
|
+
*/
|
|
56
|
+
|
|
57
|
+
hr {
|
|
58
|
+
height: 0; /* 1 */
|
|
59
|
+
color: inherit; /* 2 */
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/*
|
|
63
|
+
Text-level semantics
|
|
64
|
+
====================
|
|
65
|
+
*/
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
Add the correct text decoration in Chrome, Edge, and Safari.
|
|
69
|
+
*/
|
|
70
|
+
|
|
71
|
+
abbr[title] {
|
|
72
|
+
text-decoration: underline dotted;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
Add the correct font weight in Edge and Safari.
|
|
77
|
+
*/
|
|
78
|
+
|
|
79
|
+
b,
|
|
80
|
+
strong {
|
|
81
|
+
font-weight: bolder;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
1. Improve consistency of default fonts in all browsers. (https://github.com/sindresorhus/modern-normalize/issues/3)
|
|
86
|
+
2. Correct the odd 'em' font sizing in all browsers.
|
|
87
|
+
*/
|
|
88
|
+
|
|
89
|
+
code,
|
|
90
|
+
kbd,
|
|
91
|
+
samp,
|
|
92
|
+
pre {
|
|
93
|
+
font-family: ui-monospace, SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; /* 1 */
|
|
94
|
+
font-size: 1em; /* 2 */
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
Add the correct font size in all browsers.
|
|
99
|
+
*/
|
|
100
|
+
|
|
101
|
+
small {
|
|
102
|
+
font-size: 80%;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
Prevent 'sub' and 'sup' elements from affecting the line height in all browsers.
|
|
107
|
+
*/
|
|
108
|
+
|
|
109
|
+
sub,
|
|
110
|
+
sup {
|
|
111
|
+
font-size: 75%;
|
|
112
|
+
line-height: 0;
|
|
113
|
+
position: relative;
|
|
114
|
+
vertical-align: baseline;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
sub {
|
|
118
|
+
bottom: -0.25em;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
sup {
|
|
122
|
+
top: -0.5em;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/*
|
|
126
|
+
Tabular data
|
|
127
|
+
============
|
|
128
|
+
*/
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297)
|
|
132
|
+
2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016)
|
|
133
|
+
*/
|
|
134
|
+
|
|
135
|
+
table {
|
|
136
|
+
text-indent: 0; /* 1 */
|
|
137
|
+
border-color: inherit; /* 2 */
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/*
|
|
141
|
+
Forms
|
|
142
|
+
=====
|
|
143
|
+
*/
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
1. Change the font styles in all browsers.
|
|
147
|
+
2. Remove the margin in Firefox and Safari.
|
|
148
|
+
*/
|
|
149
|
+
|
|
150
|
+
button,
|
|
151
|
+
input,
|
|
152
|
+
optgroup,
|
|
153
|
+
select,
|
|
154
|
+
textarea {
|
|
155
|
+
font-family: inherit; /* 1 */
|
|
156
|
+
font-size: 100%; /* 1 */
|
|
157
|
+
line-height: 1.15; /* 1 */
|
|
158
|
+
margin: 0; /* 2 */
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
Remove the inheritance of text transform in Edge and Firefox.
|
|
163
|
+
*/
|
|
164
|
+
|
|
165
|
+
button,
|
|
166
|
+
select {
|
|
167
|
+
text-transform: none;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
Correct the inability to style clickable types in iOS and Safari.
|
|
172
|
+
*/
|
|
173
|
+
|
|
174
|
+
button,
|
|
175
|
+
[type='button'],
|
|
176
|
+
[type='reset'],
|
|
177
|
+
[type='submit'] {
|
|
178
|
+
-webkit-appearance: button;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
Remove the inner border and padding in Firefox.
|
|
183
|
+
*/
|
|
184
|
+
|
|
185
|
+
::-moz-focus-inner {
|
|
186
|
+
border-style: none;
|
|
187
|
+
padding: 0;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
Restore the focus styles unset by the previous rule.
|
|
192
|
+
*/
|
|
193
|
+
|
|
194
|
+
:-moz-focusring {
|
|
195
|
+
outline: 1px dotted ButtonText;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
Remove the additional ':invalid' styles in Firefox.
|
|
200
|
+
See: https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737
|
|
201
|
+
*/
|
|
202
|
+
|
|
203
|
+
:-moz-ui-invalid {
|
|
204
|
+
box-shadow: none;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
Remove the padding so developers are not caught out when they zero out 'fieldset' elements in all browsers.
|
|
209
|
+
*/
|
|
210
|
+
|
|
211
|
+
legend {
|
|
212
|
+
padding: 0;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
Add the correct vertical alignment in Chrome and Firefox.
|
|
217
|
+
*/
|
|
218
|
+
|
|
219
|
+
progress {
|
|
220
|
+
vertical-align: baseline;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
Correct the cursor style of increment and decrement buttons in Safari.
|
|
225
|
+
*/
|
|
226
|
+
|
|
227
|
+
::-webkit-inner-spin-button,
|
|
228
|
+
::-webkit-outer-spin-button {
|
|
229
|
+
height: auto;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
1. Correct the odd appearance in Chrome and Safari.
|
|
234
|
+
2. Correct the outline style in Safari.
|
|
235
|
+
*/
|
|
236
|
+
|
|
237
|
+
[type='search'] {
|
|
238
|
+
-webkit-appearance: textfield; /* 1 */
|
|
239
|
+
outline-offset: -2px; /* 2 */
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
Remove the inner padding in Chrome and Safari on macOS.
|
|
244
|
+
*/
|
|
245
|
+
|
|
246
|
+
::-webkit-search-decoration {
|
|
247
|
+
-webkit-appearance: none;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
1. Correct the inability to style clickable types in iOS and Safari.
|
|
252
|
+
2. Change font properties to 'inherit' in Safari.
|
|
253
|
+
*/
|
|
254
|
+
|
|
255
|
+
::-webkit-file-upload-button {
|
|
256
|
+
-webkit-appearance: button; /* 1 */
|
|
257
|
+
font: inherit; /* 2 */
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/*
|
|
261
|
+
Interactive
|
|
262
|
+
===========
|
|
263
|
+
*/
|
|
264
|
+
|
|
265
|
+
/*
|
|
266
|
+
Add the correct display in Chrome and Safari.
|
|
267
|
+
*/
|
|
268
|
+
|
|
269
|
+
summary {
|
|
270
|
+
display: list-item;
|
|
271
|
+
}
|
package-lock.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "atoms-element",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"lockfileVersion": 2,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
|
-
"version": "
|
|
8
|
+
"version": "2.0.0",
|
|
9
9
|
"license": "MIT",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"parse5": "^6.0.1"
|
package.json
CHANGED
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"node": ">=14.0.0"
|
|
34
34
|
},
|
|
35
35
|
"scripts": {
|
|
36
|
-
"example": "
|
|
36
|
+
"example": "node example/main.js",
|
|
37
37
|
"test": "NODE_OPTIONS=--experimental-vm-modules jest"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
page.js
CHANGED
|
@@ -1,57 +1,38 @@
|
|
|
1
1
|
import parse5 from 'parse5';
|
|
2
|
-
import AtomsElement,
|
|
2
|
+
import { AtomsElement, render } from './element.js';
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
const find = (node) => {
|
|
5
|
+
for (const child of node.childNodes) {
|
|
6
|
+
if (AtomsElement.getElement(child.tagName)) {
|
|
5
|
-
|
|
7
|
+
const Clazz = AtomsElement.getElement(child.tagName);
|
|
6
|
-
this.config = config;
|
|
7
|
-
|
|
8
|
+
const instance = new Clazz(child.attrs);
|
|
8
|
-
this.item = item;
|
|
9
|
-
|
|
9
|
+
const res = instance.renderTemplate();
|
|
10
|
+
const frag = parse5.parseFragment(res);
|
|
10
|
-
|
|
11
|
+
child.childNodes.push(...frag.childNodes);
|
|
11
|
-
|
|
12
|
+
}
|
|
12
|
-
|
|
13
|
-
find(node) {
|
|
14
|
-
for (const child of node.childNodes) {
|
|
15
|
-
if (AtomsElement.getElement(child.tagName)) {
|
|
16
|
-
const Clazz = AtomsElement.getElement(child.tagName);
|
|
17
|
-
const instance = new Clazz(child.attrs);
|
|
18
|
-
const res = instance.renderTemplate();
|
|
19
|
-
const frag = parse5.parseFragment(res);
|
|
20
|
-
child.childNodes.push(...frag.childNodes);
|
|
21
|
-
child.childNodes.push({
|
|
22
|
-
nodeName: 'style',
|
|
23
|
-
tagName: 'style',
|
|
24
|
-
attrs: [],
|
|
25
|
-
childNodes: [
|
|
26
|
-
{
|
|
27
|
-
nodeName: '#text',
|
|
28
|
-
value: Clazz.styles.toString(),
|
|
29
|
-
},
|
|
30
|
-
],
|
|
31
|
-
});
|
|
32
|
-
}
|
|
33
|
-
|
|
13
|
+
if (child.childNodes) {
|
|
34
|
-
|
|
14
|
+
find(child);
|
|
35
|
-
}
|
|
36
15
|
}
|
|
37
16
|
}
|
|
17
|
+
};
|
|
38
18
|
|
|
39
|
-
|
|
19
|
+
const ssr = (template) => {
|
|
40
|
-
|
|
20
|
+
const text = render(template);
|
|
41
|
-
|
|
21
|
+
const h = parse5.parseFragment(text);
|
|
42
|
-
|
|
22
|
+
find(h);
|
|
43
|
-
|
|
23
|
+
return parse5.serialize(h);
|
|
44
|
-
|
|
24
|
+
};
|
|
45
25
|
|
|
26
|
+
const createPage = ({ route, datapaths, head, body, styles }) => {
|
|
46
|
-
|
|
27
|
+
return ({ config, data, item, headScript, bodyScript }) => {
|
|
47
28
|
const isProd = process.env.NODE_ENV === 'production';
|
|
48
|
-
const props = { config
|
|
29
|
+
const props = { config, data, item };
|
|
49
|
-
const headHtml =
|
|
30
|
+
const headHtml = ssr(head(props));
|
|
31
|
+
const bodyHtml = ssr(body(props));
|
|
50
|
-
const stylesCss =
|
|
32
|
+
const stylesCss = styles(props);
|
|
51
|
-
const bodyHtml = this.ssr(this.body(props));
|
|
52
33
|
return `
|
|
53
34
|
<!DOCTYPE html>
|
|
54
|
-
<html lang="${
|
|
35
|
+
<html lang="${config.lang}">
|
|
55
36
|
<head>
|
|
56
37
|
<meta charset="utf-8" />
|
|
57
38
|
<meta http-equiv="x-ua-compatible" content="ie=edge" />
|
|
@@ -61,21 +42,23 @@ export default class Page {
|
|
|
61
42
|
<link rel="icon" type="image/png" href="/assets/icon.png" />
|
|
62
43
|
${headHtml}
|
|
63
44
|
<style>
|
|
64
|
-
${stylesCss}
|
|
45
|
+
${stylesCss.toString()}
|
|
65
46
|
</style>
|
|
66
|
-
${
|
|
47
|
+
${headScript}
|
|
67
48
|
</head>
|
|
68
49
|
<body>
|
|
69
50
|
${bodyHtml}
|
|
70
51
|
<script>
|
|
71
52
|
window.__DEV__ = ${!isProd};
|
|
72
|
-
window.config = ${JSON.stringify(
|
|
53
|
+
window.config = ${JSON.stringify(config)};
|
|
73
|
-
window.data = ${JSON.stringify(
|
|
54
|
+
window.data = ${JSON.stringify(data)};
|
|
74
|
-
window.item = ${JSON.stringify(
|
|
55
|
+
window.item = ${JSON.stringify(item)};
|
|
75
56
|
</script>
|
|
76
|
-
${
|
|
57
|
+
${bodyScript}
|
|
77
58
|
</body>
|
|
78
59
|
</html>
|
|
79
60
|
`;
|
|
80
|
-
}
|
|
61
|
+
};
|
|
81
|
-
}
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export default createPage;
|
page.test.js
CHANGED
|
@@ -1,46 +1,44 @@
|
|
|
1
1
|
import { expect, test } from '@jest/globals';
|
|
2
2
|
import { html, css } from './element.js';
|
|
3
|
-
import
|
|
3
|
+
import createPage from './page.js';
|
|
4
4
|
|
|
5
5
|
test('Page', () => {
|
|
6
|
-
|
|
6
|
+
const route = () => {
|
|
7
|
-
route() {
|
|
8
|
-
|
|
7
|
+
const langPart = this.config.lang === 'en' ? '' : `/${this.config.lang}`;
|
|
9
|
-
|
|
8
|
+
return `${langPart}`;
|
|
10
|
-
|
|
9
|
+
};
|
|
10
|
+
const styles = () =>
|
|
11
|
+
css({
|
|
12
|
+
div: {
|
|
13
|
+
color: 'red',
|
|
14
|
+
},
|
|
15
|
+
});
|
|
16
|
+
const head = ({ config }) => {
|
|
17
|
+
return html`
|
|
18
|
+
<title>${config.title}</title>
|
|
19
|
+
<meta name="title" content=${config.title} />
|
|
20
|
+
<meta name="description" content=${config.title} />
|
|
21
|
+
`;
|
|
22
|
+
};
|
|
11
23
|
|
|
12
|
-
|
|
24
|
+
const body = ({ config }) => {
|
|
13
|
-
|
|
25
|
+
return html`
|
|
14
|
-
div {
|
|
15
|
-
|
|
26
|
+
<div>
|
|
27
|
+
<app-header></app-header>
|
|
28
|
+
<main class="flex flex-1 flex-col mt-20 items-center">
|
|
29
|
+
<h1 class="text-5xl">${config.title}</h1>
|
|
30
|
+
</main>
|
|
16
|
-
|
|
31
|
+
</div>
|
|
17
|
-
|
|
32
|
+
`;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
head() {
|
|
21
|
-
const { title } = this.config;
|
|
22
|
-
return html`
|
|
23
|
-
<title>${title}</title>
|
|
24
|
-
<meta name="title" content=${title} />
|
|
25
|
-
<meta name="description" content=${title} />
|
|
26
|
-
|
|
33
|
+
};
|
|
34
|
+
const renderPage = createPage({
|
|
35
|
+
route,
|
|
27
|
-
|
|
36
|
+
head,
|
|
28
|
-
|
|
29
|
-
body
|
|
37
|
+
body,
|
|
30
|
-
const { title } = this.config;
|
|
31
|
-
return html`
|
|
32
|
-
|
|
38
|
+
styles,
|
|
33
|
-
<app-header></app-header>
|
|
34
|
-
<main class="flex flex-1 flex-col mt-20 items-center">
|
|
35
|
-
<h1 class="text-5xl">${title}</h1>
|
|
36
|
-
</main>
|
|
37
|
-
</div>
|
|
38
|
-
|
|
39
|
+
});
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
40
|
const scripts = '<script type="module"><script>';
|
|
42
|
-
const
|
|
41
|
+
const res = renderPage({ config: { lang: 'en', title: '123' }, headScript: scripts, bodyScript: scripts });
|
|
43
|
-
const res = mainPage.render();
|
|
44
42
|
expect(res).toEqual(`
|
|
45
43
|
<!DOCTYPE html>
|
|
46
44
|
<html lang="en">
|
|
@@ -52,28 +50,29 @@ test('Page', () => {
|
|
|
52
50
|
<link rel="sitemap" type="application/xml" href="/sitemap.xml" />
|
|
53
51
|
<link rel="icon" type="image/png" href="/assets/icon.png" />
|
|
54
52
|
|
|
55
|
-
|
|
53
|
+
<title>123</title>
|
|
56
|
-
|
|
54
|
+
<meta name="title" content="123">
|
|
57
|
-
|
|
55
|
+
<meta name="description" content="123">
|
|
58
|
-
|
|
56
|
+
|
|
59
57
|
<style>
|
|
60
|
-
|
|
61
|
-
|
|
58
|
+
.div-1gao8uk {
|
|
62
|
-
|
|
59
|
+
color: red;
|
|
60
|
+
|
|
63
|
-
|
|
61
|
+
}
|
|
64
|
-
|
|
62
|
+
|
|
63
|
+
|
|
65
64
|
</style>
|
|
66
65
|
<script type="module"><script>
|
|
67
66
|
</head>
|
|
68
67
|
<body>
|
|
69
68
|
|
|
70
|
-
|
|
69
|
+
<div>
|
|
71
|
-
|
|
70
|
+
<app-header></app-header>
|
|
72
|
-
|
|
71
|
+
<main class="flex flex-1 flex-col mt-20 items-center">
|
|
73
|
-
|
|
72
|
+
<h1 class="text-5xl">123</h1>
|
|
74
|
-
|
|
73
|
+
</main>
|
|
75
|
-
|
|
74
|
+
</div>
|
|
76
|
-
|
|
75
|
+
|
|
77
76
|
<script>
|
|
78
77
|
window.__DEV__ = true;
|
|
79
78
|
window.config = {"lang":"en","title":"123"};
|