boxwood 0.57.1 → 0.59.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 (47) hide show
  1. package/README.md +0 -126
  2. package/package.json +10 -11
  3. package/src/Linter.js +11 -3
  4. package/src/Statistics.js +0 -4
  5. package/src/bundlers/esbuild/index.js +55 -0
  6. package/src/bundlers/esbuild/plugins/css.js +40 -0
  7. package/src/bundlers/esbuild/plugins/html.js +27 -0
  8. package/src/bundlers/esbuild/plugins/image.js +38 -0
  9. package/src/bundlers/esbuild/plugins/resolve.js +11 -0
  10. package/src/bundlers/esbuild/plugins/yaml.js +38 -0
  11. package/src/bundlers/esbuild/utilities/asset.js +19 -0
  12. package/src/compilers/html/Renderer.js +32 -28
  13. package/src/compilers/js/{Compiler.js → Compiler/index.js} +9 -5
  14. package/src/plugins/InlinePlugin/css.js +2 -3
  15. package/src/plugins/ScopedStylesPlugin/css.js +2 -2
  16. package/src/plugins/ScopedStylesPlugin/index.js +12 -1
  17. package/src/render.js +1 -1
  18. package/src/tags/img.js +1 -8
  19. package/src/tags/index.js +0 -2
  20. package/src/tags/script/index.js +1 -19
  21. package/src/transpilers/css/index.js +34 -0
  22. package/src/transpilers/{expression.js → html/expression.js} +43 -22
  23. package/src/transpilers/{html.js → html/index.js} +16 -7
  24. package/src/transpilers/html/node.js +244 -0
  25. package/src/transpilers/{tags → html/tags}/doctype.js +0 -0
  26. package/src/transpilers/{tags → html/tags}/import.js +0 -0
  27. package/src/transpilers/{tags → html/tags}/index.js +2 -1
  28. package/src/transpilers/{tags → html/tags}/partial.js +1 -1
  29. package/src/transpilers/html/tags/slot.js +7 -0
  30. package/src/utilities/collect.js +6 -23
  31. package/src/utilities/convert.js +7 -10
  32. package/src/utilities/errors.js +0 -8
  33. package/src/utilities/node.js +2 -32
  34. package/src/utilities/options.js +0 -11
  35. package/src/utilities/string.js +8 -9
  36. package/src/vdom/browser/render.js +6 -0
  37. package/src/vdom/nodes.js +4 -1
  38. package/src/vdom/server/index.js +2 -0
  39. package/src/vdom/server/render.js +10 -2
  40. package/src/vdom/tag.js +1 -1
  41. package/src/vdom/utilities/classes.js +22 -0
  42. package/src/bundlers/esbuild.js +0 -75
  43. package/src/plugins/RoutesPlugin/index.js +0 -29
  44. package/src/tags/svg.js +0 -18
  45. package/src/transpilers/node.js +0 -139
  46. package/src/utilities/css.js +0 -64
  47. 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.57.1",
3
+ "version": "0.59.0",
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": "^20.0.0",
51
- "@rollup/plugin-node-resolve": "^13.0.4",
52
- "abstract-syntax-tree": "^2.20.0",
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.21.1",
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.12.17",
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.55.1",
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.1.0",
83
- "standard": "16.0.3",
84
- "typescript": "^4.3.5",
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, isSVGNode } = require('./utilities/node')
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) || isSVGNode(node)) {
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
- const Bundler = require('./Bundler')
3
- const { OBJECT_VARIABLE } = require('../../utilities/enum')
2
+ const Bundler = require('../Bundler')
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 bundler = new Bundler(this.options)
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 (asset, options, isFont) {
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) {
@@ -1,12 +1,12 @@
1
1
  'use strict'
2
2
 
3
3
  const { parse, walk, generate } = require('css-tree')
4
- const hash = require('string-hash')
4
+ const { hash } = require('../../utilities/string')
5
5
  const { normalizeNewline } = require('../../utilities/string')
6
6
 
7
7
  function addScopeToCssSelectors (input, scopes) {
8
8
  const content = normalizeNewline(input).trim()
9
- const id = `scope-${hash(content)}`
9
+ const id = hash(content)
10
10
  const tree = parse(content)
11
11
  const keyframes = {}
12
12
  walk(tree, node => {