postcss / postcss-mixins

PostCSS plugin for mixins
MIT License
466 stars 47 forks source link

Document parsing mixins to load into `mixins` option #97

Closed hipstersmoothie closed 5 years ago

hipstersmoothie commented 5 years ago

On #96 when @ai was asked "Is there a way to just pass a string to mixins?" his response was "You can create function, which will take string, parse it and create mixins object with functions."

I have tried to do this on my own

const mixinsPlugin = require('postcss-mixins');
const fs = require('fs');
const postcss = require('postcss');

const mixins = [
  fs.readFileSync(require.resolve('@cgds/styles/elevation.css'), 'utf8'),
  fs.readFileSync(require.resolve('@cgds/styles/typography.css'), 'utf8')
];

function defineMixin(mixins, rule) {
  const name = rule.params.split(/\s/, 1)[0];
  const other = rule.params.slice(name.length).trim();
  let args = [];

  if (other.length) {
    args = postcss.list.comma(other).map(function(str) {
      const arg = str.split(':', 1)[0];
      const defaults = str.slice(arg.length + 1);
      return [arg.slice(1).trim(), defaults.trim()];
    });
  }

  mixins[name] = { mixin: rule, args, content: false };
  rule.remove();
}

function makeMixinDefinition(file) {
  const mixins = {};

  postcss.parse(file).walkAtRules('define-mixin', atrule => {
    defineMixin(mixins, atrule);
  });

  console.log(mixins);

  return mixins;
}

module.exports = () => {
  return {
    plugins: [
      mixinsPlugin({
        mixins: mixins
          .map(makeMixinDefinition)
          .reduce((all, item) => ({ ...all, ...item }), {})
      })
    ]
  };
};

But I cannot get it to work.

If this is the supported way to synchronously load mixins stored in CSS (as #73 #95 and #96 have all been closed) files, I think the method for this should be documented in a simple way.

hipstersmoothie commented 5 years ago

There does not seem to be an easy way to convert a parsed mixin into a function...

hipstersmoothie commented 5 years ago

It seem like with the recommended approach I'm basically rewriting what this plugin is already doing

hipstersmoothie commented 5 years ago

Okay this seems to work with:

const mixinsPlugin = require('postcss-mixins');
const fs = require('fs');
const postcss = require('postcss');

const mixins = [
  fs.readFileSync(require.resolve('@cgds/styles/elevation.css'), 'utf8'),
  fs.readFileSync(require.resolve('@cgds/styles/typography.css'), 'utf8')
];

function makeMixinDefinition(file) {
  const defs = {};

  postcss.parse(file).walkAtRules('define-mixin', atrule => {
    const name = atrule.params.split(/\s/, 1)[0];
    defs[name] = atrule;
  });

  return defs;
}

module.exports = () => {
  return {
    plugins: [
      mixinsPlugin({
        mixins: mixins
          .map(makeMixinDefinition)
          .reduce((all, item) => ({ ...all, ...item }), {})
      })
    ]
  };
};

I get this error though:

(node:4723) UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'length' of undefined
    at /Users/alisowski/Documents/cgds/components/Card/src/Card.css:2:3
    at insertMixin (/Users/alisowski/Documents/cgds/node_modules/postcss-mixins/index.js:66:31)

I have fixed this error here https://github.com/postcss/postcss-mixins/pull/98

ai commented 5 years ago

You should have an object not an array

require('postcss-mixins')({
    mixins: {
        clearfix: {
            '&::after': {
                content: '""',
                display: 'table',
                clear: 'both'
            }
        }
    }
});
hipstersmoothie commented 5 years ago

It gets reduced into and array.

mixins: mixins
          .map(makeMixinDefinition)
          .reduce((all, item) => ({ ...all, ...item }), {})
ai commented 5 years ago

Can you give me more details about your use case? I remember that you can’t use sync methods because of the limit of the TS processor.

But what system are you creating? Do you need to create TS plugin with few build-in PostCSS mixins?

ai commented 5 years ago

Can you show me the content of @cgds/styles/elevation.css and @cgds/styles/typography.css?

hipstersmoothie commented 5 years ago

In the end what I'm trying to do (typescript or whatever) doesn't really matter. Basically its a custom typescript compiler that does some validation through postcss. Currently it chokes on this plugin.

This is what I want:

  1. have a mixin in a css file (this might be much more complex than this)
@define-mixin black { color:  black }
  1. load this into postcss-mixins synchronously.

So far you told me:

You can create function, which will take string, parse it and create mixins object with functions

The current docs show how to do it as either an object or function but I do not want to rewrite anything. I want to keep all the mixins in CSS.

I interpreted your above statement as "use postcss.parse to parse all the mixins and load them into the plugin through the mixins option." In doing that I encounter the error i fixed in #98. With that change everything works as expected.

Would you accept a PR if I added more tests to it? Or are you unwilling to accept any PR and I should try to solve my problem in a different way?

ai commented 5 years ago

Why do you need to run postcss-mixins if you do linter? Are you creating linter for CSS-in-JS?

hipstersmoothie commented 5 years ago

in my system people can have mixins and I'm trying to validate css-modules usage. a mixin might add classes and i need those

ai commented 5 years ago

So, you have TS scripts and CSS files with CSS Modules (which may contain mixins). Am I right that you need to compare classes in TS and CSS (after mixins will be applied)?

hipstersmoothie commented 5 years ago

yes

ai commented 5 years ago

Maybe we should not put PostCSS inside TS parser? We can use TS parser and collect folks and links to CSS Modules. After this step you can run PostCSS (this time with async support since you are working outside TS parser).

Will it work in your case?