Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

A production build

Production is the version of a site uploaded to the server for visitors. While working you want readable CSS with source maps; for publishing you want it minified and without maps. Let us put all the plugins of the course into one chain with a switch between these modes.

Project structure

blog/
├── index.html
├── css/
│   └── main.css          ← build output
├── styles/
│   ├── main.css
│   ├── base.css
│   └── post-card.css
├── postcss.config.js
└── package.json

Install all the plugins at once:

npm install --save-dev postcss postcss-cli postcss-import postcss-preset-env cssnano

A config with a switch

The config file can export a function instead of an object. postcss-cli passes it a ctx object, where ctx.env is the value of the NODE_ENV environment variable:

// postcss.config.js
import postcssImport from 'postcss-import';
import postcssPresetEnv from 'postcss-preset-env';
import cssnano from 'cssnano';
 
export default (ctx) => {
  const isProduction = ctx.env === 'production';
 
  return {
    map: isProduction ? false : { inline: false },
    plugins: [
      postcssImport(),
      postcssPresetEnv(),
      isProduction ? cssnano() : false,
    ],
  };
};

In development cssnano is replaced with false — PostCSS skips such list items. The source map, on the contrary, is written only in development.

Scripts

The --env flag sets NODE_ENV the same way on Windows, macOS and Linux:

{
  "scripts": {
    "dev": "postcss styles/main.css -o css/main.css --watch",
    "build": "postcss styles/main.css -o css/main.css --env production"
  },
  "browserslist": ["defaults"]
}

Source styles

/* styles/main.css */
@import "./base.css";
@import "./post-card.css";
/* styles/base.css */
@custom-media --tablet (width >= 768px);
 
body {
  margin: 0;
  font-family: system-ui, sans-serif;
}
/* styles/post-card.css */
.post-card {
  padding: 16px;
  border-radius: 8px;
  user-select: none;
 
  & h2 {
    margin: 0;
  }
 
  @media (--tablet) {
    padding: 32px;
  }
}

Development

npm run dev watches all three files — postcss-cli knows which of them are included through @import. css/main.css holds readable CSS and a link to the map:

body {
  margin: 0;
  font-family: system-ui,-apple-system,Segoe UI,Roboto,Ubuntu,Cantarell,Noto Sans, sans-serif;
}
 
.post-card {
  padding: 16px;
  border-radius: 8px;
  -webkit-user-select: none;
     -moz-user-select: none;
          user-select: none;
}
 
.post-card h2 {
    margin: 0;
  }
 
@media (min-width: 768px) {
 
.post-card {
    padding: 32px;
}
  }
 
/*# sourceMappingURL=main.css.map */

The files are joined, nesting is unwrapped, --tablet is replaced with the condition, prefixes are added, and for older browsers a list of similar system fonts is added to system-ui.

Publishing

npm run build produces a single line without a map:

body{margin:0;font-family:system-ui,-apple-system,Segoe UI,Roboto,Ubuntu,Cantarell,Noto Sans,sans-serif}.post-card{padding:16px;border-radius:8px;-webkit-user-select:none;-moz-user-select:none;user-select:none}.post-card h2{margin:0}@media(min-width:768px){.post-card{padding:32px}}

You upload index.html and the css/ folder to the server. There is no need to upload styles/ or node_modules/.

In a Vite project

In Vite the chain is shorter: it handles @import and minification by itself, so postcss.config.js keeps only postcssPresetEnv(). npm run build puts the finished CSS into dist/assets/.

Common mistake

Writing NODE_ENV=production postcss ... in a script. It works on macOS and Linux, but fails in the standard Windows command prompt, so a teammate on Windows cannot build the project. Use the --env production flag — postcss-cli sets the variable itself.

Thanks for reading!

Donate to the site — we work hard for you.