├── .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 |
3 |
4 |
5 | # :dog2: [bepis](#drincc)  
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 |
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 |
--------------------------------------------------------------------------------