├── .github └── FUNDING.yml ├── .gitignore ├── .npmignore ├── README.md ├── SECURITY.md ├── UPDATOR_FUNCTIONS.md ├── index.html ├── index.js ├── package-lock.json ├── package.json ├── readme-images ├── .npmignore ├── bepis-logo.jpg └── bepiswatnsyou.jpg └── test.js /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: crislin2046 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | #build 2 | .cache 3 | 4 | # Logs 5 | logs 6 | *.log 7 | npm-debug.log* 8 | yarn-debug.log* 9 | yarn-error.log* 10 | lerna-debug.log* 11 | 12 | # Diagnostic reports (https://nodejs.org/api/report.html) 13 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 14 | 15 | # Runtime data 16 | pids 17 | *.pid 18 | *.seed 19 | *.pid.lock 20 | 21 | # Directory for instrumented libs generated by jscoverage/JSCover 22 | lib-cov 23 | 24 | # Coverage directory used by tools like istanbul 25 | coverage 26 | *.lcov 27 | 28 | # nyc test coverage 29 | .nyc_output 30 | 31 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 32 | .grunt 33 | 34 | # Bower dependency directory (https://bower.io/) 35 | bower_components 36 | 37 | # node-waf configuration 38 | .lock-wscript 39 | 40 | # Compiled binary addons (https://nodejs.org/api/addons.html) 41 | build/Release 42 | 43 | # Dependency directories 44 | node_modules/ 45 | jspm_packages/ 46 | 47 | # TypeScript v1 declaration files 48 | typings/ 49 | 50 | # TypeScript cache 51 | *.tsbuildinfo 52 | 53 | # Optional npm cache directory 54 | .npm 55 | 56 | # Optional eslint cache 57 | .eslintcache 58 | 59 | # Microbundle cache 60 | .rpt2_cache/ 61 | .rts2_cache_cjs/ 62 | .rts2_cache_es/ 63 | .rts2_cache_umd/ 64 | 65 | # Optional REPL history 66 | .node_repl_history 67 | 68 | # Output of 'npm pack' 69 | *.tgz 70 | 71 | # Yarn Integrity file 72 | .yarn-integrity 73 | 74 | # dotenv environment variables file 75 | .env 76 | .env.test 77 | 78 | # parcel-bundler cache (https://parceljs.org/) 79 | .cache 80 | 81 | # Next.js build output 82 | .next 83 | 84 | # Nuxt.js build / generate output 85 | .nuxt 86 | dist 87 | 88 | # Gatsby files 89 | .cache/ 90 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 91 | # https://nextjs.org/blog/next-9-1#public-directory-support 92 | # public 93 | 94 | # vuepress build output 95 | .vuepress/dist 96 | 97 | # Serverless directories 98 | .serverless/ 99 | 100 | # FuseBox cache 101 | .fusebox/ 102 | 103 | # DynamoDB Local files 104 | .dynamodb/ 105 | 106 | # TernJS port file 107 | .tern-port 108 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .cache 2 | *.html 3 | 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Bepis Logo 3 |

4 | 5 | # :dog2: [bepis](#drincc) ![download badge](https://img.shields.io/npm/dt/bepis) ![version badge](https://img.shields.io/npm/v/bepis/latest) 6 | 7 | Dynamic HTML + CSS in JavaScript. Implemented using a custom parser for a new HTML templating DSL. 8 | 9 | [It Is On Npm](https://www.npmjs.com/package/bepis) 10 | 11 | ```console 12 | npm i bepis 13 | ``` 14 | 15 | ## Examples 16 | 17 | Simple keyed list, play with it [here](https://codesandbox.io/s/bepis-latest-playground-6cggy): 18 | 19 | First, import: 20 | ```js 21 | import { w, clone } from "bepis"; 22 | ``` 23 | 24 | Then set up some data: 25 | ```js 26 | const myItems = [ 27 | { name: "Screw", description: "Part", key: "a3" }, 28 | { name: "Moxie", description: "Intangible", key: "x5" }, 29 | { name: "Sand", description: "Material", key: "p4" }, 30 | ]; 31 | const newName = "Mojo"; 32 | ``` 33 | 34 | Make some views: 35 | ```js 36 | const KeyedItem = item => 37 | w` ${item.key} 38 | li p, 39 | :text ${item.description}. 40 | a ${{ href: item.url }} :text ${item.name}..`; 41 | 42 | const SingletonList = items => 43 | w` ${true} 44 | ul :map ${items} ${KeyedItem}`; 45 | ``` 46 | 47 | Render the data and mount the view to the document 48 | ```js 49 | SingletonList(myItems)(document.body); 50 | ``` 51 | 52 | Make a change and see it 53 | ```js 54 | const myChangedItems = clone(myItems); 55 | myChangedItems[1].name = newName; 56 | 57 | setTimeout(() => SingletonList(myChangedItems), 2000); 58 | ``` 59 | 60 | ## :text, :map and :comp directives. 61 | 62 | - Use `:text` to insert text, and `:map` to insert lists, as in the above example. 63 | - Use `:comp` to insert components: 64 | ```javascript 65 | w` 66 | main, 67 | h1 ${"Demo"}. 68 | :comp ${myChangedItems} ${SingletonList}..` 69 | ``` 70 | 71 | ## Basics 72 | 73 | - Use template literals tagged with `w`. This creates a 'bepis' 74 | - Use ',' operator to save an insertion point 75 | - Use '.' operator to load an insertion point 76 | - ` ${attributes} ${styles}` is the format. 77 | - Whitespace is ignored. 78 | 79 | ## Up next 80 | 81 | - minimal diffing with updator functions. 82 | 83 | ## Related Projects 84 | 85 | I don't know. I didn't search any "prior art." Let me know how I reinvented this beautiful wheel by opening a PR request. 86 | 87 | 88 | ---------- 89 | 90 |

91 | Bepis Wants You 92 |

93 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | Use this section to tell people about which versions of your project are 6 | currently being supported with security updates. 7 | 8 | | Version | Supported | 9 | | ------- | ------------------ | 10 | | Latest | :white_check_mark: | 11 | 12 | 13 | ## Reporting a Vulnerability 14 | 15 | To report a vulnerability, contact: cris@dosycorp.com 16 | 17 | To view previous responsible disclosure vulnerability reports, mediation write ups, notes and other information, please visit the [Dosyago Responsible Dislcousre Center](https://github.com/dosyago/vulnerability-reports) 18 | -------------------------------------------------------------------------------- /UPDATOR_FUNCTIONS.md: -------------------------------------------------------------------------------- 1 | # Notes 2 | 3 | 4 | In order to update a node, we need a few pieces of information: 5 | 6 | - the node we want to update 7 | - the part of the node we want to update 8 | - what the old value was (to see if we need to update, or to undo before adding the new value) 9 | - the new value 10 | 11 | We can get the node in the first time we call treeToDOM 12 | 13 | We can get the part of the node in buildTree, since it's obvious from the parse and the slot what the part is. 14 | 15 | It can be: 16 | 17 | - Element 18 | - String 19 | - Attributes object 20 | - Style object 21 | - null / empty 22 | 23 | We can get what the old value was from the first time we call buildTree, and then save whenever we update it. 24 | 25 | We get the new value when it's passed in. 26 | 27 | # Implementation 28 | 29 | This information can be put into an updator function. 30 | 31 | The function can be created at parse time, and saved in the tree. 32 | 33 | Each updator function handles 1 slot. We also return the updator functions in a list, where each position corresponds to the slot it updates. 34 | 35 | The updator function is easily found for any slots that change. 36 | 37 | # Sketches 38 | 39 | ```javascript 40 | function updateText(textNode, newValue) { 41 | if ( textNode.nodeValue != newValue ) { 42 | textNode.nodeValue = newValue; 43 | } 44 | } 45 | 46 | function updateAttribute(node, attrName, attrValue) { 47 | const idlValue = node[attrName]; 48 | const markupValue = node.getAttribute(attrName); 49 | 50 | const alreadyEquals = idlValue == attrValue || markupValue == attrValue; 51 | 52 | if ( alreadyEquals ) return; 53 | 54 | try { 55 | node.setAttribute(attrName, attrvalue); 56 | } catch(e) {} 57 | 58 | try { 59 | node[attrName] = attrValue; 60 | } catch(e) {} 61 | } 62 | 63 | // functions for updateAttrObject, updateStyleObject 64 | 65 | // also functions for updateList, updateComponent 66 | ``` 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 6 | 7 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const DEBUG = false; 2 | const DEV = false; 3 | const IsBepis = Symbol('[[Bepis]]'); 4 | const AsyncFunction = Object.getPrototypeOf(async () => 1).constructor; 5 | const Cache = new Map(); 6 | 7 | export function w(code, ...slots) { 8 | let pinValue = false; 9 | let cacheKey = null; 10 | code = Array.from(code).join("$").trim(); 11 | 12 | say(code); 13 | 14 | if ( code.startsWith('$') ) { // we got the pin parameter 15 | code = code.slice(1).trim(); 16 | pinValue = slots.shift(); 17 | say("Got pin parameter", pinValue); 18 | } 19 | 20 | if ( pinValue == true ) { 21 | // pinValue == true, the default, a singleton component 22 | cacheKey = `${code}:singleton`; 23 | } else if ( pinValue != false ) { 24 | // use the cache with a key, an insteance component 25 | cacheKey = `${code}:${pinValue}`; 26 | } 27 | 28 | let update = true; 29 | let existingRootElement; 30 | 31 | if ( cacheKey != null ) { 32 | if ( Cache.has(cacheKey) ) { 33 | const {rootElement, slots:existingSlots} = Cache.get(cacheKey); 34 | update = !equals(slots, existingSlots); 35 | if ( update ) { 36 | say(DEV, "Slots changed. Updating", rootElement); 37 | } else { 38 | say(DEV, "No change in slots. Not updating", rootElement); 39 | } 40 | existingRootElement = rootElement; 41 | } 42 | } 43 | 44 | if ( update ) { 45 | const root = buildTree(code, ...slots); 46 | let rootElement; 47 | 48 | if ( existingRootElement ) { 49 | //say(DEV,"Replace", existingRootElement, "with", rootElement); 50 | rootElement = treeToDOM(root); 51 | //rootElement = updateDOMWithTree(existingRootElement, root); 52 | existingRootElement.replaceWith(rootElement); 53 | } else { 54 | rootElement = treeToDOM(root); 55 | } 56 | existingRootElement = rootElement; 57 | 58 | Cache.set(cacheKey, {rootElement:existingRootElement, slots}); 59 | } 60 | 61 | const inserter = point => { 62 | if ( !! point ) { 63 | say("Insert at", point, existingRootElement); 64 | point.insertAdjacentElement('beforeEnd', existingRootElement); 65 | } 66 | return existingRootElement; 67 | }; 68 | 69 | inserter[IsBepis] = true; 70 | 71 | return inserter; 72 | } 73 | 74 | function buildTree(code, ...slots) { 75 | const updators = []; 76 | const N = slots.length; 77 | let slice = {tag: '', params: [], children: []}; 78 | const stack = [slice]; 79 | 80 | for( const char of code ) { 81 | switch(char) { 82 | case ' ': 83 | case '\t': 84 | case '\n': 85 | case '\r': { 86 | if ( ! slice.finished && slice.tag.length ) { 87 | slice.finished = true; 88 | say("Got", slice.tag, slice); 89 | } 90 | }; break; 91 | 92 | case '$': { 93 | const oldSlot = slots.shift(); 94 | slice.params.push(oldSlot); 95 | const paramIndex = slice.params.length-1; 96 | const Updator = { 97 | oldSlot, 98 | element: null, 99 | paramIndex, 100 | slotIndex: N - (slots.length) - 1 101 | }; 102 | slice[`updator${paramIndex}`] = Updator; 103 | updators.push(Updator); 104 | }; break; 105 | 106 | case ',': { 107 | slice.finished = true; 108 | stack.push(slice); 109 | say("Saved", slice.tag); 110 | const newSlice = {tag: '', params: [], children: []}; 111 | slice.children.push(newSlice); 112 | slice = newSlice; 113 | }; break; 114 | 115 | case '.': { 116 | if ( slice.tag.length ) { 117 | slice.finished = true; 118 | // this can create an empty item that we remove after loop 119 | const newSlice = {tag: '', params: [], children: []}; 120 | const oldSlice = stack.pop(); 121 | oldSlice.children.push(newSlice); 122 | say("Reset to", oldSlice.tag); 123 | stack.push(oldSlice); 124 | slice = newSlice; 125 | } else { 126 | let oldSlice = stack.pop(); 127 | const idx = oldSlice.children.indexOf(slice); 128 | oldSlice.children.splice(idx,1); 129 | oldSlice = stack.pop(); 130 | oldSlice.children.push(slice); 131 | say("Reset to", oldSlice.tag); 132 | stack.push(oldSlice); 133 | } 134 | }; break; 135 | 136 | case ':': { 137 | // note that no space is required between a tag and a directive 138 | if ( ! slice.finished && slice.tag.length ) { 139 | slice.finished = true; 140 | say("Got", slice.tag); 141 | } 142 | if ( slice.finished ) { 143 | const newSlice = {tag: '', params: [], children: [], directive: true}; 144 | slice.children.push(newSlice); 145 | slice = newSlice; 146 | } else { 147 | slice.directive = true; 148 | } 149 | slice.tag += char; 150 | say("Directive start", slice); 151 | }; break; 152 | 153 | default: { 154 | if ( slice.finished ) { 155 | const newSlice = {tag: '', params: [], children: []}; 156 | slice.children.push(newSlice); 157 | slice = newSlice; 158 | } 159 | slice.tag += char; 160 | }; break; 161 | } 162 | } 163 | 164 | 165 | // there could be an empty item 166 | if (! slice.tag.length ) { 167 | const parent = stack[0]; 168 | if ( parent ) { 169 | const idx = parent.children.indexOf(slice); 170 | parent.children.splice(idx,1); 171 | } 172 | } 173 | 174 | while ( stack.length ) { 175 | slice = stack.pop(); 176 | } 177 | 178 | 179 | return slice; 180 | } 181 | 182 | function treeToDOM(root) { 183 | say("Root", root); 184 | const stack = [root]; 185 | let parentElement; 186 | 187 | while( stack.length ) { 188 | const item = stack.pop(); 189 | 190 | if ( item instanceof Element ) { 191 | if ( item.parentElement ) { 192 | parentElement = item.parentElement; 193 | } else break; 194 | } else if ( item.tag.length ) { 195 | if ( item.directive ) { 196 | say("Directive", item); 197 | switch(item.tag) { 198 | case ':text': { 199 | if ( item.params.length != 1 ) { 200 | console.warn({errorDetails:{item}}); 201 | throw new TypeError(`Sorry, :text takes 1 parameter. ${item.params.length} given.`); 202 | } 203 | const data = getData(item.params[0]); 204 | if ( typeof data != "string" ) { 205 | console.warn({errorDetails:{item}}); 206 | throw new TypeError(`Sorry, :text requires string data. ${data} given.`); 207 | } 208 | if ( ! parentElement ) { 209 | console.warn({errorDetails:{item}}); 210 | throw new TypeError('Sorry, :text cannot insert at top level'); 211 | } 212 | parentElement.insertAdjacentText('beforeEnd', data); 213 | }; break; 214 | 215 | case ':map': { 216 | say("Got map", item); 217 | const [list, func] = item.params; 218 | if ( ! parentElement ) { 219 | console.warn({errorDetails:{list,func}}); 220 | throw new TypeError(':map cannot be used top level, sorry. Wrap in Element'); 221 | } 222 | if ( item.params.length == 0 ) { 223 | console.warn({errorDetails:{list,func}}); 224 | throw new TypeError(':map requires at least 1 argument'); 225 | } 226 | if ( !!func && !(func instanceof Function) ) { 227 | console.warn({errorDetails:{list,func}}); 228 | throw new TypeError(':map second parameter, if given, must be a function'); 229 | } else if (func instanceof AsyncFunction ) { 230 | console.warn({errorDetails:{list,func}}); 231 | throw new TypeError('Sorry, :map does not support AsyncFunctions. Maybe later.'); 232 | } 233 | let data = getData(list); 234 | try { 235 | data = Array.from(data); 236 | } catch(e) { 237 | console.warn({errorDetails:{list,data,func}}); 238 | throw new TypeError("Sorry, :map requires data that can be iterated."); 239 | } 240 | const resultItems = []; 241 | for ( const item of data ) { 242 | let result; 243 | if ( !! func ) { 244 | // create a bepis with no mount 245 | DEBUG && console.log(func); 246 | result = func(item)(/* no mount */); 247 | } else { 248 | result = item; 249 | } 250 | 251 | if ( result instanceof Element || typeof result == "string" ) { 252 | resultItems.push(result); 253 | say("Result", result); 254 | } else { 255 | console.warn({errorDetails:{list,func}}); 256 | throw new TypeError(":map must produce a list where each item is either an Element or a string"); 257 | } 258 | } 259 | for ( const resultItem of resultItems ) { 260 | if ( resultItem instanceof Element ) { 261 | say(DEV,"Append resultItem", resultItem, "to", parentElement); 262 | parentElement.append(resultItem); 263 | } else if ( typeof resultItem == "string" ) { 264 | parentElement.insertAdjacentText('beforeEnd', resultItem); 265 | } 266 | } 267 | }; break; 268 | 269 | case ':comp': { 270 | let [maybeDataOrFunc, func] = item.params; 271 | if ( item.params.length == 0 ) { 272 | console.warn({errorDetails:{maybeDataOrFunc,func}}); 273 | throw new TypeError(':comp requires at least 1 argument'); 274 | } 275 | if ( !!func && !(func instanceof Function) ) { 276 | console.warn({errorDetails:{maybeDataOrFunc,func}}); 277 | throw new TypeError(':comp second parameter, if given, must be a function'); 278 | } else if (func instanceof AsyncFunction ) { 279 | console.warn({errorDetails:{maybeDataOrFunc,func}}); 280 | throw new TypeError('Sorry, :comp does not support AsyncFunctions. Maybe later.'); 281 | } 282 | if ( !func ) { 283 | func = x => x; 284 | } 285 | let data = getData(maybeDataOrFunc); 286 | let result = func(data); 287 | 288 | if ( result[IsBepis] ) { 289 | result = result(); 290 | } 291 | 292 | if ( result instanceof Element ) { 293 | if ( parentElement ) { 294 | say(DEV,"Append result", result, "to", parentElement); 295 | parentElement.append(result); 296 | } 297 | parentElement = result; 298 | stack.push(result); 299 | } else if ( typeof result == "string" ) { 300 | if ( parentElement ) { 301 | parentElement.insertAdjacentText('beforeEnd', result); 302 | } else { 303 | console.warn({errorDetails:{maybeDataOrFunc,func, result}}); 304 | throw new TypeError('Sorry, :comp cannot insert text at the top level.'); 305 | } 306 | } else { 307 | console.warn({errorDetails:{maybeDataOrFunc,func, result, data}}); 308 | throw new TypeError(`Sorry, :comp can only insert an Element or a string. ${result} given.`); 309 | } 310 | }; break; 311 | 312 | default: { 313 | console.warn({errorDetails:{item}}); 314 | throw new TypeError(`${item.tag} is not a directive.`); 315 | } 316 | } 317 | if ( item.children.length ) { 318 | console.warn({errorDetails:{item}}); 319 | throw new TypeError("Sorry, this directive cannot have children"); 320 | } 321 | } else { 322 | const element = document.createElement(item.tag); 323 | say("Making", element); 324 | 325 | specify(element, ...item.params); 326 | 327 | if ( parentElement ) { 328 | say(DEV,"Append el", element, "to", parentElement); 329 | parentElement.append(element); 330 | } 331 | parentElement = element; 332 | 333 | stack.push(element); 334 | if ( item.children.length ) { 335 | stack.push(...item.children.reverse()); 336 | } 337 | } 338 | } else { 339 | say("Empty item", item); 340 | } 341 | } 342 | 343 | while( parentElement.parentElement ) { 344 | parentElement = parentElement.parentElement; 345 | } 346 | 347 | say("Stack", stack, parentElement); 348 | return parentElement; 349 | } 350 | 351 | function specify(element, content, style) { 352 | // insert local content at stack top 353 | if ( content == null || content == undefined ) { 354 | // do nothing, but put a statement here so it will not be cut away by transpilers and produce lint / type errors 355 | content = ''; 356 | } else if ( typeof content == "string" ) { 357 | element.innerText = content; 358 | } else if ( typeof content == "object" ) { 359 | Object.keys(content).forEach(attrName => { 360 | const attrValue = content[attrName]; 361 | try { 362 | element.setAttribute(attrName, attrValue); 363 | } catch(e) {} 364 | try { 365 | element[attrName] = attrValue; 366 | } catch(e) {} 367 | }); 368 | } 369 | 370 | // apply style 371 | if ( style instanceof Function ) { 372 | style = style(element, content); 373 | } 374 | if ( typeof style == "string" ) { 375 | element.setAttribute("stylist", style); 376 | } else { 377 | Object.assign(element.style, style); 378 | } 379 | } 380 | 381 | function getData(maybeFunc) { 382 | let data; 383 | if ( maybeFunc instanceof Function ) { 384 | if ( maybeFunc instanceof AsyncFunction ) { 385 | console.warn({errorDetails:{maybeFunc}}); 386 | throw new TypeError("Sorry, AsyncFunctions as data producing functions are not supported. Maybe in future."); 387 | } 388 | data = maybeFunc(); 389 | } else { 390 | data = maybeFunc; 391 | } 392 | return data; 393 | } 394 | 395 | function say(...args) { 396 | if ( DEBUG ) { 397 | console.log(...args); 398 | } else if ( typeof args[0] == "boolean" && args[0] == true ) { 399 | args.shift(); 400 | console.log(...args); 401 | } 402 | } 403 | 404 | function equals(arr1, arr2) { 405 | if (arr1.length != arr2.length) { 406 | return false; 407 | } 408 | 409 | let eq = true; 410 | 411 | for( let i = 0; i < arr1.length; i++) { 412 | const item1 = arr1[i]; 413 | const item2 = arr2[i]; 414 | 415 | if ( typeof item1 == "string" ) { 416 | eq = item1 == item2; 417 | } else if ( item1 instanceof Function ) { 418 | eq = item1 == item2; 419 | } else if ( typeof item1 == "number" ) { 420 | eq = item1 == item2; 421 | } else if ( Array.isArray(item1) ) { 422 | eq = equals(item1, item2); 423 | } else if ( !!item1 && typeof item1 == "object" ) { 424 | eq = JSON.stringify(item1) == JSON.stringify(item2); 425 | } else { 426 | eq = false; 427 | } 428 | 429 | if ( ! eq ) break; 430 | } 431 | 432 | return eq; 433 | } 434 | 435 | export function clone(o) { 436 | return JSON.parse(JSON.stringify(o)); 437 | } 438 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bepis", 3 | "version": "2.2.1", 4 | "description": "bepis is a crazy new way to write HTML + CSS in JavaScript", 5 | "main": "index.js", 6 | "module": "index.js", 7 | "browser": "dist/index.js", 8 | "browserslist": [ 9 | "> 5%" 10 | ], 11 | "pre-commit": [ 12 | "build" 13 | ], 14 | "scripts": { 15 | "test": "serve -p 8080", 16 | "build": "parcel build index.js --no-source-maps" 17 | }, 18 | "repository": { 19 | "type": "git", 20 | "url": "git+https://github.com/crislin2046/bepis.git" 21 | }, 22 | "author": "@dosy", 23 | "license": "MIT", 24 | "bugs": { 25 | "url": "https://github.com/crislin2046/bepis/issues" 26 | }, 27 | "homepage": "https://github.com/crislin2046/bepis#readme", 28 | "devDependencies": { 29 | "parcel": "^1.12.4", 30 | "pre-commit": "latest", 31 | "serve": "latest" 32 | }, 33 | "dependencies": {}, 34 | "keywords": [ 35 | "static", 36 | "HTML", 37 | "CSS", 38 | "crazy" 39 | ] 40 | } 41 | -------------------------------------------------------------------------------- /readme-images/.npmignore: -------------------------------------------------------------------------------- 1 | *.jpg 2 | -------------------------------------------------------------------------------- /readme-images/bepis-logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/o0101/bepis/307e6120a2e5e82d18d27da9bf04d33f64bda3ec/readme-images/bepis-logo.jpg -------------------------------------------------------------------------------- /readme-images/bepiswatnsyou.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/o0101/bepis/307e6120a2e5e82d18d27da9bf04d33f64bda3ec/readme-images/bepiswatnsyou.jpg -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | import {w,clone} from './index.js'; 2 | 3 | { 4 | const o = undefined; 5 | const {body} = document; 6 | const myStyle = { 7 | color: '#808080', 8 | background: 'linear-gradient(to right, lime, dodgerblue)', 9 | margin: 0 10 | }; 11 | 12 | { 13 | w`form ${o} ${myStyle}, 14 | p label ${"Name"} input ${{required: true, type:'text', placeholder:'your name'}}. 15 | p label ${"Email"} input ${{required: true, type:'email', placeholder:'your email'}}. 16 | p label ${"Password"} input ${{required: true, type:'password', placeholder:'your password'}}. 17 | p button ${"Sign up"} 18 | `(body); 19 | } 20 | 21 | { 22 | w` 23 | main, 24 | style ${'nav.menubar { position: sticky; top: 0 }'}. 25 | nav ${{innerText:'Good', class:'menubar'}} ${{background: 'purple'}}. 26 | header ${{class:'banner'}}. 27 | article ${{class:'feature-box'}}, 28 | ul, 29 | li aside, 30 | h1 ${"A stunning feature"}. 31 | h2 ${"Amazing byline of the feature"}. 32 | . 33 | li aside, 34 | h1 ${"A stunning feature"}. 35 | h2 ${"Amazing byline of the feature"}. 36 | . 37 | li aside, 38 | h1 ${"A stunning feature"}. 39 | h2 ${"Amazing byline of the feature"}. 40 | . 41 | . 42 | . 43 | article ${{class:'social-proof'}}, 44 | ul, 45 | li aside, 46 | h1 ${"A fawning testimonial"}. 47 | h2 ${"Some Jerk paid to say nice things"}. 48 | . 49 | li aside, 50 | h1 ${"A fawning testimonial"}. 51 | h2 ${"Some Jerk paid to say nice things"}.. 52 | li aside, 53 | h1 ${"A fawning testimonial"}. 54 | h2 ${"Some Jerk paid to say nice things"}.. 55 | . 56 | . 57 | article ${{class:'cta plan-chooser'}}, 58 | ul, 59 | li aside, 60 | h1 ${"Free tier"}. 61 | h2 ${"THis one is for penniless losers"}.. 62 | li aside, 63 | h1 ${"Reccomended options"} 64 | h2 ${"You'll subsidize the free tier"}.. 65 | li aside, 66 | h1 ${"Enterprise jerks"}. 67 | h2 ${"You'll pay us more than we need"}.. 68 | . 69 | p ${"You only got one shot at this"} button ${"Purchase something now!"}. 70 | . 71 | footer ${{class:'meaningless-legaleze'}}, 72 | ul, 73 | li a ${{innerText: "Some link you'll never be able to contact us by", href:"#go-die"}}. 74 | li a ${{innerText: "Some link you'll never be able to contact us by", href:"#go-die"}}. 75 | li a ${{innerText: "Some link you'll never be able to contact us by", href:"#go-die"}}. 76 | li a ${{innerText: "Some link you'll never be able to contact us by", href:"#go-die"}}. 77 | li a ${{innerText: "Some link you'll never be able to contact us by", href:"#go-die"}}. 78 | li a ${{innerText: "Some link you'll never be able to contact us by", href:"#go-die"}}. 79 | . 80 | . 81 | `(document.body); 82 | } 83 | 84 | // :text test 85 | { 86 | w` 87 | h1, 88 | :text ${"Bepis is "}. 89 | em ${"REALLY"}. 90 | :text ${" the best!"}. 91 | :text ${" YES"} 92 | `(document.body); 93 | } 94 | 95 | // :comp test 96 | { 97 | const data = [ 98 | {name:"Name", spec: {required: true, type:'text', placeholder:'your name'}}, 99 | {name:"Email", spec: {required: true, type:'text', placeholder:'your email'}}, 100 | {name:"Password", spec: {required: true, type:'text', placeholder:'your password'}}, 101 | ]; 102 | const field = ({name, spec}) => w`p label ${name} input ${spec}`; 103 | 104 | const form = ({x,y} = {}) => w`form ${o} ${myStyle}, 105 | :map ${data} ${field}. 106 | p button ${x || y ? `Sign Up ${x+y}`: "Sign Up"} 107 | `(); 108 | 109 | w` 110 | article, 111 | h1 ${"Godot Waited!!"}. 112 | section ${{class:'form'}}, 113 | p ${"Fill it out"}. 114 | :comp ${o} ${form}. 115 | p ${"End of section"} 116 | `(document.body); 117 | 118 | w` 119 | article, 120 | h1 ${"Godot Waited with data function"}. 121 | section ${{class:'form'}}, 122 | p ${"Fill it out"}. 123 | :comp ${() => ({x:1,y:2})} ${form}. 124 | p ${"End of section"} 125 | `(document.body); 126 | 127 | w` 128 | article, 129 | h1 ${"Godot Waited with 1 param comp on form"}. 130 | section ${{class:'form'}}, 131 | p ${"Fill it out"}. 132 | :comp ${form}. 133 | p ${"End of section"} 134 | `(document.body); 135 | } 136 | 137 | // :map test 138 | { 139 | const data = [ 140 | {name:"Name", spec: {required: true, type:'text', placeholder:'your name'}}, 141 | {name:"Email", spec: {required: true, type:'text', placeholder:'your email'}}, 142 | {name:"Password", spec: {required: true, type:'text', placeholder:'your password'}}, 143 | ]; 144 | const field = ({name, spec}) => w`p label ${name} input ${spec}`; 145 | 146 | w`form ${o} ${myStyle}, 147 | :map ${data} ${field}. 148 | p button ${"Sign up"} 149 | `(body); 150 | 151 | w`form ${"Excellent Form with Data Function"} ${myStyle}, 152 | :map ${() => data} ${field}. 153 | p button ${"Sign up"} 154 | `(body); 155 | } 156 | 157 | const intervals = []; 158 | // pin test (instance) 159 | // passes 160 | { 161 | // should print 2 widgets that are 'pinned' to their mount locations 162 | 163 | let count = 0; 164 | let print = key => w` 165 | ${key} 166 | p label ${"Great " + key + " " + count++} input ${{value:"hat " + count}}`; 167 | 168 | // mount 169 | print('abc123')(document.body); 170 | print('abc124')(document.body); 171 | 172 | intervals.push(setInterval(() => print('abc123'), 1000)); 173 | intervals.push(setInterval(() => print('abc124'), 500)); 174 | } 175 | 176 | // pin true test (singleton) 177 | // passes 178 | { 179 | // should print only 1 widget updating every 500ms 180 | 181 | let count = 0; 182 | let print = key => w` 183 | ${true} 184 | p label ${"Great " + count++} input ${{value:"hat " + count}}`; 185 | 186 | //mount 187 | print()(document.body); 188 | 189 | intervals.push(setInterval(() => print('abc123'), 1000)); 190 | intervals.push(setInterval(() => print('abc124'), 500)); 191 | } 192 | 193 | // pin false test (free) 194 | // passes 195 | { 196 | // should print a new widget every 500ms 197 | 198 | let count = 0; 199 | let print = key => w` 200 | ${false} 201 | p label ${"Great " + count++} input ${{value:"hat " + count}}`; 202 | 203 | intervals.push(setInterval(() => print('abc123')(document.body), 1000)); 204 | intervals.push(setInterval(() => print('abc124')(document.body), 500)); 205 | } 206 | 207 | // pin no duplication on re-mount test 208 | // passes 209 | { 210 | // should print only 2 widgets even tho re-mounted each call 211 | 212 | let count = 0; 213 | let print = key => w` 214 | ${key} 215 | p label ${"Great " + key + " " + count++} input ${{value:"hat " + count}}`; 216 | 217 | // mount 218 | print('abc123')(document.body); 219 | print('abc124')(document.body); 220 | 221 | intervals.push(setInterval(() => print('abc123')(document.body), 1000)); 222 | intervals.push(setInterval(() => print('abc124')(document.body), 500)); 223 | } 224 | 225 | setTimeout(() => { 226 | console.log("Clearing intervals " + intervals.join(',')); 227 | intervals.forEach(i => clearInterval(i)); 228 | }, 5000); 229 | 230 | { 231 | // setup 232 | const Item = item => w`${item.key} 233 | li p, 234 | :text ${item.description}. 235 | a ${{ href: item.url }} :text ${item.name}. 236 | .`; 237 | const List = items => w`${true} ul :map ${items} ${Item}`; 238 | const myItems = [ 239 | { name: "Ratchet", description: "Tool", key: "z2" }, 240 | { name: "Screw", description: "Part", key: "a3" }, 241 | { name: "Sand", description: "Material", key: "p4" }, 242 | { name: "Moxie", description: "Intangible", key: "x5" }, 243 | { name: "Delilah", description: "Name", key: "s1" } 244 | ]; 245 | const newName = "Mojo"; 246 | 247 | Object.assign(window, { Item, List, myItems, newName }); 248 | 249 | // use 250 | List(myItems)(document.body); // mount it 251 | const myChangedItems = clone(myItems); 252 | myChangedItems[3].name = newName; // change something 253 | console.clear(); 254 | setTimeout(() => List(myChangedItems), 1000) // only item 3 will change 255 | } 256 | 257 | // function in style slot test 258 | { 259 | const print = value => w`p label ${"Label"} input ${{value}} ${styler}`; 260 | 261 | print('abc124')(document.body); 262 | 263 | function styler() { 264 | return { 265 | background: 'dodgerblue', 266 | color: 'white', 267 | fontWeight: 'bold', 268 | fontSize: '21pt' 269 | }; 270 | } 271 | } 272 | 273 | // string (stylist function name for style.dss) in style slot test 274 | { 275 | const print = value => w`p label ${"Label"} input ${{value}} ${"styler"}`; 276 | 277 | print('abc124')(document.body); 278 | 279 | function styler() { 280 | return ` 281 | * { 282 | background: 'dodgerblue'; 283 | color: 'white'; 284 | font-weight: 'bold'; 285 | } 286 | `; 287 | } 288 | } 289 | } 290 | 291 | --------------------------------------------------------------------------------