Enable webpack to dynamically load an extra or different css file based on certain conditions

In my current project using Angular 4 and webpack 3 for bundling, I am working on implementing a CSS theming feature. This project is designed to be used by multiple companies, each with their own brand guidelines that need to be reflected in the design. To achieve this, we require the ability to switch between different themes easily.

Instead of creating separate versions of code for each theme, we aim to keep all themes within the same codebase. The differences between themes will mainly involve changes to colors, icons, fonts, and other styling elements that can be adjusted through CSS.

I have considered various methods to implement theming, such as using :host-context for components and changing the body class based on environment variables in webpack. However, including every theme inside the bundle may not be the most efficient approach. I am exploring alternative options.

One idea I have been considering is having webpack dynamically load CSS files based on a certain pattern rather than loading specific files. For instance, when a component like button.component.ts imports button.component.css, webpack could check for a file named button.component.theme-name.css in the directory. If this file exists, it would be imported instead or alongside the default CSS file.

This concept extends beyond CSS files and could also apply to HTML templates in angular components. I am curious if there are plugins or advanced loader configurations that could facilitate this dynamic loading process.

If you have any insights or suggestions on how to approach this task differently, please share your thoughts in the comments!

Answer №1

My latest project involved developing a custom loader capable of appending or replacing the content of a loaded file with the content of another file bearing a specific theme title.

In summary...

  1. Create a file using the loader.
  2. Integrate it into your webpack configuration.
  3. Execute webpack in an environment specified by THEME=<themeName>.

theme-loader.js

const fs = require('fs');
const loaderUtils = require('loader-utils');

module.exports = function (mainData) {
  const options = loaderUtils.getOptions(this);
  let themeName = options.theme;
  let mode = options.mode;

  if (themeName) {
    // default mode
    if (!Object.keys(transform).includes(mode)) {
      mode = 'replace';
    }

    // Update the asset path to include the theme name
    const themeAssetPath = this.resourcePath.replace(/\.([^\.]*)$/, `.${themeName}.$1`);
    const callback = this.async();

    // For Hot Module Replacement to function properly
    this.addDependency(themeAssetPath);

    fs.readFile(themeAssetPath, 'utf8', (err, themeData) => {
      if (!err) {
        callback(null, transform[mode](mainData, themeData));
      } else if (err.code === 'ENOENT') {
        // No worries! If the file isn't found, no action is needed
        callback(null, mainData);
      } else {
        callback(err);
      }
    });
  } else {
    return mainData;
  }
};

const transform = {
  // Concatenate theme file with main file 
  concat: (mainData, themeData) => mainData + '\n' + themeData,
  // Replace main file with theme file
  replace: (mainData, themeData) => themeData
};

An excerpt from the webpack.config.js utilizing this custom loader:

resolveLoader: {
  modules: [
    paths.libs, // ./node_modules
    paths.config // custom loader directory
  ]
},

module: {
  rules: [
    // Component styles
    {
      test: /\.css$/,
      include: path.join(paths.src, 'app'),
      use: [
        'raw-loader',
        // Search for themed styles and include them in the main file if found
        {
          loader: 'theme-loader',
          options: {
            theme: process.env.THEME,
            mode: 'concat'
          }
        }
      ]
    },

    // Angular templates — Use themed version if available
    {
      test: /\.html$/,
      use: ['raw-loader',
        {
          loader: 'theme-loader',
          options: {
            theme: process.env.THEME,
            mode: 'replace'
          }
        }
      ]
    }
  ]
}

For instance, consider an app.component.css:

:host {
  background: #f0f0f0;
  color: #333333;

  padding: 1rem 2rem;

  display: flex;
  flex-direction: column;
  flex: 1;
  justify-content: center;
}

nav {
  /* ... */
  /* Styles for nav element */
  /* ... */
}

header {
  /* ... */
  /* Header styles */
  /* ... */
}

To implement a dark theme without altering existing styles extensively, we create app.component.dark.css:

:host {
  background: #222222;
  color: #e0e0e0;
}

We can run webpack with the environment variable THEME set to dark. The loader processes app.component.css, loads app.component.dark.css, and appends the themed styles at the end of the resulting file.

If multiple selectors have equal importance and specificity, later rules take precedence over earlier ones (MDN).

While CSS allows cascading changes based on selector priority, HTML requires manual template adjustments for theme variations. This may involve rewriting templates to align with desired branding requirements.

This endeavor represents my initial foray into creating a webpack loader. Feel free to share feedback if you encounter any issues.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Tips for removing a specific shape from an HTML element

After spending 48 hours immersed in the world of clip, clip-path, masking, and all things CSS-related, I find myself unable to crack the code on the following challenge: Imagine a red background filling the body of the page. Layered on top of this is a wh ...

Is it possible to effectively interpret raw data from an ionic Bluetooth module?

I am currently facing an issue where I am trying to read raw data from a device using Ionic Bluetooth Serial. The device sends 506 bytes per transmission to the app and waits for a response of "OK" before sending the next 506 bytes. However, there are ins ...

Encountered a Webpack issue when trying to load the primeng.min

I recently initiated a fresh project using yo aspnetcore-spa. My goal is to integrate the PrimeNG component library. Upon installing font-awesome and primeng: npm install font-awesome primeng --save I included CSS in the webpack vendor list: vendor: [ ...

Using a BehaviorSubject in conjunction with ngIf can rearrange the placement of elements

I am facing an issue with the placement of my HTML tags. Here is a snippet from my service: public showExportCsvModal = new BehaviorSubject<boolean>(false); public showDownloadModal = new BehaviorSubject<boolean>(false); And here is how it loo ...

Having difficulties generating ngc and tsc AOT ES5 compatible code

I've explored various options before seeking help here. I have an angular2 library that has been AOT compiled using ngc. Currently, I am not using webpack and solely relying on plain npm scripts. Below is the tsconfig file being utilized: { "comp ...

The mask feature is not functioning properly in Internet Explorer

Hello everyone, I'm attempting to blur the edges of an image from one side only using the mask property so that the background of the body tag blends with the image. Take a look at the example image below. I've tried implementing it as demonstra ...

What implications does dependency injection have for performance in Angular?

Our Angular 2/4 application is quite extensive, utilizing reactive forms with a multitude of form controls. I'm wondering about the impact of injecting a ChangeDetectorRef instance into approximately 200 form control components. Will there be a notic ...

Tips for applying CSS styles exclusively to the homepage

I am currently managing a SMF forum and I am facing an issue while trying to apply a background image exclusively to the homepage. Whenever I add a style to the .body class, it changes the background of not just the homepage but also other sections like ...

What is the best way to ensure the table header is aligned and maintained at the top of the table?

Currently tackling a virtualized table issue with an example. Any tips on aligning the table header correctly and making sure it stays at the top of the table when scrolling? Check out the code sandbox for reference:- https://codesandbox.io/s/material-ui ...

Arrangement of 3 columns on the left and 1 column on the right using flex-box layout

My goal is to utilize flexbox in conjunction with bootstrap 4 to produce a design featuring 3 vertically stacked columns on the left, accompanied by a single column on the right that matches the height of the left columns. Following that, there will be som ...

Shifting static information both above and below a 100vh element

At the moment, I have a stationary image in the center of my screen that moves horizontally when scrolling with the mouse. Now, I want to include a screen above and below this element, each with a height of 100vh. However, when I attempt to do so, the fixe ...

Ways to display JSON data in Angular 2

My goal is to display a list of JSON data, but I keep encountering an error message ERROR TypeError: Cannot read property 'title' of undefined. Interestingly, the console log shows that the JSON data is being printed. mydata.service.ts import { ...

PhantomJS does not recognize external CSS on the page

Currently, I have Phantom running on a Node server to generate pages from data and render them as PDFs. Despite the pages rendering correctly as PDFs, I am facing an issue where the external CSS file is not being taken into account. Below is a simplified ...

What is the trick to have a CSS element overflow the boundaries of its containing div entirely?

Currently, I have a JS Fiddle project where I am attempting to achieve a specific effect. In the project, there is a circle positioned at the center of a div. When the script runs, the circle expands evenly in all directions until it reaches the borders on ...

Steps for incorporating moment.js into an Angular 2 project

Having trouble importing moment.js into my angular2 application despite following various guides and solutions provided. Even though the package is present in my IDE (Visual Studio) and the moment.d.ts file is easily found, I keep encountering errors when ...

Activating a tab in Angular from a parent component

Hello there, I have created a products dashboard.html where I'm managing all my products. Below is the code snippet for the navigation tabs: <nav #nav> <div class="nav nav-tabs" id="nav-tab" role="tablist"> ...

Encountering the 'CORS policy has blocked' error message when attempting to upload a file

I encountered an issue while trying to implement a user interface for uploading an Excel file using Angular 8. The frontend and backend (Node.js) applications are running on different ports, and when I click the upload button, I am receiving errors. I att ...

I am having trouble retrieving edge labels asynchronously in jsplumb. When I subscribe to the observable to retrieve the labels of the edges, I am receiving undefined. Is there a solution to this issue

I need to retrieve data from the backend and use it as labels for the edges instead of +N. Can someone assist me in resolving this issue? Thank you in advance. Trying to asynchronously fetch jsplumb graph edge labels ...

Retrieve the value of a PHP array within a for loop and transfer it to JQuery

*edited format I came across a PHP code for a calendar on the internet and I am currently working on implementing an onclick event that captures the date selected by a user as a variable (which will be used later in a database query). At this point, my g ...

Is Angular sending strings through data binding?

As a beginner in angular, I have a query about sending a string to the Html file with a variable inside. Is there a way to achieve this? Here is an example of what I mean: test: string = "Display this {{testText}}"; testText: string = "Success"; In the ...