├── .babelrc ├── .browserslistrc ├── .gitignore ├── README.md ├── images └── memory │ ├── arcade.png │ ├── billiard.png │ ├── cards.png │ ├── chess.png │ ├── game-controller.png │ ├── game-controller2.png │ ├── gaming.png │ ├── ghost.png │ ├── question.png │ ├── sword.png │ └── tetris.png ├── package-lock.json ├── package.json ├── src.html ├── arrays.html ├── calculator.html ├── dialog.html ├── fetch.html ├── fizzbuzz.html ├── memory-game.html ├── objects.html └── todo.html ├── src ├── exec.js ├── fetch.js ├── for2.js ├── index.js ├── lib │ ├── children.js │ ├── events.js │ ├── range.js │ ├── setAttribute.js │ └── setVisibility.js ├── scopes.js ├── stylesheet.js ├── subroutine.js └── values.js ├── umd ├── arrays.html ├── calculator.html ├── dialog.html ├── fetch.html ├── fizzbuzz.html ├── html-lang.js ├── memory-game.html ├── objects.html └── todo.html ├── webpack.config.js └── what-the-hell-are-you-doing.jpg /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [["@babel/preset-env", { "useBuiltIns": false }]], 3 | "plugins": [ 4 | [ 5 | "@babel/plugin-transform-runtime", 6 | { 7 | "regenerator": false 8 | } 9 | ], 10 | "babel-plugin-transform-async-to-promises" 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /.browserslistrc: -------------------------------------------------------------------------------- 1 | defaults -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # HTML is a Programming Language! 2 | 3 | HTML is now a Turing complete programming language with the `html-lang` library. 4 | 5 | `html-lang` is experimental and currently in development. So that means don't go slipping this into your production site! Proceed at your own risk! 6 | 7 | `html-lang` features no build step, no bundling, no webpack configs. Open up your HTML, include the script and start writing HTML. 8 | 9 | At only 5.7kB (gzip) in size, `html-lang` is a tiny but powerful framework. 10 | 11 | ## Why? 12 | 13 | Most frameworks are designed to be template engines. These typically mix their template language and JavaScript. Meaning you write and tie together both. 14 | 15 | While `html-lang` is similar to quite a bit of existing tech out there, the focus of this library is to bring that programming feel to HTML. 16 | 17 | This allows you to stay inside the framework. 18 | 19 | ## Install 20 | 21 | Include the script tag into the `` section of your HTML. 22 | 23 | ```html 24 | 25 | ``` 26 | 27 | ## Variables 28 | 29 | Variables are globally scoped. 30 | 31 | ```html 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | ``` 47 | 48 | ## Computed Values 49 | 50 | The computed value syntax has a `:?` at the end of the Variable name. 51 | 52 | ```html 53 | 54 | 55 | 56 | 57 | 58 | ``` 59 | 60 | ## Output 61 | 62 | Display a Variable 63 | 64 | ```html 65 | 66 | 67 | 68 | 69 | 70 | ``` 71 | 72 | ## If / Conditional 73 | 74 | ```html 75 | X is GREATER than 10! 76 | ``` 77 | 78 | An `else` can follow an `if` element. 79 | 80 | ```html 81 | X is GREATER than 10! 82 | X is NOT GREATER than 10! 83 | ``` 84 | 85 | ## Loops 86 | 87 | A `for-of` loop will loop through all the items in the collection, setting the item to the variable specified. 88 | 89 | ```html 90 | 91 | 92 | 93 | 94 | 97 | 98 | 99 | 100 | 101 | ``` 102 | 103 | A `for-in` loop will loop through all the items in the collection, setting the index to the variable specified. 104 | 105 | ```html 106 | 107 | 108 | 109 | 110 | 113 | 114 | 115 | 116 | 117 | ``` 118 | 119 | `for-of` and `for-in` can be combined together if they both point to the same collection. 120 | 121 | ```html 122 | 123 | 124 | 125 | 126 | 129 | 130 | 131 | 132 | 133 | ``` 134 | 135 | ## Subroutines 136 | 137 | A Subroutine can be created to run common tasks. Subroutines take no arguments and return no values, but do have access to the variables. 138 | 139 | ## Fetching Data 140 | 141 | ```html 142 | 143 | 144 | 145 | 146 | 151 | 152 | 153 | Loading... 154 | 155 | 156 | Error: 157 | 158 | 159 | 160 |
name:
161 |
gender:
162 |
height: cm
163 |
164 | ``` 165 | 166 | ## Examples 167 | 168 | ### FizzBuzz 169 | 170 | ```html 171 | 172 |
173 | 174 | 175 | 176 |
177 | 178 | FizzBuzz 179 | 180 | 181 | 182 | 183 | 184 |
185 |
186 | ``` 187 | 188 | ### TODO List 189 | 190 | ```html 191 | 192 | 193 | 194 | 195 | 196 |
197 | 198 |
199 | 205 |
206 | ``` 207 | 208 | ### Dialog 209 | 210 | ```html 211 | 212 | 213 |
214 | 215 | 216 | 217 |

Hello Dialog!

218 |
219 |
220 | ``` 221 | 222 | ### More Examples 223 | 224 | - [Memory Game](https://codepen.io/joelnet/pen/BadymQz) 225 | - [Calculator](https://codepen.io/joelnet/pen/porzEPv) 226 | 227 | ## Alternatives 228 | 229 | - [Alpine.js](https://alpinejs.dev/) — Alpine is a rugged, minimal tool for composing behavior directly in your markup. Think of it like jQuery for the modern web. Plop in a script tag and get going. 230 | -------------------------------------------------------------------------------- /images/memory/arcade.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelnet/html-lang/ad478b7f562cac32f5e951ff00dab52832437535/images/memory/arcade.png -------------------------------------------------------------------------------- /images/memory/billiard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelnet/html-lang/ad478b7f562cac32f5e951ff00dab52832437535/images/memory/billiard.png -------------------------------------------------------------------------------- /images/memory/cards.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelnet/html-lang/ad478b7f562cac32f5e951ff00dab52832437535/images/memory/cards.png -------------------------------------------------------------------------------- /images/memory/chess.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelnet/html-lang/ad478b7f562cac32f5e951ff00dab52832437535/images/memory/chess.png -------------------------------------------------------------------------------- /images/memory/game-controller.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelnet/html-lang/ad478b7f562cac32f5e951ff00dab52832437535/images/memory/game-controller.png -------------------------------------------------------------------------------- /images/memory/game-controller2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelnet/html-lang/ad478b7f562cac32f5e951ff00dab52832437535/images/memory/game-controller2.png -------------------------------------------------------------------------------- /images/memory/gaming.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelnet/html-lang/ad478b7f562cac32f5e951ff00dab52832437535/images/memory/gaming.png -------------------------------------------------------------------------------- /images/memory/ghost.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelnet/html-lang/ad478b7f562cac32f5e951ff00dab52832437535/images/memory/ghost.png -------------------------------------------------------------------------------- /images/memory/question.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelnet/html-lang/ad478b7f562cac32f5e951ff00dab52832437535/images/memory/question.png -------------------------------------------------------------------------------- /images/memory/sword.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelnet/html-lang/ad478b7f562cac32f5e951ff00dab52832437535/images/memory/sword.png -------------------------------------------------------------------------------- /images/memory/tetris.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelnet/html-lang/ad478b7f562cac32f5e951ff00dab52832437535/images/memory/tetris.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@joelnet/html-lang", 3 | "version": "0.1.1", 4 | "description": "HTML is a Programming Language!", 5 | "keywords": [ 6 | "html", 7 | "template" 8 | ], 9 | "author": "Joel Thoms", 10 | "license": "MIT", 11 | "scripts": { 12 | "clean": "rm -rf umd", 13 | "prebuild": "npm run clean", 14 | "build": "webpack --mode production", 15 | "dev": "webpack serve --mode development" 16 | }, 17 | "devDependencies": { 18 | "@babel/core": "^7.15.5", 19 | "@babel/plugin-transform-runtime": "^7.15.0", 20 | "@babel/preset-env": "^7.15.6", 21 | "babel-loader": "^8.2.2", 22 | "babel-plugin-transform-async-to-promises": "^0.8.15", 23 | "glob": "^7.2.0", 24 | "html-webpack-plugin": "^5.3.2", 25 | "serve": "^12.0.1", 26 | "webpack": "^5.56.0", 27 | "webpack-cli": "^4.8.0", 28 | "webpack-dev-server": "^4.3.0" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src.html/arrays.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 |
11 | 12 | 13 | 14 |
15 | list[] = 16 | 17 |
18 |
19 |
20 | 21 | 22 | -------------------------------------------------------------------------------- /src.html/calculator.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | 30 | Calculator 31 | 32 | 33 |
34 |

Calculator

35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 |
74 | 75 | 76 | 77 | 78 | 79 |
80 | 81 |
82 | 83 | 84 | 85 | 86 |
87 |
88 | 89 | 90 | 91 | 92 |
93 |
94 | 95 | 96 | 97 | 98 |
99 |
100 | 103 | 104 | 105 | 106 |
107 |
108 | 109 | 110 | -------------------------------------------------------------------------------- /src.html/dialog.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 27 | 28 | 29 |
30 |

Dialog

31 | 32 | 33 | 34 |
35 | 36 | 37 | 38 |

Hello Dialog!

39 |
40 |
41 |
42 | 43 | 44 | -------------------------------------------------------------------------------- /src.html/fetch.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 |
11 |

Fetch

12 | 13 | 18 | Loading... 19 | Error: 20 | 21 |
name:
22 |
gender:
23 |
height: cm
24 |
25 |
26 | 27 | 28 | -------------------------------------------------------------------------------- /src.html/fizzbuzz.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 |
11 | 12 |
13 | FizzBuzz 14 | 15 | 16 | 17 |
18 |
19 | 20 | 21 | -------------------------------------------------------------------------------- /src.html/memory-game.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 34 | Memory Game 35 | 36 | 37 |
38 |

Memory Game

39 | 40 | 41 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 |
67 | 68 |
69 |
70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 |
95 | 96 |

Congratulations! You won in moves!

97 | 98 |
99 |
100 | 101 |
102 |
103 |
104 |
105 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 121 | 122 | 123 | 124 | 125 | 126 | 127 |
128 |
129 |
130 |
131 |
132 | 133 | 134 | -------------------------------------------------------------------------------- /src.html/objects.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 |
11 | 12 |
x.abc =
13 |
14 | 15 | 16 | -------------------------------------------------------------------------------- /src.html/todo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | TODO 8 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 |
25 | 33 |
34 | 35 | 36 | -------------------------------------------------------------------------------- /src/exec.js: -------------------------------------------------------------------------------- 1 | import { 2 | appendChildren, 3 | cloneChildren, 4 | removeAllChildren, 5 | } from "./lib/children"; 6 | import { computeValue } from "./values"; 7 | 8 | const isCmdAttribute = (attr) => attr.name === "cmd"; 9 | 10 | // runs 11 | const runExecSub = (element, name) => { 12 | const sub = document.querySelector(`SUB[name=${name}]`); 13 | if (!sub) { 14 | throw new Error(`SUB "${name}" was expected to exist.`); 15 | } 16 | 17 | const children = cloneChildren(sub.children); 18 | 19 | removeAllChildren(element); 20 | appendChildren(element, children); 21 | 22 | return true; 23 | }; 24 | 25 | export const runExec = (element) => { 26 | const attribute = [...element.attributes].find(isCmdAttribute); 27 | 28 | if (attribute.value.match(/^\$.*/g)) { 29 | return runExecSub(element, attribute.value.substring(1)); 30 | } 31 | 32 | return computeValue(element, attribute.value); 33 | }; 34 | -------------------------------------------------------------------------------- /src/fetch.js: -------------------------------------------------------------------------------- 1 | const isJson = (response) => { 2 | const contentType = response.headers.get("content-type"); 3 | return contentType && contentType.includes("application/json"); 4 | }; 5 | 6 | export const fetchJson = async (...args) => { 7 | const response = await fetch(...args); 8 | if (!response.ok) { 9 | throw new Error( 10 | `HTTP Error Response: ${response.status} ${response.statusText}` 11 | ); 12 | } 13 | 14 | if (isJson(response)) { 15 | return { 16 | response, 17 | json: await response.json(), 18 | }; 19 | } 20 | 21 | return { response, json: null }; 22 | }; 23 | -------------------------------------------------------------------------------- /src/for2.js: -------------------------------------------------------------------------------- 1 | import { 2 | appendChildren, 3 | cloneChildren, 4 | removeAllChildren, 5 | } from "./lib/children"; 6 | import { saveScope } from "./scopes"; 7 | import { computeValue } from "./values"; 8 | 9 | const isForOfAttribute = (attribute) => attribute.name.endsWith(":for:of"); 10 | const isForInAttribute = (attribute) => attribute.name.endsWith(":for:in"); 11 | const isForAttribute = (attribute) => 12 | isForOfAttribute(attribute) || isForInAttribute(attribute); 13 | 14 | const getForAttributes = (element) => 15 | [...element.attributes].filter(isForAttribute); 16 | 17 | export const isFor = (element) => getForAttributes(element).length > 0; 18 | 19 | const getValName = ({ name }) => name.substring(0, name.length - 7); 20 | 21 | const childrenCache = new WeakMap(); 22 | 23 | const cacheAllChildren = (element) => { 24 | if (!childrenCache.has(element)) { 25 | childrenCache.set(element, [...element.children]); 26 | } 27 | }; 28 | 29 | const saveScopes = (elements, scope) => { 30 | for (let element of elements) { 31 | saveScope(element, scope); 32 | } 33 | }; 34 | 35 | export const runFor2 = (element) => { 36 | const attributes = getForAttributes(element); 37 | const forOfAttribute = attributes.find(isForOfAttribute); 38 | const forInAttribute = attributes.find(isForInAttribute); 39 | const ofName = forOfAttribute ? getValName(forOfAttribute) : null; 40 | const inName = forInAttribute ? getValName(forInAttribute) : null; 41 | 42 | cacheAllChildren(element); 43 | removeAllChildren(element); 44 | 45 | const items = computeValue(element, forOfAttribute.value); 46 | const children = childrenCache.get(element); 47 | 48 | let index = 0; 49 | for (let item of items) { 50 | const clones = cloneChildren(children); 51 | const scope = { 52 | ...(ofName && { [ofName]: item }), 53 | ...(inName && { [inName]: index }), 54 | }; 55 | 56 | saveScopes(clones, scope); 57 | appendChildren(element, clones); 58 | 59 | index = index + 1; 60 | } 61 | }; 62 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import { runExec } from "./exec"; 2 | import { fetchJson } from "./fetch"; 3 | import { addEventListener, removeAllEventListeners } from "./lib/events"; 4 | import { createScope, saveScope } from "./scopes"; 5 | import { injectStyleSheet } from "./stylesheet"; 6 | import { runSub } from "./subroutine"; 7 | import { 8 | attributeValue, 9 | computeValue, 10 | globals, 11 | globalsSet, 12 | watchers, 13 | } from "./values"; 14 | import { setVisibility } from "./lib/setVisibility"; 15 | import { isFor, runFor2 } from "./for2"; 16 | import { setAttribute } from "./lib/setAttribute"; 17 | 18 | const childrenCache = new WeakMap(); 19 | 20 | const getSiblings = (element) => 21 | [...element.parentNode.children].filter((el) => el !== element); 22 | 23 | const saveVal = async (element) => { 24 | for (let attribute of element.attributes) { 25 | let name = attribute.name; 26 | let value = attribute.value; 27 | 28 | if ( 29 | name.endsWith(":number") || 30 | name.endsWith(":bool") || 31 | name.endsWith(":object") 32 | ) { 33 | ({ name, value } = attributeValue(element, attribute)); 34 | } 35 | 36 | if (name.endsWith(":fetch")) { 37 | name = name.substring(0, name.length - 6); 38 | value = { loading: true, error: null, response: null, json: null }; 39 | 40 | const url = computeValue(element, attribute.value); 41 | fetchJson(url) 42 | .then(({ response, json }) => { 43 | const value = { loading: false, error: null, response, json }; 44 | globalsSet(element, name, value); 45 | }) 46 | .catch((error) => { 47 | const value = { loading: false, error, response: null, json: null }; 48 | globalsSet(element, name, value); 49 | }) 50 | .then(() => { 51 | const siblings = getSiblings(element); 52 | processChildren(siblings); 53 | }); 54 | } 55 | 56 | if (name.endsWith(":?")) { 57 | name = name.substring(0, name.length - 2); 58 | value = computeValue(element, value); 59 | } 60 | 61 | globalsSet(element, name, value, true); 62 | } 63 | }; 64 | 65 | const runIfElement = (element) => { 66 | if (!element.hasAttribute("test")) return false; 67 | const truthy = computeValue(element, element.getAttribute("test")); 68 | 69 | // ELSE? 70 | const next = element.nextElementSibling; 71 | if (next?.tagName === "ELSE") { 72 | next.removeAttribute("test"); 73 | setAttribute(next, "test", truthy ? "false" : "true"); 74 | } 75 | 76 | return !!truthy; 77 | }; 78 | 79 | const runWhileElement = async (element) => { 80 | let scope = createScope(element); 81 | saveScope(element, scope); 82 | let truthy = runIfElement(element); 83 | 84 | // cache children 85 | if (!childrenCache.has(element)) { 86 | childrenCache.set(element, [...element.children]); 87 | } 88 | 89 | // remove elements real children 90 | while (element.hasChildNodes()) { 91 | element.removeChild(element.lastChild); 92 | } 93 | 94 | setVisibility(element, truthy); 95 | 96 | const children = childrenCache.get(element); 97 | while (truthy) { 98 | const clones = children.map((child) => child.cloneNode(true)); 99 | for (let child of clones) { 100 | scope = { ...scope }; 101 | if (child.tagName !== "VAL") { 102 | // VAL has no scope 103 | saveScope(child, scope); 104 | } else { 105 | saveScope(element, scope); 106 | } 107 | element.appendChild(child); 108 | } 109 | await processChildren(clones); 110 | truthy = runIfElement(element); 111 | } 112 | 113 | return false; 114 | }; 115 | 116 | const parseAttributes = (element) => { 117 | removeAllEventListeners(element); 118 | 119 | for (let attribute of element.attributes) { 120 | if (attribute.name === "#text") { 121 | element.textContent = computeValue(element, attribute.value); 122 | } else if (attribute.name.startsWith("#")) { 123 | const value = computeValue(element, attribute.value); 124 | setAttribute(element, attribute.name.substring(1), value); 125 | } 126 | // TODO: bind:value is new. deprecate onchange:set. 127 | if (attribute.name === "onchange:set" || attribute.name === "bind:value") { 128 | globalsSet(element, attribute.value, element.value); 129 | 130 | const handler = ({ target }) => { 131 | globalsSet(element, attribute.value, target.value); 132 | }; 133 | 134 | addEventListener(element, "keyup", handler); 135 | } 136 | if (attribute.name === "watch") { 137 | const keys = attribute.value.split(",").map((key) => key.trim()); 138 | for (let key of keys) { 139 | if (!watchers.has(key)) { 140 | watchers.set(key, new Set()); 141 | } 142 | watchers.get(key).add(element); 143 | } 144 | } 145 | if (attribute.name.startsWith("on:")) { 146 | const match = attribute.name.match(/on:(?[^:]*)(:(?.*))?/); 147 | const eventName = match ? match.groups.event : null; 148 | const name = match ? match.groups.name : null; 149 | 150 | const handler = () => { 151 | if (attribute.value.match(/^\$.*/g)) { 152 | return runSub(attribute.value.substring(1)); 153 | } 154 | 155 | const value = computeValue(element, attribute.value); 156 | if (name) { 157 | globalsSet(element, name, value); 158 | } 159 | }; 160 | 161 | addEventListener(element, eventName, handler); 162 | } 163 | } 164 | }; 165 | 166 | const parseElement = async (element) => { 167 | if (element.tagName === "VAL") { 168 | return await saveVal(element); 169 | } 170 | if (element.tagName === "IF" || element.tagName === "ELSE") { 171 | const truthy = runIfElement(element); 172 | setVisibility(element, truthy); 173 | return truthy; 174 | } 175 | if (element.tagName === "WHILE") { 176 | return await runWhileElement(element); 177 | } 178 | if (element.tagName === "SUB") { 179 | return false; 180 | } 181 | if (element.tagName === "EXEC") { 182 | return await runExec(element); 183 | } 184 | if (isFor(element)) { 185 | return await runFor2(element); 186 | } 187 | 188 | parseAttributes(element); 189 | }; 190 | 191 | export const processChildren = async (children) => { 192 | for (let element of children) { 193 | const truthy = await parseElement(element); 194 | if (element.hasChildNodes() && truthy !== false) { 195 | await processChildren(element.children); 196 | } 197 | } 198 | }; 199 | 200 | const domContentLoaded = async () => { 201 | try { 202 | injectStyleSheet(); 203 | await processChildren(document.body.children); 204 | } catch (err) { 205 | console.error(err); 206 | } 207 | window.globals = globals; 208 | }; 209 | 210 | window.addEventListener("DOMContentLoaded", domContentLoaded, true); 211 | -------------------------------------------------------------------------------- /src/lib/children.js: -------------------------------------------------------------------------------- 1 | export const cloneChildren = (children) => 2 | [...children].map((child) => child.cloneNode(true)); 3 | 4 | export const appendChildren = (element, children) => { 5 | for (let child of children) { 6 | element.appendChild(child); 7 | } 8 | }; 9 | 10 | export const removeAllChildren = (element) => { 11 | while (element.hasChildNodes()) { 12 | element.removeChild(element.lastChild); 13 | } 14 | }; 15 | -------------------------------------------------------------------------------- /src/lib/events.js: -------------------------------------------------------------------------------- 1 | const eventListeners = new WeakMap(); 2 | 3 | export const addEventListener = (element, type, handler) => { 4 | element.addEventListener(type, handler); 5 | if (!eventListeners.has(element)) { 6 | eventListeners.set(element, []); 7 | } 8 | eventListeners.set(element, [ 9 | ...eventListeners.get(element), 10 | [type, handler], 11 | ]); 12 | }; 13 | 14 | export const removeAllEventListeners = (element) => { 15 | if (!eventListeners.has(element)) return; 16 | for (let [type, handler] of eventListeners.get(element)) { 17 | element.removeEventListener(type, handler); 18 | } 19 | }; 20 | -------------------------------------------------------------------------------- /src/lib/range.js: -------------------------------------------------------------------------------- 1 | const rangeInc = (start, end) => { 2 | const value = []; 3 | 4 | for (let i = start; i <= end; i++) { 5 | value.push(i); 6 | } 7 | 8 | return value; 9 | }; 10 | 11 | const rangeDec = (start, end) => { 12 | const value = []; 13 | 14 | for (let i = start; i >= end; i--) { 15 | value.push(i); 16 | } 17 | 18 | return value; 19 | }; 20 | 21 | export const range = (start, end) => 22 | end >= start ? rangeInc(start, end) : rangeDec(start, end); 23 | -------------------------------------------------------------------------------- /src/lib/setAttribute.js: -------------------------------------------------------------------------------- 1 | export const setAttribute = (element, name, value) => { 2 | if (value === true) { 3 | element.setAttribute(name, ""); 4 | } else if (value === false) { 5 | element.removeAttribute(name); 6 | } else { 7 | element.setAttribute(name, value); 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /src/lib/setVisibility.js: -------------------------------------------------------------------------------- 1 | export const setVisibility = (element, visible) => { 2 | element.style.display = visible ? "inline-block" : ""; 3 | }; 4 | -------------------------------------------------------------------------------- /src/scopes.js: -------------------------------------------------------------------------------- 1 | import { attributeValue, scopes } from "./values"; 2 | 3 | const isScopeAttribute = (attribute) => attribute.name.startsWith("scope:"); 4 | 5 | export const createScope = (element) => { 6 | if (!element.hasAttributes()) { 7 | return null; 8 | } 9 | 10 | const attributes = [...element.attributes].filter(isScopeAttribute); 11 | 12 | const scope = {}; 13 | 14 | for (let attribute of attributes) { 15 | const { name, value } = attributeValue(element, attribute); 16 | scope[name] = value; 17 | } 18 | 19 | return scope; 20 | }; 21 | 22 | export const getScope = (element) => { 23 | const scope = {}; 24 | 25 | let limit = 100; 26 | 27 | while (element != null && limit-- > 0) { 28 | const elementScope = scopes.get(element); 29 | 30 | if (elementScope) { 31 | for (let key of Object.keys(elementScope)) { 32 | if (key in scope === false) { 33 | scope[key] = elementScope[key]; 34 | } 35 | } 36 | } 37 | 38 | element = element.parentNode; 39 | } 40 | 41 | return scope; 42 | }; 43 | 44 | export const saveScope = (element, scope) => { 45 | scopes.set(element, scope); 46 | }; 47 | 48 | export const setValue = (element, key, value) => { 49 | let limit = 100; 50 | 51 | while (element != null && limit-- > 0) { 52 | const elementScope = scopes.get(element); 53 | 54 | if (elementScope && key in elementScope) { 55 | elementScope[key] = value; 56 | return; 57 | } 58 | 59 | element = element.parentNode; 60 | } 61 | }; 62 | -------------------------------------------------------------------------------- /src/stylesheet.js: -------------------------------------------------------------------------------- 1 | const styleSheet = ` 2 | val, 3 | if, 4 | else, 5 | while, 6 | sub { 7 | display: none; 8 | } 9 | `; 10 | 11 | export const injectStyleSheet = () => { 12 | const style = document.createElement("style"); 13 | style.setAttribute("type", "text/css"); 14 | style.textContent = styleSheet; 15 | 16 | document.head.appendChild(style); 17 | }; 18 | -------------------------------------------------------------------------------- /src/subroutine.js: -------------------------------------------------------------------------------- 1 | import { processChildren } from "./index"; 2 | 3 | export const runSub = async (name, element) => { 4 | const sub = document.querySelector(`SUB[name=${name}]`); 5 | if (!sub) { 6 | throw new Error(`SUB "${name}" was expected to exist.`); 7 | } 8 | 9 | return await processChildren(sub.children); 10 | }; 11 | -------------------------------------------------------------------------------- /src/values.js: -------------------------------------------------------------------------------- 1 | import { processChildren } from "./index"; 2 | import { getScope, setValue } from "./scopes"; 3 | import { range } from "./lib/range"; 4 | 5 | export const watchers = new Map(); 6 | export const globals = new Map(); 7 | export const scopes = new WeakMap(); 8 | const processQueue = []; 9 | let isProcessing = false; 10 | 11 | const startQueue = () => { 12 | if (isProcessing || processQueue.length === 0) return; 13 | isProcessing = true; 14 | 15 | const action = processQueue.shift(1); 16 | action() 17 | .catch((err) => console.error(err)) 18 | .finally(() => { 19 | isProcessing = false; 20 | startQueue(); 21 | }); 22 | }; 23 | 24 | export const globalsSet = (element, key, value, runWatchers = true) => { 25 | const scope = getScope(element); 26 | if (key in scope) { 27 | setValue(element, key, value); 28 | } else { 29 | globals.set(key, value); 30 | } 31 | 32 | if (runWatchers && watchers.has(key)) { 33 | const elements = watchers.get(key); 34 | processQueue.push(() => processChildren(elements.values())); 35 | startQueue(); 36 | } 37 | }; 38 | 39 | export const computeValue = (element, value) => { 40 | const keyValues = new Map([...globals]); 41 | 42 | // include scope values 43 | const scope = getScope(element); 44 | Object.entries(scope).forEach(([key, value]) => { 45 | keyValues.set(key, value); 46 | }); 47 | 48 | const keys = keyValues.keys(); 49 | const values = keyValues.values(); 50 | 51 | const func = new Function(...keys, "range", `return ${value}`); 52 | const result = func(...values, range); 53 | return result; 54 | }; 55 | 56 | export const attributeValue = (element, { name, value }) => { 57 | if (name.startsWith("scope:")) { 58 | name = name.substring(6); 59 | } 60 | 61 | if (name.endsWith(":number")) { 62 | return { 63 | name: name.substring(0, name.length - 7), 64 | value: Number(value), 65 | }; 66 | } 67 | 68 | if (name.endsWith(":bool")) { 69 | return { 70 | name: name.substring(0, name.length - 5), 71 | value: computeValue(element, value), 72 | }; 73 | } 74 | 75 | if (name.endsWith(":object")) { 76 | return { 77 | name: name.substring(0, name.length - 7), 78 | value: computeValue(element, value), 79 | }; 80 | } 81 | 82 | return { 83 | name, 84 | value, 85 | }; 86 | }; 87 | -------------------------------------------------------------------------------- /umd/arrays.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 |
11 | 12 | 13 | 14 |
15 | list[] = 16 | 17 |
18 |
19 |
20 | 21 | 22 | -------------------------------------------------------------------------------- /umd/calculator.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | 30 | Calculator 31 | 32 | 33 |
34 |

Calculator

35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 |
74 | 75 | 76 | 77 | 78 | 79 |
80 | 81 |
82 | 83 | 84 | 85 | 86 |
87 |
88 | 89 | 90 | 91 | 92 |
93 |
94 | 95 | 96 | 97 | 98 |
99 |
100 | 103 | 104 | 105 | 106 |
107 |
108 | 109 | 110 | -------------------------------------------------------------------------------- /umd/dialog.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 27 | 28 | 29 |
30 |

Dialog

31 | 32 | 33 | 34 |
35 | 36 | 37 | 38 |

Hello Dialog!

39 |
40 |
41 |
42 | 43 | 44 | -------------------------------------------------------------------------------- /umd/fetch.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 |
11 |

Fetch

12 | 13 | 18 | Loading... 19 | Error: 20 | 21 |
name:
22 |
gender:
23 |
height: cm
24 |
25 |
26 | 27 | 28 | -------------------------------------------------------------------------------- /umd/fizzbuzz.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 |
11 | 12 |
13 | FizzBuzz 14 | 15 | 16 | 17 |
18 |
19 | 20 | 21 | -------------------------------------------------------------------------------- /umd/html-lang.js: -------------------------------------------------------------------------------- 1 | !function(t,n){if("object"==typeof exports&&"object"==typeof module)module.exports=n();else if("function"==typeof define&&define.amd)define([],n);else{var e=n();for(var r in e)("object"==typeof exports?exports:t)[r]=e[r]}}(self,(function(){return function(){"use strict";var t={d:function(n,e){for(var r in e)t.o(e,r)&&!t.o(n,r)&&Object.defineProperty(n,r,{enumerable:!0,get:e[r]})},o:function(t,n){return Object.prototype.hasOwnProperty.call(t,n)},r:function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})}},n={};function e(t){return e="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},e(t)}function r(t,n){return r=Object.setPrototypeOf||function(t,n){return t.__proto__=n,t},r(t,n)}function o(t,n){if("function"!=typeof n&&null!==n)throw new TypeError("Super expression must either be null or a function");t.prototype=Object.create(n&&n.prototype,{constructor:{value:t,writable:!0,configurable:!0}}),n&&r(t,n)}function i(t,n,e){return n in t?Object.defineProperty(t,n,{value:e,enumerable:!0,configurable:!0,writable:!0}):t[n]=e,t}function u(t,n){(null==n||n>t.length)&&(n=t.length);for(var e=0,r=new Array(n);et.length)&&(n=t.length);for(var e=0,r=new Array(n);e=t.length?{done:!0}:{done:!1,value:t[r++]}},e:function(t){throw t},f:o}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}var i,u=!0,a=!1;return{s:function(){e=e.call(t)},n:function(){var t=e.next();return u=t.done,t},e:function(t){a=!0,i=t},f:function(){try{u||null==e.return||e.return()}finally{if(a)throw i}}}}(n);try{for(r.s();!(e=r.n()).done;){var o=e.value;t.appendChild(o)}}catch(t){r.e(t)}finally{r.f()}},v=function(t){for(;t.hasChildNodes();)t.removeChild(t.lastChild)};function h(){if("undefined"==typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if("function"==typeof Proxy)return!0;try{return Boolean.prototype.valueOf.call(Reflect.construct(Boolean,[],(function(){}))),!0}catch(t){return!1}}function y(t,n,e){return y=h()?Reflect.construct:function(t,n,e){var o=[null];o.push.apply(o,n);var i=new(Function.bind.apply(t,o));return e&&r(i,e.prototype),i},y.apply(null,arguments)}function p(t,n){return function(t){if(Array.isArray(t))return t}(t)||function(t,n){var e=null==t?null:"undefined"!=typeof Symbol&&t[Symbol.iterator]||t["@@iterator"];if(null!=e){var r,o,i=[],u=!0,a=!1;try{for(e=e.call(t);!(u=(r=e.next()).done)&&(i.push(r.value),!n||i.length!==n);u=!0);}catch(t){a=!0,o=t}finally{try{u||null==e.return||e.return()}finally{if(a)throw o}}return i}}(t,n)||a(t,n)||function(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function d(t,n){(null==n||n>t.length)&&(n=t.length);for(var e=0,r=new Array(n);e0;){var r=A.get(t);if(r)for(var o=0,i=Object.keys(r);o0;){var o=A.get(t);if(o&&n in o)return void(o[n]=e);t=t.parentNode}},j=function(t,n){return n>=t?function(t,n){for(var e=[],r=t;r<=n;r++)e.push(r);return e}(t,n):function(t,n){for(var e=[],r=t;r>=n;r--)e.push(r);return e}(t,n)},S=new Map,O=new Map,A=new WeakMap,E=[],P=!1,x=function t(){P||0===E.length||(P=!0,E.shift(1)().catch((function(t){return console.error(t)})).finally((function(){P=!1,t()})))},I=function(t,n,e){var r=!(arguments.length>3&&void 0!==arguments[3])||arguments[3],o=m(t);if(n in o?w(t,n,e):O.set(n,e),r&&S.has(n)){var i=S.get(n);E.push((function(){return jt(i.values())})),x()}},C=function(t,n){var e=new Map(c(O)),r=m(t);Object.entries(r).forEach((function(t){var n=p(t,2),r=n[0],o=n[1];e.set(r,o)}));var o=e.keys(),i=e.values();return y(Function,c(o).concat(["range","return ".concat(n)])).apply(void 0,c(i).concat([j]))},W=function(t,n){var e=n.name,r=n.value;return e.startsWith("scope:")&&(e=e.substring(6)),e.endsWith(":number")?{name:e.substring(0,e.length-7),value:Number(r)}:e.endsWith(":bool")?{name:e.substring(0,e.length-5),value:C(t,r)}:e.endsWith(":object")?{name:e.substring(0,e.length-7),value:C(t,r)}:{name:e,value:r}},M=function(t){return"cmd"===t.name};function N(t,n,e){return e?n?n(t):t:(t&&t.then||(t=Promise.resolve(t)),n?t.then(n):t)}var k=function(t){var n=t.headers.get("content-type");return n&&n.includes("application/json")};function T(t,n){var e=t();return e&&e.then?e.then(n):n(e)}var U,$=(U=function(){return N(fetch.apply(void 0,arguments),(function(t){var n=!1;if(!t.ok)throw new Error("HTTP Error Response: ".concat(t.status," ").concat(t.statusText));return T((function(){if(k(t))return n=!0,N(t.json(),(function(n){return{response:t,json:n}}))}),(function(e){return n?e:{response:t,json:null}}))}))},function(){for(var t=[],n=0;nt.length)&&(n=t.length);for(var e=0,r=new Array(n);e=t.length?{done:!0}:{done:!1,value:t[r++]}},e:function(t){throw t},f:o}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}var i,u=!0,a=!1;return{s:function(){e=e.call(t)},n:function(){var t=e.next();return u=t.done,t},e:function(t){a=!0,i=t},f:function(){try{u||null==e.return||e.return()}finally{if(a)throw i}}}}function V(t,n){(null==n||n>t.length)&&(n=t.length);for(var e=0,r=new Array(n);e]+)>/g,(function(t,n){return"$"+i[n]})))}if("function"==typeof o){var a=this;return t[Symbol.replace].call(this,r,(function(){var t=arguments;return"object"!==e(t[t.length-1])&&(t=[].slice.call(t)).push(u(t,a)),o.apply(this,t)}))}return t[Symbol.replace].call(this,r,o)},ot.apply(this,arguments)}function it(t,n){var e=Object.keys(t);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(t);n&&(r=r.filter((function(n){return Object.getOwnPropertyDescriptor(t,n).enumerable}))),e.push.apply(e,r)}return e}function ut(t){for(var n=1;n=t.length?{done:!0}:{done:!1,value:t[r++]}},e:function(t){throw t},f:o}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}var i,u=!0,a=!1;return{s:function(){e=e.call(t)},n:function(){var t=e.next();return u=t.done,t},e:function(t){a=!0,i=t},f:function(){try{u||null==e.return||e.return()}finally{if(a)throw i}}}}function vt(t,n){(null==n||n>t.length)&&(n=t.length);for(var e=0,r=new Array(n);e=t.length?{done:!0}:{done:!1,value:t[r++]}},e:function(t){throw t},f:o}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}var i,u=!0,a=!1;return{s:function(){e=e.call(t)},n:function(){var t=e.next();return u=t.done,t},e:function(t){a=!0,i=t},f:function(){try{u||null==e.return||e.return()}finally{if(a)throw i}}}}(c(t.attributes).filter(b));try{for(r.s();!(n=r.n()).done;){var o=n.value,i=W(t,o),u=i.name,a=i.value;e[u]=a}}catch(t){r.e(t)}finally{r.f()}return e}(t);g(t,n);var e=bt(t);for(yt.has(t)||yt.set(t,c(t.children));t.hasChildNodes();)t.removeChild(t.lastChild);_(t,e);var r=yt.get(t);return lt(function(t,n,e){for(var r;;){var o=t();if(ft(o)&&(o=o.v),!o)return u;if(o.then){r=0;break}var i,u=e();if(u&&u.then){if(!ft(u)){r=1;break}u=u.s}}var a=new ct,c=at.bind(null,a,2);return(0===r?o.then(l):1===r?u.then(f):i.then(s)).then(void 0,c),a;function f(n){u=n;do{if(!(o=t())||ft(o)&&!o.v)return void at(a,1,u);if(o.then)return void o.then(l).then(void 0,c);ft(u=e())&&(u=u.v)}while(!u||!u.then);u.then(f).then(void 0,c)}function l(t){t?(u=e())&&u.then?u.then(f).then(void 0,c):f(u):at(a,1,u)}function s(){(o=t())?o.then?o.then(l).then(void 0,c):l(o):at(a,1,u)}}((function(){return!!e}),0,(function(){var o,i=r.map((function(t){return t.cloneNode(!0)})),u=st(i);try{for(u.s();!(o=u.n()).done;){var a=o.value;n=ut({},n),"VAL"!==a.tagName?g(a,n):g(t,n),t.appendChild(a)}}catch(t){u.e(t)}finally{u.f()}return ht(jt(i),(function(){e=bt(t)}))})),(function(){return!1}))})),gt=function(t){!function(t){if(L.has(t)){var n,e=function(t,n){var e="undefined"!=typeof Symbol&&t[Symbol.iterator]||t["@@iterator"];if(!e){if(Array.isArray(t)||(e=function(t,n){if(t){if("string"==typeof t)return D(t,n);var e=Object.prototype.toString.call(t).slice(8,-1);return"Object"===e&&t.constructor&&(e=t.constructor.name),"Map"===e||"Set"===e?Array.from(t):"Arguments"===e||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(e)?D(t,n):void 0}}(t))||n&&t&&"number"==typeof t.length){e&&(t=e);var r=0,o=function(){};return{s:o,n:function(){return r>=t.length?{done:!0}:{done:!1,value:t[r++]}},e:function(t){throw t},f:o}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}var i,u=!0,a=!1;return{s:function(){e=e.call(t)},n:function(){var t=e.next();return u=t.done,t},e:function(t){a=!0,i=t},f:function(){try{u||null==e.return||e.return()}finally{if(a)throw i}}}}(L.get(t));try{for(e.s();!(n=e.n()).done;){var r=p(n.value,2),o=r[0],i=r[1];t.removeEventListener(o,i)}}catch(t){e.e(t)}finally{e.f()}}}(t);var n,e=st(t.attributes);try{var r=function(){var e=n.value;if("#text"===e.name)t.textContent=C(t,e.value);else if(e.name.startsWith("#")){var r=C(t,e.value);Z(t,e.name.substring(1),r)}if("onchange:set"!==e.name&&"bind:value"!==e.name||(I(t,e.value,t.value),R(t,"keyup",(function(n){var r=n.target;I(t,e.value,r.value)}))),"watch"===e.name){var o,i=e.value.split(",").map((function(t){return t.trim()})),u=st(i);try{for(u.s();!(o=u.n()).done;){var a=o.value;S.has(a)||S.set(a,new Set),S.get(a).add(t)}}catch(t){u.e(t)}finally{u.f()}}if(e.name.startsWith("on:")){var c=e.name.match(ot(/on:((?:(?!:)[\s\S])*)(:(.*))?/,{event:1,name:3})),f=c?c.groups.event:null,l=c?c.groups.name:null;R(t,f,(function(){if(e.value.match(/^\$.*/g))return B(e.value.substring(1));var n=C(t,e.value);l&&I(t,l,n)}))}};for(e.s();!(n=e.n()).done;)r()}catch(t){e.e(t)}finally{e.f()}},wt=pt((function(t){var n=!1;return tt((function(){if("VAL"===t.tagName)return n=!0,ht(dt(t))}),(function(e){var r=!1;if(n)return e;if("IF"===t.tagName||"ELSE"===t.tagName){var o=bt(t);return _(t,o),o}return tt((function(){if("WHILE"===t.tagName)return r=!0,ht(mt(t))}),(function(n){var e=!1;return r?n:"SUB"!==t.tagName&&tt((function(){if("EXEC"===t.tagName)return e=!0,ht(function(t){var n=c(t.attributes).find(M);return n.value.match(/^\$.*/g)?function(t,n){var e=document.querySelector("SUB[name=".concat(n,"]"));if(!e)throw new Error('SUB "'.concat(n,'" was expected to exist.'));var r=l(e.children);return v(t),s(t,r),!0}(t,n.value.substring(1)):C(t,n.value)}(t))}),(function(n){var r=!1;return e?n:tt((function(){if(function(t){return J(t).length>0}(t))return r=!0,ht(function(t){var n=J(t),e=n.find(X),r=n.find(z),o=e?K(e):null,u=r?K(r):null;!function(t){Q.has(t)||Q.set(t,c(t.children))}(t),v(t);var a,f=C(t,e.value),h=Q.get(t),y=0,p=H(f);try{for(p.s();!(a=p.n()).done;){var d=a.value,b=l(h),m=q(q({},o&&i({},o,d)),u&&i({},u,y));Y(b,m),s(t,b),y+=1}}catch(t){p.e(t)}finally{p.f()}}(t))}),(function(n){if(r)return n;gt(t)}))}))}))}))})),jt=pt((function(t){return function(t){if(t&&t.then)return t.then(nt)}(function(t,n,e){if("function"==typeof t[rt]){var r,o,i,u=t[rt]();function c(t){try{for(;!((r=u.next()).done||e&&e());)if((t=n(r.value))&&t.then){if(!ft(t))return void t.then(c,i||(i=at.bind(null,o=new ct,2)));t=t.v}o?at(o,1,t):o=t}catch(t){at(o||(o=new ct),2,t)}}if(c(),u.return){var a=function(t){try{r.done||u.return()}catch(t){}return t};if(o&&o.then)return o.then(a,(function(t){throw a(t)}));a()}return o}if(!("length"in t))throw new TypeError("Object is not iterable");for(var c=[],f=0;f 2 | 3 | 4 | 5 | 6 | 7 | 8 | 34 | Memory Game 35 | 36 | 37 |
38 |

Memory Game

39 | 40 | 41 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 |
67 | 68 |
69 |
70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 |
95 | 96 |

Congratulations! You won in moves!

97 | 98 |
99 |
100 | 101 |
102 |
103 |
104 |
105 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 121 | 122 | 123 | 124 | 125 | 126 | 127 |
128 |
129 |
130 |
131 |
132 | 133 | 134 | -------------------------------------------------------------------------------- /umd/objects.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 |
11 | 12 |
x.abc =
13 |
14 | 15 | 16 | -------------------------------------------------------------------------------- /umd/todo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | TODO 8 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 |
25 |
    26 |
  • 27 | 30 | 31 |
  • 32 |
33 |
34 | 35 | 36 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const fs = require("fs"); 3 | const HtmlWebpackPlugin = require("html-webpack-plugin"); 4 | 5 | module.exports = { 6 | entry: { 7 | ["html-lang"]: "./src/index.js", 8 | }, 9 | output: { 10 | libraryTarget: "umd", 11 | path: path.resolve(__dirname, "umd"), 12 | }, 13 | devServer: { 14 | static: { 15 | directory: path.join(__dirname, "src.html"), 16 | }, 17 | compress: false, 18 | port: 8080, 19 | }, 20 | module: { 21 | rules: [ 22 | { 23 | test: /\.m?js$/, 24 | exclude: /node_modules/, 25 | use: { 26 | loader: "babel-loader", 27 | }, 28 | }, 29 | ], 30 | }, 31 | plugins: fs.readdirSync("src.html").map( 32 | (filename) => 33 | new HtmlWebpackPlugin({ 34 | filename, 35 | template: `src.html/${filename}`, 36 | minify: false, 37 | }) 38 | ), 39 | }; 40 | -------------------------------------------------------------------------------- /what-the-hell-are-you-doing.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelnet/html-lang/ad478b7f562cac32f5e951ff00dab52832437535/what-the-hell-are-you-doing.jpg --------------------------------------------------------------------------------