Angular-cli: Specify custom PostCSS plugins

Created on 9 Nov 2017  路  39Comments  路  Source: angular/angular-cli

Bug Report or Feature Request (mark with an x)

- [ ] bug report -> please search issues before submitting
- [x] feature request

Versions.

@angular/cli: 1.4.9
node: 6.11.4
os: linux x64
@angular/animations: 4.4.6
@angular/common: 4.4.6
@angular/compiler: 4.4.6
@angular/core: 4.4.6
@angular/forms: 4.4.6
@angular/http: 4.4.6
@angular/platform-browser: 4.4.6
@angular/platform-browser-dynamic: 4.4.6
@angular/router: 4.4.6
@angular/cli: 1.4.9
@angular/compiler-cli: 4.4.6
@angular/language-service: 4.4.6
typescript: 2.3.4

Desired functionality.

I would like the ability to specify custom postCss plugins, without having to eject the angular webpack.config.js file.

Mention any other details that might be useful.

This would enable frameworks like TailwindCSS (https://tailwindcss.com) and other useful PostCSS plugins through the CLI without having to deal with the ejected webpack config file.

devkibuild-angular further discussion feature

Most helpful comment

Any progress update on this feature? Almost 3 years since it was suggested. I am dropping SASS and going PostCSS. Right now it looks like I will have to switch to React to do that.

All 39 comments

Hi, just wondering if there was a workaround for using TailwindCSS via PostCSS in Angular while we wait for this item. I've googled and couldn't find any good documentation on how to. Thanks.

Found a hackable workaround.

As there is no "official" way to provide Angular CLI a postcss-custom-properties config - yet.

Open index.js from your projects node_modules\postcss-custom-properties\dist folder
Find this line:
warnings: options.warnings === undefined ? true : options.warnings,

We're going to change warnings property from true to false.
warnings: options.warnings === undefined ? false : options.warnings,

Save and run ng serve
image

Thanks @trimox!

But change files in node_modules folder is a big problem for me 馃槩

I am waiting the official solution...

100% agreed. Hope the issue gets more attention!

The typical workaround for this sort of thing is to write a NPM postinstall script in your application which uses sed or similar to monkey-patch (via regex) the relevant line of code inside node_modules. This is as awful a hack as it sounds, but I've used it several times to get past the need for a temporary feature "right now" awaiting a real solution down the road.

Overall I think a monkey patch like this is much less bad than ejecting, or rolling your own build solution, to get access to a needed setting.

Vote up! This could be very useful option.

@filipesilva could you please tell us what the "comp: cli/build" tag you just added means? any chance of this feature landing in cli anytime soon-ish? :)

thanks

Just wanted to see if I could get a definite answer- does this mean if I wanted to use a third-party postcss plugin with angular-cli (eg. postcss-media-variables), I have to eject the webpack config? In other words, there's currently no native way to add additional postcss plugins to angular-cli?

Thanks for any help!

With Angular CLI 6 no longer allowing for ng eject, this has become much more important. We are currently relying on an ejected webpack config to add Tailwind CSS, but with all the changes from Angular 5 to 6 (not to mention going from Webpack 3 to 4), it would be awesome if there was a way to specify PostCSS plugins in the config. Since we already have autoprefixer, I'd imagine we could perhaps move autoprefixer into a postCSS section of the configuration, keeping it all exposed (but preconfigured) to the user.

Are there any updates about using postcss plugins? We are using cssnext rather than sass, and are relying on postcss-cssnext to do that. We would really like to be able to use Angular CLI - but we aren't able to do any hacking of the node_modules due to constraints of our (overly) constrained build system :/

@filipesilva - it's been almost 4 months since you were unassigned, any idea if someone else will be assigned soon, or should we just give up on CLI for now? I'd have a shot at convincing our team to hang tight if we knew it would be worked on soon-ish...but the natives are becoming restless!!

any updates?

I think that having postcss run the auto prefixer and allowing other plugins is an awesome idea - @phroggyy

@d4nc3r postcss-cssnext is dead, now have to use postcss-preset-env going forward.

So looking at the generation of the webpack config, postcss is run for all scss|less|stylus|css right before the raw-loader with the following plugins -> postcss-url, postcss-import, and autoprefixer, plus another cli specific plugin. I really think most of us are wanting to run plugins right before the autoprefixer, but if a plugin like cssinject needs to run before the url plugin then the ordering would need to be managed. The plugin removeRoot is another one that requires ordering. It also looks like plugin configs need to be substantially modified to be usable in this environment. In addition, plugins like postcss-preset-env, which handles cssnext behavior, also configures autoprefixer driven by browserslist which could conflict with the execution of the autoprefixer in the cli.

https://github.com/angular/devkit/blob/master/packages/angular_devkit/build_angular/src/angular-cli-files/models/webpack-configs/styles.ts

I guess one alternative is to fork https://github.com/angular/devkit and add the webpack config lines directly in to that file and just try to keep them up to date... but a supported option would be nice

hi everyone, are there any promising developments on this ?

Hey guys!
What about custom builders for Angular 6+?
I haven't tried, however package author says it is good alternative for deprecated ng eject and it allows to extend webpack config (without overriding built-in config).
See for details: https://github.com/meltedspark/angular-builders/tree/master/packages/custom-webpack

Isnt this the most requested feature since Angular 2?

Personally i need to add flexbugs to PostCSS.

Also looking for this feature to add the postcss-momentum-scrolling plugin. I have tried using the custom builders for Angular 7, but haven't had success adding getting it to add a plugin. Instead it seems to replace the entire ruleset for /\.scss$|\.sass$/.

Started a question here to see if anyone knows how and can point me in the right direction.
https://stackoverflow.com/questions/53955119/how-do-i-add-an-additional-postcss-plugin-via-the-new-angular-cli-v7-angular-js

any solution with angular6?

I have the same problem.

Bumping this, we dropped Sass and other preprocessors for PostCSS for about 3 years.

It would be really great to be able to add custom PostCSS plugins into an project utilizing the @angular/cli tooling.

Here is a great article about how to extend postcss with @angular-builders/custom-webpack I think it will solve the issue for us for now.

https://netbasal.com/customize-webpack-configuration-in-your-angular-application-d09683f6bd22

No, it wont.
angular-cli injects own internal postcss plugin to postcss loader.
angular-cli -> webpack -> potscss loader -> postcss plugins.

See node_modules/@angular-devkit/build-angular/src/angular-cli-files/models/webpack-configs/styles.js

we had the same issue, since we needed additional loader for loading scss.

We are doing it dirty like this, using @angular-builders/custom-webpack:

module.exports = (config) => {

  // removing old loader
  config.module.rules = Array.from(config.module.rules).filter(e => !`${e.test}`.startsWith('/\\.scss'));

  // Adding proper scss rule with loaders
  config.module.rules.push({
    test: /\.scss$|\.sass$/,
    use: [
      {
        loader: 'postcss-loader',
        options: {
          sourceMap: false,
        }
      },
      {
        loader: 'resolve-url-loader',
        options: {sourceMap: false}
      },
      {
        loader: 'sass-loader',
        options: {sourceMap: false}
      }
    ]
  });

  return config;
};

Would love to get rid of it though

Adding PostCss plugin by Custom webpack builders does not work https://github.com/just-jeb/angular-builders/issues/634 is this valid?

didn't realise I wanted PostCSS Plugins until I saw this: https://github.com/larsenwork/postcss-easing-gradients

https://github.com/just-jeb/angular-builders/issues/634 and https://github.com/just-jeb/angular-builders/issues/638 contain information on how this can be achieved using the excellent angular-builders.

This isn't something we're looking at doing as a first party option because exposing PostCSS configuration is very much similar to exposing Webpack configuration, insofar as it requires code configuration and is not guaranteed to be isolated. In webpack, even small configuration changes in one place can require larger edits across other loaders and plugins to work correctly.

I agree it's not easy to do with angular-builders, but it is possible. It would be great to have more composable webpack setup in order to better enable third party modifications.

Simply append or prepend rules didn't work for me. The following custom webpack config worked:

const postcssCustomProperties = require('postcss-custom-properties');

module.exports = (config, options) => {

    const scssRule = config.module.rules.find(x => x.test.toString().includes('scss'));
    const postcssLoader = scssRule.use.find(x => x.loader === 'postcss-loader');
    const pluginFunc = postcssLoader.options.plugins;
    const newPluginFunc = function () {
        var plugs = pluginFunc.apply(this, arguments);
        plugs.splice(plugs.length - 1, 0, postcssCustomProperties({ preserve: true }));
        return plugs;
    }
    postcssLoader.options.plugins = newPluginFunc;

    return config;
};

I've tried this approach and it did not work for me (angular 9.0.0-rc.7).
Build hangs (stuck) on Generating ES5 bundles for differential loading...
But looks like plugins injected correctly.

@ToniaDemchuk how does your angular.json file looks like
do you have a repo that i can refer?

@anoopsinghbayes Unfortunately, no public repo I can share to you.
Here is angular.json file:

{
    "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
    "version": 1,
    "newProjectRoot": "projects",
    "projects": {
        "MyProject": {
            "root": "",
            "sourceRoot": "src",
            "projectType": "application",
            "prefix": "app",
            "schematics": {
                "@schematics/angular:component": {
                    "style": "scss"
                }
            },
            "architect": {
                "build": {
                    "builder": "@angular-builders/custom-webpack:browser",
                    "options": {
                        "outputPath": "wwwroot",
                        "index": "src/index.html",
                        "main": "src/main.ts",
                        "polyfills": "src/polyfills.ts",
                        "tsConfig": "src/tsconfig.app.json",
                        "assets": [
                            "src/favicon.ico",
                            "src/assets"                            
                        ],
                        "styles": [
                            "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css",
                            "src/styles.scss",
                            "src/styles/global-styles.scss",
                            "./node_modules/quill-mention/dist/quill.mention.min.css",
                            "./node_modules/quill/dist/quill.core.css",
                            "./node_modules/quill/dist/quill.snow.css"
                        ],
                        "scripts": [],
                        "customWebpackConfig": {
                            "path": "./custom-webpack.config.js",
                            "replaceDuplicatePlugins": true,
                            "mergeStrategies": {
                                "module.rules": "prepend"
                            }
                        }
                    },
                    "configurations": {
                        "production": {
                            "fileReplacements": [
                                {
                                    "replace": "src/environments/environment.ts",
                                    "with": "src/environments/environment.prod.ts"
                                }
                            ],
                            "optimization": true,
                            "outputHashing": "all",
                            "sourceMap": false,
                            "extractCss": true,
                            "namedChunks": false,
                            "aot": true,
                            "extractLicenses": true,
                            "vendorChunk": false,
                            "buildOptimizer": true,
                            "budgets": [
                                {
                                    "type": "initial",
                                    "maximumWarning": "2mb",
                                    "maximumError": "5mb"
                                }
                            ]
                        }
                    }
                },
                "serve": {
                    "builder": "@angular-builders/custom-webpack:dev-server",
                    "options": {
                        "browserTarget": "MyProject:build"
                    },
                    "configurations": {
                        "production": {
                            "browserTarget": "MyProject:build:production"
                        }
                    }
                },
                "extract-i18n": {
                    "builder": "@angular-devkit/build-angular:extract-i18n",
                    "options": {
                        "browserTarget": "MyProject:build"
                    }
                },
                "test": {
                    "builder": "@angular-devkit/build-angular:karma",
                    "options": {
                        "main": "src/test.ts",
                        "polyfills": "src/polyfills.ts",
                        "tsConfig": "src/tsconfig.spec.json",
                        "karmaConfig": "src/karma.conf.js",
                        "styles": [
                            "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css",
                            "src/styles.scss"
                        ],
                        "scripts": [],
                        "assets": [
                            "src/favicon.ico",
                            "src/assets"
                        ]
                    }
                },
                "lint": {
                    "builder": "@angular-devkit/build-angular:tslint",
                    "options": {
                        "tsConfig": [
                            "src/tsconfig.app.json",
                            "src/tsconfig.spec.json"
                        ],
                        "exclude": [
                            "**/node_modules/**"
                        ]
                    }
                }
            }
        }
    },
    "defaultProject": "MyProject"
}

Angular versions from package.json:

"@angular/animations": "8.2.14",
"@angular/cdk": "8.2.3",
"@angular/common": "8.2.14",
"@angular/compiler": "8.2.14",
"@angular/core": "8.2.14",
"@angular/flex-layout": "8.0.0-beta.27",
"@angular/forms": "8.2.14",
"@angular/material": "8.2.3",
"@angular/platform-browser": "8.2.14",
"@angular/platform-browser-dynamic": "8.2.14",
"@angular/router": "8.2.14",
"@angular-builders/custom-webpack": "8.4.1",
"@angular-builders/dev-server": "7.3.1",
"@angular-devkit/build-angular": "0.803.20",
"@angular/cli": "8.3.20",
"@angular/compiler-cli": "8.2.14",
"@angular/language-service": "8.2.14",

I tried something based on what @ToniaDemchuk did, and wasn't able to get it to work. I did some debugging to make sure the plugin was correctly added to the list of postcss plugins, and I was able to get that to work, but the plugin still isn't doing anything.

I have another project using postcss within "plain"(?) webpack config, and the postcss plugins work fine there.

Here's what I have - it fixes a bug in finding the postcss-loader, and it successfully adds a plugin, but there's no evidence that the plugin is doing anything:

angular.json:

 "projects": {
    "(app name)": {
      "projectType": "application",
      "architect": {
        "build": {
          "builder": "@angular-builders/custom-webpack:browser",
          "options": {
...
            "customWebpackConfig": {
              "path": "ng-webpack-ext.config.js"
            },

ng-webpack-ext.config.js :

const combineMediaQueryPlugin = require('postcss-combine-media-query');

/**
 * Patches the webpack postcss-loader plugins function to add the specified postcss
 * plugins, in addition to angular's defaults.
 *
 * @param {object} webpackConfig - The webpack config, created by angular-devkit
 * @param {array} addPlugins - An array of postcss plugins to insert at the beginning of the postcss plugins
 */
function patchWebpackPostcssPlugins(webpackConfig, addPlugins) {
  const webpackScssRule = webpackConfig.module.rules.find(x => x.test.toString().includes('scss'));
  if (!webpackScssRule) {
    console.warn('[ng-webpack-ext.config.js]: No scss rule found');
  }
  else {
    const postcssLoader = webpackScssRule.use.find(x => x.loader.includes('postcss-loader'));
    const pluginFunc = postcssLoader.options.plugins;
    const newPluginFunc = function () {
      let plugins = pluginFunc.apply(this, arguments);
      plugins = plugins.concat(addPlugins);
      console.dir(plugins); // Shows the plugins
      return plugins;
    }
    postcssLoader.options.plugins = newPluginFunc;
  }
};

/** Function returning webpack config. */
module.exports = (config, options) => {
  patchWebpackPostcssPlugins(config, [combineMediaQueryPlugin()]);

  return config;
};

Looking at the current angular-cli code, it looks like there's an OptimizeCssWebpackPlugin which wraps cssnano and postcss. That runs as a webpack minimizer.

The console.dir line above show that my plugin is included in the array, after the others created by angular's plugin creator:

[
  [Function (anonymous)] {
    postcssPlugin: 'postcss-import',
    postcssVersion: '7.0.27'
  },
  [Function (anonymous)] {
    postcssPlugin: 'postcss-cli-resources',
    postcssVersion: '7.0.27'
  },
  [Function: plugin] {
    options: {},
    browsers: undefined,
    info: [Function (anonymous)],
    postcssPlugin: 'autoprefixer',
    postcssVersion: '7.0.27'
  },
  [Function (anonymous)] {
    postcssPlugin: 'postcss-combine-media-query',
    postcssVersion: '7.0.27'
  }
]

I'm pretty much stuck figuring out why the postcss plugin isn't being used. I've about hit my limit on painful troubleshooting for one day.

Any progress update on this feature? Almost 3 years since it was suggested. I am dropping SASS and going PostCSS. Right now it looks like I will have to switch to React to do that.

as mentioned above and in this article I had no problem integrating postcss/tailwindcss.

The integration is straightforward and all one has to do is provide the custom build part themselves, no need to get the hands dirtly with Angular Cli internals.

@banjankri getting tailwind to work is not a problem, even with material. But we still face an issue when debugging css in chrome. All imported styles in styles.scss gets inlined. You don't see the actual file in chrome. I would love to add just the plugin instead of override the webpack config

for applying custom post-css plugins I found a solution in this ticket: https://github.com/just-jeb/angular-builders/issues/638

Was this page helpful?
0 / 5 - 0 ratings