ES6 import of CSS file results in string output instead of object

Too long; Didn't read

I'm facing an issue where the css file I import into a typescript module resolves to a string instead of an object. Can someone help me understand why this is happening?

For Instance

// preview.ts

import test from './src/assets/test.theme.css';

// also tried this:
// import * as test from './src/assets/test.theme.css';

console.log('typeof test: ', typeof test);
console.log(test);

What's displayed on the console: https://i.stack.imgur.com/j30hP.png

In-depth Explanation

Currently, I am setting up a Storybook for my Angular12 component library.

To provide different themes, I intend to utilize the

@etchteam/storybook-addon-css-variables-theme
plugin, which mentions the inline loader syntax of Webpack in its documentation.

import myTheme from '!!style-loader?injectType=lazyStyleTag!css-loader!./assets/my-theme.css';

Upon implementing this in my code, my browser console started throwing errors

Error: myTheme.use is not a function

While investigating, I discovered that the imported stylesheet isn't an evaluated javascript object but rather a string containing the source code created by the style-loader.

I also found that this issue extends beyond the style-loader and applies to other loaders like css-loader, raw-loader, etc.

This problem isn't limited to the inline loader syntax and also occurs with loaders defined in a basic webpack configuration.

Environment details:

  • Angular 12
  • Webpack 5

Replication

I have created a GIT repository demonstrating the problem.

The readme file explains how to reproduce the issue.

Answer №1

Hooray! The problem has been resolved!

Investigation

The root issue lies within the webpack configuration generated by the @angular-devkit/build-angular package. I delved into this by debugging a new angular12 application (you can view it here).

By setting a break-point at

/node_modules/@angular-devkit/build-angular/src/utils/webpack-browser-config.js
, function: generateWebpackConfig(...), I was able to examine the final webpackConfig object in the debugger.

The relevant rule appears as follows:

The crucial part is the rule that defines the module type as asset/source, directing webpack not to assess the loader's result.

https://i.stack.imgur.com/6g0ij.png

Resolution Approach 1: inline loader

With insights from alexander-kait and his valuable suggestions on this issue, I discovered an inline-loader syntax that supersedes webpack's module declaration:

import Test from 'test.css.webpack[javascript/auto]!=!!!style-loader?injectType=lazyStyleTag!css-loader!./test.css';

console.log(typeof Test); // output: object
console.log(Test); // output: Object { use: () => void, unuse: () => void }
Test.use(); // this should usually be called by a theme switcher...

I am unsure about the url pattern here, as it seems to be an undocumented feature, but my assumption is that it resembles

<query-pattern>.webpack[<module-type>]!=!<loaders><query>
.

Yet, due to its undocumented nature, I displayed caution in utilizing it.

Resolution Approach 2: webpackConfig customization

Given the context of storybook, I opted to customize the webpack configuration following the guidance in the storybook documentation.

My solution necessitates establishing a naming convention (e.g. *.theme.css).

// .storybook/main.js

module.exports = {
  webpackFinal: async (config) => {

    // exclude *.theme.css from the *.css ruleset
    config.module.rules.find(rule => '.css'.match(rule.test)).exclude = [ /\.(?:theme\.css)$/i ];

    // add a rule for *.theme.css
    config.module.rules.push({
      test: /\.(?:theme\.css)$/i,
      use: [
        { loader: 'style-loader', options: { injectType: 'lazyStyleTag' } },
        'css-loader',
      ],
    });
  },
};

With these directives in place, I can now easily execute the following:

// preview.js

import LightTheme from './light.theme.css';
import DarkTheme from './dark.theme.css';

setupThemeSwitcher(LightTheme, DarkTheme);

Please note that the setupThemeSwitcher function serves as pseudo code for illustration purposes only. In practice, I utilize the @etchteam/storybook-addon-css-variables-theme addon...

Answer №3

Apologies, I need to retract my previous statement. @Akxe's comment did not resolve my issue as I had hoped. Even after using the import statement import * as test from '...', the problem still persists.

To showcase the issue, I have created a GIT Repository. The repro and problem are detailed in the included readme.md file.

It appears that Webpack is failing to properly interpret the output of the loader.

This issue extends beyond just the css-loader; similar results happen with raw-loader, sass-loader, style-loader, and others.

My ultimate objective is to dynamically load theme files into a storybook environment. I am attempting to follow the instructions provided by @etchteam/storybook-addon-css-variables-> theme.

Answer №4

Dealing with storybook and this particular extension presented me with a similar challenge, although I am loading .scss files. I customized solution 2 to fit my specific .scss scenario, and it ended up working perfectly. Solution 1 proved to be elusive for me, but like you mentioned, it seemed hacky compared to the cleaner approach of solution 2 in my view. I truly appreciate you sharing this solution, as I had been struggling for hours before finding it.

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

Issue with the navbar toggler not displaying the list items

When the screen is minimized, the toggle button appears. However, clicking it does not reveal the contents of the navbar on a small screen. I have tried loading jQuery before the bootstrap JS file as suggested by many, but it still doesn't work. Can s ...

Padding-left may cause text to not wrap correctly

Every link in the left menu has a padding-left: 15px;. This is done to accommodate a background image (the blue arrow). However, when text wraps (like in "Paintings, prints, and watercolours"), it seems to ignore the padding. I've searched extensive ...

Experimenting with the routerLink directive in Angular 2

Currently, I am in the process of testing routing functionality. As part of this, I have moved my navbar to a separate component called MdNavbar, which primarily consists of HTML and CSS. The RouteConfig is located in another component where MdNavbar is in ...

Preserve StepContent information within Material-ui Stepper during updates to the active step

I have a Stepper component with multiple steps, each containing several TextFields. The issue is that material-ui unmounts the step contents when you switch steps, causing all the data in the TextFields to be lost. My question is: Is there a way to prese ...

Creating a table and populating its cells with values all within the confines of a single function

This section of code aims to create 3 arrays by extracting values inputted by the user from a popup menu in the HTML file. These values are then utilized to populate the table displayed below. var arrM = new Array; var arrT = new Array; var ar ...

Retrieve information according to the object ID with AngularJS UI-Router and $stateParams

I have a unique application that directs to a custom URL based on a specific employee ID parameter when an individual employee is clicked in a list. Upon clicking the employee, users are redirected to a detailed employee page with their ID property as a pa ...

Error: Unexpected token '<' encountered in Vue causing SyntaxErrorParsed: the parser expected an expression but found '<' instead

While working on my Vue project in my local environment (running the npm run dev command), I encountered an issue. When the first page loads, there are no errors. However, upon hitting the refresh button, the console displays a "SyntaxError: expected expre ...

Angular 8 experiencing unexpected collision issues

Currently, I am utilizing Angular 8 with "typescript": "~3.5.3". My objective is to handle the undefined collision in my code. const { testLocation } = this.ngr.getState(); this.step2 = testLocation && testLocation.step2 ? testLocat ...

Converting the structure of an object into an array structure for highcharts can be achieved through

I am looking to transform the object structure below: [ { "tahun": "2010", "apel": 100, "pisang": 200, "anggur": 300, "nanas": 400, "melon": 500 }, { ...

Changing colors using JavaScript: A step-by-step guide

Hey there! I'm looking to change the color code in this script from $("#Tcounter").css("color","black") which uses the color word "black", to "#317D29". Can someone help me figure out how to do this? <script type="text/javascript"> $(document). ...

Is there a way to arrange my bootstrap navigation tabs vertically on my mobile device?

I have a group of five bootstrap navigation tabs that are evenly spaced and visually appealing on desktop screens. However, when viewed on a mobile device, the text within the tabs becomes cramped together. How can I make the tabs stack vertically on mobil ...

Vue.js has encountered a situation where the maximum call stack size has been exceeded

I have implemented a method called cartTotal that calculates the total price of my products along with any discounts applied, and I am trying to obtain the final value by subtracting the discount from the total. cartTotal() { var total = 0; var di ...

conceal a division beneath a YouTube video frame upon clicking

I need to hide the 'div .blind' element when a YouTube video (inside 'div #player') is clicked. How can I achieve this? Here's an example: JS: ... var player; function onYouTubeIframeAPIReady() { player = new YT.Player('pl ...

Extracting content from a concealed frame and displaying it on a visible frame via javascript

Is there a method to extract a specific DIV element from an HTML frame and display it in a visible frame using JavaScript? For instance, if I create a hidden frame containing Google search results, is there a way to show only the search results on the vis ...

"Navigate back to a previous page in Vue Router without having to

I am currently exploring the option of creating a back button in my Vue.js application using vue-router that mimics the behavior of the browser's native back button. The challenge I'm facing is that when using history mode for the router and tryi ...

Implementing a click event on a button within an infowindow on Google Maps in Angular

I've successfully set up the infowindow on my Google Maps and I'm looking to include a button inside it. Here's the code I used: InitializeMap() { this.map = new google.maps.Map(document.getElementById('map'), { zoom: 10, cen ...

The TransferList component in Material UI does not update its state based on props when using the useState

My TransferList component in Material UI receives an array of previously selected items as props.selectedItems. The props.Items contains all available items. I expected to see props.selectedItems in the left panel of TransferList, but the left panel is em ...

What is the best way to load a component every time the function is called?

Currently, I am utilizing Angular and endeavoring to create reusable actions such as bulk updates, deletes, and deactivations. I have incorporated all of these actions into another component and aim to use it as a generic method. This implies that I have ...

Modifying attributes of an object within a document using Mongoose

Encountering an issue where the sentiment object in my document is not updating. Within my Model Class, there's a field named sentiment of type Object structured like this: sentiment: { terrible: 0, bad: 0, okay: 0, good: 0, fantastic: 0 } ...

Mocking objects in unit test cases to simulate real-life scenarios and test the

How do I pass a mocked event object and ensure it is validated? onCallFunction() { const eventValue = event; if (!eventValue.relatedTarget || !eventValue.relatedTarget.last.contain('value')) { super.onCallFuncti ...