npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

postcss-params

v1.0.2

Published

Parse media-query-like params into PostCSS plugins

Downloads

55

Readme

npm version Build Status Coverage Status Code Climate dependencies Status devDependencies Status

PostCSS Params

postcss-params has two usage modes:

  1. Target devices/clients based on a build configuration, much like media queries.
  2. Pass strings from css to your postcss plugin, using a familiar syntax.

Some sites serve different assets to different clients. For example, you may have some IE-specific css hacks that you only want to serve to IE browsers. Or you want to load certain fonts for certain countries. PostCSS is a good way to keep all your code in one file, then generate separate assets.

@my-plugin (browser: ie) {
  button {
    background-color: red;
  }
}
@my-plugin not (browser: ie) {
  button {
    background-color: green;
  }
}

postcss-params helps you write a plugin which reads the (browser: ie) parameter string, and keep or discard the block accordingly.

Build two assets, with configurations {browser: ie} and {}. PostCSS will generate two assets. One you can serve to your locked-in customers browsing from their lunch breaks at BigCorp. The other asset you can serve to the rest of the civilized world.


buildComparator

buildComparator accepts a param string and returns a function. The resulting comparator function accepts a configuration object, and returns true if the params match the configuration, and false otherwise.

See the tests in tests/buildComparator for more examples.

CSS:

@my-plugin (region: cn) {
  body {
    font-family: ".PingFang-SC-Regular", sans-serif;
  }
}
@my-plugin not (region: cn) {
  body {
    font-family: "Helvetica Neue", Arial, sans-serif !default;
  }
}

Plugin:

const { buildComparator } = require('postcss-params');
postcss.plugin('my-plugin', (configuration) => (root) => {
  root.walkAtRules('my-plugin', (atRule) => {
    const comparator = buildComparator(atRule.params);
    if (comparator(configuration)) {
      atRule.replaceWith(atRule.nodes);
    } else {
      atRule.remove();
    }
  });
});

Running postcss with various configuration objects will result in css assets suitable for separate intended audiences. For example, you may serve a different font-family in China, but not want to load this asset for all countries.

configuration is provided to PostCSS through your build tool.

Example configuration:

{
  debug:  true,
  region: "us",
  theme:  "blue"
}

buildAst

buildAst gives you finer control and access to the params written in css.

Given this simple rule:

@my-plugin (theme: red) {
  body {
    background-color: theme-color;
  }
}

and this plugin:

const { buildAst } = require('postcss-params');
postcss.plugin('my-plugin', (configuration) => (root) => {
  root.walkAtRules('my-plugin', (atRule) => {
    const ast = buildAst(atRule.params);
    console.log(ast);
  });
});

This AST is generated:

{ feature: "theme", value: "red" }

Given this more complicated rule:

@my-plugin (debug),
           (region: cn) and (theme: red),
           (region: us) and (theme: blue),
           not (production) and (staging) {
  body {
    background-color: red;
  }
}

Plugin:

const { buildAst } = require('postcss-params');
postcss.plugin('my-plugin', (configuration) => (root) => {
  root.walkAtRules('my-plugin', (atRule) => {
    const ast = buildAst(atRule.params);
    console.log(ast);
  });
});

This AST is generated:

any
├─ { feature: debug, value: true }
├─ all
│  ├─ { feature: region, value: cn }
│  └─ { feature: theme, value: red }
├─ all
│  ├─ { feature: region, value: us }
│  └─ { feature: theme, value: blue }
└─ all
   ├─ { feature: production, not: true }
   └─ { feature: staging, not: true }

Now your PostCSS plugin can make use of this AST to pull values into variables, or decide to keep or discard the block.

Limitations:

  • Feature names and values must NOT contain characters (),:
  • Errors are fairly opaque ('Expected L_PAREN')
  • Feature values are optional, so comparators must expect String or Undefined
  • Feature can only have one value. (f: a), (f: b) can't be (f: a, b)
    • As a hack, (f: a|b) is legal, and the comparator can split on |

AST Object Reference

The resulting AST from buildAst is a tree structure. There are three possible nodes in this tree:

  • any: if any item resolves true, return true.

  • all: if all items resolve true, return true.

  • feature: compare the param value with the config value.

    • not: if feature returns true, return false. And vice-versa.

    Given this contrived rule:

    @my-plugin (debug),
               (region: cn) and (theme: red),
               (region: us) and (theme: blue),
               not (production) and (staging) {
      body {
        background-color: red;
      }
    }

This AST is generated:

any
├─ { feature: debug, value: true }
├─ all
│  ├─ { feature: region, value: cn }
│  └─ { feature: theme, value: red }
├─ all
│  ├─ { feature: region, value: us }
│  └─ { feature: theme, value: blue }
└─ all
   ├─ { feature: production, not: true }
   └─ { feature: staging, not: true }

See the tests in tests/buildAst for more examples.


About the syntax

All PostCSS at-rules follow the structure @plugin-name params { body }. Plugins are given the params as a string, with no provisions for parsing, or even a standard format for clients to write params.

CSS already has an analogous structure for media queries:

  • @media media-query [, media-query]* { rule-list }

where media-query can take either form:

  • [NOT|ONLY]? media-type [AND (media-feature[: value]?)]*
  • (media-feature[: value]?) [AND (media-feature[: value]?)]*

However, this has some limitations:

  1. media-type is not meant for general-purpose use. It accepts specific values, e.g., all, screen, print. We are only interested in media-feature usage.
  2. NOT must be used with media-type. (NOT (media-feature) is illegal).
  3. ONLY was a hack for older browsers.

So we have defined a similar grammar which:

  1. removes media-type entirely.
  2. allows NOT to be juxtaposed with media-feature.
  3. removes ONLY.

ASTs are automatically flattened

any and all nodes with a single child are replaced with that child node.

any
└─ all
   └─ { feature: debug, value: true }

is the same as

{ feature: debug, value: true }

LL(1) Grammar Reference - Please do not LL(2+)

CommaSeparatedList
 : MediaQuery [ COMMA MediaQuery ]*
MediaQuery
 : [NOT]? Feature [ AND Feature ]*
Feature
 : L_PAREN IDENT [ COLON IDENT ]? R_PAREN