Issue
TL;DR
I'm importing a css file into a typescript module, but the import resolves to a string instead of an object. Can anyone tell me why I don't get an object??
Example
// 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);
Detailed explanation
Actually, I'm trying to set up a Storybook for my Angular12 component library.
In order to provide various themes, I want to use the @etchteam/storybook-addon-css-variables-theme
plugin, which in its documentation refers to the inline loader syntax of Webpack.
import myTheme from '!!style-loader?injectType=lazyStyleTag!css-loader!./assets/my-theme.css';
When applying this to my code my browser console started to complain
Error: myTheme.use is not a function
During my research I recognized that the imported stylesheet is not an evaluated javascript object, but instead it is provided as a string containing the sourcecode generated by the style-loader.
I also recognized, that this issue is not specific to the style-loader, but also occurs for all other loaders, e.g. css-loader, raw-loader, etc.
This issue is also not related to inline loader syntax, as it also shows up with loaders being defined in a minimalistic webpack config.
Environment:
- Angular 12
- Webpack 5
Reproduction
I have set up a GIT repo reproducing the issue.
The readme file explains the repro and the issue.
Solution
Finally I got my issue solved!
Analysis
The main issue here is the webpack configuration generated by the @angular-devkit/build-angular
package. I was able to analyze it by debugging a fresh angular12 application (you can check it out here).
By setting a break-point at /node_modules/@angular-devkit/build-angular/src/utils/webpack-browser-config.js
, function: generateWebpackConfig(...)
, I could inspect the final webpackConfig
object in the debugger.
The relevant rule looks like this:
The important part here is the rule setting the module type to
asset/source
, instructing webpack not to evaluate the loader's result.
Solution concept 1: inline loader
With the help of alexander-kait and his great hints at this issue, I was able to find an inline-loader syntax that overrides 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'm not really sure about the url pattern here, as it seems to be an undocumented feature, but I assume that it's something like <query-pattern>.webpack[<module-type>]!=!<loaders><query>
.
However, since this is an undocumented feature, I was rather reluctant to use it.
Solution concept 2: webpackConfig customization
Since I'm in a storybook context, I decided to customize the webpack configuration according to the storybook documentation.
My solution requires to set up 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 rules in place, I can now simply do 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 is just pseudocode merely there for the example. In reality I'm using the @etchteam/storybook-addon-css-variables-theme addon...
Answered By - Markus Windhager
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.