Repository: maizzle/framework
Pull request #503 (2021-07-07 12:26:41)

Refactor watchers
This PR refactors watchers when using `maizzle serve`.

### New

Changes to Maizzle `config.*.js` files now also trigger a rebuild and browser refresh.

### Fixed

Changes to `src/` files other than templates now properly trigger a rebuild and browser refresh.

File events, like adding/removing files, should also rebuild & refresh the browser.



Patch:
diff --git a/package.json b/package.json
index 485772d..e39bf6b 100644
--- a/package.json
+++ b/package.json
@@ -75,5 +75,10 @@
   },
   "engines": {
     "node": ">=12.13.0"
+  },
+  "ava": {
+    "files": [
+      "test/**/test*.js"
+    ]
   }
 }
diff --git a/src/generators/config.js b/src/generators/config.js
index 351f8d6..58197c6 100644
--- a/src/generators/config.js
+++ b/src/generators/config.js
@@ -1,26 +1,29 @@
 const path = require('path')
 const {merge} = require('lodash')
+const {requireUncached} = require('../utils/helpers')
 
 module.exports = {
   getMerged: async (env = 'local') => {
     if (typeof env !== 'string') {
-      throw new TypeError(`env name must be a string, received ${env}`)
+      throw new TypeError(`env name must be a string, received ${typeof env}(${env})`)
     }
 
     let baseConfig = {env}
     let envConfig = {env}
 
+    const cwd = env === 'maizzle-ci' ? './test/stubs/config' : process.cwd()
+
     for (const module of ['./config', './config.local']) {
       try {
-        baseConfig = require(path.resolve(process.cwd(), module))
+        baseConfig = merge(baseConfig, requireUncached(path.resolve(cwd, module)))
       } catch {}
     }
 
     if (typeof env === 'string' && env !== 'local') {
       try {
-        envConfig = require(path.resolve(process.cwd(), `./config.${env}`))
+        envConfig = merge(envConfig, requireUncached(path.resolve(cwd, `./config.${env}`)))
       } catch {
-        throw new Error(`could not load 'config.${env}.js'`)
+        throw new Error(`could not load config.${env}.js`)
       }
     }
 
diff --git a/src/generators/tailwindcss.js b/src/generators/tailwindcss.js
index fd4536b..c61e48b 100644
--- a/src/generators/tailwindcss.js
+++ b/src/generators/tailwindcss.js
@@ -11,7 +11,24 @@ const {get, isObject, isEmpty, merge} = require('lodash')
 module.exports = {
   compile: async (css = '', html = '', tailwindConfig = {}, maizzleConfig = {}) => {
     tailwindConfig = (isObject(tailwindConfig) && !isEmpty(tailwindConfig)) ? tailwindConfig : get(maizzleConfig, 'build.tailwind.config', 'tailwind.config.js')
-    const userConfig = (isObject(tailwindConfig) && !isEmpty(tailwindConfig)) ? tailwindConfig : requireUncached(path.resolve(process.cwd(), tailwindConfig))
+
+    // Compute the Tailwind config to use
+    const userConfig = () => {
+      // If a custom config object was passed, use that
+      if (isObject(tailwindConfig) && !isEmpty(tailwindConfig)) {
+        return tailwindConfig
+      }
+
+      /**
+       * Try loading a fresh tailwind.config.js, with fallback to an empty object.
+       * This will use the default Tailwind config (with rem units etc)
+       */
+      try {
+        return requireUncached(path.resolve(process.cwd(), tailwindConfig))
+      } catch {
+        return {}
+      }
+    }
 
     // Merge user's Tailwind config on top of a 'base' config
     const config = merge({
@@ -38,7 +55,7 @@ module.exports = {
         textOpacity: false
       },
       plugins: []
-    }, userConfig)
+    }, userConfig())
 
     // Merge user's Tailwind plugins with our default ones
     config.plugins.push(require('tailwindcss-box-shadow'))
diff --git a/src/index.js b/src/index.js
index f4dc6cb..72d2e2b 100644
--- a/src/index.js
+++ b/src/index.js
@@ -45,7 +45,7 @@ const self = module.exports = { // eslint-disable-line
 
         const templatePaths = [...new Set(templates.map(config => `${get(config, 'source', 'src')}/**`))]
         const globalPaths = [
-          `src/!(${templatePaths.join('|').replace(/src\//g, '').replace(/\/\*/g, '')})/**`,
+          'src/**',
           get(config, 'build.tailwind.config', 'tailwind.config.js'),
           [...new Set(get(config, 'build.browsersync.watch', []))]
         ]
@@ -113,8 +113,19 @@ const self = module.exports = { // eslint-disable-line
           })
 
         // Watch for changes in all other files
-        bs.watch(globalPaths)
-          .on('change', () => self.build(env).then(() => bs.reload()))
+        bs.watch(globalPaths, {ignored: templatePaths})
+          .on('change', () => self.build(env, config).then(() => bs.reload()))
+          .on('unlink', () => self.build(env, config).then(() => bs.reload()))
+
+        // Watch for changes in config files
+        bs.watch('config*.js')
+          .on('change', async file => {
+            const parsedEnv = path.parse(file).name.split('.')[1] || 'local'
+
+            Config
+              .getMerged(parsedEnv)
+              .then(config => self.build(parsedEnv, config).then(() => bs.reload()))
+          })
 
         // Browsersync options
         const baseDir = templates.map(t => t.destination.path)
diff --git a/src/transformers/transform.js b/src/transformers/transform.js
index 39b17ab..1002636 100644
--- a/src/transformers/transform.js
+++ b/src/transformers/transform.js
@@ -1,9 +1,7 @@
-const path = require('path')
 const posthtml = require('posthtml')
 const posthtmlContent = require('posthtml-content')
 const Tailwind = require('../generators/tailwindcss')
-const {requireUncached} = require('../utils/helpers')
-const {get, isObject, isEmpty, merge, omit} = require('lodash')
+const {get, omit} = require('lodash')
 
 module.exports = async (html, config = {}, direct = false) => {
   const replacements = direct ? config : get(config, 'transform', {})
@@ -14,14 +12,11 @@ module.exports = async (html, config = {}, direct = false) => {
    */
   const maizzleConfig = omit(config, ['build.tailwind.css', 'css'])
   const tailwindConfig = get(config, 'build.tailwind.config', 'tailwind.config.js')
-  let tailwindObject = (isObject(tailwindConfig) && !isEmpty(tailwindConfig)) ? tailwindConfig : requireUncached(path.resolve(process.cwd(), tailwindConfig))
-  // Use JIT by default, for faster compilation
-  tailwindObject = merge(tailwindObject, {mode: 'jit'})
 
   replacements.postcss = css => Tailwind.compile(
     `@tailwind components; @tailwind utilities; ${css}`,
     html,
-    tailwindObject,
+    tailwindConfig,
     maizzleConfig
   )
 
diff --git a/src/utils/helpers.js b/src/utils/helpers.js
index 9477e1c..b114570 100644
--- a/src/utils/helpers.js
+++ b/src/utils/helpers.js
@@ -9,7 +9,7 @@ module.exports = {
       delete require.cache[require.resolve(module)]
       return require(module)
     } catch {
-      return {}
+      throw new Error(`could not load ${module}`)
     }
   }
 }


Test patch:
diff --git a/test/stubs/config/config.js b/test/stubs/config/config.js
new file mode 100644
index 0000000..32acacf
--- /dev/null
+++ b/test/stubs/config/config.js
@@ -0,0 +1,10 @@
+module.exports = {
+  build: {
+    templates: {
+      source: '../templates',
+      destination: {
+        path: 'build_local'
+      }
+    }
+  }
+}
diff --git a/test/stubs/config/config.maizzle-ci.js b/test/stubs/config/config.maizzle-ci.js
new file mode 100644
index 0000000..1d7aa2a
--- /dev/null
+++ b/test/stubs/config/config.maizzle-ci.js
@@ -0,0 +1,10 @@
+module.exports = {
+  build: {
+    templates: {
+      source: '../templates',
+      destination: {
+        path: 'build_production'
+      }
+    }
+  }
+}
diff --git a/test/test-config.js b/test/test-config.js
index d3ca22f..9fc3c03 100644
--- a/test/test-config.js
+++ b/test/test-config.js
@@ -1,19 +1,19 @@
 const test = require('ava')
 const Config = require('../src/generators/config')
 
-test('throws if a config could not be loaded for the specified environment', async t => {
-  await t.throwsAsync(async () => {
-    await Config.getMerged('production')
-  }, {instanceOf: Error, message: `could not load 'config.production.js'`})
+test('returns the merged config', async t => {
+  const config = await Config.getMerged('maizzle-ci')
+  t.is(config.env, 'maizzle-ci')
 })
 
 test('throws if env name is not a string', async t => {
   await t.throwsAsync(async () => {
     await Config.getMerged(false)
-  }, {instanceOf: TypeError, message: `env name must be a string, received false`})
+  }, {instanceOf: TypeError, message: `env name must be a string, received boolean(false)`})
 })
 
-test('returns the merged config', async t => {
-  const config = await Config.getMerged()
-  t.is(config.env, 'local')
+test('throws if a config could not be loaded for the specified environment', async t => {
+  await t.throwsAsync(async () => {
+    await Config.getMerged('fake')
+  }, {instanceOf: Error, message: `could not load config.fake.js`})
 })
diff --git a/test/test-todisk.js b/test/test-todisk.js
index 515054f..29d3b72 100644
--- a/test/test-todisk.js
+++ b/test/test-todisk.js
@@ -17,7 +17,7 @@ test.afterEach.always(async t => {
 test('throws if config cannot be computed', async t => {
   await t.throwsAsync(async () => {
     await Maizzle.build('production')
-  }, {instanceOf: Error, message: `could not load 'config.production.js'`})
+  }, {instanceOf: Error, message: `could not load config.production.js`})
 })
 
 test('skips if no templates found', async t => {