Use Postcss in Custom Angular Library

I used postcss, postcss-css-modules and posthtml-css-modules to implement CSS Modules in a Angular Application. I also used @angular-builders/custom-webpack to achieved this.

Now, I want to do the same with my Custom Angular Library. However, I cannot use @angular-builders/custom-webpack because the Angular Libraries are build using ng-packagr.

So, @angular-builders/custom-webpack is not available to use: https://github.com/just-jeb/angular-builders/issues/356

On the other hand, ng-packagr does not support postcss: https://github.com/ng-packagr/ng-packagr/issues/1471

I have read that it’s possible to extend rollup config (is the compiler that use ng-packagr at the end of the build) in ng-packagr:

But I didn’t find any documentation to achieve this.

Does anybody know how to do it?

Other solution that I thought, it’s make all the styles global and compile them using scss-bundle and postcss like I did here: https://stackoverflow.com/questions/60712566/nodejs-script-that-compiles-scss-files-fails-because-of-postcss-rule-for-undefin

And after If I can use lodash I will be able to replace the class names by their hashed class name like is proposed here: https://stackoverflow.com/questions/60512235/use-hashbase645-in-javascript-typescript-file

But to do that, I will need to know how to invoke lodash in the build of ng-packagr.

Does anybody knows how to do that?

Any other solution is more than welcome.

Thanks in advance.

I’d start with checking rollups docs how to do that and then explore ng-packagr internals and find where it’s using Rollup. You could figure that out if you do some debugging. And then it could be great material for an article.

Hi @m.koretskyi , thanks for your response.
Good advice, I will research how to do that.

Do you know if exist another way to extend the build of a Custom Angular Library?

I have been researching a lot but I didn’t find anything. Because of that, I thought about extending the rollup.config

can you show how you did it? then maybe I can figure out where you can do the same in ng-packagr

1 Like

Sure!!!

Currently, the Angular Project where I am working on use webpack as compiler and not angular cli. (My next step after this is migrate from webpack to Angular CLI), but the Library use Angular CLI.

I implement CSS Modules in the Angular Project that use webpack but I know that I can do the same in Angular CLI thanks to the help of @angular-builders/custom-webpack

To implement CSS Modules in Angular first I use postcss and post-css-modules to hash all the styles.

post-css-modules takes all the component.scss files and hash the class names, also it creates a component.scss.json file per component where it stores the mapping between the class names and their corresponding hashed names.

Then, with posthtml-css-modules, the compiler iterate over each component.html file and changes the classes name by their hashed name using the component.scss.json created before.

That’s it.

If it’s not clear, I can create a repo to show you that in action.

Thanks in advance!!!

1 Like

yeah, that would be great

1 Like

Hi @m.koretskyi , here I leave you the url of Angular 8 Project that uses @angular-builders/custom-webpack to use postcss-modules and posthtml-css-modules to implements CSS Modules:

Angular 8 with Css Modules

Now I would like to use the same approach on a Custom Angular Library to implements CSS Modules. But, like ng-packagr do not use webpack I am not able to use @angular-builders/custom-webpack to later use postcss-modules and posthtml-css-modules.

In a few minutes I will do another post explaining with more detail how postcss-modules and posthtml-css-modules works.

Thanks.

First, postcss-modules hash all the class names in the component.scss files in build time.

Example: takes app.component.scss

.grid-container {
    display: grid;
    grid-template-rows: auto;
    grid-template-rows: 90px calc(100vh - 170px) 80px;
    width: 100vw;
    height: 100vh;
}

.header {
    background-color: #1ba0f7;
    display: flex;
    align-items: center;
    justify-content: flex-start;

    img {
        width: 64px;
        height: 64px;
    }
}

.footer {
    background-color: #1ba0f7;
}

And hashes all the clases like this:

._3Kuna {
    display: grid;
    grid-template-rows: auto;
    grid-template-rows: 90px calc(100vh - 170px) 80px;
    width: 100vw;
    height: 100vh;
}

._2-SC8 {
    background-color: #1ba0f7;
    display: flex;
    align-items: center;
    justify-content: flex-start;

    img {
        width: 64px;
        height: 64px;
    }
}

._2w5qX {
    background-color: #1ba0f7;
}

Also, it creates a .json file for each component.scss where it stores the mapping between class nammes and hashed class names.
For example, the .json of app.component.scss is app.component.scss.json

{
  "grid-container":"_3Kuna",
  "header":"_2-SC8",
  "footer":"_2w5qX"
}

Then, posthtml-css-modules takes every component.html file, and changes the class names by their corresponding hashed class name.
But, in order to do that, first, we have to change the attributes class by css-module, because is the way posthtml-css-modules identify what name has to change.

For example, posthtml-css-modules will take app.component.html (you can see it has css-modules as attribute instead of class):

<div class="grid-container">
  <div class="header">
    <a href="https://angular.io/" target="_blank">
      <img src="assets/img/angular_logo.png">
    </a>
  </div>
  <div>
      <router-outlet></router-outlet>
  </div>
  <div class="footer">
  </div> 
</div>

And will replace css-modules for class but leaving as value of class the hashed value:

<div css-module="_3Kuna">
  <div css-module="_2-SC8">
    <a href="https://angular.io/" target="_blank">
      <img src="assets/img/angular_logo.png">
    </a>
  </div>
  <div>
      <router-outlet></router-outlet>
  </div>
  <div css-module="_2w5qX">
  </div>  
</div>

And that is it :slight_smile:

Any doubt or question please write to me and I will answer as soon as posible :smiley:

You’re my hero, German! I love CSS Modules!

Did you come up with the css-module="class-name" syntax or is that a convention/part of a plugin like posthtml-css-modules?

I’m a bit amazed/disappointed that JavaScript is not involved. Could we somehow use a component with an inline template, import styles using import statements and apply them to the template?

Hi @LayZee, your welcome. I am happy to know that this was helpul for you :tada:

About using inline template I saw a repo where they used it like that, however, when I try did the same approach it doesn’t work for me. This is the repo I mentioned you: http://joaogarin.github.io/css-modules-angular2/

Then, the other issue I had when I did this solution is how to handle ngClass, in that case, I resolved it using lodash like is proposed here: https://stackoverflow.com/questions/60512235/use-hashbase645-in-javascript-typescript-file

Because of that, I created a local custom loader (I named it html-css-modules-replacer.js) in my project to handle the ngClass with that approach:

var fs = require('fs');
var lodash = require('lodash');
var loaderUtils = require('loader-utils');

module.exports = function(source) {
    var options = loaderUtils.getOptions(this); // loader options
    var newTemplate = source; // Default response current html file
    try {
        // path of the .html that we are going to use to get .json file.
        var pathJsonFile = options.file.replace('.html','.scss.json');
        if (fs.existsSync(pathJsonFile)) {

            // Read Json mapped classes names with hashed names.
            var jsonFile = JSON.parse(fs.readFileSync(pathJsonFile).toString());

            // Function to lookup hashed class names
            var getHashedClass = function(unhashedClass) {
                return jsonFile[unhashedClass];
            }

            // Use lodash to template it, passing the class lookup function
            var compiled = lodash.template(source);
            newTemplate = compiled({
                getHashedClass: getHashedClass
            });
        }
    }
    catch(error) {
        console.log(`Fail on file: ${options.file} \n Error Details: + ${error}`);
    }
    finally {
        return newTemplate;
    }
}

Then in the html rules I had to add my custom loader:

{
    test: /\.html$/,
    use: [
        { loader: 'raw-loader' },
        {
            loader: path.resolve('scripts/loaders/html-css-modules-replacer.js'),
            options: {
                file: info.resource
            }
        },
        {
            loader: 'posthtml-loader',
            options: {
                config: {
                    path: './',
                    ctx: {
                        include: { ...options },
                        content: { ...options }
                    }
                },
            }
        },
    ]
},

And that is it. We that we support ngClass with Css Modules.

After I get to know how to apply CSS Modules in a Custom Angular Library build, I will write a article with all this solution and improve the solution in the repo (for example adding the support to ngClass).

1 Like

Hi @gquinteros93, nice to see you again.
Before I get into ng-packagr and why it’s so complicated to do what you want, may I ask one question?
What is it exactly you’re trying to achieve and why ViewEncapsulation is not a good option for you?

Hi @jeb , nice to see you again too.

Sure, I will explain to you why.
I am working in an Angular Project that is deployed as a web component. That web component is used/embedded in thirds party web sites.

Some thirds party website uses css global selectors in a bad way causing overriding in our web component.
One way we see to prevent the css overriding that work for us is using CSS Modules.

Also, when there are multiples CSS rules for the same element we know that it will win the rule with more specific selectors. Because of that, we can use the postcss-prefix-selector in order to make our class name value has more specific selectors.

Example:

Before Deploy 1:

<div class="grid-container">
</div>

After Deploy 1:

<div class="german-prefix grid-container">
</div>

Additionally, we can hash it in order to prevent having a conflict with other CSS rule of an external site:

<div class="_2q1pPTls NF9ou4CX">
</div>

Finally, we can add a different prefix selector every time we make a new deployment and then hash the class name + the prefix, in order can prevent (or at least make it more difficult) the web component be automated by external software.

About ViewEncapsulation it wasn’t enough to prevent CSS overriding.

On resume, the web component is already Obfuscated thanks to CSS Modules. However, the web component is a custom library of components that we would like to implement CSS Modules in order to prevent CSS overrides.

An example, is that a form that should seem like this:

In a third party web site is view like this because of css overriding:

(Sorry I added the second image in other response, but the system only allows me to upload one image for response).

Other solution that I thought, it’s make all the styles of the Angular library global, in order to use scss-bundle + postcss-modules to hash them all, like it I did in the response of my own question :sweat_smile: :

However, but I can figure out yet, is how to replace the class names in the HTML’s files. I thought perhaps using lodash like I did before to change the values in the ngClass but instead of applying to only the ngCass doing for all the class names. But. I didn’t figure out how to modify the HTML’s files during or after the build.

@gquinteros93 I can feel your pain but this is exactly what ViewEncapsulation is designed for - to encapsulate components from each other and global styles.
Essentially adding a hash to class names is exactly what Angular Compiler does under the hood. I wonder why it wasn’t enough for you. Could you please elaborate more on that?

1 Like

Hi @jeb , thanks for your response.

Yes, I use ViewEncapsulation on my components, however it’s not enough to prevent the css overrides.

The issue is that some third party websites use class names as selector to set css rules.
I want to prevent that through hashing my classes names. Because of that, I need to use postcss-modules and posthtml-css-modules.

As the images of the form I showed you before, the ViewEncapsulation wasn’t enough to prevent the third party website add a gray frame to my inputs in the contact form.

@gquinteros93 Got you. Have you tried ViewEncapsulation.ShadowDom? It might give you what you want.

1 Like

Keep in might though it’s not available in all the browsers (had to post it as a separate comments since I’m a new user and not allowed more than 2 links in the post :man_facepalming:)

hi @jeb

Yes, ViewEncapsulation.ShadowDom was an option that we handle.

But, like you said it’s not available for all the Browser, because of that I used the CSS modules approach.

My mistake/error was thinking that I would be able to extend the Angular Library build like I did in the Angular Project. I didn’t know that ng-packagr is not able to extend.

There is a discussion to extend/modify template and styles compilation in ng-packagr :

But, the PR is still open and they didn’t merge it yet.

Good afternoon, @m.koretskyi, @LayZee and @jeb.

Did you have the opportunity to see if there is a possible solution to modify the scss and html files during the compilation of an angular library?

In my case, I researched and I find a possible solution that are developing in ng-packagr:


It seems to be ready, however, they have a pull request open from Mar 5, 2018 and they didn’t merge it yet.

Thanks in advance for your time and effort.

Regards,
German.

Could it help you to use ng-packagr API in a script? See comment by Alan Agius: