Justin Morgan
Justin Morgan

Reputation: 30700

Programmatically import a CSS file by its relative file path

Background

I'm hosting legacy React components in an Angular app using a method similar to this one. There are lots of React components to bring in. They all import their own CSS with JS module imports:

import "./Admin.css";

This means every component is responsible for importing its own stylesheet, which is great. If I could just get the compiler to be happy with this like it was in the React app, that would be the best possible outcome.

The problem

The problem is this doesn't work when the React component is hosted in Angular. Importing a CSS file this way breaks compilation:

X [ERROR] Could not resolve "./Admin.css"

src/app/components/react/Admin/Admin.tsx:3:7:
  3 │ import "./Admin.css";
    ╵        ~~~~~~~~~~~~~

Instead, I have to use CSS imports to include the React component's stylesheet in the .scss file of the Angular host:

// Inside AdminHost.component.scss
@import '../../react/Admin/Admin';

This comes with a much bigger problem: Now that each React component isn't responsible for its own styles, I have to dig through all of Admin.tsx's child components and manually import all their stylesheets as above. Then their children, and so on. Even if a component is the child of several parents, I have to repeat this process every time unless I happen to remember all the stylesheets it needs. There are a lot of components in the project, so it's a ton of tree-walking.

Attempt 1

I tried programmatically importing the CSS files with a helper function:

// sanitizer is injected by the Angular host
export function importCss(cssUrl: string, sanitizer: DomSanitizer) {
  const head = document.getElementsByTagName('head')[0];
  const style = document.createElement('link');
  style.rel = 'stylesheet';
  style.href = sanitizer.sanitize(SecurityContext.URL, cssUrl);
  head.appendChild(style);
}

// inside Admin.tsx:
importCss("path/to/Admin.css");

You may have already spotted the problem: cssUrl is a URL, not a relative file path. This means I can no longer import "./Admin.css". I have to use a URL, and that means Admin.css has to live in a static assets folder. I explored this a little before deciding it wouldn't save me any work.

Attempt 2

My current approach is to use a convention. Whenever I bring over a React component, I use the following rules:

  1. The stylesheet of the base React component gets imported into the Angular host's .scss.
  2. Each React component should have a .scss file of the same name in the same folder.
  3. If a component has children, it should import its immediate children's stylesheets into its own .scss. Each level is responsible for providing its own styles and importing the next level.
  4. The React stylesheets are all .css files. Whenever I complete this process for a component and its entire descendant tree, I rename the stylesheet to .scss. This way, if some other component includes it as a child, I know I can just import that one sheet and don't have to dig through all its descendants looking for styles.

The advantage here is it saves a lot of repeated effort. The problem is it doesn't save enough. There's still a lot of pain and manual tree-walking. I'm hoping there's a better way.

Ideas

  1. In a perfect world, there's a way to make Angular's compiler recognize the import "./Admin.css" line and play nicely with it. All my attempts to do this have failed. (I should say I'm just assuming this is from Angular's compiler, since it worked in the React app.)
  2. I could use a Bash script to dump all the style files in a static assets folder. That would make the idea from Attempt 1 pretty doable. The downside is they wouldn't be as easy to find when you're working on a component, and since some have duplicate names, the proper way to do it would be to recreate the same folder structure. That seems a little awkward, and it's pushing the limits of my Bash skills.
  3. There might be a way to import the CSS file as a blob, turn it into a data URL, and use that in a link tag. Then Attempt 1 would work. I don't know how to do that, though.

Upvotes: 3

Views: 356

Answers (2)

Mukilan
Mukilan

Reputation: 96

We use your 3rd point from 'Ideas' to set CSS from API responses;

  1. Return data as new Buffer.from('yourCssHere').toString('base64')
  2. Add data:text/css;base64, to your converted base64 buffer as data:text/css;base64,yourBase64Here.
  3. In front end a function to create a style link
    getLink(){
      const linkEl = document.createElement('link');
      linkEl.setAttribute('rel', 'stylesheet');
      document.head.appendChild(linkEl);
    }
    
  4. Then
    setStyle(dataFromApi) {
      getLink().setAttribute('href',dataFromApi)
    }
    

This sets your CSS from API response.

Hope this Helps!!!

Upvotes: 0

Ian Carter
Ian Carter

Reputation: 2166

in case you are using webpack, you should be able to make relative imports work like so:

1 ) npm install css-loader style-loader --save-dev

2 ) register css in webpack.config.js (or webpack.partial.js with builders)

module.exports = {
    module: {
        rules: [
            {
                test: /\.css$/,
                use: ['style-loader', 'css-loader'], // Ensures CSS is bundled
            },
        ],
    },
};

3 ) enable custom webpack config in angular.json

"architect": {
    "build": {
        "builder": "@angular-builders/custom-webpack:browser",
        "options": {
            "customWebpackConfig": {
                "path": "./webpack.partial.js"
            }
        }
    }
}

Upvotes: 0

Related Questions