@maizzle/framework 3.7.2 → 4.0.0-alpha.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.
Files changed (37) hide show
  1. package/.github/workflows/nodejs.yml +28 -29
  2. package/package.json +87 -84
  3. package/src/generators/output/to-disk.js +139 -111
  4. package/src/generators/output/to-string.js +5 -2
  5. package/src/generators/posthtml.js +61 -60
  6. package/src/generators/tailwindcss.js +49 -18
  7. package/src/index.js +24 -36
  8. package/src/transformers/{attribute-to-style.js → attributeToStyle.js} +0 -0
  9. package/src/transformers/baseUrl.js +45 -0
  10. package/src/transformers/{extra-attributes.js → extraAttributes.js} +0 -0
  11. package/src/transformers/index.js +57 -57
  12. package/src/transformers/{inline.js → inlineCss.js} +0 -0
  13. package/src/transformers/{posthtml-mso.js → posthtmlMso.js} +0 -0
  14. package/src/transformers/{prevent-widows.js → preventWidows.js} +0 -0
  15. package/src/transformers/{remove-attributes.js → removeAttributes.js} +1 -1
  16. package/src/transformers/{remove-inline-bgcolor.js → removeInlineBackgroundColor.js} +0 -0
  17. package/src/transformers/{remove-inline-sizes.js → removeInlineSizes.js} +0 -0
  18. package/src/transformers/{remove-unused-css.js → removeUnusedCss.js} +0 -0
  19. package/src/transformers/{replace-strings.js → replaceStrings.js} +0 -0
  20. package/src/transformers/{safe-class-names.js → safeClassNames.js} +8 -2
  21. package/src/transformers/{six-hex.js → sixHex.js} +10 -10
  22. package/src/transformers/transform.js +4 -6
  23. package/src/transformers/{url-params.js → urlParameters.js} +0 -0
  24. package/src/utils/helpers.js +2 -8
  25. package/test/expected/transformers/base-image-url.html +83 -7
  26. package/test/expected/transformers/transform-postcss.html +19 -0
  27. package/test/expected/useConfig.html +9 -0
  28. package/test/fixtures/transformers/base-image-url.html +85 -7
  29. package/test/fixtures/useConfig.html +9 -0
  30. package/test/stubs/tailwind/preserve.html +1 -0
  31. package/test/test-misc.js +8 -8
  32. package/test/test-tailwind.js +100 -73
  33. package/test/test-todisk.js +70 -28
  34. package/test/test-tostring.js +8 -0
  35. package/test/test-transformers.js +343 -320
  36. package/xo.config.js +22 -19
  37. package/src/transformers/base-image-url.js +0 -9
@@ -1,29 +1,28 @@
1
- # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
2
- # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
3
-
4
- name: Node.js CI
5
-
6
- on:
7
- push:
8
- branches: [master]
9
- pull_request:
10
- branches: [master]
11
-
12
- jobs:
13
- build:
14
- runs-on: ubuntu-latest
15
-
16
- strategy:
17
- matrix:
18
- node-version: [12, 14, 16]
19
-
20
- steps:
21
- - uses: actions/checkout@v2
22
- - name: Use Node.js ${{ matrix.node-version }}
23
- uses: actions/setup-node@v1
24
- with:
25
- node-version: ${{ matrix.node-version }}
26
- - run: npm install
27
- - run: npm test
28
- env:
29
- CI: true
1
+ # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
2
+ # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
3
+
4
+ name: Node.js CI
5
+
6
+ on:
7
+ push:
8
+ branches: [master]
9
+ pull_request:
10
+
11
+ jobs:
12
+ build:
13
+ runs-on: ubuntu-latest
14
+
15
+ strategy:
16
+ matrix:
17
+ node-version: [12, 14, 16]
18
+
19
+ steps:
20
+ - uses: actions/checkout@v2
21
+ - name: Use Node.js ${{ matrix.node-version }}
22
+ uses: actions/setup-node@v1
23
+ with:
24
+ node-version: ${{ matrix.node-version }}
25
+ - run: npm install
26
+ - run: npm test
27
+ env:
28
+ CI: true
package/package.json CHANGED
@@ -1,84 +1,87 @@
1
- {
2
- "name": "@maizzle/framework",
3
- "version": "3.7.2",
4
- "description": "Maizzle is a framework that helps you quickly build HTML emails with Tailwind CSS.",
5
- "license": "MIT",
6
- "main": "src/index.js",
7
- "repository": {
8
- "type": "git",
9
- "url": "https://github.com/maizzle/framework.git"
10
- },
11
- "bugs": "https://github.com/maizzle/framework/issues",
12
- "homepage": "https://maizzle.com",
13
- "author": "Cosmin Popovici (https://github.com/cossssmin)",
14
- "keywords": [
15
- "maizzle",
16
- "tailwindcss",
17
- "responsive-email",
18
- "email-framework",
19
- "email-template",
20
- "email-marketing",
21
- "email-campaigns",
22
- "email-newsletter",
23
- "email-boilerplate",
24
- "html-emails"
25
- ],
26
- "publishConfig": {
27
- "access": "public"
28
- },
29
- "scripts": {
30
- "test": "xo && nyc ava -s",
31
- "style": "xo",
32
- "release": "np"
33
- },
34
- "dependencies": {
35
- "browser-sync": "^2.26.13",
36
- "color-shorthand-hex-to-six-digit": "^3.0.2",
37
- "email-comb": "^5.0.0",
38
- "front-matter": "^4.0.0",
39
- "fs-extra": "^10.0.0",
40
- "glob-promise": "^4.1.0",
41
- "html-crush": "^4.0.0",
42
- "juice": "^8.0.0",
43
- "lodash": "^4.17.20",
44
- "ora": "^5.1.0",
45
- "postcss": "^8.2.14",
46
- "postcss-import": "^14.0.0",
47
- "postcss-merge-longhand": "^5.0.1",
48
- "posthtml": "^0.16.4",
49
- "posthtml-attrs-parser": "^0.1.1",
50
- "posthtml-content": "^0.0.3",
51
- "posthtml-expressions": "^1.8.1",
52
- "posthtml-extend": "^0.6.0",
53
- "posthtml-extra-attributes": "^1.0.0",
54
- "posthtml-fetch": "^2.0.0",
55
- "posthtml-markdownit": "^1.2.2",
56
- "posthtml-modules": "^0.7.4",
57
- "posthtml-mso": "^1.0.2",
58
- "posthtml-postcss-merge-longhand": "^1.0.2",
59
- "posthtml-remove-attributes": "^1.0.0",
60
- "posthtml-safe-class-names": "^1.0.4",
61
- "posthtml-url-parameters": "^1.0.4",
62
- "pretty": "^2.0.0",
63
- "prevent-widows": "^1.0.2",
64
- "query-string": "^7.0.0",
65
- "string-strip-html": "^8.2.0",
66
- "tailwindcss": "^2.2.2",
67
- "tailwindcss-box-shadow": "^1.0.0"
68
- },
69
- "devDependencies": {
70
- "autoprefixer": "^10.0.2",
71
- "ava": "^3.13.0",
72
- "np": "*",
73
- "nyc": "^15.1.0",
74
- "xo": "0.39.1"
75
- },
76
- "engines": {
77
- "node": ">=12.13.0"
78
- },
79
- "ava": {
80
- "files": [
81
- "test/**/test*.js"
82
- ]
83
- }
84
- }
1
+ {
2
+ "name": "@maizzle/framework",
3
+ "version": "4.0.0-alpha.1",
4
+ "description": "Maizzle is a framework that helps you quickly build HTML emails with Tailwind CSS.",
5
+ "license": "MIT",
6
+ "main": "src/index.js",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/maizzle/framework.git"
10
+ },
11
+ "bugs": "https://github.com/maizzle/framework/issues",
12
+ "homepage": "https://maizzle.com",
13
+ "author": "Cosmin Popovici (https://github.com/cossssmin)",
14
+ "keywords": [
15
+ "maizzle",
16
+ "tailwindcss",
17
+ "responsive-email",
18
+ "email-framework",
19
+ "email-template",
20
+ "email-marketing",
21
+ "email-campaigns",
22
+ "email-newsletter",
23
+ "email-boilerplate",
24
+ "html-emails"
25
+ ],
26
+ "publishConfig": {
27
+ "access": "public"
28
+ },
29
+ "scripts": {
30
+ "test": "c8 ava -s",
31
+ "pretest": "xo",
32
+ "style": "xo",
33
+ "release": "np"
34
+ },
35
+ "dependencies": {
36
+ "autoprefixer": "^10.4.0",
37
+ "browser-sync": "^2.26.13",
38
+ "color-shorthand-hex-to-six-digit": "^3.0.2",
39
+ "email-comb": "^5.0.0",
40
+ "front-matter": "^4.0.0",
41
+ "fs-extra": "^10.0.0",
42
+ "glob-promise": "^4.1.0",
43
+ "html-crush": "^4.0.0",
44
+ "is-url-superb": "^5.0.0",
45
+ "juice": "^8.0.0",
46
+ "lodash": "^4.17.20",
47
+ "ora": "^5.1.0",
48
+ "postcss": "^8.4.4",
49
+ "postcss-import": "^14.0.0",
50
+ "postcss-merge-longhand": "^5.0.1",
51
+ "posthtml": "^0.16.4",
52
+ "posthtml-attrs-parser": "^0.1.1",
53
+ "posthtml-base-url": "^1.0.1",
54
+ "posthtml-content": "^0.0.3",
55
+ "posthtml-expressions": "^1.8.1",
56
+ "posthtml-extend": "^0.6.0",
57
+ "posthtml-extra-attributes": "^1.0.0",
58
+ "posthtml-fetch": "^2.0.0",
59
+ "posthtml-markdownit": "^1.2.2",
60
+ "posthtml-modules": "^0.8.0",
61
+ "posthtml-mso": "^1.0.2",
62
+ "posthtml-postcss-merge-longhand": "^1.0.2",
63
+ "posthtml-remove-attributes": "^1.0.0",
64
+ "posthtml-safe-class-names": "^1.0.4",
65
+ "posthtml-url-parameters": "^1.0.4",
66
+ "pretty": "^2.0.0",
67
+ "prevent-widows": "^1.0.2",
68
+ "query-string": "^7.1.0",
69
+ "string-strip-html": "^8.2.0",
70
+ "tailwindcss": "^3.0.0",
71
+ "tailwindcss-box-shadow": "^1.0.0"
72
+ },
73
+ "devDependencies": {
74
+ "ava": "^4.0.1",
75
+ "c8": "^7.11.0",
76
+ "np": "*",
77
+ "xo": "0.39.1"
78
+ },
79
+ "engines": {
80
+ "node": ">=12.13.0"
81
+ },
82
+ "ava": {
83
+ "files": [
84
+ "test/**/test*.js"
85
+ ]
86
+ }
87
+ }
@@ -29,130 +29,158 @@ module.exports = async (env, spinner, config) => {
29
29
 
30
30
  const css = (typeof get(config, 'tailwind.compiled') === 'string')
31
31
  ? config.tailwind.compiled
32
- : await Tailwind.compile('@tailwind components; @tailwind utilities;', '', {}, config)
32
+ : await Tailwind.compile('@tailwind components; @tailwind utilities;', '', {}, config, spinner)
33
33
 
34
+ // Parse each template config object
34
35
  await asyncForEach(templatesConfig, async templateConfig => {
35
36
  const outputDir = get(templateConfig, 'destination.path', `build_${env}`)
36
37
 
37
38
  await fs.remove(outputDir)
38
39
 
39
- await fs
40
- .copy(templateConfig.source, outputDir)
41
- .then(async () => {
42
- const extensions = Array.isArray(templateConfig.filetypes)
43
- ? templateConfig.filetypes.join('|')
44
- : templateConfig.filetypes || get(templateConfig, 'filetypes', 'html')
45
-
46
- const templates = await glob(`${outputDir}/**/*.+(${extensions})`)
47
-
48
- if (templates.length === 0) {
49
- spinner.warn(`Error: no files with the .${extensions} extension found in ${templateConfig.source}`)
50
- return
51
- }
52
-
53
- // Store template config currently being processed
54
- config.build.currentTemplates = templateConfig
55
-
56
- if (config.events && typeof config.events.beforeCreate === 'function') {
57
- await config.events.beforeCreate(config)
58
- }
59
-
60
- await asyncForEach(templates, async file => {
61
- const html = await fs.readFile(file, 'utf8')
62
-
63
- await render(html, {
64
- maizzle: {
65
- ...config,
66
- env
67
- },
68
- tailwind: {
69
- compiled: css
70
- },
71
- ...config.events
72
- })
73
- .then(async ({html, config}) => {
74
- const destination = config.permalink || file
75
-
76
- /**
77
- * Generate plaintext
78
- *
79
- * We do this first so that we can remove the <plaintext>
80
- * tags from the markup before outputting the file.
81
- */
82
-
83
- // Make this a breaking change in 4.0, get only from `templateConfig`
84
- const plaintextConfig = get(templateConfig, 'plaintext', get(config, 'plaintext'))
85
- const plaintextDestination = get(plaintextConfig, 'destination', config.permalink || file)
86
-
87
- if ((typeof plaintextConfig === 'boolean' && plaintextConfig) || !isEmpty(plaintextConfig)) {
88
- await Plaintext
89
- .generate(html, plaintextDestination, merge(config, {filepath: file}))
90
- .then(({plaintext, destination}) => fs.outputFile(destination, plaintext))
91
- }
40
+ /**
41
+ * Get all files in the template config's source directory
42
+ * Supports `source` defined as:
43
+ * - string
44
+ * - array of strings
45
+ * - function that returns either of the above
46
+ *
47
+ * */
48
+ const templateSource = []
49
+
50
+ if (typeof templateConfig.source === 'function') {
51
+ const sources = templateConfig.source()
52
+ if (Array.isArray(sources)) {
53
+ templateSource.push(...sources)
54
+ } else {
55
+ templateSource.push(sources)
56
+ }
57
+ } else {
58
+ if (Array.isArray(templateConfig.source)) {
59
+ templateSource.push(...templateConfig.source)
60
+ } else {
61
+ templateSource.push(templateConfig.source)
62
+ }
63
+ }
64
+
65
+ // Parse each template source
66
+ await asyncForEach(templateSource, async source => {
67
+ await fs
68
+ .copy(source, outputDir)
69
+ .then(async () => {
70
+ const extensions = Array.isArray(templateConfig.filetypes)
71
+ ? templateConfig.filetypes.join('|')
72
+ : templateConfig.filetypes || get(templateConfig, 'filetypes', 'html')
73
+
74
+ const templates = await glob(`${outputDir}/**/*.+(${extensions})`)
75
+
76
+ if (templates.length === 0) {
77
+ spinner.warn(`Error: no files with the .${extensions} extension found in ${templateConfig.source}`)
78
+ return
79
+ }
92
80
 
93
- html = removePlaintextTags(html, config)
94
-
95
- /**
96
- * Output file
97
- */
98
- const parts = path.parse(destination)
99
- const extension = get(templateConfig, 'destination.extension', 'html')
100
- const finalDestination = `${parts.dir}/${parts.name}.${extension}`
101
-
102
- await fs.outputFile(finalDestination, html)
103
- .then(async () => {
104
- /**
105
- * Remove original file if its path is different
106
- * from the final destination path.
107
- *
108
- * This ensures non-HTML files do not pollute
109
- * the build destination folder.
110
- */
111
- if (finalDestination !== file) {
112
- await fs.remove(file)
113
- }
114
-
115
- // Keep track of handled files
116
- files.push(file)
117
- parsed.push(file)
118
- })
119
- })
120
- .catch(error => {
121
- switch (config.build.fail) {
122
- case 'silent':
123
- spinner.warn(`Failed to compile template: ${path.basename(file)}`)
124
- break
125
- case 'verbose':
126
- spinner.warn(`Failed to compile template: ${path.basename(file)}`)
127
- console.error(error)
128
- break
129
- default:
130
- spinner.fail(`Failed to compile template: ${path.basename(file)}`)
131
- throw error
132
- }
81
+ // Store template config currently being processed
82
+ config.build.currentTemplates = templateConfig
83
+
84
+ if (config.events && typeof config.events.beforeCreate === 'function') {
85
+ await config.events.beforeCreate(config)
86
+ }
87
+
88
+ await asyncForEach(templates, async file => {
89
+ const html = await fs.readFile(file, 'utf8')
90
+
91
+ await render(html, {
92
+ maizzle: {
93
+ ...config,
94
+ env
95
+ },
96
+ tailwind: {
97
+ compiled: css
98
+ },
99
+ ...config.events
133
100
  })
134
- })
101
+ .then(async ({html, config}) => {
102
+ const destination = config.permalink || file
103
+
104
+ /**
105
+ * Generate plaintext
106
+ *
107
+ * We do this first so that we can remove the <plaintext>
108
+ * tags from the markup before outputting the file.
109
+ */
110
+
111
+ const plaintextConfig = get(templateConfig, 'plaintext')
112
+ const plaintextDestination = get(plaintextConfig, 'destination', config.permalink || file)
113
+
114
+ if ((typeof plaintextConfig === 'boolean' && plaintextConfig) || !isEmpty(plaintextConfig)) {
115
+ await Plaintext
116
+ .generate(html, plaintextDestination, merge(config, {filepath: file}))
117
+ .then(({plaintext, destination}) => fs.outputFile(destination, plaintext))
118
+ }
119
+
120
+ html = removePlaintextTags(html, config)
121
+
122
+ /**
123
+ * Output file
124
+ */
125
+ const parts = path.parse(destination)
126
+ const extension = get(templateConfig, 'destination.extension', 'html')
127
+ const finalDestination = `${parts.dir}/${parts.name}.${extension}`
128
+
129
+ await fs.outputFile(finalDestination, html)
130
+ .then(async () => {
131
+ /**
132
+ * Remove original file if its path is different
133
+ * from the final destination path.
134
+ *
135
+ * This ensures non-HTML files do not pollute
136
+ * the build destination folder.
137
+ */
138
+ if (finalDestination !== file) {
139
+ await fs.remove(file)
140
+ }
141
+
142
+ // Keep track of handled files
143
+ files.push(file)
144
+ parsed.push(file)
145
+ })
146
+ })
147
+ .catch(error => {
148
+ switch (config.build.fail) {
149
+ case 'silent':
150
+ spinner.warn(`Failed to compile template: ${path.basename(file)}`)
151
+ break
152
+ case 'verbose':
153
+ spinner.warn(`Failed to compile template: ${path.basename(file)}`)
154
+ console.error(error)
155
+ break
156
+ default:
157
+ spinner.fail(`Failed to compile template: ${path.basename(file)}`)
158
+ throw error
159
+ }
160
+ })
161
+ })
135
162
 
136
- const assets = {source: '', destination: 'assets', ...get(templateConfig, 'assets')}
163
+ const assets = {source: '', destination: 'assets', ...get(templateConfig, 'assets')}
137
164
 
138
- if (Array.isArray(assets.source)) {
139
- await asyncForEach(assets.source, async source => {
140
- if (fs.existsSync(source)) {
141
- await fs.copy(source, path.join(templateConfig.destination.path, assets.destination)).catch(error => spinner.warn(error.message))
165
+ if (Array.isArray(assets.source)) {
166
+ await asyncForEach(assets.source, async source => {
167
+ if (fs.existsSync(source)) {
168
+ await fs.copy(source, path.join(templateConfig.destination.path, assets.destination)).catch(error => spinner.warn(error.message))
169
+ }
170
+ })
171
+ } else {
172
+ if (fs.existsSync(assets.source)) {
173
+ await fs.copy(assets.source, path.join(templateConfig.destination.path, assets.destination)).catch(error => spinner.warn(error.message))
142
174
  }
143
- })
144
- } else {
145
- if (fs.existsSync(assets.source)) {
146
- await fs.copy(assets.source, path.join(templateConfig.destination.path, assets.destination)).catch(error => spinner.warn(error.message))
147
175
  }
148
- }
149
176
 
150
- await glob(path.join(templateConfig.destination.path, '/**/*.*'))
151
- .then(contents => {
152
- files = [...new Set([...files, ...contents])]
153
- })
154
- })
155
- .catch(error => spinner.warn(error.message))
177
+ await glob(path.join(templateConfig.destination.path, '/**/*.*'))
178
+ .then(contents => {
179
+ files = [...new Set([...files, ...contents])]
180
+ })
181
+ })
182
+ .catch(error => spinner.warn(error.message))
183
+ })
156
184
  })
157
185
 
158
186
  if (config.events && typeof config.events.afterBuild === 'function') {
@@ -3,7 +3,8 @@ const {get, merge} = require('lodash')
3
3
  const posthtml = require('../posthtml')
4
4
  const Tailwind = require('../tailwindcss')
5
5
  const Transformers = require('../../transformers')
6
- const posthtmlMSO = require('../../transformers/posthtml-mso')
6
+ const posthtmlMSO = require('../../transformers/posthtmlMso')
7
+ const Config = require('../config')
7
8
 
8
9
  module.exports = async (html, options) => {
9
10
  process.env.NODE_ENV = get(options, 'maizzle.env', 'local')
@@ -16,7 +17,9 @@ module.exports = async (html, options) => {
16
17
  throw new RangeError('received empty string')
17
18
  }
18
19
 
19
- let config = get(options, 'maizzle', {})
20
+ const fileConfig = await Config.getMerged(process.env.NODE_ENV)
21
+
22
+ let config = merge(fileConfig, get(options, 'maizzle', {}))
20
23
 
21
24
  const tailwindConfig = get(options, 'tailwind.config', {})
22
25
  const cssString = get(options, 'tailwind.css', '@tailwind components; @tailwind utilities;')
@@ -1,60 +1,61 @@
1
- const fm = require('front-matter')
2
- const posthtml = require('posthtml')
3
- const {get, merge} = require('lodash')
4
- const fetch = require('posthtml-fetch')
5
- const layouts = require('posthtml-extend')
6
- const modules = require('posthtml-modules')
7
- const expressions = require('posthtml-expressions')
8
-
9
- module.exports = async (html, config) => {
10
- const layoutsOptions = get(config, 'build.layouts', {})
11
-
12
- const fetchOptions = get(config, 'build.posthtml.fetch', {})
13
- const fetchPlugin = fetch({...fetchOptions})
14
-
15
- const modulesOptions = get(config, 'build.components', {})
16
- // Fake `from` option so we can reference modules relatively
17
- const modulesRoot = modulesOptions.root || './'
18
- const modulesFrom = modulesOptions.from || `${modulesRoot}/fake`
19
-
20
- const posthtmlOptions = get(config, 'build.posthtml.options', {})
21
- const posthtmlPlugins = get(config, 'build.posthtml.plugins', [])
22
-
23
- const expressionsOptions = merge({strictMode: false}, get(config, 'build.posthtml.expressions', {}))
24
-
25
- const locals = merge(
26
- get(expressionsOptions, 'locals', {}),
27
- get(config, 'locals', {}),
28
- {page: config}
29
- )
30
-
31
- return posthtml([
32
- fetchPlugin,
33
- layouts(
34
- merge(
35
- {
36
- strict: false,
37
- plugins: [
38
- expressions({...expressionsOptions, locals})
39
- ]
40
- },
41
- layoutsOptions
42
- )
43
- ),
44
- modules({
45
- parser: posthtmlOptions,
46
- from: modulesFrom,
47
- root: modulesRoot,
48
- tag: 'component',
49
- attribute: 'src',
50
- plugins: [
51
- fetchPlugin
52
- ],
53
- locals,
54
- ...modulesOptions
55
- }),
56
- ...posthtmlPlugins
57
- ])
58
- .process(html, {...posthtmlOptions})
59
- .then(result => fm(result.html).body)
60
- }
1
+ const fm = require('front-matter')
2
+ const posthtml = require('posthtml')
3
+ const {get, merge} = require('lodash')
4
+ const fetch = require('posthtml-fetch')
5
+ const layouts = require('posthtml-extend')
6
+ const modules = require('posthtml-modules')
7
+ const expressions = require('posthtml-expressions')
8
+
9
+ module.exports = async (html, config) => {
10
+ const layoutsOptions = get(config, 'build.layouts', {})
11
+
12
+ const fetchOptions = get(config, 'build.posthtml.fetch', {})
13
+ const fetchPlugin = fetch({...fetchOptions})
14
+
15
+ const modulesOptions = get(config, 'build.components', {})
16
+ // Fake `from` option so we can reference modules relatively
17
+ const modulesRoot = modulesOptions.root || './'
18
+ const modulesFrom = modulesOptions.from || `${modulesRoot}/fake`
19
+
20
+ const posthtmlOptions = get(config, 'build.posthtml.options', {})
21
+ const posthtmlPlugins = get(config, 'build.posthtml.plugins', [])
22
+
23
+ const expressionsOptions = merge({strictMode: false}, get(config, 'build.posthtml.expressions', {}))
24
+
25
+ const locals = merge(
26
+ get(expressionsOptions, 'locals', {}),
27
+ get(config, 'locals', {}),
28
+ {page: config}
29
+ )
30
+
31
+ return posthtml([
32
+ fetchPlugin,
33
+ layouts(
34
+ merge(
35
+ {
36
+ strict: false,
37
+ plugins: [
38
+ expressions({...expressionsOptions, locals})
39
+ ]
40
+ },
41
+ layoutsOptions
42
+ )
43
+ ),
44
+ modules({
45
+ parser: posthtmlOptions,
46
+ attributeAsLocals: true,
47
+ from: modulesFrom,
48
+ root: modulesRoot,
49
+ tag: 'component',
50
+ attribute: 'src',
51
+ plugins: [
52
+ fetchPlugin
53
+ ],
54
+ locals,
55
+ ...modulesOptions
56
+ }),
57
+ ...posthtmlPlugins
58
+ ])
59
+ .process(html, {...posthtmlOptions})
60
+ .then(result => fm(result.html).body)
61
+ }