@maizzle/framework 3.7.2 → 4.0.0-alpha.10

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 (84) hide show
  1. package/.github/workflows/nodejs.yml +28 -29
  2. package/bin/maizzle +3 -0
  3. package/package.json +91 -84
  4. package/src/commands/build.js +32 -0
  5. package/src/commands/serve.js +156 -0
  6. package/src/functions/plaintext.js +5 -0
  7. package/src/functions/render.js +5 -0
  8. package/src/generators/output/to-disk.js +120 -79
  9. package/src/generators/output/to-string.js +7 -8
  10. package/src/generators/plaintext.js +49 -52
  11. package/src/generators/postcss.js +29 -0
  12. package/src/generators/posthtml.js +66 -60
  13. package/src/generators/tailwindcss.js +116 -84
  14. package/src/index.js +14 -160
  15. package/src/transformers/{attribute-to-style.js → attributeToStyle.js} +0 -0
  16. package/src/transformers/baseUrl.js +69 -0
  17. package/src/transformers/{extra-attributes.js → extraAttributes.js} +0 -0
  18. package/src/transformers/filters/defaultFilters.js +126 -0
  19. package/src/transformers/filters/index.js +55 -0
  20. package/src/transformers/index.js +63 -57
  21. package/src/transformers/{inline.js → inlineCss.js} +1 -14
  22. package/src/transformers/minify.js +1 -1
  23. package/src/transformers/{posthtml-mso.js → posthtmlMso.js} +0 -0
  24. package/src/transformers/prettify.js +16 -9
  25. package/src/transformers/{prevent-widows.js → preventWidows.js} +0 -0
  26. package/src/transformers/{remove-attributes.js → removeAttributes.js} +1 -1
  27. package/src/transformers/{remove-inline-bgcolor.js → removeInlineBackgroundColor.js} +1 -1
  28. package/src/transformers/{remove-inline-sizes.js → removeInlineSizes.js} +0 -0
  29. package/src/transformers/removeInlinedSelectors.js +70 -0
  30. package/src/transformers/removeUnusedCss.js +40 -0
  31. package/src/transformers/{replace-strings.js → replaceStrings.js} +0 -0
  32. package/src/transformers/{safe-class-names.js → safeClassNames.js} +8 -2
  33. package/src/transformers/shorthandInlineCSS.js +19 -0
  34. package/src/transformers/sixHex.js +33 -0
  35. package/src/transformers/{url-params.js → urlParameters.js} +0 -0
  36. package/src/utils/helpers.js +2 -8
  37. package/test/expected/posthtml/component.html +13 -0
  38. package/test/expected/{inheritance.html → posthtml/extend-template.html} +2 -2
  39. package/test/expected/posthtml/fetch.html +5 -0
  40. package/test/expected/posthtml/layout.html +3 -0
  41. package/test/expected/transformers/atimport-in-style.html +15 -0
  42. package/test/expected/transformers/base-url.html +99 -0
  43. package/test/expected/transformers/filters.html +81 -0
  44. package/test/expected/transformers/preserve-transform-css.html +36 -0
  45. package/test/expected/useConfig.html +9 -0
  46. package/test/fixtures/basic.html +9 -9
  47. package/test/fixtures/posthtml/component.html +19 -0
  48. package/test/fixtures/{inheritance.html → posthtml/extend-template.html} +7 -7
  49. package/test/fixtures/posthtml/fetch.html +9 -0
  50. package/test/fixtures/posthtml/layout.html +11 -0
  51. package/test/fixtures/transformers/atimport-in-style.html +11 -0
  52. package/test/fixtures/transformers/base-url.html +101 -0
  53. package/test/fixtures/transformers/filters.html +87 -0
  54. package/test/fixtures/transformers/preserve-transform-css.html +25 -0
  55. package/test/fixtures/useConfig.html +9 -0
  56. package/test/stubs/components/component.html +5 -0
  57. package/test/stubs/data.json +14 -0
  58. package/test/stubs/layouts/basic.html +1 -0
  59. package/test/stubs/{layout.html → layouts/full.html} +0 -0
  60. package/test/stubs/{layout-basic.html → layouts/template.html} +5 -5
  61. package/test/stubs/post.css +6 -0
  62. package/test/stubs/tailwind/content-source.html +1 -0
  63. package/test/stubs/tailwind/tailwind.css +3 -0
  64. package/test/stubs/template.html +10 -10
  65. package/test/stubs/templates/1.html +1 -1
  66. package/test/stubs/templates/2.test +1 -0
  67. package/test/test-config.js +19 -19
  68. package/test/test-misc.js +8 -8
  69. package/test/test-postcss.js +8 -0
  70. package/test/test-posthtml.js +72 -0
  71. package/test/test-tailwindcss.js +117 -0
  72. package/test/test-todisk.js +142 -49
  73. package/test/test-tostring.js +148 -124
  74. package/test/test-transformers.js +510 -320
  75. package/xo.config.js +22 -19
  76. package/src/transformers/base-image-url.js +0 -9
  77. package/src/transformers/remove-unused-css.js +0 -20
  78. package/src/transformers/six-hex.js +0 -10
  79. package/src/transformers/transform.js +0 -24
  80. package/test/expected/transformers/base-image-url.html +0 -7
  81. package/test/fixtures/transformers/base-image-url.html +0 -7
  82. package/test/stubs/templates/2.html +0 -1
  83. package/test/stubs/templates/3.mzl +0 -1
  84. package/test/test-tailwind.js +0 -73
@@ -29,48 +29,87 @@ 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('', '', {}, 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}) => {
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
+ const templateTypeErrorMessage = 'Invalid template source: expected string or array of strings, got '
50
+
51
+ if (typeof templateConfig.source === 'function') {
52
+ const sources = templateConfig.source(config)
53
+
54
+ if (Array.isArray(sources)) {
55
+ templateSource.push(...sources)
56
+ } else if (typeof sources === 'string') {
57
+ templateSource.push(sources)
58
+ } else {
59
+ throw new TypeError(templateTypeErrorMessage + typeof sources)
60
+ }
61
+ } else {
62
+ if (Array.isArray(templateConfig.source)) {
63
+ templateSource.push(...templateConfig.source)
64
+ } else if (typeof templateConfig.source === 'string') {
65
+ templateSource.push(templateConfig.source)
66
+ } else {
67
+ throw new TypeError(templateTypeErrorMessage + typeof templateConfig.source)
68
+ }
69
+ }
70
+
71
+ // Parse each template source
72
+ await asyncForEach(templateSource, async source => {
73
+ /**
74
+ * Copy single-file sources correctly
75
+ * If `src` is a file, `dest` cannot be a directory
76
+ * https://github.com/jprichardson/node-fs-extra/issues/323
77
+ */
78
+ const out = fs.lstatSync(source).isFile() ? `${outputDir}/${path.basename(source)}` : outputDir
79
+
80
+ await fs
81
+ .copy(source, out)
82
+ .then(async () => {
83
+ const extensions = Array.isArray(templateConfig.filetypes)
84
+ ? templateConfig.filetypes.join('|')
85
+ : templateConfig.filetypes || get(templateConfig, 'filetypes', 'html')
86
+
87
+ const templates = await glob(`${outputDir}/**/*.+(${extensions})`)
88
+
89
+ if (templates.length === 0) {
90
+ spinner.warn(`Error: no files with the .${extensions} extension found in ${templateConfig.source}`)
91
+ return
92
+ }
93
+
94
+ if (config.events && typeof config.events.beforeCreate === 'function') {
95
+ await config.events.beforeCreate(config)
96
+ }
97
+
98
+ await asyncForEach(templates, async file => {
99
+ const html = await fs.readFile(file, 'utf8')
100
+
101
+ try {
102
+ const compiled = await render(html, {
103
+ maizzle: {
104
+ ...config,
105
+ env
106
+ },
107
+ tailwind: {
108
+ compiled: css
109
+ },
110
+ ...config.events
111
+ })
112
+
74
113
  const destination = config.permalink || file
75
114
 
76
115
  /**
@@ -80,17 +119,20 @@ module.exports = async (env, spinner, config) => {
80
119
  * tags from the markup before outputting the file.
81
120
  */
82
121
 
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)
122
+ const plaintextConfig = get(templateConfig, 'plaintext')
123
+ const plaintextPath = get(plaintextConfig, 'destination.path', config.permalink || file)
86
124
 
87
- if ((typeof plaintextConfig === 'boolean' && plaintextConfig) || !isEmpty(plaintextConfig)) {
125
+ if (Boolean(plaintextConfig) || !isEmpty(plaintextConfig)) {
88
126
  await Plaintext
89
- .generate(html, plaintextDestination, merge(config, {filepath: file}))
127
+ .generate(
128
+ compiled.html,
129
+ plaintextPath,
130
+ merge(plaintextConfig, {filepath: file})
131
+ )
90
132
  .then(({plaintext, destination}) => fs.outputFile(destination, plaintext))
91
133
  }
92
134
 
93
- html = removePlaintextTags(html, config)
135
+ compiled.html = removePlaintextTags(compiled.html, config)
94
136
 
95
137
  /**
96
138
  * Output file
@@ -99,25 +141,23 @@ module.exports = async (env, spinner, config) => {
99
141
  const extension = get(templateConfig, 'destination.extension', 'html')
100
142
  const finalDestination = `${parts.dir}/${parts.name}.${extension}`
101
143
 
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 => {
144
+ await fs.outputFile(finalDestination, compiled.html)
145
+
146
+ /**
147
+ * Remove original file if its path is different
148
+ * from the final destination path.
149
+ *
150
+ * This ensures non-HTML files do not pollute
151
+ * the build destination folder.
152
+ */
153
+ if (finalDestination !== file) {
154
+ await fs.remove(file)
155
+ }
156
+
157
+ // Keep track of handled files
158
+ files.push(file)
159
+ parsed.push(file)
160
+ } catch (error) {
121
161
  switch (config.build.fail) {
122
162
  case 'silent':
123
163
  spinner.warn(`Failed to compile template: ${path.basename(file)}`)
@@ -130,29 +170,30 @@ module.exports = async (env, spinner, config) => {
130
170
  spinner.fail(`Failed to compile template: ${path.basename(file)}`)
131
171
  throw error
132
172
  }
133
- })
134
- })
173
+ }
174
+ })
135
175
 
136
- const assets = {source: '', destination: 'assets', ...get(templateConfig, 'assets')}
176
+ const assets = {source: '', destination: 'assets', ...get(templateConfig, 'assets')}
137
177
 
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))
178
+ if (Array.isArray(assets.source)) {
179
+ await asyncForEach(assets.source, async source => {
180
+ if (fs.existsSync(source)) {
181
+ await fs.copy(source, path.join(templateConfig.destination.path, assets.destination)).catch(error => spinner.warn(error.message))
182
+ }
183
+ })
184
+ } else {
185
+ if (fs.existsSync(assets.source)) {
186
+ await fs.copy(assets.source, path.join(templateConfig.destination.path, assets.destination)).catch(error => spinner.warn(error.message))
142
187
  }
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
188
  }
148
- }
149
189
 
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))
190
+ await glob(path.join(templateConfig.destination.path, '/**/*.*'))
191
+ .then(contents => {
192
+ files = [...new Set([...files, ...contents])]
193
+ })
194
+ })
195
+ .catch(error => spinner.warn(error.message))
196
+ })
156
197
  })
157
198
 
158
199
  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,16 +17,14 @@ 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)
20
21
 
21
- const tailwindConfig = get(options, 'tailwind.config', {})
22
- const cssString = get(options, 'tailwind.css', '@tailwind components; @tailwind utilities;')
22
+ let config = merge(fileConfig, get(options, 'maizzle', {}))
23
23
 
24
- let {frontmatter} = fm(html)
24
+ const tailwindConfig = get(options, 'tailwind.config', {})
25
+ const cssString = get(options, 'tailwind.css', '')
25
26
 
26
- if (frontmatter) {
27
- frontmatter = await posthtml(frontmatter, config)
28
- }
27
+ const {frontmatter} = fm(html)
29
28
 
30
29
  html = `---\n${frontmatter}\n---\n\n${fm(html).body}`
31
30
 
@@ -1,52 +1,49 @@
1
- const path = require('path')
2
- const {get} = require('lodash')
3
- const {stripHtml} = require('string-strip-html')
4
-
5
- module.exports.generate = async (html, destination, config) => {
6
- const options = get(config, 'build.currentTemplates.plaintext', get(config, 'plaintext', {}))
7
- const configDestinationPath = get(options, 'destination.path')
8
- const extension = get(options, 'destination.extension', 'txt')
9
- const plaintext = stripHtml(html, {
10
- dumpLinkHrefsNearby: {
11
- enabled: true
12
- },
13
- ...options
14
- }).result
15
-
16
- // If we set plaintext.destination.path in config/fm
17
- if (configDestinationPath) {
18
- // Naive path checking, this does not support dot folders
19
- const isFilePath = Boolean(path.parse(configDestinationPath).ext)
20
-
21
- /**
22
- * Using a file path will generate a single plaintext file,
23
- * no matter how many templates there are.
24
- *
25
- * It will be based on the last-processed template.
26
- */
27
- if (isFilePath) {
28
- destination = configDestinationPath
29
-
30
- return {plaintext, destination}
31
- }
32
-
33
- /**
34
- * Using a directory-like path for plaintext.destination.path
35
- */
36
- destination = path.join(configDestinationPath, path.basename(config.filepath, path.extname(config.filepath)) + '.' + extension)
37
-
38
- return {plaintext, destination}
39
- }
40
-
41
- /**
42
- * Use template's `permalink` Front Matter key,
43
- * fall back to the original `destination`.
44
- */
45
- destination = get(config, 'permalink', destination)
46
-
47
- if (typeof destination === 'string') {
48
- destination = path.join(path.dirname(destination), path.basename(destination, path.extname(destination)) + '.' + extension)
49
- }
50
-
51
- return {plaintext, destination}
52
- }
1
+ const path = require('path')
2
+ const {get} = require('lodash')
3
+ const {stripHtml} = require('string-strip-html')
4
+
5
+ module.exports.generate = async (html, destination, config) => {
6
+ const configDestinationPath = get(config, 'destination.path')
7
+ const extension = get(config, 'destination.extension', 'txt')
8
+
9
+ const plaintext = stripHtml(html, {
10
+ dumpLinkHrefsNearby: {
11
+ enabled: true
12
+ },
13
+ ...get(config, 'options', {})
14
+ }).result
15
+
16
+ // If we set plaintext.destination.path in config/fm
17
+ if (configDestinationPath) {
18
+ /**
19
+ * Using a file path will generate a single plaintext file,
20
+ * no matter how many templates there are.
21
+ *
22
+ * It will be based on the last-processed template.
23
+ */
24
+ if (path.extname(configDestinationPath)) {
25
+ destination = configDestinationPath
26
+
27
+ return {plaintext, destination}
28
+ }
29
+
30
+ /**
31
+ * Using a directory-like path for plaintext.destination.path
32
+ */
33
+ destination = path.join(configDestinationPath, path.basename(config.filepath, path.extname(config.filepath)) + '.' + extension)
34
+
35
+ return {plaintext, destination}
36
+ }
37
+
38
+ /**
39
+ * Use template's `permalink` Front Matter key,
40
+ * fall back to the original `destination`.
41
+ */
42
+ destination = get(config, 'permalink', destination)
43
+
44
+ if (typeof destination === 'string') {
45
+ destination = path.join(path.dirname(destination), path.basename(destination, path.extname(destination)) + '.' + extension)
46
+ }
47
+
48
+ return {plaintext, destination}
49
+ }
@@ -0,0 +1,29 @@
1
+ const path = require('path')
2
+ const {get} = require('lodash')
3
+ const postcss = require('postcss')
4
+ const postcssImport = require('postcss-import')
5
+ const postcssNested = require('tailwindcss/nesting')
6
+ const mergeLonghand = require('postcss-merge-longhand')
7
+
8
+ module.exports = {
9
+ process: async (css = '', maizzleConfig = {}, spinner = null) => {
10
+ const userFilePath = get(maizzleConfig, 'build.tailwind.css', path.join(process.cwd(), 'src/css/tailwind.css'))
11
+
12
+ return postcss([
13
+ postcssImport({path: path.dirname(userFilePath)}),
14
+ postcssNested(),
15
+ maizzleConfig.env === 'local' ? () => {} : mergeLonghand(),
16
+ ...get(maizzleConfig, 'build.postcss.plugins', [])
17
+ ])
18
+ .process(css, {from: undefined})
19
+ .then(result => result.css)
20
+ .catch(error => {
21
+ console.error(error)
22
+ if (spinner) {
23
+ spinner.stop()
24
+ }
25
+
26
+ throw new Error(`PostCSS processing failed`)
27
+ })
28
+ }
29
+ }
@@ -1,60 +1,66 @@
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 modulesOptions = get(config, 'build.components', {})
13
+ // Fake `from` option so we can reference modules relatively
14
+ const modulesRoot = modulesOptions.root || './'
15
+ const modulesFrom = modulesOptions.from || `${modulesRoot}/fake`
16
+
17
+ const posthtmlOptions = get(config, 'build.posthtml.options', {})
18
+ const posthtmlPlugins = get(config, 'build.posthtml.plugins', [])
19
+
20
+ const expressionsOptions = merge({strictMode: false}, get(config, 'build.posthtml.expressions', {}))
21
+
22
+ const locals = merge(
23
+ get(expressionsOptions, 'locals', {}),
24
+ get(config, 'locals', {}),
25
+ {page: config}
26
+ )
27
+
28
+ const fetchPlugin = fetch(
29
+ merge(
30
+ {
31
+ expressions: merge({...expressionsOptions, locals})
32
+ },
33
+ get(config, 'build.posthtml.fetch', {})
34
+ )
35
+ )
36
+
37
+ return posthtml([
38
+ fetchPlugin,
39
+ expressions({...expressionsOptions, locals}),
40
+ layouts(
41
+ merge(
42
+ {
43
+ strict: false,
44
+ expressions: merge({...expressionsOptions, locals})
45
+ },
46
+ layoutsOptions
47
+ )
48
+ ),
49
+ modules({
50
+ parser: posthtmlOptions,
51
+ attributeAsLocals: true,
52
+ from: modulesFrom,
53
+ root: modulesRoot,
54
+ tag: 'component',
55
+ attribute: 'src',
56
+ plugins: [
57
+ fetchPlugin
58
+ ],
59
+ locals,
60
+ ...modulesOptions
61
+ }),
62
+ ...posthtmlPlugins
63
+ ])
64
+ .process(html, {...posthtmlOptions})
65
+ .then(result => fm(result.html).body)
66
+ }