so://phie

figure

The vanilla alternative for writing JSX-based React applications
git clone git://soophie.de/figure

blob: 41c4096798f540df98dff69cfad315978efc4e5b (5915 bytes)[doc][blame][raw]

1import { CreateElement, Option, Options, Props, ReactElement, ReactFunction, Refs, Slices, Values } from './types.ts';

2

3const parser = new DOMParser();

4let count = 1000;

5let create: Option<CreateElement> = null;

6

7function figure({ createElement }: Options): void {

8 create = createElement;

9}

10

11function html(slices: Slices, ...values: Values): ReactElement {

12 const elements = parse(slices, ...values);

13 if (elements.length === 0) {

14 throw 'No DOM element was returned!';

15 }

16 if (elements.length > 1) {

17 console.warn('Only one DOM element can be returned!');

18 }

19 return elements[0];

20}

21

22function parse(slices: Slices, ...values: Values): ReactElement[] {

23 const [html, refs] = compose(slices, values);

24 let dom;

25 try {

26 dom = parser.parseFromString(html, 'text/html');

27 }

28 catch (error) {

29 console.error(error);

30 throw 'Invalid DOM structure!';

31 }

32 const nodes = [...dom.head.childNodes, ...dom.body.childNodes];

33 return nodes.map((node) => render(node, refs));

34}

35

36/**

37 * Joins the template literal slices together and replaces the values with references.

38 * The values are being mapped to there corresponding references and with the populated

39 * HTML string returned.

40 *

41 * @param {string[]} slices - The template literal slices

42 * @param {any[]} values - The template literal values

43 * @return {any[]} The joined HTML string and the values mapped to there references.

44 */

45function compose(slices: Slices, values: Values): [string, Refs] {

46 if (slices == null) {

47 // handles dyn function without body

48 return ['', {}];

49 }

50 const refs: Refs = {};

51 let slice = '';

52 for (let i = 0; i < slices.length; i++) {

53 slice += slices[i];

54 if (values[i] != null) {

55 const uid = `$seg-${count++}`;

56 refs[uid] = values[i];

57 slice += uid ?? '';

58 }

59 }

60 return [slice.trim(), refs];

61}

62

63/**

64 * Injects the values into the corresponding reference locations of the string.

65 *

66 * @param {string} slice - The string containing references

67 * @param {any[]} refs - The values mapped to there references

68 * @return {any[]} The string populated with the passed values

69 */

70function feed(slice: string, refs: Refs): ReactElement[] {

71 const expr = /\$seg-\d+/g;

72 const elements: ReactElement[] = [];

73 let match = null;

74 let last = 0;

75 while ((match = expr.exec(slice)) !== null) {

76 const index = match.index;

77 const uid = match[0];

78 elements.push(slice.substring(last, index));

79 let value = refs[uid];

80 if (value instanceof Function) {

81 value = value();

82 }

83 elements.push(value);

84 last = index + uid.length;

85 }

86 elements.push(slice.substring(last));

87 return elements;

88}

89

90function dyn(element: ReactFunction, props: Props): (slices: Slices, ...values: Values) => ReactElement {

91 return (slices: Slices, ...values: Values) => {

92 if (create == null) {

93 throw 'Invalid state! Figure was not initialized!';

94 }

95 return create(element, props, ...parse(slices, ...values));

96 };

97}

98

99function render(node: Node, refs: Refs): ReactElement[] {

100 if (node.nodeType === Node.TEXT_NODE) {

101 const text = node as Text;

102 if (text.textContent == null) {

103 return [];

104 }

105 return feed(text.textContent, refs);

106 }

107 if (node.nodeType === Node.COMMENT_NODE) {

108 return [];

109 }

110 const element = node as HTMLElement;

111 const tag = element.tagName;

112 const attributes: Props = {};

113 for (const attribute of element.attributes) {

114 const key = attribute.name;

115 const slice = attribute.textContent;

116 if (slice == null) {

117 continue;

118 }

119 let isDynamic = false;

120 for (const ref in refs) {

121 if (slice === ref) {

122 let match = null;

123 if ((match = /^@(\w+)$/.exec(key)) !== null) {

124 const event = match[1];

125 attributes[`on${event.substring(0, 1).toUpperCase()}${event.substring(1)}`] = refs[ref];

126 }

127 else if ((match = /^\[(\w+)\]$/.exec(key)) !== null) {

128 const property = match[1];

129 attributes[property] = refs[ref];

130 }

131 else {

132 attributes[key] = refs[ref];

133 }

134 isDynamic = true;

135 break;

136 }

137 }

138 if (!isDynamic) {

139 if (key === 'style') {

140 const styles: Record<string, string> = {};

141 for (const item of slice.split(';')) {

142 if (item.trim().length === 0) {

143 break;

144 }

145 let [key, value] = item.split(':');

146 key = key.trim();

147 let match = null;

148 if ((match = /-(\w)/.exec(key)) !== null) {

149 const char = match[1];

150 key = key.replace(`-${char}`, char.toUpperCase());

151 }

152 value = value.trim();

153 styles[key] = feed(value, refs).join('');

154 }

155 attributes[key] = styles;

156 }

157 else {

158 attributes[key] = feed(slice, refs).join('');

159 }

160 }

161 }

162 const children: ReactElement[] = [];

163 (node.childNodes ?? []).forEach((child) => children.push(...render(child, refs)));

164 if (create == null) {

165 throw 'Invalid state! Figure was not initialized!';

166 }

167 return [create(tag, attributes, ...children)];

168}

169

170function css(slices: Slices, ...values: Values): ReactElement {

171 const [css, refs] = compose(slices, values);

172 return html`

173 <style type="text/css">

174 ${feed(css, refs).join('')}

175 </style>

176 `;

177}

178

179function style(slices: Slices, ...values: Values): Record<string, string> {

180 const [css, refs] = compose(slices, values);

181 const styles: Record<string, string> = {};

182 for (const item of css.split(';')) {

183 if (item.trim().length === 0) {

184 break;

185 }

186 let [key, value] = item.split(':');

187 key = key.trim();

188 let match = null;

189 if ((match = /-(\w)/.exec(key)) !== null) {

190 const char = match[1];

191 key = key.replace(`-${char}`, char.toUpperCase());

192 }

193 value = value.trim();

194 styles[key] = feed(value, refs).join('');

195 }

196 return styles;

197}

198

199export { figure, html, dyn, css, style };