boxwood 0.64.0 → 0.65.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 (2) hide show
  1. package/index.js +225 -210
  2. package/package.json +3 -3
package/index.js CHANGED
@@ -1,7 +1,7 @@
1
- const { join } = require('path')
2
- const { readFileSync } = require('fs')
3
- const csstree = require('css-tree')
4
- const toHash = require('string-hash')
1
+ const { join } = require("path")
2
+ const { readFileSync } = require("fs")
3
+ const csstree = require("css-tree")
4
+ const toHash = require("string-hash")
5
5
 
6
6
  async function compile(path) {
7
7
  const fn = require(path)
@@ -12,21 +12,23 @@ async function compile(path) {
12
12
  const styles = []
13
13
  const scripts = []
14
14
  const walk = (node) => {
15
- if (!node) { return }
16
- if (node.name === 'head') {
15
+ if (!node) {
16
+ return
17
+ }
18
+ if (node.name === "head") {
17
19
  nodes.head = node
18
20
  }
19
- if (node.name === 'body') {
21
+ if (node.name === "body") {
20
22
  nodes.body = node
21
23
  }
22
- if (node.name === 'style') {
24
+ if (node.name === "style") {
23
25
  const css = node.children
24
26
  if (!styles.includes(css)) {
25
27
  styles.push(css)
26
28
  }
27
29
  node.ignore = true
28
30
  }
29
- if (node.name === 'script') {
31
+ if (node.name === "script") {
30
32
  const js = node.children
31
33
  if (!scripts.includes(js)) {
32
34
  scripts.push(js)
@@ -43,16 +45,16 @@ async function compile(path) {
43
45
  if (nodes.head) {
44
46
  if (styles.length > 0) {
45
47
  nodes.head.children.push({
46
- name: 'style',
47
- children: styles.join(''),
48
+ name: "style",
49
+ children: styles.join(""),
48
50
  })
49
51
  }
50
52
  }
51
53
  if (nodes.body) {
52
54
  if (scripts.length > 0) {
53
55
  nodes.body.children.push({
54
- name: 'script',
55
- children: scripts.join(''),
56
+ name: "script",
57
+ children: scripts.join(""),
56
58
  })
57
59
  }
58
60
  }
@@ -62,11 +64,11 @@ async function compile(path) {
62
64
  }
63
65
 
64
66
  const ENTITIES = {
65
- '&': '&',
66
- '<': '&lt;',
67
- '>': '&gt;',
68
- "'": '&#39;',
69
- '"': '&quot;',
67
+ "&": "&amp;",
68
+ "<": "&lt;",
69
+ ">": "&gt;",
70
+ "'": "&#39;",
71
+ '"': "&quot;",
70
72
  }
71
73
 
72
74
  const REGEXP = /[&<>'"]/g
@@ -78,47 +80,47 @@ const escapeHTML = (string) => {
78
80
  }
79
81
 
80
82
  const BOOLEAN_ATTRIBUTES = [
81
- 'async',
82
- 'autofocus',
83
- 'autoplay',
84
- 'border',
85
- 'challenge',
86
- 'checked',
87
- 'compact',
88
- 'contenteditable',
89
- 'controls',
90
- 'default',
91
- 'defer',
92
- 'disabled',
93
- 'formnovalidate',
94
- 'frameborder',
95
- 'hidden',
96
- 'indeterminate',
97
- 'ismap',
98
- 'loop',
99
- 'multiple',
100
- 'muted',
101
- 'nohref',
102
- 'noresize',
103
- 'noshade',
104
- 'novalidate',
105
- 'nowrap',
106
- 'open',
107
- 'readonly',
108
- 'required',
109
- 'reversed',
110
- 'scoped',
111
- 'scrolling',
112
- 'seamless',
113
- 'selected',
114
- 'sortable',
115
- 'spellcheck',
116
- 'translate',
83
+ "async",
84
+ "autofocus",
85
+ "autoplay",
86
+ "border",
87
+ "challenge",
88
+ "checked",
89
+ "compact",
90
+ "contenteditable",
91
+ "controls",
92
+ "default",
93
+ "defer",
94
+ "disabled",
95
+ "formnovalidate",
96
+ "frameborder",
97
+ "hidden",
98
+ "indeterminate",
99
+ "ismap",
100
+ "loop",
101
+ "multiple",
102
+ "muted",
103
+ "nohref",
104
+ "noresize",
105
+ "noshade",
106
+ "novalidate",
107
+ "nowrap",
108
+ "open",
109
+ "readonly",
110
+ "required",
111
+ "reversed",
112
+ "scoped",
113
+ "scrolling",
114
+ "seamless",
115
+ "selected",
116
+ "sortable",
117
+ "spellcheck",
118
+ "translate",
117
119
  ]
118
120
 
119
121
  const ALIASES = {
120
- className: 'class',
121
- htmlFor: 'for'
122
+ className: "class",
123
+ htmlFor: "for",
122
124
  }
123
125
 
124
126
  const attributes = (options) => {
@@ -132,59 +134,59 @@ const attributes = (options) => {
132
134
  const name = ALIASES[key] || key
133
135
  const value = options[key]
134
136
  const content = Array.isArray(value) ? classes(...value) : value
135
- result.push(name + '=' + '"' + content + '"')
137
+ result.push(name + "=" + '"' + content + '"')
136
138
  }
137
139
  }
138
140
  }
139
- return result.join(' ')
141
+ return result.join(" ")
140
142
  }
141
143
 
142
144
  const SELF_CLOSING_TAGS = [
143
- 'area',
144
- 'base',
145
- 'br',
146
- 'col',
147
- 'command',
148
- 'embed',
149
- 'hr',
150
- 'img',
151
- 'input',
152
- 'keygen',
153
- 'link',
154
- 'meta',
155
- 'param',
156
- 'source',
157
- 'track',
158
- 'wbr',
159
- '!DOCTYPE html',
145
+ "area",
146
+ "base",
147
+ "br",
148
+ "col",
149
+ "command",
150
+ "embed",
151
+ "hr",
152
+ "img",
153
+ "input",
154
+ "keygen",
155
+ "link",
156
+ "meta",
157
+ "param",
158
+ "source",
159
+ "track",
160
+ "wbr",
161
+ "!DOCTYPE html",
160
162
  ]
161
163
 
162
164
  const isUnescapedTag = (name) => {
163
- return !['script', 'style', 'template'].includes(name)
165
+ return !["script", "style", "template"].includes(name)
164
166
  }
165
167
 
166
168
  const render = (input, escape = true) => {
167
169
  if (input.ignore) {
168
- return ''
170
+ return ""
169
171
  }
170
172
  if (Array.isArray(input)) {
171
- return input.filter(Boolean).map(render).join('')
173
+ return input.filter(Boolean).map(render).join("")
172
174
  }
173
- if (typeof input === 'number') {
175
+ if (typeof input === "number") {
174
176
  return input.toString()
175
177
  }
176
- if (typeof input === 'string') {
178
+ if (typeof input === "string") {
177
179
  return escape ? escapeHTML(input) : input
178
180
  }
179
- if (input.name === 'fragment') {
181
+ if (input.name === "fragment") {
180
182
  return render(input.children)
181
183
  }
182
- if (input.name === 'raw') {
184
+ if (input.name === "raw") {
183
185
  return render(input.children, false)
184
186
  }
185
187
  if (SELF_CLOSING_TAGS.includes(input.name)) {
186
188
  if (input.attributes) {
187
- return `<${input.name} ` + attributes(input.attributes) + '>'
189
+ return `<${input.name} ` + attributes(input.attributes) + ">"
188
190
  }
189
191
  return `<${input.name}>`
190
192
  }
@@ -192,7 +194,7 @@ const render = (input, escape = true) => {
192
194
  return (
193
195
  `<${input.name} ` +
194
196
  attributes(input.attributes) +
195
- '>' +
197
+ ">" +
196
198
  render(input.children, isUnescapedTag(input.name)) +
197
199
  `</${input.name}>`
198
200
  )
@@ -203,22 +205,26 @@ const render = (input, escape = true) => {
203
205
  )
204
206
  }
205
207
  if (input.children) {
206
- return `<${input.name}>` + render(input.children, isUnescapedTag(input.name)) + `</${input.name}>`
208
+ return (
209
+ `<${input.name}>` +
210
+ render(input.children, isUnescapedTag(input.name)) +
211
+ `</${input.name}>`
212
+ )
207
213
  }
208
214
  return `<${input.name}></${input.name}>`
209
215
  }
210
216
 
211
217
  const fragment = (children) => {
212
- return { name: 'fragment', children }
218
+ return { name: "fragment", children }
213
219
  }
214
220
 
215
221
  const raw = (children) => {
216
- return { name: 'raw', children }
222
+ return { name: "raw", children }
217
223
  }
218
224
 
219
225
  raw.load = function () {
220
226
  const path = join(...arguments)
221
- const content = readFileSync(path, 'utf8')
227
+ const content = readFileSync(path, "utf8")
222
228
  return raw(content)
223
229
  }
224
230
 
@@ -248,7 +254,7 @@ const tag = (a, b, c) => {
248
254
  }
249
255
 
250
256
  function css(inputs) {
251
- let result = ''
257
+ let result = ""
252
258
  for (let i = 0, ilen = inputs.length; i < ilen; i += 1) {
253
259
  const input = inputs[i]
254
260
  const value = arguments[i + 1]
@@ -263,7 +269,7 @@ function css(inputs) {
263
269
  const classes = {}
264
270
 
265
271
  csstree.walk(tree, (node) => {
266
- if (node.type === 'ClassSelector') {
272
+ if (node.type === "ClassSelector") {
267
273
  const name = `__${node.name}__${hash}`
268
274
  classes[node.name] = name
269
275
  node.name = name
@@ -272,18 +278,20 @@ function css(inputs) {
272
278
 
273
279
  return {
274
280
  ...classes,
275
- css: tag('style', csstree.generate(tree)),
281
+ css: tag("style", csstree.generate(tree)),
276
282
  }
277
283
  }
278
284
 
279
285
  css.load = function () {
280
286
  const path = join(...arguments)
281
- const content = readFileSync(path, 'utf8')
282
- return css`${content}`
287
+ const content = readFileSync(path, "utf8")
288
+ return css`
289
+ ${content}
290
+ `
283
291
  }
284
292
 
285
293
  function js(inputs) {
286
- let result = ''
294
+ let result = ""
287
295
  for (let i = 0, ilen = inputs.length; i < ilen; i += 1) {
288
296
  const input = inputs[i]
289
297
  const value = arguments[i + 1]
@@ -294,130 +302,130 @@ function js(inputs) {
294
302
  }
295
303
  }
296
304
  return {
297
- js: tag('script', result),
305
+ js: tag("script", result),
298
306
  }
299
307
  }
300
308
 
301
309
  js.load = function () {
302
310
  const path = join(...arguments)
303
- const content = readFileSync(path, 'utf8')
311
+ const content = readFileSync(path, "utf8")
304
312
  return js`${content}`
305
313
  }
306
314
 
307
315
  const node = (name) => (options, children) => tag(name, options, children)
308
- const doctype = node('!DOCTYPE html')
316
+ const doctype = node("!DOCTYPE html")
309
317
 
310
318
  const nodes = [
311
- 'a',
312
- 'abbr',
313
- 'address',
314
- 'area',
315
- 'article',
316
- 'aside',
317
- 'audio',
318
- 'b',
319
- 'base',
320
- 'bdi',
321
- 'bdo',
322
- 'blockquote',
323
- 'body',
324
- 'br',
325
- 'button',
326
- 'canvas',
327
- 'caption',
328
- 'cite',
329
- 'code',
330
- 'col',
331
- 'colgroup',
332
- 'data',
333
- 'datalist',
334
- 'dd',
335
- 'del',
336
- 'details',
337
- 'dfn',
338
- 'dialog',
339
- 'div',
340
- 'dl',
341
- 'dt',
342
- 'em',
343
- 'embed',
344
- 'fieldset',
345
- 'figcaption',
346
- 'figure',
347
- 'footer',
348
- 'form',
349
- 'h1',
350
- 'h2',
351
- 'h3',
352
- 'h4',
353
- 'h5',
354
- 'h6',
355
- 'head',
356
- 'header',
357
- 'hr',
358
- 'html',
359
- 'i',
360
- 'iframe',
361
- 'img',
362
- 'input',
363
- 'ins',
364
- 'kbd',
365
- 'label',
366
- 'legend',
367
- 'li',
368
- 'link',
369
- 'main',
370
- 'map',
371
- 'mark',
372
- 'meta',
373
- 'meter',
374
- 'nav',
375
- 'noscript',
376
- 'object',
377
- 'ol',
378
- 'optgroup',
379
- 'option',
380
- 'output',
381
- 'p',
382
- 'param',
383
- 'picture',
384
- 'pre',
385
- 'progress',
386
- 'q',
387
- 'rp',
388
- 'rt',
389
- 'ruby',
390
- 's',
391
- 'samp',
392
- 'script',
393
- 'section',
394
- 'select',
395
- 'small',
396
- 'source',
397
- 'span',
398
- 'strong',
399
- 'style',
400
- 'sub',
401
- 'summary',
402
- 'sup',
403
- 'svg',
404
- 'table',
405
- 'tbody',
406
- 'td',
407
- 'template',
408
- 'textarea',
409
- 'tfoot',
410
- 'th',
411
- 'thead',
412
- 'time',
413
- 'title',
414
- 'tr',
415
- 'track',
416
- 'u',
417
- 'ul',
418
- 'var',
419
- 'video',
420
- 'wbr',
319
+ "a",
320
+ "abbr",
321
+ "address",
322
+ "area",
323
+ "article",
324
+ "aside",
325
+ "audio",
326
+ "b",
327
+ "base",
328
+ "bdi",
329
+ "bdo",
330
+ "blockquote",
331
+ "body",
332
+ "br",
333
+ "button",
334
+ "canvas",
335
+ "caption",
336
+ "cite",
337
+ "code",
338
+ "col",
339
+ "colgroup",
340
+ "data",
341
+ "datalist",
342
+ "dd",
343
+ "del",
344
+ "details",
345
+ "dfn",
346
+ "dialog",
347
+ "div",
348
+ "dl",
349
+ "dt",
350
+ "em",
351
+ "embed",
352
+ "fieldset",
353
+ "figcaption",
354
+ "figure",
355
+ "footer",
356
+ "form",
357
+ "h1",
358
+ "h2",
359
+ "h3",
360
+ "h4",
361
+ "h5",
362
+ "h6",
363
+ "head",
364
+ "header",
365
+ "hr",
366
+ "html",
367
+ "i",
368
+ "iframe",
369
+ "img",
370
+ "input",
371
+ "ins",
372
+ "kbd",
373
+ "label",
374
+ "legend",
375
+ "li",
376
+ "link",
377
+ "main",
378
+ "map",
379
+ "mark",
380
+ "meta",
381
+ "meter",
382
+ "nav",
383
+ "noscript",
384
+ "object",
385
+ "ol",
386
+ "optgroup",
387
+ "option",
388
+ "output",
389
+ "p",
390
+ "param",
391
+ "picture",
392
+ "pre",
393
+ "progress",
394
+ "q",
395
+ "rp",
396
+ "rt",
397
+ "ruby",
398
+ "s",
399
+ "samp",
400
+ "script",
401
+ "section",
402
+ "select",
403
+ "small",
404
+ "source",
405
+ "span",
406
+ "strong",
407
+ "style",
408
+ "sub",
409
+ "summary",
410
+ "sup",
411
+ "svg",
412
+ "table",
413
+ "tbody",
414
+ "td",
415
+ "template",
416
+ "textarea",
417
+ "tfoot",
418
+ "th",
419
+ "thead",
420
+ "time",
421
+ "title",
422
+ "tr",
423
+ "track",
424
+ "u",
425
+ "ul",
426
+ "var",
427
+ "video",
428
+ "wbr",
421
429
  ].reduce((result, name) => {
422
430
  result[name] = node(name)
423
431
  return result
@@ -431,9 +439,9 @@ function classes() {
431
439
  continue
432
440
  }
433
441
  const type = typeof arg
434
- if (type === 'string') {
442
+ if (type === "string") {
435
443
  array.push(arg)
436
- } else if (type === 'object') {
444
+ } else if (type === "object") {
437
445
  for (const key in arg) {
438
446
  if (arg[key]) {
439
447
  array.push(key)
@@ -441,7 +449,13 @@ function classes() {
441
449
  }
442
450
  }
443
451
  }
444
- return array.join(' ')
452
+ return array.join(" ")
453
+ }
454
+
455
+ function i18n(translations) {
456
+ return function translate(language, key) {
457
+ return translations[key][language]
458
+ }
445
459
  }
446
460
 
447
461
  module.exports = {
@@ -454,5 +468,6 @@ module.exports = {
454
468
  css,
455
469
  js,
456
470
  tag,
471
+ i18n,
457
472
  ...nodes,
458
473
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "boxwood",
3
- "version": "0.64.0",
3
+ "version": "0.65.0",
4
4
  "description": "Compile HTML templates into JS",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -45,11 +45,11 @@
45
45
  },
46
46
  "homepage": "https://github.com/buxlabs/boxwood#readme",
47
47
  "devDependencies": {
48
- "ava": "^5.1.0",
48
+ "ava": "^5.2.0",
49
49
  "benchmark": "2.1.4",
50
50
  "express": "^4.18.2",
51
51
  "handlebars": "^4.7.7",
52
- "jsdom": "^20.0.3",
52
+ "jsdom": "^21.1.0",
53
53
  "lodash.template": "4.5.0",
54
54
  "mustache": "^4.2.0",
55
55
  "nyc": "15.1.0",