The PostCSS Ecosystem issue

Long Ho
3 min readFeb 1, 2019

--

This doc serves to explain a fundamental issue in build system interop. This is primarily relevant for people who have to deal with PostCSS and respective build system for JS.

Background

For those who are not familiar with PostCSS it’s a CSS pre-processor which parses CSS into AST and has a plugin architecture that allows you to do node transformation. PostCSS used to be synchronous until circa 2014 when a bunch of plugins took a very long time to process (e.g img compression), it decided to support an async interface.

The 1st core issue

However, it does not mandate plugins to support both interfaces (sync & async). The LOE to support both interfaces is also fairly high (almost a complete rewrite in cases where there’re a lot of File IO like postcss-import). Therefore, some has made a stance to only support async.

Sync obviously takes longer, but as it turns out async opens up a new class of problem.

Cross-build system interop

There’re a few big build systems that have considerable interop issue with PostCSS: webpack (along with it rollup, parcel… mainly the asset dist packager) & babel (along with it typescript, browserify & even jest).

  • webpack is almost completely async from code transformation to chunk split/merge, which benefits from PostCSS async plugins. webpack also walks the dep tree from an entry point, thus having ability to stage transformations.
  • typescript and friends do not. These tools do not have an entry point and walks through a glob of files as input. The real problem is that require & import have to be resolved synchronously.

When you initialize PostCSS processor with a bunch of plugins, everything has to be sync for the pipeline to be sync, but not for async.

The manifest of the issue

The real problem is CSS Modules, where we’re allowed to do things like:

import * as css from ‘foo.css’

The classnames have to be resolved synchronously if used in typescript, babel or jest, which is not possible if running a live PostCSS pipeline. postcss-modules, however, is async.

Even if postcss-modules is sync, postcss-import isn’t (which led to forks like postcss-import-sync & postcss-import-sync2 bc the author refused to support both interfaces). postcss-custom-properties is async but postcss-css-variables (which does very similar things) is sync.

Wait, but I’ve seen pipeline like this work before, how? This is likely due to:

  1. They have custom watered-down implementations. Take https://github.com/Connormiha/jest-css-modules-transform as an example. The source code literally just walk through the CSS AST tree & pull out the class name manually, which has so many unhandled edge cases.
  2. They use very old pre-async versions of those plugins (Some people still use postcss-import@7 and latest version is 10)
  3. They use the underlying libs that those plugins use, but not the plugins themselves. See https://github.com/css-modules/css-modules-require-hook/blob/master/package.json

Foolproof solution

The most foolproof solution is to run the PostCSS pipeline separately, dumps out all the artifacts you use in your code & then do transformations on top of that. E.g: dump out all class names & vars into a json file & do transformation using those.

The 2nd (not really core PostCSS) issue

CSS as a standard only support 1 way to import: via a URL. However, some plugins allow you to resolve import locally or Node style (look at node_modules 1st). This turns PostCSS import resolution into kind of a nightmare. Why?

  1. It complicates the lookup hierarchy.
  2. Some build system (like webpack) loads & produce chunk in memory, which has no actual backing file, on top of that it supports import 'css-loader?foobarcustom syntax.
  3. Node-style lookup hierarchy is also fairly complex, especially when it comes to symlinks.

Foolproof solution

This is the reason why a lot of import-related plugins give you an escape hatch in the form of a resolveModuleByYourself callback because it’s sometimes impossible to try to figure out systematically. If the plugin you’re using doesn’t offer, make a suggestion.

--

--

Long Ho

Senior SDE @Dropbox. Previously @YahooFinance, @iHeartRadio, @Selerity. Views are my own.