Repository: maizzle/framework
Pull request #951 (2023-03-24 19:02:50)

Fix TailwindCSS content paths
This PR:

- fixes an issue where utilities in Templates inside directories with dots in their name (like `src/templates/test.com`) were not being generated
- adds `{raw: html, extension: 'html'}` to Tailwind's `content.files` only if `html` was passed to the Tailwind compiler - this happens/is needed only when using Maizzle programmatically, so we removed it for normal use cases



Patch:
diff --git a/src/generators/posthtml/defaultComponentsConfig.js b/src/generators/posthtml/defaultComponentsConfig.js
new file mode 100644
index 0000000..722841d
--- /dev/null
+++ b/src/generators/posthtml/defaultComponentsConfig.js
@@ -0,0 +1,9 @@
+module.exports = {
+  root: './',
+  folders: ['src/components', 'src/layouts', 'src/templates'],
+  fileExtension: 'html',
+  tag: 'component',
+  attribute: 'src',
+  yield: 'content',
+  propsAttribute: 'locals'
+}
diff --git a/src/generators/posthtml/index.js b/src/generators/posthtml/index.js
index 96d3a98..940a0a9 100644
--- a/src/generators/posthtml/index.js
+++ b/src/generators/posthtml/index.js
@@ -5,6 +5,7 @@ const fetch = require('posthtml-fetch')
 const layouts = require('posthtml-extend')
 const components = require('posthtml-component')
 const defaultConfig = require('./defaultConfig')
+const defaultComponentsConfig = require('./defaultComponentsConfig')
 
 module.exports = async (html, config) => {
   const layoutsOptions = get(config, 'build.layouts', {})
@@ -35,6 +36,20 @@ module.exports = async (html, config) => {
     )
   )
 
+  const defaultComponentsOptions = merge(
+    defaultComponentsConfig,
+    {
+      folders: [
+        ...defaultComponentsConfig.folders,
+        ...get(componentsOptions, 'folders', [])
+      ]
+    },
+    {
+      root: componentsOptions.root || './',
+      expressions: {...expressionsOptions, locals}
+    }
+  )
+
   return posthtml([
     fetchPlugin,
     layouts(
@@ -48,15 +63,7 @@ module.exports = async (html, config) => {
     ),
     components(
       merge(
-        {
-          root: componentsOptions.root || './',
-          folders: ['src/components', 'src/layouts', 'src/templates'],
-          tag: 'component',
-          attribute: 'src',
-          yield: 'content',
-          propsAttribute: 'locals',
-          expressions: {...expressionsOptions, locals}
-        },
+        defaultComponentsOptions,
         componentsOptions
       )
     ),
diff --git a/src/generators/tailwindcss.js b/src/generators/tailwindcss.js
index 599258b..167ba58 100644
--- a/src/generators/tailwindcss.js
+++ b/src/generators/tailwindcss.js
@@ -7,6 +7,7 @@ const postcssNested = require('tailwindcss/nesting')
 const {requireUncached} = require('../utils/helpers')
 const mergeLonghand = require('postcss-merge-longhand')
 const {get, isObject, isEmpty, merge} = require('lodash')
+const defaultComponentsConfig = require('./posthtml/defaultComponentsConfig')
 
 const addImportantPlugin = () => {
   return {
@@ -48,22 +49,24 @@ module.exports = {
 
     // Merge user's Tailwind config on top of a 'base' config
     const layoutsRoot = get(config, 'build.layouts.root')
-    const componentsRoot = get(config, 'build.components.root')
+    const componentsRoot = get(config, 'build.components.root', defaultComponentsConfig.root)
 
     const layoutsPath = typeof layoutsRoot === 'string' && layoutsRoot ?
-      `${layoutsRoot}/**/*.html`.replace(/\/\//g, '/') :
-      './src/layouts/**/*.html'
+      `${layoutsRoot}/**/*.*`.replace(/\/\//g, '/') :
+      'src/layouts/**/*.*'
 
-    const componentsPath = typeof componentsRoot === 'string' && componentsRoot ?
-      `${componentsRoot}/**/*.html`.replace(/\/\//g, '/') :
-      './src/components/**/*.html'
+    const componentsPath = defaultComponentsConfig.folders.map(folder => {
+      return path
+        .join(componentsRoot, folder, `**/*.${defaultComponentsConfig.fileExtension}`)
+        .replace(/\\/g, '/')
+        .replace(/\/\//g, '/')
+    })
 
     const tailwindConfig = merge({
       content: {
         files: [
-          layoutsPath,
-          componentsPath,
-          {raw: html, extension: 'html'}
+          ...componentsPath,
+          layoutsPath
         ]
       }
     }, userConfig(config))
@@ -72,48 +75,53 @@ module.exports = {
     if (Array.isArray(tailwindConfig.content)) {
       tailwindConfig.content = {
         files: [
+          ...componentsPath,
           layoutsPath,
-          componentsPath,
-          ...tailwindConfig.content,
-          {raw: html, extension: 'html'}
+          ...tailwindConfig.content
         ]
       }
     }
 
+    // Add raw HTML if using API
+    if (html) {
+      tailwindConfig.content.files.push({raw: html, extension: 'html'})
+    }
+
     // Include all `build.templates.source` paths when scanning for selectors to preserve
     const buildTemplates = get(config, 'build.templates')
 
     if (buildTemplates) {
       const templateObjects = Array.isArray(buildTemplates) ? buildTemplates : [buildTemplates]
-      const templateSources = templateObjects.map(template => {
+      const fileTypes = get(buildTemplates, 'filetypes', 'html')
+
+      templateObjects.forEach(template => {
         const source = get(template, 'source')
 
         if (typeof source === 'function') {
           const sources = source(config)
 
           if (Array.isArray(sources)) {
-            sources.map(s => tailwindConfig.content.files.push(s))
+            sources.map(s => tailwindConfig.content.files.push(`${s}/**/*.${fileTypes}`))
           } else if (typeof sources === 'string') {
             tailwindConfig.content.files.push(sources)
           }
-
-          // Must return a valid `content` entry
-          return {raw: '', extension: 'html'}
         }
 
         // Support single-file sources i.e. src/templates/index.html
-        if (typeof source === 'string' && Boolean(path.extname(source))) {
+        else if (typeof source === 'string' && Boolean(path.extname(source))) {
           tailwindConfig.content.files.push(source)
-
-          return {raw: '', extension: 'html'}
         }
 
-        return `${source}/**/*.*`
+        // Default behavior - directory sources as a string
+        else {
+          tailwindConfig.content.files.push(`${source}/**/*.${fileTypes}`)
+        }
       })
-
-      tailwindConfig.content.files.push(...templateSources)
     }
 
+    // Filter out any duplicate content paths
+    tailwindConfig.content.files = [...new Set(tailwindConfig.content.files)]
+
     const userFilePath = get(config, 'build.tailwind.css', path.join(process.cwd(), 'src/css/tailwind.css'))
     const userFileExists = await fs.pathExists(userFilePath)
 
diff --git a/xo.config.js b/xo.config.js
index 74f197b..f5434fe 100644
--- a/xo.config.js
+++ b/xo.config.js
@@ -4,6 +4,7 @@ module.exports = {
     semi: 0,
     complexity: 0,
     'max-params': 0,
+    'brace-style': 0,
     'no-lonely-if': 0,
     'unicorn/no-reduce': 0,
     'import/extensions': 0,


Test patch:
diff --git a/test/test-posthtml.js b/test/test-posthtml.js
index 0fde4bf..69b0770 100644
--- a/test/test-posthtml.js
+++ b/test/test-posthtml.js
@@ -50,35 +50,35 @@ template: second
     Child in second.html`)
 })
 
-test('components', async t => {
-  const source = await fixture('components/kitchen-sink')
+test('components (legacy)', async t => {
+  const source = await fixture('components/backwards-compatibility')
 
   const options = {
     maizzle: {
       env: 'maizzle-ci',
       build: {
-        components: {
-          folders: ['test/stubs/layouts', 'test/stubs/components']
+        posthtml: {
+          expressions: {
+            delimiters: ['[[', ']]']
+          }
         }
       }
-    },
-    beforeRender(html, config) {
-      config.foo = 'bar'
-
-      return html
     }
   }
 
   const html = await renderString(source, options)
 
-  t.is(
-    await prettify(html, {ocd: true}),
-    await expected('components/kitchen-sink')
-  )
+  t.is(html.replace(/\n+/g, '\n').trim(), await expected('components/backwards-compatibility'))
 })
 
-test('components (legacy)', async t => {
-  const source = await fixture('components/backwards-compatibility')
+test('fetch component', async t => {
+  const source = `<extends src="test/stubs/layouts/legacy.html">
+    <block name="template">
+      <fetch url="test/stubs/data.json">
+        <each loop="user in response">[[ user.name + (loop.last ? '' : ', ') ]]</each>
+      </fetch>
+    </block>
+  </extends>`
 
   const options = {
     maizzle: {
@@ -95,32 +95,32 @@ test('components (legacy)', async t => {
 
   const html = await renderString(source, options)
 
-  t.is(html.replace(/\n+/g, '\n').trim(), await expected('components/backwards-compatibility'))
+  t.is(html.trim(), 'Leanne Graham, Ervin Howell, Clementine Bauch')
 })
 
-test('fetch component', async t => {
-  const source = `<extends src="test/stubs/layouts/legacy.html">
-  <block name="template">
-    <fetch url="test/stubs/data.json">
-      <each loop="user in response">[[ user.name + (loop.last ? '' : ', ') ]]</each>
-    </fetch>
-  </block>
-</extends>`
+test.serial('components', async t => {
+  const source = await fixture('components/kitchen-sink')
 
   const options = {
     maizzle: {
       env: 'maizzle-ci',
       build: {
-        posthtml: {
-          expressions: {
-            delimiters: ['[[', ']]']
-          }
+        components: {
+          folders: ['test/stubs/layouts', 'test/stubs/components']
         }
       }
+    },
+    beforeRender(html, config) {
+      config.foo = 'bar'
+
+      return html
     }
   }
 
   const html = await renderString(source, options)
 
-  t.is(html.trim(), 'Leanne Graham, Ervin Howell, Clementine Bauch')
+  t.is(
+    await prettify(html, {ocd: true}),
+    await expected('components/kitchen-sink')
+  )
 })
diff --git a/test/test-tailwindcss.js b/test/test-tailwindcss.js
index a02dc9a..32a1866 100644
--- a/test/test-tailwindcss.js
+++ b/test/test-tailwindcss.js
@@ -166,25 +166,6 @@ test('works with custom `layouts.root`', async t => {
   t.true(css.includes('.bg-red-500'))
 })
 
-test('works with custom `components.root`', async t => {
-  const css = await Tailwind.compile({
-    config: {
-      build: {
-        components: {
-          root: './test/stubs/components'
-        },
-        tailwind: {
-          config: {
-            content: ['./test/stubs/tailwind/*.html']
-          }
-        }
-      }
-    }
-  })
-
-  t.true(css.includes('.flex'))
-})
-
 test('adds `!important` to selectors that will not be inlined', async t => {
   const css = await Tailwind.compile({
     config: {
diff --git a/test/test-tostring.js b/test/test-tostring.js
index 829d1c8..6be5137 100644
--- a/test/test-tostring.js
+++ b/test/test-tostring.js
@@ -88,7 +88,7 @@ test('locals work when defined in all supported places', async t => {
   t.is(result, `1, 2, 3, undefined`)
 })
 
-test('prevents overwriting page object', async t => {
+test.serial('prevents overwriting page object', async t => {
   const result = await renderString(`{{ page.one }}, {{ two }}, {{ three }}, {{ inline }}`, {
     maizzle: {
       one: 1,