postcss / postcss-load-config

Autoload Config for PostCSS
MIT License
642 stars 72 forks source link

feat(index): add ability to return a `{Promise}` from the config #174

Closed kossnocorp closed 3 years ago

kossnocorp commented 5 years ago

This PR adds an ability to return a Promise from the config:

module.exports = readFile(someFile, 'utf8')
  .then(JSON.parse)
  .then((someData) => (ctx) => ({
    parser: someData.parser,
    map: ctx.env === 'development' ? ctx.map : false,
    plugins: {
      'postcss-plugin': ctx.options.plugin
    }
  }))

It enables advanced configuration, for example, syncing with webpack config:

const configPromise = getWebpackConfig().then(webpackConfig => {
  const resolver = ResolverFactory.createResolver({
    alias: webpackConfig.resolve.alias
  })

  return {
    plugins: [
      require('postcss-import')({
        resolve: (id, basedir) => resolver.resolveSync({}, basedir, id)
      })
    ]
  }
})
module.exports = configPromise

I also had to update Jest because it was throwing SecurityError: localStorage is not available for opaque origins. See for more details: https://github.com/facebook/jest/issues/6766.

ai commented 5 years ago

I like it 👍

coveralls commented 5 years ago

Coverage Status

Coverage increased (+0.07%) to 97.333% when pulling 069fb8dd1734e39dbe7e57bf6ad14cd19d19f94e on kossnocorp:promise into d193bb368a7e6bc460a06045af271a1d360a7444 on michael-ciniawsky:master.

kossnocorp commented 5 years ago

@michael-ciniawsky I've tried to came up with a code example that is easy to comprehend.

I'm building a foundation (framework) for several projects that have a similar stack, same UI library but differ in paths and other details. I'm extracting common configuration logic such as webpack, Babel, PostCSS into a module. The module reads configuration from the file system and derives the rest data required for the build system (where to build, where's sources, syncs webpack, etc.). webpack supports config promises, so halfway through I realized that PostCSS don't.

kossnocorp commented 5 years ago

@michael-ciniawsky I've fixed your comments.

michael-ciniawsky commented 5 years ago

I need more info about this logic you intend to introduce with your framework, e.g postcss-load-config starts looking up the config relative to the file (dirname) and uses the first occurrence found (bottom-up), so maybe dropping a postcss.config.js in the root of your project (framework) would already by sufficient in this case

App
|– src
||– app.css (2)
|
|– node_modules
||– framework
|||– file.css (1)
|||– postcss.config.js (1)
|
|– postcss.config.js (2)
|– webpack.config.js 
kossnocorp commented 5 years ago

My postcss.config.js is:

module.exports = require('./GECK/postcss/config')

./GECK/postcss/config reads app config and returns promise to PostCSS configuration:

const path = require('path')
const ResolverFactory = require('enhanced-resolve/lib/ResolverFactory')
const NodeJsInputFileSystem = require('enhanced-resolve/lib/NodeJsInputFileSystem')
const CachedInputFileSystem = require('enhanced-resolve/lib/CachedInputFileSystem')

require('ts-node').register()
const { getBundleConfig } = require('../bundleConfig')

const rootPath = process.cwd()
const cachedDuration = 60000
const fileSystem = new CachedInputFileSystem(
  new NodeJsInputFileSystem(),
  cachedDuration
)

const configPromise = getBundleConfig().then(bundleConfig => {
  const resolver = ResolverFactory.createResolver({
    alias: {
      '#app': path.resolve(rootPath, `src/${bundleConfig.name}`),
      '#GECK': path.resolve(rootPath, 'GECK')
    },
    extensions: ['.css'],
    modules: ['node_modules'],
    useSyncFileSystemCalls: true,
    fileSystem
  })

  return {
    plugins: [
      require('postcss-import')({
        resolve: (id, basedir) => resolver.resolveSync({}, basedir, id)
      }),
      require('postcss-preset-env')({ stage: 0 }),
      require('cssnano')()
    ]
  }
})

module.exports = configPromise

Placement of postcss.config.js couldn't help in this case.

michael-ciniawsky commented 5 years ago

Could you post a intended file tree/structure (especially for the @import's)?

  1. Why using a {Promise} for webbpack.config.js (Is it a requirement)? Everyting alse seems to be sync
  2. Don't use postcss-import, use the css-loader instead to avoid duplicates
  3. The config ctx has a file prop (the current file being processed) which can be used to resolve dependencies in a different way ([modified] and passed as an potential plugin option) for/per file. Why isn't that sufficient?
michael-ciniawsky commented 5 years ago

Which concrete problems during development lead you to solve it in that particular way? Support for webpack aliases?

michael-ciniawsky commented 5 years ago

There is also (since webpack >= v4.0.0)

webpack.config.js

{
   module: {
     rules: [
        {
            test: /\.css$/,
            use: [..., 'css-loader', 'postcss-loader', ...],
            resolve: { // <= Scoped resolver options (enhanced-resolve)
               ...options
            }
        }
     ]
   }
}