boxwood 0.74.0 → 0.75.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +111 -0
  3. package/benchmark/fixtures/basic/boxwood.js +3 -0
  4. package/benchmark/fixtures/basic/data.json +1 -0
  5. package/benchmark/fixtures/basic/handlebars.hbs +1 -0
  6. package/benchmark/fixtures/basic/lodash.ejs +1 -0
  7. package/benchmark/fixtures/basic/mustache.mst +1 -0
  8. package/benchmark/fixtures/basic/underscore.ejs +1 -0
  9. package/benchmark/fixtures/basic/vanilla.js +3 -0
  10. package/benchmark/fixtures/div/boxwood.js +5 -0
  11. package/benchmark/fixtures/div/data.json +1 -0
  12. package/benchmark/fixtures/div/handlebars.hbs +1 -0
  13. package/benchmark/fixtures/div/lodash.ejs +1 -0
  14. package/benchmark/fixtures/div/mustache.mst +1 -0
  15. package/benchmark/fixtures/div/underscore.ejs +1 -0
  16. package/benchmark/fixtures/div/vanilla.js +4 -0
  17. package/benchmark/fixtures/escape/boxwood.js +3 -0
  18. package/benchmark/fixtures/escape/data.json +3 -0
  19. package/benchmark/fixtures/escape/handlebars.hbs +1 -0
  20. package/benchmark/fixtures/escape/lodash.ejs +1 -0
  21. package/benchmark/fixtures/escape/mustache.mst +1 -0
  22. package/benchmark/fixtures/escape/underscore.ejs +1 -0
  23. package/benchmark/fixtures/escape/vanilla.js +5 -0
  24. package/benchmark/fixtures/friends/boxwood.js +56 -0
  25. package/benchmark/fixtures/friends/data.json +30 -0
  26. package/benchmark/fixtures/friends/handlebars.hbs +45 -0
  27. package/benchmark/fixtures/friends/lodash.ejs +48 -0
  28. package/benchmark/fixtures/friends/mustache.mst +45 -0
  29. package/benchmark/fixtures/friends/underscore.ejs +48 -0
  30. package/benchmark/fixtures/friends/vanilla.js +36 -0
  31. package/benchmark/fixtures/if/boxwood.js +21 -0
  32. package/benchmark/fixtures/if/data.json +12 -0
  33. package/benchmark/fixtures/if/handlebars.hbs +13 -0
  34. package/benchmark/fixtures/if/lodash.ejs +12 -0
  35. package/benchmark/fixtures/if/mustache.mst +13 -0
  36. package/benchmark/fixtures/if/underscore.ejs +12 -0
  37. package/benchmark/fixtures/if/vanilla.js +14 -0
  38. package/benchmark/fixtures/projects/boxwood.js +20 -0
  39. package/benchmark/fixtures/projects/data.json +26 -0
  40. package/benchmark/fixtures/projects/handlebars.hbs +15 -0
  41. package/benchmark/fixtures/projects/lodash.ejs +17 -0
  42. package/benchmark/fixtures/projects/mustache.mst +16 -0
  43. package/benchmark/fixtures/projects/underscore.ejs +17 -0
  44. package/benchmark/fixtures/projects/vanilla.js +21 -0
  45. package/benchmark/fixtures/search/boxwood.js +18 -0
  46. package/benchmark/fixtures/search/data.json +35 -0
  47. package/benchmark/fixtures/search/handlebars.hbs +18 -0
  48. package/benchmark/fixtures/search/lodash.ejs +22 -0
  49. package/benchmark/fixtures/search/mustache.mst +18 -0
  50. package/benchmark/fixtures/search/underscore.ejs +22 -0
  51. package/benchmark/fixtures/search/vanilla.js +26 -0
  52. package/benchmark/fixtures/todos/boxwood.js +9 -0
  53. package/benchmark/fixtures/todos/data.json +9 -0
  54. package/benchmark/fixtures/todos/handlebars.hbs +7 -0
  55. package/benchmark/fixtures/todos/lodash.ejs +7 -0
  56. package/benchmark/fixtures/todos/mustache.mst +7 -0
  57. package/benchmark/fixtures/todos/underscore.ejs +7 -0
  58. package/benchmark/fixtures/todos/vanilla.js +12 -0
  59. package/benchmark/index.js +112 -0
  60. package/index.js +19 -8
  61. package/package.json +9 -11
@@ -0,0 +1,26 @@
1
+ {
2
+ "title": "Projects",
3
+ "text": "Your projects",
4
+ "projects": [
5
+ {
6
+ "name": "foo",
7
+ "url": "foo.bar",
8
+ "description": "bar"
9
+ },
10
+ {
11
+ "name": "bar",
12
+ "url": "foo.bar",
13
+ "description": "bar"
14
+ },
15
+ {
16
+ "name": "baz",
17
+ "url": "foo.bar",
18
+ "description": "bar"
19
+ },
20
+ {
21
+ "name": "qux",
22
+ "url": "foo.bar",
23
+ "description": "bar"
24
+ }
25
+ ]
26
+ }
@@ -0,0 +1,15 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>{{title}}</title>
5
+ </head>
6
+ <body>
7
+ <p>{{text}}</p>
8
+ {{#each projects}}
9
+ <a href="{{this.url}}">{{this.name}}</a>
10
+ <p>{{this.description}}</p>
11
+ {{else}}
12
+ No projects
13
+ {{/each}}
14
+ </body>
15
+ </html>
@@ -0,0 +1,17 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title><%- title %></title>
5
+ </head>
6
+ <body>
7
+ <p><%- text %></p>
8
+ <% for (var i = 0, ilen = projects.length; i < ilen; i++) { %>
9
+ <% var project = projects[i]; %>
10
+ <a href="<%- project.url %>"><%- project.name %></a>
11
+ <p><%- project.description %></p>
12
+ <% } %>
13
+ <% if (projects.length === 0) { %>
14
+ No projects
15
+ <% } %>
16
+ </body>
17
+ </html>
@@ -0,0 +1,16 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>{{title}}</title>
5
+ </head>
6
+ <body>
7
+ <p>{{text}}</p>
8
+ {{#projects}}
9
+ <a href="{{url}}">{{name}}</a>
10
+ <p>{{description}}</p>
11
+ {{/projects}}
12
+ {{^projects}}
13
+ No projects
14
+ {{/projects}}
15
+ </body>
16
+ </html>
@@ -0,0 +1,17 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title><%- title %></title>
5
+ </head>
6
+ <body>
7
+ <p><%- text %></p>
8
+ <% for (var i = 0, ilen = projects.length; i < ilen; i++) { %>
9
+ <% var project = projects[i]; %>
10
+ <a href="<%- project.url %>"><%- project.name %></a>
11
+ <p><%- project.description %></p>
12
+ <% } %>
13
+ <% if (projects.length === 0) { %>
14
+ No projects
15
+ <% } %>
16
+ </body>
17
+ </html>
@@ -0,0 +1,21 @@
1
+ module.exports = ({ title, projects, text }) => {
2
+ let output =
3
+ "<!DOCTYPE html><html><head><title>" +
4
+ title +
5
+ "</title></head><body><p>" +
6
+ text +
7
+ "</p>"
8
+ projects.forEach((project) => {
9
+ output +=
10
+ '<a href="' +
11
+ project.url +
12
+ '">' +
13
+ project.name +
14
+ "</a><p>" +
15
+ project.description +
16
+ "</p>"
17
+ })
18
+ output += projects.length === 0 ? "No projects" : ""
19
+ output += "</body></html>"
20
+ return output
21
+ }
@@ -0,0 +1,18 @@
1
+ const { fragment, div, ul, li, p } = require("../../..")
2
+
3
+ module.exports = function ({ count, results }) {
4
+ return div({ class: "search" }, [
5
+ div({ class: "loader" }, "Loading..."),
6
+ div({ class: "results" }, [
7
+ p(`${count} results`),
8
+ ...results.map((result) =>
9
+ fragment([
10
+ div({ class: "title" }, result.title),
11
+ div({ class: "description" }, result.description),
12
+ result.featured && div({ class: "highlight" }, "Featured!"),
13
+ result.sizes.length && ul(result.sizes.map((size) => li(size))),
14
+ ])
15
+ ),
16
+ ]),
17
+ ])
18
+ }
@@ -0,0 +1,35 @@
1
+ {
2
+ "count": 5,
3
+ "results": [
4
+ {
5
+ "title": "foo",
6
+ "description": "foo",
7
+ "featured": true,
8
+ "sizes": [5, 10, 15]
9
+ },
10
+ {
11
+ "title": "bar",
12
+ "description": "bar",
13
+ "featured": false,
14
+ "sizes": [5, 10, 15, 20]
15
+ },
16
+ {
17
+ "title": "baz",
18
+ "description": "baz",
19
+ "featured": true,
20
+ "sizes": [5, 10, 15, 20, 25]
21
+ },
22
+ {
23
+ "title": "qux",
24
+ "description": "qux",
25
+ "featured": false,
26
+ "sizes": [5, 10, 15, 20, 25, 30]
27
+ },
28
+ {
29
+ "title": "quux",
30
+ "description": "quux",
31
+ "featured": true,
32
+ "sizes": [5, 10, 15, 20, 25, 30, 35]
33
+ }
34
+ ]
35
+ }
@@ -0,0 +1,18 @@
1
+ <div class="search">
2
+ <div class="loader">Loading...</div>
3
+ <div class="results">
4
+ <p>{{count}} results</p>
5
+ {{#each results}}
6
+ <div class="title">{{this.title}}</div>
7
+ <div class="description">{{this.description}}</div>
8
+ {{#if this.featured}}
9
+ <div class="highlight">Featured!</div>
10
+ {{/if}}
11
+ <ul>
12
+ {{#each sizes}}
13
+ <li>{{this}}</li>
14
+ {{/each}}
15
+ </ul>
16
+ {{/each}}
17
+ </div>
18
+ </div>
@@ -0,0 +1,22 @@
1
+ <div class="search">
2
+ <div class="loader">Loading...</div>
3
+ <div class="results">
4
+ <p><%- count %> results</p>
5
+ <% for (var i = 0, ilen = results.length; i < ilen; i++) { %>
6
+ <% var result = results[i]; %>
7
+ <div class="title"><%- result.title %></div>
8
+ <div class="description"><%- result.description %></div>
9
+ <% if (result.featured) { %>
10
+ <div class="highlight">Featured!</div>
11
+ <% } %>
12
+ <% if (result.sizes.length > 0) { %>
13
+ <ul>
14
+ <% for (var j = 0, jlen = result.sizes.length; j < jlen; j++) { %>
15
+ <% var size = result.sizes[j]; %>
16
+ <li><%- size %></li>
17
+ <% } %>
18
+ </ul>
19
+ <% } %>
20
+ <% } %>
21
+ </div>
22
+ </div>
@@ -0,0 +1,18 @@
1
+ <div class="search">
2
+ <div class="loader">Loading...</div>
3
+ <div class="results">
4
+ <p>{{count}} results</p>
5
+ {{#results}}
6
+ <div class="title">{{title}}</div>
7
+ <div class="description">{{description}}</div>
8
+ {{#featured}}
9
+ <div class="highlight">Featured!</div>
10
+ {{/featured}}
11
+ <ul>
12
+ {{#sizes}}
13
+ <li>{{.}}</li>
14
+ {{/sizes}}
15
+ </ul>
16
+ {{/results}}
17
+ </div>
18
+ </div>
@@ -0,0 +1,22 @@
1
+ <div class="search">
2
+ <div class="loader">Loading...</div>
3
+ <div class="results">
4
+ <p><%- count %> results</p>
5
+ <% for (var i = 0, ilen = results.length; i < ilen; i++) { %>
6
+ <% var result = results[i]; %>
7
+ <div class="title"><%- result.title %></div>
8
+ <div class="description"><%- result.description %></div>
9
+ <% if (result.featured) { %>
10
+ <div class="highlight">Featured!</div>
11
+ <% } %>
12
+ <% if (result.sizes.length > 0) { %>
13
+ <ul>
14
+ <% for (var j = 0, jlen = result.sizes.length; j < jlen; j++) { %>
15
+ <% var size = result.sizes[j]; %>
16
+ <li><%- size %></li>
17
+ <% } %>
18
+ </ul>
19
+ <% } %>
20
+ <% } %>
21
+ </div>
22
+ </div>
@@ -0,0 +1,26 @@
1
+ module.exports = function ({ count, results }) {
2
+ let template = '<div class="search">' +
3
+ '<div class="loader">Loading...</div>' +
4
+ '<div class="results">' +
5
+ '<p>' + count + ' results</p>'
6
+
7
+ for (let i = 0, ilen = results.length; i < ilen; i++) {
8
+ const result = results[i]
9
+ template += '<div class="title">' + result.title + '</div>'
10
+ template += '<div class="description">' + result.description + '</div>'
11
+ if (result.featured) {
12
+ template += '<div class="highlight">Featured!</div>'
13
+ }
14
+ if (result.sizes.length) {
15
+ template += '<ul>'
16
+ for (let j = 0, jlen = result.sizes.length; j < jlen; j += 1) {
17
+ const size = result.sizes[j]
18
+ template += '<li>' + size + '</li>'
19
+ }
20
+ template += '</ul>'
21
+ }
22
+ }
23
+
24
+ template += '</div></div>'
25
+ return template
26
+ }
@@ -0,0 +1,9 @@
1
+ const { h1, h2, ul, li, fragment } = require("../../..")
2
+
3
+ module.exports = function ({ title, subtitle, todos }) {
4
+ return fragment([
5
+ h1(title),
6
+ h2(subtitle),
7
+ ul(todos.map((todo) => li(todo.description))),
8
+ ])
9
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "title": "foo",
3
+ "subtitle": "baz",
4
+ "todos": [
5
+ { "description": "lorem ipsum" },
6
+ { "description": "dolor sit" },
7
+ { "description": "amet" }
8
+ ]
9
+ }
@@ -0,0 +1,7 @@
1
+ <h1>{{title}}</h1>
2
+ <h2>{{subtitle}}</h2>
3
+ <ul>
4
+ {{#each todos}}
5
+ <li>{{this.description}}</li>
6
+ {{/each}}
7
+ </ul>
@@ -0,0 +1,7 @@
1
+ <h1><%- title %></h1>
2
+ <h2><%- subtitle %></h2>
3
+ <ul>
4
+ <% for(var i = 0, ilen = todos.length; i < ilen; i++) { %>
5
+ <li><%- todos[i].description %></li>
6
+ <% } %>
7
+ </ul>
@@ -0,0 +1,7 @@
1
+ <h1>{{title}}</h1>
2
+ <h2>{{subtitle}}</h2>
3
+ <ul>
4
+ {{#todos}}
5
+ <li>{{description}}</li>
6
+ {{/todos}}
7
+ </ul>
@@ -0,0 +1,7 @@
1
+ <h1><%- title %></h1>
2
+ <h2><%- subtitle %></h2>
3
+ <ul>
4
+ <% for(var i = 0, ilen = todos.length; i < ilen; i++) { %>
5
+ <li><%- todos[i].description %></li>
6
+ <% } %>
7
+ </ul>
@@ -0,0 +1,12 @@
1
+ module.exports = ({ title, subtitle, todos }) => {
2
+ let template = ''
3
+ template += '<h1>' + title + '</h1>'
4
+ template += '<h2>' + subtitle + '</h2>'
5
+ template += '<ul>'
6
+ for (let i = 0, ilen = todos.length; i < ilen; i++) {
7
+ const todo = todos[i]
8
+ template += '<li>' + todo.description + '</li>'
9
+ }
10
+ template += '</ul>'
11
+ return template
12
+ }
@@ -0,0 +1,112 @@
1
+ const test = require("node:test")
2
+ const assert = require("node:assert")
3
+ const path = require("path")
4
+ const {
5
+ promises: { readFile },
6
+ } = require("fs")
7
+ const { Suite } = require("benchmark")
8
+ const underscore = require("underscore")
9
+ const handlebars = require("handlebars")
10
+ const mustache = require("mustache")
11
+ const { compile } = require("..")
12
+
13
+ async function benchmark(dir) {
14
+ const source2 = await readFile(
15
+ path.join(__dirname, `./fixtures/${dir}/underscore.ejs`),
16
+ "utf8"
17
+ )
18
+ const source3 = await readFile(
19
+ path.join(__dirname, `./fixtures/${dir}/lodash.ejs`),
20
+ "utf8"
21
+ )
22
+ const source4 = await readFile(
23
+ path.join(__dirname, `./fixtures/${dir}/handlebars.hbs`),
24
+ "utf8"
25
+ )
26
+ const source5 = await readFile(
27
+ path.join(__dirname, `./fixtures/${dir}/mustache.mst`),
28
+ "utf8"
29
+ )
30
+
31
+ const suite = new Suite()
32
+ const { template: fn1 } = await compile(
33
+ path.join(__dirname, `./fixtures/${dir}/boxwood.js`)
34
+ )
35
+ const fn2 = underscore.template(source2)
36
+ const fn3 = handlebars.compile(source4)
37
+ const fn4 = (data) => mustache.render(source5, data)
38
+ const fn5 = require(path.join(__dirname, `./fixtures/${dir}/vanilla.js`))
39
+ mustache.parse(source5)
40
+
41
+ const data = require(path.join(__dirname, `./fixtures/${dir}/data.json`))
42
+
43
+ function normalize(string) {
44
+ return string.replace(/\s/g, "")
45
+ }
46
+ const result = normalize(fn1(data))
47
+
48
+ assert.deepEqual(result, normalize(fn1(data)))
49
+ assert.deepEqual(result, normalize(fn2(data)))
50
+ assert.deepEqual(result, normalize(fn3(data)))
51
+ assert.deepEqual(result, normalize(fn4(data)))
52
+ assert.deepEqual(result, normalize(fn5(data)))
53
+
54
+ await new Promise((resolve) => {
55
+ suite
56
+ .add("vanilla[js]", function () {
57
+ fn5(data)
58
+ })
59
+ .add("boxwood[js]", function () {
60
+ fn1(data)
61
+ })
62
+ .add("underscore[ejs]", function () {
63
+ fn2(data)
64
+ })
65
+ .add("handlebars[hbs]", function () {
66
+ fn3(data)
67
+ })
68
+ .add("mustache[mst]", function () {
69
+ fn4(data)
70
+ })
71
+ .on("cycle", function (event) {
72
+ console.log(`${dir}: ${String(event.target)}`)
73
+ })
74
+ .on("complete", function () {
75
+ console.log("Fastest is " + this.filter("fastest").map("name"))
76
+ resolve()
77
+ })
78
+ .run({ async: true })
79
+ })
80
+ }
81
+
82
+ test("benchmark: basic", async () => {
83
+ await benchmark("basic")
84
+ })
85
+
86
+ test("benchmark: escape", async () => {
87
+ await benchmark("escape")
88
+ })
89
+
90
+ test("benchmark: div", async () => {
91
+ await benchmark("div")
92
+ })
93
+
94
+ test("benchmark: if", async () => {
95
+ await benchmark("if")
96
+ })
97
+
98
+ test("benchmark: todos", async () => {
99
+ await benchmark("todos")
100
+ })
101
+
102
+ test("benchmark: friends", async () => {
103
+ await benchmark("friends")
104
+ })
105
+
106
+ test("benchmark: search", async () => {
107
+ await benchmark("search")
108
+ })
109
+
110
+ test("benchmark: projects", async () => {
111
+ await benchmark("projects")
112
+ })
package/index.js CHANGED
@@ -314,9 +314,20 @@ function js(inputs) {
314
314
  }
315
315
 
316
316
  js.load = function () {
317
- const path = join(...arguments)
317
+ const parts = []
318
+ for (const param of arguments) {
319
+ if (typeof param === "string") {
320
+ parts.push(param)
321
+ }
322
+ }
323
+ const path = join(...parts)
318
324
  const file = path.endsWith(".js") ? path : join(path, "index.js")
319
325
  const content = readFileSync(file, "utf8")
326
+
327
+ const options = arguments[arguments.length - 1]
328
+ if (options && options.transform) {
329
+ return js`${options.transform(content)}`
330
+ }
320
331
  return js`${content}`
321
332
  }
322
333
 
@@ -539,7 +550,7 @@ i18n.load = function () {
539
550
  }
540
551
  }
541
552
 
542
- function component(fn, { styles, i18n, code } = {}) {
553
+ function component(fn, { styles, i18n, scripts } = {}) {
543
554
  function execute(a, b) {
544
555
  if (typeof a === "string" || typeof a === "number" || Array.isArray(a)) {
545
556
  return fn({}, a)
@@ -571,11 +582,11 @@ function component(fn, { styles, i18n, code } = {}) {
571
582
  }
572
583
  return function (a, b) {
573
584
  const tree = execute(a, b)
574
- if (styles && code) {
585
+ if (styles && scripts) {
575
586
  if (Array.isArray(tree)) {
576
- return tree.concat(styles.css, code.js)
587
+ return tree.concat(styles.css, scripts.js)
577
588
  }
578
- return [tree, styles.css, code.js]
589
+ return [tree, styles.css, scripts.js]
579
590
  }
580
591
  if (styles) {
581
592
  if (Array.isArray(tree)) {
@@ -583,11 +594,11 @@ function component(fn, { styles, i18n, code } = {}) {
583
594
  }
584
595
  return [tree, styles.css]
585
596
  }
586
- if (code) {
597
+ if (scripts) {
587
598
  if (Array.isArray(tree)) {
588
- return tree.concat(code.js)
599
+ return tree.concat(scripts.js)
589
600
  }
590
- return [tree, code.js]
601
+ return [tree, scripts.js]
591
602
  }
592
603
  return tree
593
604
  }
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "boxwood",
3
- "version": "0.74.0",
3
+ "version": "0.75.0",
4
4
  "description": "Compile HTML templates into JS",
5
5
  "main": "index.js",
6
6
  "scripts": {
7
- "test": "ava '**/*.spec.js'",
8
- "coverage": "nyc npm test",
9
- "benchmark": "ava test/benchmark/index.js --verbose",
7
+ "test": "node --test",
8
+ "coverage": "c8 npm test",
9
+ "benchmark": "node --test benchmark/index.js",
10
10
  "watch": "npm test -- --watch",
11
11
  "prepush": "npm test"
12
12
  },
@@ -17,7 +17,7 @@
17
17
  ]
18
18
  },
19
19
  "engines": {
20
- "node": ">= 16.13.1"
20
+ "node": ">= 20.11.1"
21
21
  },
22
22
  "repository": {
23
23
  "type": "git",
@@ -45,14 +45,12 @@
45
45
  },
46
46
  "homepage": "https://github.com/buxlabs/boxwood#readme",
47
47
  "devDependencies": {
48
- "ava": "^5.3.1",
49
48
  "benchmark": "2.1.4",
50
- "express": "^4.18.2",
49
+ "c8": "^9.1.0",
50
+ "express": "^4.19.2",
51
51
  "handlebars": "^4.7.8",
52
- "jsdom": "^22.1.0",
53
- "lodash.template": "4.5.0",
52
+ "jsdom": "^24.0.0",
54
53
  "mustache": "^4.2.0",
55
- "nyc": "15.1.0",
56
54
  "underscore": "^1.13.6"
57
55
  },
58
56
  "standard": {
@@ -65,6 +63,6 @@
65
63
  "dependencies": {
66
64
  "css-tree": "^2.3.1",
67
65
  "string-hash": "^1.1.3",
68
- "yaml": "^2.3.2"
66
+ "yaml": "^2.4.2"
69
67
  }
70
68
  }