boxwood 0.57.2 → 0.59.1
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.
- package/README.md +0 -126
- package/package.json +10 -11
- package/src/Linter.js +11 -3
- package/src/Statistics.js +0 -4
- package/src/bundlers/esbuild/index.js +55 -0
- package/src/bundlers/esbuild/plugins/css.js +40 -0
- package/src/bundlers/esbuild/plugins/html.js +27 -0
- package/src/bundlers/esbuild/plugins/image.js +38 -0
- package/src/bundlers/esbuild/plugins/resolve.js +11 -0
- package/src/bundlers/esbuild/plugins/yaml.js +38 -0
- package/src/bundlers/esbuild/utilities/asset.js +19 -0
- package/src/compilers/html/Renderer.js +32 -28
- package/src/compilers/js/Compiler/index.js +7 -3
- package/src/optimizers/html.js +5 -0
- package/src/plugins/InlinePlugin/css.js +2 -3
- package/src/plugins/ScopedStylesPlugin/css.js +2 -2
- package/src/plugins/ScopedStylesPlugin/index.js +12 -1
- package/src/render.js +5 -3
- package/src/tags/img.js +1 -8
- package/src/tags/index.js +0 -2
- package/src/tags/script/index.js +1 -19
- package/src/transpilers/css/index.js +34 -0
- package/src/transpilers/{expression.js → html/expression.js} +43 -22
- package/src/transpilers/{html.js → html/index.js} +16 -7
- package/src/transpilers/html/node.js +244 -0
- package/src/transpilers/{tags → html/tags}/doctype.js +0 -0
- package/src/transpilers/{tags → html/tags}/import.js +0 -0
- package/src/transpilers/{tags → html/tags}/index.js +2 -1
- package/src/transpilers/{tags → html/tags}/partial.js +1 -1
- package/src/transpilers/html/tags/slot.js +7 -0
- package/src/utilities/collect.js +6 -23
- package/src/utilities/convert.js +7 -10
- package/src/utilities/errors.js +0 -8
- package/src/utilities/node.js +2 -32
- package/src/utilities/options.js +0 -11
- package/src/utilities/string.js +8 -9
- package/src/vdom/browser/render.js +6 -0
- package/src/vdom/nodes.js +4 -1
- package/src/vdom/server/render.js +10 -2
- package/src/vdom/tag.js +1 -1
- package/src/bundlers/esbuild.js +0 -75
- package/src/plugins/RoutesPlugin/index.js +0 -29
- package/src/tags/svg.js +0 -18
- package/src/transpilers/node.js +0 -139
- package/src/utilities/css.js +0 -64
- package/src/utilities/routes.js +0 -69
package/README.md
CHANGED
|
@@ -11,8 +11,6 @@
|
|
|
11
11
|
- [Install](#install)
|
|
12
12
|
- [Usage](#usage)
|
|
13
13
|
- [API](#api)
|
|
14
|
-
- [Examples](#examples)
|
|
15
|
-
- [Benchmarks](#benchmarks)
|
|
16
14
|
- [REPL](https://buxlabs.pl/en/tools/js/boxwood)
|
|
17
15
|
- [Maintainers](#maintainers)
|
|
18
16
|
- [Contributing](#contributing)
|
|
@@ -228,15 +226,6 @@ subtitle: Hey!
|
|
|
228
226
|
</layout>
|
|
229
227
|
```
|
|
230
228
|
|
|
231
|
-
#### template
|
|
232
|
-
|
|
233
|
-
You can define local components as well. It can be useful for tiny bits of html. Don't forget to specify the name of the component.
|
|
234
|
-
|
|
235
|
-
```html
|
|
236
|
-
<template foo>{bar}</template>
|
|
237
|
-
<foo {bar}/>
|
|
238
|
-
```
|
|
239
|
-
|
|
240
229
|
### Filters
|
|
241
230
|
|
|
242
231
|
There are many filters available out of the box.
|
|
@@ -294,14 +283,6 @@ It's possible to inline images as base64 strings.
|
|
|
294
283
|
<img src="images/foo.png" inline>
|
|
295
284
|
```
|
|
296
285
|
|
|
297
|
-
#### svg[from]
|
|
298
|
-
|
|
299
|
-
You can inline svgs too.
|
|
300
|
-
|
|
301
|
-
```html
|
|
302
|
-
<svg from="images/foo.svg"/>
|
|
303
|
-
```
|
|
304
|
-
|
|
305
286
|
### Styles
|
|
306
287
|
|
|
307
288
|
#### scoped
|
|
@@ -337,16 +318,6 @@ render(
|
|
|
337
318
|
</script>
|
|
338
319
|
```
|
|
339
320
|
|
|
340
|
-
#### polyfills
|
|
341
|
-
|
|
342
|
-
Polyfills can be injected.
|
|
343
|
-
|
|
344
|
-
```html
|
|
345
|
-
<script polyfills="['promise.js']">
|
|
346
|
-
new Promise(resolve => resolve())
|
|
347
|
-
</script>
|
|
348
|
-
```
|
|
349
|
-
|
|
350
321
|
### Variables
|
|
351
322
|
|
|
352
323
|
#### globals
|
|
@@ -403,103 +374,6 @@ For more complicated texts, you can also use inline translations.
|
|
|
403
374
|
</translation>
|
|
404
375
|
```
|
|
405
376
|
|
|
406
|
-
## Examples
|
|
407
|
-
|
|
408
|
-
The engine transforms html templates to a single rendering function. The compiler inlines variables, uses only the paths it needs and does other optimizations to create a fast template. There's still a big space for improvements, but the benchmarks look promising.
|
|
409
|
-
|
|
410
|
-
Let's have a look at some examples.
|
|
411
|
-
|
|
412
|
-
In this one, we'd like to render `{bar}`. The function has two parameters - __o (options) and __e (escape), which are referenced and used below.
|
|
413
|
-
|
|
414
|
-
```
|
|
415
|
-
<if foo.length equals 0>{bar}</if>
|
|
416
|
-
```
|
|
417
|
-
|
|
418
|
-
```js
|
|
419
|
-
function render(__o, __e) {
|
|
420
|
-
var __t = "";
|
|
421
|
-
if (__o.foo.length === 0) {
|
|
422
|
-
__t += __e(__o.bar);
|
|
423
|
-
}
|
|
424
|
-
return __t;
|
|
425
|
-
}
|
|
426
|
-
```
|
|
427
|
-
|
|
428
|
-
Let's have a look at a simple loop now.
|
|
429
|
-
|
|
430
|
-
```
|
|
431
|
-
<for month in months>{month}</for>
|
|
432
|
-
```
|
|
433
|
-
|
|
434
|
-
```js
|
|
435
|
-
function render(__o, __e) {
|
|
436
|
-
var __t = "";
|
|
437
|
-
for (var a = 0, b = __o.months.length; a < b; a += 1) {
|
|
438
|
-
var month = __o.months[a];
|
|
439
|
-
__t += __e(month);
|
|
440
|
-
}
|
|
441
|
-
return __t;
|
|
442
|
-
}
|
|
443
|
-
```
|
|
444
|
-
|
|
445
|
-
Using `<foreach` results with a slighty different code.
|
|
446
|
-
|
|
447
|
-
```
|
|
448
|
-
<foreach month in months>{month}</foreach>
|
|
449
|
-
```
|
|
450
|
-
|
|
451
|
-
```js
|
|
452
|
-
function render(__o, __e) {
|
|
453
|
-
var __t = "";
|
|
454
|
-
__o.months.forEach(function (month) {
|
|
455
|
-
__t += __e(month);
|
|
456
|
-
});
|
|
457
|
-
return __t;
|
|
458
|
-
}
|
|
459
|
-
```
|
|
460
|
-
|
|
461
|
-
## Benchmarks
|
|
462
|
-
|
|
463
|
-
`npm run benchmark`
|
|
464
|
-
|
|
465
|
-
```
|
|
466
|
-
todos: boxwood x 6,272,859 ops/sec ±0.36% (88 runs sampled)
|
|
467
|
-
todos: underscore x 284,402 ops/sec ±0.53% (91 runs sampled)
|
|
468
|
-
todos: lodash x 351,229 ops/sec ±0.51% (89 runs sampled)
|
|
469
|
-
todos: handlebars x 244,253 ops/sec ±0.62% (87 runs sampled)
|
|
470
|
-
todos: mustache x 535,452 ops/sec ±0.38% (92 runs sampled)
|
|
471
|
-
Fastest is boxwood
|
|
472
|
-
✔ benchmark: todos (30.9s)
|
|
473
|
-
friends: boxwood x 1,736,236 ops/sec ±0.33% (88 runs sampled)
|
|
474
|
-
friends: underscore x 100,276 ops/sec ±0.18% (89 runs sampled)
|
|
475
|
-
friends: lodash x 131,153 ops/sec ±0.39% (92 runs sampled)
|
|
476
|
-
friends: handlebars x 295,538 ops/sec ±0.15% (95 runs sampled)
|
|
477
|
-
friends: mustache x 164,524 ops/sec ±0.47% (92 runs sampled)
|
|
478
|
-
Fastest is boxwood
|
|
479
|
-
✔ benchmark: friends (31s)
|
|
480
|
-
if: boxwood x 62,801,617 ops/sec ±0.15% (87 runs sampled)
|
|
481
|
-
if: underscore x 530,691 ops/sec ±0.20% (90 runs sampled)
|
|
482
|
-
if: lodash x 549,457 ops/sec ±0.93% (86 runs sampled)
|
|
483
|
-
if: handlebars x 285,902 ops/sec ±0.54% (91 runs sampled)
|
|
484
|
-
if: mustache x 742,208 ops/sec ±0.41% (88 runs sampled)
|
|
485
|
-
Fastest is boxwood
|
|
486
|
-
✔ benchmark: if (30.8s)
|
|
487
|
-
projects: boxwood x 1,955,177 ops/sec ±0.27% (92 runs sampled)
|
|
488
|
-
projects: underscore x 117,698 ops/sec ±0.14% (90 runs sampled)
|
|
489
|
-
projects: lodash x 148,609 ops/sec ±0.46% (91 runs sampled)
|
|
490
|
-
projects: handlebars x 217,347 ops/sec ±0.25% (90 runs sampled)
|
|
491
|
-
projects: mustache x 228,487 ops/sec ±0.60% (88 runs sampled)
|
|
492
|
-
Fastest is boxwood
|
|
493
|
-
✔ benchmark: projects (31.1s)
|
|
494
|
-
search: boxwood x 648,366 ops/sec ±0.74% (91 runs sampled)
|
|
495
|
-
search: underscore x 22,410 ops/sec ±0.60% (90 runs sampled)
|
|
496
|
-
search: lodash x 26,543 ops/sec ±0.60% (90 runs sampled)
|
|
497
|
-
search: handlebars x 263,402 ops/sec ±0.45% (92 runs sampled)
|
|
498
|
-
search: mustache x 101,305 ops/sec ±0.25% (93 runs sampled)
|
|
499
|
-
Fastest is boxwood
|
|
500
|
-
✔ benchmark: search (31s)
|
|
501
|
-
```
|
|
502
|
-
|
|
503
377
|
## Maintainers
|
|
504
378
|
|
|
505
379
|
[@emilos](https://github.com/emilos)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "boxwood",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.59.1",
|
|
4
4
|
"description": "Compile HTML templates into JS",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -47,25 +47,24 @@
|
|
|
47
47
|
},
|
|
48
48
|
"homepage": "https://github.com/buxlabs/boxwood#readme",
|
|
49
49
|
"dependencies": {
|
|
50
|
-
"@rollup/plugin-commonjs": "^
|
|
51
|
-
"@rollup/plugin-node-resolve": "^13.0.
|
|
52
|
-
"abstract-syntax-tree": "^2.20.
|
|
50
|
+
"@rollup/plugin-commonjs": "^21.0.0",
|
|
51
|
+
"@rollup/plugin-node-resolve": "^13.0.5",
|
|
52
|
+
"abstract-syntax-tree": "^2.20.3",
|
|
53
53
|
"ansi-colors": "^4.1.1",
|
|
54
|
-
"axios": "0.
|
|
54
|
+
"axios": "^0.22.0",
|
|
55
55
|
"axios-extensions": "3.1.3",
|
|
56
56
|
"css-tree": "^1.1.3",
|
|
57
57
|
"csso": "^4.2.0",
|
|
58
|
-
"esbuild": "^0.
|
|
58
|
+
"esbuild": "^0.13.4",
|
|
59
59
|
"himalaya": "1.1.0",
|
|
60
60
|
"himalaya-walk": "1.0.0",
|
|
61
61
|
"html-lexer": "0.4.0",
|
|
62
|
-
"image-size": "^1.0.0",
|
|
63
62
|
"memoizee": "0.4.15",
|
|
64
63
|
"negate-sentence": "0.2.0",
|
|
65
64
|
"path-to-regexp": "6.2.0",
|
|
66
65
|
"pure-conditions": "1.2.1",
|
|
67
66
|
"pure-utilities": "^1.2.3",
|
|
68
|
-
"rollup": "^2.
|
|
67
|
+
"rollup": "^2.58.0",
|
|
69
68
|
"rollup-plugin-includepaths": "0.2.4",
|
|
70
69
|
"string-hash": "1.1.3",
|
|
71
70
|
"yaml": "^1.10.2"
|
|
@@ -79,9 +78,9 @@
|
|
|
79
78
|
"lodash.template": "4.5.0",
|
|
80
79
|
"mustache": "^4.2.0",
|
|
81
80
|
"nyc": "15.1.0",
|
|
82
|
-
"puppeteer": "^10.
|
|
83
|
-
"standard": "16.0.
|
|
84
|
-
"typescript": "^4.3
|
|
81
|
+
"puppeteer": "^10.4.0",
|
|
82
|
+
"standard": "^16.0.4",
|
|
83
|
+
"typescript": "^4.4.3",
|
|
85
84
|
"underscore": "^1.13.1"
|
|
86
85
|
},
|
|
87
86
|
"standard": {
|
package/src/Linter.js
CHANGED
|
@@ -4,12 +4,20 @@ const walk = require('himalaya-walk')
|
|
|
4
4
|
const { isImportTag } = require('./utilities/string')
|
|
5
5
|
const { unique, duplicates } = require('pure-utilities/array')
|
|
6
6
|
const { getComponentNames } = require('./utilities/attributes')
|
|
7
|
-
const { getAssetPaths, isImageNode
|
|
7
|
+
const { getAssetPaths, isImageNode } = require('./utilities/node')
|
|
8
|
+
|
|
9
|
+
function isStyleImport (node) {
|
|
10
|
+
const from = node.attributes.find(attr => attr.key === 'from')
|
|
11
|
+
if (from && from.value.endsWith('.css')) {
|
|
12
|
+
return true
|
|
13
|
+
}
|
|
14
|
+
return false
|
|
15
|
+
}
|
|
8
16
|
|
|
9
17
|
function analyze (tree) {
|
|
10
18
|
const components = []
|
|
11
19
|
walk(tree, node => {
|
|
12
|
-
if (isImportTag(node.tagName)) {
|
|
20
|
+
if (isImportTag(node.tagName) && !isStyleImport(node)) {
|
|
13
21
|
const names = getComponentNames(node.attributes)
|
|
14
22
|
names.forEach(name => components.push(name))
|
|
15
23
|
}
|
|
@@ -91,7 +99,7 @@ module.exports = class Linter {
|
|
|
91
99
|
})
|
|
92
100
|
imports.forEach(node => {
|
|
93
101
|
let assetPaths = getAssetPaths(node)
|
|
94
|
-
if (isImageNode(node, options)
|
|
102
|
+
if (isImageNode(node, options)) {
|
|
95
103
|
assetPaths = assetPaths.filter(item => !assetPaths.includes(item))
|
|
96
104
|
}
|
|
97
105
|
allPaths = allPaths.concat(assetPaths)
|
package/src/Statistics.js
CHANGED
|
@@ -10,7 +10,6 @@ class Statistics {
|
|
|
10
10
|
constructor () {
|
|
11
11
|
this.components = []
|
|
12
12
|
this.partials = []
|
|
13
|
-
this.svgs = []
|
|
14
13
|
this.images = []
|
|
15
14
|
this.scripts = []
|
|
16
15
|
this.stylesheets = []
|
|
@@ -26,7 +25,6 @@ class Statistics {
|
|
|
26
25
|
this
|
|
27
26
|
.concat('components', statistics.components)
|
|
28
27
|
.concat('partials', statistics.partials)
|
|
29
|
-
.concat('svgs', statistics.svgs)
|
|
30
28
|
.concat('images', statistics.images)
|
|
31
29
|
.concat('scripts', statistics.scripts)
|
|
32
30
|
.concat('stylesheets', statistics.stylesheets)
|
|
@@ -37,7 +35,6 @@ class Statistics {
|
|
|
37
35
|
return uniq([].concat(
|
|
38
36
|
this.components.map(item => item.path),
|
|
39
37
|
this.partials.map(item => item.path),
|
|
40
|
-
this.svgs.map(item => item.path),
|
|
41
38
|
this.images.map(item => item.path),
|
|
42
39
|
this.scripts.map(item => item.path),
|
|
43
40
|
this.stylesheets.map(item => item.path),
|
|
@@ -49,7 +46,6 @@ class Statistics {
|
|
|
49
46
|
return {
|
|
50
47
|
components: uniq(this.components),
|
|
51
48
|
partials: uniq(this.partials),
|
|
52
|
-
svgs: uniq(this.svgs),
|
|
53
49
|
images: uniq(this.images),
|
|
54
50
|
scripts: uniq(this.scripts),
|
|
55
51
|
stylesheets: uniq(this.stylesheets),
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
const esbuild = require('esbuild')
|
|
2
|
+
const AbstractSyntaxTree = require('abstract-syntax-tree')
|
|
3
|
+
const { writeFileSync, unlinkSync } = require('fs')
|
|
4
|
+
const { join } = require('path')
|
|
5
|
+
const { tmpdir } = require('os')
|
|
6
|
+
const { uid } = require('pure-utilities/string')
|
|
7
|
+
const ResolvePlugin = require('./plugins/resolve')
|
|
8
|
+
const HTMLPlugin = require('./plugins/html')
|
|
9
|
+
const CSSPlugin = require('./plugins/css')
|
|
10
|
+
const YAMLPlugin = require('./plugins/yaml')
|
|
11
|
+
const ImagePlugin = require('./plugins/image')
|
|
12
|
+
|
|
13
|
+
const bundle = async (source, options = {}) => {
|
|
14
|
+
const paths = options.paths || []
|
|
15
|
+
const styles = []
|
|
16
|
+
const input = join(tmpdir(), `${uid()}.js`)
|
|
17
|
+
|
|
18
|
+
writeFileSync(input, source)
|
|
19
|
+
const result = await esbuild.build({
|
|
20
|
+
platform: options.platform || 'node',
|
|
21
|
+
bundle: true,
|
|
22
|
+
plugins: [
|
|
23
|
+
ResolvePlugin({ paths }),
|
|
24
|
+
HTMLPlugin({ paths }),
|
|
25
|
+
CSSPlugin({ paths, styles }),
|
|
26
|
+
YAMLPlugin({ paths }),
|
|
27
|
+
ImagePlugin({ paths })
|
|
28
|
+
],
|
|
29
|
+
entryPoints: [input],
|
|
30
|
+
format: options.format || 'iife',
|
|
31
|
+
minify: false,
|
|
32
|
+
write: false,
|
|
33
|
+
target: 'es2016'
|
|
34
|
+
})
|
|
35
|
+
const file = result.outputFiles[0]
|
|
36
|
+
unlinkSync(input)
|
|
37
|
+
const tree = new AbstractSyntaxTree(file.text)
|
|
38
|
+
tree.replace(node => {
|
|
39
|
+
// TODO we need a better way to match the global scoped style tag
|
|
40
|
+
// this could lead to false
|
|
41
|
+
if (
|
|
42
|
+
node.type === 'ObjectExpression' &&
|
|
43
|
+
node.properties.length === 1 &&
|
|
44
|
+
node.properties[0].type === 'Property' &&
|
|
45
|
+
node.properties[0].key.type === 'Identifier' &&
|
|
46
|
+
node.properties[0].key.name === 'scoped'
|
|
47
|
+
) {
|
|
48
|
+
return { type: 'Literal', value: styles.join(' ') }
|
|
49
|
+
}
|
|
50
|
+
return node
|
|
51
|
+
})
|
|
52
|
+
return tree.source
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
module.exports = { bundle }
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
const { promises: { readFile } } = require('fs')
|
|
2
|
+
const { findAsset } = require('../utilities/asset')
|
|
3
|
+
const { transpile: transpileCSS, getSelectors } = require('../../../transpilers/css')
|
|
4
|
+
|
|
5
|
+
module.exports = ({ paths, styles }) => ({
|
|
6
|
+
name: 'css',
|
|
7
|
+
setup (build) {
|
|
8
|
+
build.onResolve({ filter: /\.css/ }, args => ({
|
|
9
|
+
path: args.path,
|
|
10
|
+
namespace: 'boxwood-css'
|
|
11
|
+
}))
|
|
12
|
+
build.onLoad({
|
|
13
|
+
filter: /.*/,
|
|
14
|
+
namespace: 'boxwood-css'
|
|
15
|
+
}, async (args) => {
|
|
16
|
+
const hasParams = args.path.includes('?')
|
|
17
|
+
const isScoped = hasParams && args.path.includes('scoped=true')
|
|
18
|
+
const path = hasParams ? args.path.substring(0, args.path.indexOf('?')) : args.path
|
|
19
|
+
const asset = findAsset(path, 'css', { paths })
|
|
20
|
+
if (!asset) {
|
|
21
|
+
// throw with a nice error message and add specs
|
|
22
|
+
}
|
|
23
|
+
const content = await readFile(asset.path, 'utf8')
|
|
24
|
+
if (isScoped) {
|
|
25
|
+
const style = transpileCSS(content)
|
|
26
|
+
const selectors = getSelectors(style)
|
|
27
|
+
styles.push(style)
|
|
28
|
+
return {
|
|
29
|
+
contents: `export default ${JSON.stringify(selectors)}`,
|
|
30
|
+
loader: 'js'
|
|
31
|
+
}
|
|
32
|
+
} else {
|
|
33
|
+
return {
|
|
34
|
+
contents: `export default \`${content}\``,
|
|
35
|
+
loader: 'js'
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
})
|
|
39
|
+
}
|
|
40
|
+
})
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
const { promises: { readFile } } = require('fs')
|
|
2
|
+
const { findAsset } = require('../utilities/asset')
|
|
3
|
+
const { transpile: transpileHTML } = require('../../../transpilers/html')
|
|
4
|
+
|
|
5
|
+
module.exports = ({ paths }) => ({
|
|
6
|
+
name: 'html',
|
|
7
|
+
setup (build) {
|
|
8
|
+
build.onResolve({ filter: /\.html?$/ }, args => ({
|
|
9
|
+
path: args.path.replace(/\.html$/, ''),
|
|
10
|
+
namespace: 'boxwood-html'
|
|
11
|
+
}))
|
|
12
|
+
build.onLoad({
|
|
13
|
+
filter: /.*/,
|
|
14
|
+
namespace: 'boxwood-html'
|
|
15
|
+
}, async (args) => {
|
|
16
|
+
const asset = findAsset(args.path, 'html', { paths })
|
|
17
|
+
if (!asset) {
|
|
18
|
+
// throw with a nice error message and add specs
|
|
19
|
+
}
|
|
20
|
+
const content = await readFile(asset.path, 'utf8')
|
|
21
|
+
return {
|
|
22
|
+
contents: transpileHTML(content.trim()),
|
|
23
|
+
loader: 'js'
|
|
24
|
+
}
|
|
25
|
+
})
|
|
26
|
+
}
|
|
27
|
+
})
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
const { promises: { readFile } } = require('fs')
|
|
2
|
+
const { findAsset } = require('../utilities/asset')
|
|
3
|
+
const { getExtension, getBase64Extension } = require('../../../utilities/string')
|
|
4
|
+
|
|
5
|
+
function getBase64String (base64, path) {
|
|
6
|
+
const extension = getExtension(path)
|
|
7
|
+
return [
|
|
8
|
+
`data:image/${getBase64Extension(extension)}`,
|
|
9
|
+
'charset=utf-8',
|
|
10
|
+
`base64,${base64}`
|
|
11
|
+
].join(';')
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
module.exports = ({ paths }) => ({
|
|
15
|
+
name: 'image',
|
|
16
|
+
setup (build) {
|
|
17
|
+
build.onResolve({ filter: /\.png|\.svg|\.jpg|\.jpeg/ }, args => ({
|
|
18
|
+
path: args.path,
|
|
19
|
+
namespace: 'boxwood-image'
|
|
20
|
+
}))
|
|
21
|
+
build.onLoad({
|
|
22
|
+
filter: /.*/,
|
|
23
|
+
namespace: 'boxwood-image'
|
|
24
|
+
}, async (args) => {
|
|
25
|
+
const asset = findAsset(args.path, null, { paths })
|
|
26
|
+
if (!asset) {
|
|
27
|
+
// TODO throw with a nice error message and add specs
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const buffer = await readFile(asset.path)
|
|
31
|
+
const base64 = buffer.toString('base64')
|
|
32
|
+
return {
|
|
33
|
+
contents: `export default "${getBase64String(base64, asset.path)}"`,
|
|
34
|
+
loader: 'js'
|
|
35
|
+
}
|
|
36
|
+
})
|
|
37
|
+
}
|
|
38
|
+
})
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
const { findAsset } = require('../utilities/asset')
|
|
2
|
+
|
|
3
|
+
module.exports = ({ paths }) => ({
|
|
4
|
+
name: 'resolve',
|
|
5
|
+
setup (build) {
|
|
6
|
+
build.onResolve({ filter: /.*/ }, args => {
|
|
7
|
+
// TODO handle libs from node_modules out of the box
|
|
8
|
+
return findAsset(args.path, undefined, { paths })
|
|
9
|
+
})
|
|
10
|
+
}
|
|
11
|
+
})
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
const YAML = require('yaml')
|
|
2
|
+
const { findAsset } = require('../utilities/asset')
|
|
3
|
+
const { promises: { readFile } } = require('fs')
|
|
4
|
+
|
|
5
|
+
module.exports = ({ paths }) => ({
|
|
6
|
+
name: 'yaml',
|
|
7
|
+
setup (build) {
|
|
8
|
+
build.onResolve({ filter: /\.yaml?$/ }, args => ({
|
|
9
|
+
path: args.path.replace(/\.yaml$/, ''),
|
|
10
|
+
namespace: 'boxwood-yaml'
|
|
11
|
+
}))
|
|
12
|
+
build.onLoad({
|
|
13
|
+
filter: /.*/,
|
|
14
|
+
namespace: 'boxwood-yaml'
|
|
15
|
+
}, async (args) => {
|
|
16
|
+
const asset = findAsset(args.path, 'yaml', { paths })
|
|
17
|
+
if (!asset) {
|
|
18
|
+
// throw with a nice error message and add specs
|
|
19
|
+
}
|
|
20
|
+
const content = await readFile(asset.path, 'utf8')
|
|
21
|
+
const data = YAML.parse(content)
|
|
22
|
+
return {
|
|
23
|
+
contents: `
|
|
24
|
+
const data = ${JSON.stringify(data)}
|
|
25
|
+
|
|
26
|
+
export function i18n (language) {
|
|
27
|
+
return function (key) {
|
|
28
|
+
return data.i18n[key][language]
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export default data
|
|
33
|
+
`,
|
|
34
|
+
loader: 'js'
|
|
35
|
+
}
|
|
36
|
+
})
|
|
37
|
+
}
|
|
38
|
+
})
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
const { existsSync } = require('fs')
|
|
2
|
+
const { join } = require('path')
|
|
3
|
+
|
|
4
|
+
function findAsset (filepath, extension = 'js', { paths }) {
|
|
5
|
+
const searchPath = extension
|
|
6
|
+
? filepath.endsWith(`.${extension}`) ? filepath : `${filepath}.${extension}`
|
|
7
|
+
: filepath
|
|
8
|
+
for (let i = 0, ilen = paths.length; i < ilen; i += 1) {
|
|
9
|
+
const path = join(paths[i], searchPath)
|
|
10
|
+
const index = join(paths[i], filepath, `index.${extension}`)
|
|
11
|
+
if (existsSync(path)) {
|
|
12
|
+
return { path }
|
|
13
|
+
} else if (extension && existsSync(index)) {
|
|
14
|
+
return { path: index }
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
module.exports = { findAsset }
|
|
@@ -9,7 +9,6 @@ const { getFilter } = require('../../utilities/filters')
|
|
|
9
9
|
const { concatenateScripts } = require('../../utilities/js')
|
|
10
10
|
const { unique } = require('pure-utilities/array')
|
|
11
11
|
const Statistics = require('../../Statistics')
|
|
12
|
-
const RoutesPlugin = require('../../plugins/RoutesPlugin')
|
|
13
12
|
const DataPlugin = require('../../plugins/DataPlugin')
|
|
14
13
|
const CurlyStylesPlugin = require('../../plugins/CurlyStylesPlugin')
|
|
15
14
|
const ScopedStylesPlugin = require('../../plugins/ScopedStylesPlugin')
|
|
@@ -22,6 +21,29 @@ const Optimizer = require('../../Optimizer')
|
|
|
22
21
|
const Scope = require('../../Scope')
|
|
23
22
|
const { getLiteral } = require('../../utilities/ast')
|
|
24
23
|
const Preprocessor = require('./Preprocessor')
|
|
24
|
+
const { transpile: transpileCSS } = require('../../transpilers/css')
|
|
25
|
+
|
|
26
|
+
function replaceNeedleOrAppend (tree, { needles, needle, tag, content }) {
|
|
27
|
+
if (content) {
|
|
28
|
+
const all = `<${tag}>${content}</${tag}>`
|
|
29
|
+
if (needles[needle]) {
|
|
30
|
+
const node = tree.first(`Literal[value=__NEEDLE_${needle.toUpperCase()}__]`)
|
|
31
|
+
node.value = all
|
|
32
|
+
} else {
|
|
33
|
+
tree.append(getTemplateAssignmentExpression(TEMPLATE_VARIABLE, getLiteral(all)))
|
|
34
|
+
}
|
|
35
|
+
} else {
|
|
36
|
+
tree.replace((node) => {
|
|
37
|
+
if (node.type === 'ExpressionStatement' &&
|
|
38
|
+
node.expression.type === 'AssignmentExpression' &&
|
|
39
|
+
node.expression.right.type === 'Literal' &&
|
|
40
|
+
node.expression.right.value === `__NEEDLE_${needle.toUpperCase()}__`) {
|
|
41
|
+
return null
|
|
42
|
+
}
|
|
43
|
+
return node
|
|
44
|
+
})
|
|
45
|
+
}
|
|
46
|
+
}
|
|
25
47
|
|
|
26
48
|
class Renderer {
|
|
27
49
|
async render (source, htmltree, options) {
|
|
@@ -46,8 +68,6 @@ class Renderer {
|
|
|
46
68
|
statistics.scripts.push(asset)
|
|
47
69
|
} else if (asset.type === 'STYLESHEET') {
|
|
48
70
|
statistics.stylesheets.push(asset)
|
|
49
|
-
} else if (asset.type === 'SVG') {
|
|
50
|
-
statistics.svgs.push(asset)
|
|
51
71
|
} else if (asset.type === 'IMAGE') {
|
|
52
72
|
statistics.images.push(asset)
|
|
53
73
|
} else if (asset.type === 'TRANSLATION') {
|
|
@@ -62,7 +82,6 @@ class Renderer {
|
|
|
62
82
|
const promises = []
|
|
63
83
|
const errors = []
|
|
64
84
|
const plugins = [
|
|
65
|
-
new RoutesPlugin(options, errors),
|
|
66
85
|
new DataPlugin(),
|
|
67
86
|
new InlinePlugin(),
|
|
68
87
|
new BoxModelPlugin(options),
|
|
@@ -83,6 +102,13 @@ class Renderer {
|
|
|
83
102
|
const styles = output.styles
|
|
84
103
|
? output.styles.map(style => style.children[0] && style.children[0].content).filter(Boolean)
|
|
85
104
|
: []
|
|
105
|
+
|
|
106
|
+
assets.forEach(asset => {
|
|
107
|
+
if (asset.type === 'COMPONENT' && asset.path.endsWith('.css')) {
|
|
108
|
+
const css = transpileCSS(asset.source)
|
|
109
|
+
styles.push(css)
|
|
110
|
+
}
|
|
111
|
+
})
|
|
86
112
|
walk(htmltree, async fragment => {
|
|
87
113
|
try {
|
|
88
114
|
const attrs = fragment.attributes || []
|
|
@@ -114,33 +140,11 @@ class Renderer {
|
|
|
114
140
|
})
|
|
115
141
|
await Promise.all(promises)
|
|
116
142
|
|
|
117
|
-
function replaceNeedleOrAppend (tree, { needle, tag, content }) {
|
|
118
|
-
if (content) {
|
|
119
|
-
const all = `<${tag}>${content}</${tag}>`
|
|
120
|
-
if (needles[needle]) {
|
|
121
|
-
const node = tree.first(`Literal[value=__NEEDLE_${needle.toUpperCase()}__]`)
|
|
122
|
-
node.value = all
|
|
123
|
-
} else {
|
|
124
|
-
tree.append(getTemplateAssignmentExpression(TEMPLATE_VARIABLE, getLiteral(all)))
|
|
125
|
-
}
|
|
126
|
-
} else {
|
|
127
|
-
tree.replace((node) => {
|
|
128
|
-
if (node.type === 'ExpressionStatement' &&
|
|
129
|
-
node.expression.type === 'AssignmentExpression' &&
|
|
130
|
-
node.expression.right.type === 'Literal' &&
|
|
131
|
-
node.expression.right.value === `__NEEDLE_${needle.toUpperCase()}__`) {
|
|
132
|
-
return null
|
|
133
|
-
}
|
|
134
|
-
return node
|
|
135
|
-
})
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
143
|
const style = unique(styles).join(' ')
|
|
140
|
-
replaceNeedleOrAppend(tree, { needle: 'head', tag: 'style', content: style })
|
|
144
|
+
replaceNeedleOrAppend(tree, { needles, needle: 'head', tag: 'style', content: style })
|
|
141
145
|
|
|
142
146
|
const script = concatenateScripts(scripts)
|
|
143
|
-
replaceNeedleOrAppend(tree, { needle: 'body', tag: 'script', content: script })
|
|
147
|
+
replaceNeedleOrAppend(tree, { needles, needle: 'body', tag: 'script', content: script })
|
|
144
148
|
|
|
145
149
|
const used = []
|
|
146
150
|
unique(filters).forEach(name => {
|
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
const AbstractSyntaxTree = require('abstract-syntax-tree')
|
|
2
2
|
const Bundler = require('../Bundler')
|
|
3
3
|
const { OBJECT_VARIABLE } = require('../../../utilities/enum')
|
|
4
|
+
const { getOptions, validateOptions } = require('../../../utilities/options')
|
|
4
5
|
|
|
5
6
|
class Compiler {
|
|
6
7
|
constructor (options) {
|
|
7
|
-
this.options = options
|
|
8
|
+
this.options = getOptions(options)
|
|
8
9
|
}
|
|
9
10
|
|
|
10
11
|
async compile (input) {
|
|
11
|
-
const
|
|
12
|
+
const { options } = this
|
|
13
|
+
const errors = validateOptions(options)
|
|
14
|
+
if (errors.length > 0) { return { errors } }
|
|
15
|
+
const bundler = new Bundler(options)
|
|
12
16
|
const bundle = await bundler.bundle(input)
|
|
13
17
|
const tree = new AbstractSyntaxTree(bundle)
|
|
14
18
|
const expression = tree.first('CallExpression > ArrowFunctionExpression')
|
|
@@ -16,7 +20,7 @@ class Compiler {
|
|
|
16
20
|
const lastNode = body.pop()
|
|
17
21
|
body.push({ type: 'ReturnStatement', argument: lastNode.expression })
|
|
18
22
|
const template = new Function(`return function render(${OBJECT_VARIABLE}) {\nreturn ${tree.source}}`)() // eslint-disable-line
|
|
19
|
-
return { template }
|
|
23
|
+
return { template, errors: [] }
|
|
20
24
|
}
|
|
21
25
|
}
|
|
22
26
|
|
|
@@ -2,8 +2,7 @@ const { parse, walk, generate } = require('css-tree')
|
|
|
2
2
|
const { getExtension, getBase64Extension } = require('../../utilities/string')
|
|
3
3
|
const { findAsset, isFileSupported } = require('../../utilities/files')
|
|
4
4
|
|
|
5
|
-
function getBase64String (
|
|
6
|
-
const { path, base64 } = asset
|
|
5
|
+
function getBase64String (base64, path, options, isFont) {
|
|
7
6
|
const extension = getExtension(path)
|
|
8
7
|
const dataType = isFont ? 'data:application/font-' : 'data:image/'
|
|
9
8
|
return [
|
|
@@ -17,7 +16,7 @@ function convertElementValueToBase64 ({ element, value, assets, options, isFont
|
|
|
17
16
|
if (!isFileSupported(value)) return
|
|
18
17
|
const asset = findAsset(value, assets, options)
|
|
19
18
|
if (!asset) return
|
|
20
|
-
element.value = getBase64String(asset, options, isFont)
|
|
19
|
+
element.value = getBase64String(asset.base64, asset.path, options, isFont)
|
|
21
20
|
}
|
|
22
21
|
|
|
23
22
|
function inlineUrls (tree, assets, options) {
|