Mini-css-extract-plugin: Compatibility with AngularCompilerPlugin

Created on 21 Jun 2018  Β·  33Comments  Β·  Source: webpack-contrib/mini-css-extract-plugin

I have an Angular 6 project where I want to use the mini-css-extract-plugin. Here is the relevant part of the webpack configuration:

module: {
    rules: [
        {
            test: /(?:\.ngfactory\.js|\.ngstyle\.js|\.ts)$/,
            loader: "@ngtools/webpack"
        },
        { test: /\.html$/, loader: "raw-loader" },
        {
            test: /\.(png|gif|jpe?g|woff|woff2|eot|ttf|svg)$/,
            loader: "url-loader?limit=100000"
        },
        {
            test: /\.css$/,
            use: [
                MiniCssExtractPlugin.loader,
                "css-loader"
            ]
        }
    ]
},
plugins: [
    new HtmlWebpackPlugin({
        template: __dirname + "/src/index.html",
        output: __dirname + "/dist",
        inject: "head",
        hash: true
    }),
    new MiniCssExtractPlugin({
        filename: "[name].css"
    }),
    new OptimizeCSSAssetsPlugin({
        assetNameRegExp: /\.optimize\.css$/g,
        cssProcessor: require("cssnano"),
        cssProcessorOptions: { discardComments: { removeAll: true } },
        canPrint: true
    }),
    new UglifyJsPlugin({
        parallel: 4,
        sourceMap: true
    }),
    new AngularCompilerPlugin({
        tsConfigPath: "./tsconfig.json",
        entryModule: "src/app/app.module#AppModule",
        skipCodeGeneration: false
    })
]

Running webpack will fail with the following message:

ERROR in Module build failed (from ./node_modules/mini-css-extract-plugin/dist/loader.js):
TypeError: Cannot read property 'replace' of undefined
at normalizeBackSlashDirection (C:\node_modules\webpack\lib\RequestShortener.js:16:16)
at new RequestShortener (C:\node_modules\webpack\lib\RequestShortener.js:26:15)
at Compiler (C:\node_modules\webpack\lib\Compiler.js:137:27)
at Compiler.createChildCompiler (C:\node_modules\webpack\lib\Compiler.js:378:25)
at Compilation.createChildCompiler (C:\node_modules\webpack\lib\Compilation.js:1892:24)
at Object.pitch (C:\node_modulesmini-css-extract-plugin\dist\loader.js:77:43)

However, if I change the "skipCodeGeneration" property to true, no error is thrown and everything works normally.

Is this a bug?

question

Most helpful comment

Compatibility with AngularCompilerPlugin would be great!

All 33 comments

It seems that this issue occurs specifically when "skipCodeGeneration" is set to false and an Angular component has its "styleUrls" property set to an array of style sheet URLs.

@sv-uml what version webpack your use?

It's webpack 4.12.0. All packages are currently at the latest version.

@sv-uml can you create minimum reproducible test repo?

@evilebottnawi I am unable to create a repository right now, but I have put test code here: https://gist.github.com/sv-uml/dec79e2402e467e027abcceba9e675a6

@evilebottnawi Any luck with the issue?

@sv-uml in todo list, feel free to investigate

I'm not able to use mini-css-extract-plugin with AngularCompilerPlugin (i.e., I'm not able to set skipCodeGeneration to true). I have to skip AOT compilation by omitting AngularCompilerPlugin.

As a workaround you can simply import './component.css' at the top of your component class and omit styleUrls option altogether. This way you loose ViewEncapsulation feature, but i beleive you can replace it with your custom css-modules configuration using post-css if you really need it.

But of course it would be great if this issue got fixed so we could use this plugin with AngularCompilerPlugin the recommended way.

Compatibility with AngularCompilerPlugin would be great!

@pshurygin
Thanks, that suggestion has been working well for me in the meantime. πŸ‘

So there is no way to extract CSS when using Angular 6 + Webpack 4 (required) at this point?

Not using styleUrls isn't going to be acceptable for any serious Angular project.

@pshurygin your suggestion now conflict with "conflicting order" warnings (see issue #250).

Well I have a pretty large project(100K LOC) using this approach and there are no warnings with mini-css-extract plugin 0.4.2. I guess it is a matter of your code organization and imports structure.

@pshurygin Could you give a code example of this sentence of yours:

import './component.css' at the top of your component class and omit styleUrls

And an organization/structure example of this sentence:

your code organization and imports structure

We import css file on top of the component it belongs to and nowhere else. Also, we import components only once: either in shared module, or a feature module. So i guess with this pattern you cant get into that issue.

@asidelnik
In practice (as I was able to get it working)

Before

import { Component } from '@angular/core';

@Component({
  selector: 'my-component',
  templateUrl: './my.component.html',
  styleUrls: ['./my.component.scss']
})

export class AppComponent { }

After

import { Component, ViewEncapsulation } from '@angular/core';
import './app.component.scss';

@Component({
  selector: 'my-component',
  templateUrl: './my.component.html',
  encapsulation: ViewEncapsulation.None
})

export class AppComponent { }

I think I have this working. The way I look at it is, Angular's view encapsulation means that your component styles are part of the component definition, not part of the page style. This means that you don't want any Webpack plugins (other than AngularCompiler) to touch it. So, I did this:

rules: { ..., { test: /\.css$/, include: resolve(__dirname, "src", "app"), use: "raw-loader" }, { test: /\.css$/, exclude: resolve(__dirname, "src", "app"), use : [CssExtractPlugin.loader, "css-loader"] }, ..., }

My page styles all live in src but not under app, while all my components are defined in directories under app. You could of course set your include/exclude rules up differently. I don't have a sample repo demo to share, but this is more or less how my project has worked since I first switched to Webpack 4, and it seems to work well.

Thanks for sharing @thw0rted.

The way I look at it is, Angular's view encapsulation means that your component styles are part of the component definition, not part of the page style.

Yup. I understood ViewEncapsulation to be Angular's API for exposing Shadow DOM to developers for use with their components, and so in this way, the styles are part of the component's Shadow DOM, which actually lives in the DOM itself (inline CSS) and not loaded via a <script> tag. (_This just my understanding mind you_)

My only concern around using Shadow DOM is what are the long term caching implications (no file, no caching), and what if the same <table-cell> component is used 1000x on a page? Is that 1000x inline styles? As opposed to one bundled / optimized CSS file that contains the single table-cell class definition and needed styles (code)? πŸ€”

Anyway, I would like to use ViewEncapsulation, but haven't had time to compare against the more traditional approaches and how to best manage CSS in a web component world.

I also don't have a super firm grip of how encapsulation works, but my understanding was that under the hood, component styles have an additional attribute attached to them, which uses a generated ID to uniquely identify component instances and limit application. That is, the rules come out looking like #mainMenuButton[_ngcontent-c0] { ... }. At runtime (maybe at template-compile time?), the element in my component with id=mainMenuButton has an attribute _ngcontent-c0="" attached in the DOM. This way, the style rule only exists in one place, but is applied in a scoped manner.

And I'm pretty sure the rule does exist in one place, for what it's worth. I have a "debug" Webpack build that generally tries to leave the Angular compiler's output as un-messed-with as possible, but otherwise pulls everything into a small number of files -- this makes it easy to deploy but also easy to read when the browser has problems with sourcemaps. I specify my "top level" CSS (everything outside src/app) as an entry point, then thanks to mini-css-extract-plugin, the raw-loader rule above means these CSS files get treated as "plain text", then handed off to the CSS extractor which bundles them all into main.css. Meanwhile, the raw Angular component CSS winds up in main.js as named Webpack modules (since I have named modules turned on for this build), one per component This named module is referenced in the component definition, e.g.

exports.AppComponent = AppComponent = __decorate([(0, _core.Component)({ selector: "app-root", template: __webpack_require__(/*! ./app.component.html */ "./src/app/app.component.html"), styles: [__webpack_require__(/*! ./app.component.css */ "./src/app/app.component.css")], providers: [_search.SearchService], animations: [_animations.GROW_DOWN_ANIMATION, _animations.SLIDE_FROM_LEFT_ANIMATION, _animations.SLIDE_FROM_RIGHT_ANIMATION] }), __metadata("design:paramtypes", [_settings.SettingsService])], AppComponent);

So, effectively the raw CSS (and apparently template HTML) strings are passed to the component decorator function and that component object is used to construct instances. This means that component CSS is not treated by webpack as CSS, but rather as a string literal -- which is exactly what I want! -- so it can't be minified, comment-stripped, etc.

That's a great write up, thank you @thw0rted , and now that you mention it, I do recall this being the behavior with Angular ("linked" classes). I think this is sort of like how CSS Modules work?

Definitely going to review your comment in depth, and hopefully see this supported in webpack and / or this plugin. πŸ‘

@thw0rted
So In your example you are only extracting "page" level styles and just using styleUrls for your components without extraction then? Does that mean your page level styles don't get import / @import ed anywhere?

Not sure if you could share an example of each, would be curious to try and solve this problem on my end. Starting to get CSS collisions now that all styles have moved into a "global" namespace (single file) and where our specificity is loose / weak. Not sure what options there are (CSS Modules, CSS-in-JS) is worth it as a stop gap measure to get component level CSS scoping too? (with or without extract I suppose, I would just prefer whatever makes CSS more modular / portable)

I have the same issue if I do: ng build --prod --build-optimizer it fails with the same error as described in this issue, I tried also @thescientist13 solution but it still fails

└─┬ @angular-devkit/[email protected] └── [email protected]

└─┬ @angular-devkit/[email protected] └── [email protected]

"@angular/compiler": "6.1.10", "@angular/core": "6.1.10",

@thescientist13 That's the idea, and I include main.css (with all the page-level style) as an entry point. The end result is that my vendor styles and page-level styles are extracted into one blob that gets injected into the page by html-webpack-plugin. What did you want to see an example of?

I faced with the same problem. Any news?

@zaikin-andrew it is open source, PR welcome

I solved this by excluding .ts issuer:

 {
            test: /\.(scss|sass|css)$/,
            issuer: {
                exclude: /\.ts$/
            },
            use: [{
                loader: MiniCssExtractPlugin.loader
            }, {
                loader: 'css-loader',
                options: {importLoaders: 2, sourceMap: isDev}
            }]
        }, {
            test: /\.(scss|sass|css)$/,

            use: [{
                loader: 'postcss-loader',
                options: {
                    ident: 'postcss',
                    sourceMap: isDev,
                    plugins: () => [
                        postcssPresetEnv(),
                        ...(!isDev
                            ? [cssnano({preset: ['default', {discardComments: {removeAll: true}}]})]
                            : [])
                    ]
                }
            }, {
                loader: 'sass-loader',
                options: {
                    sourceMap: isDev,
                }
            }]
        }, 

My bad. I should read the comments accurately. πŸ˜€
image

@zaikin-andrew what is the helpers.root in this case, can you share the whole code snippet please

Closed because not related to mini-css-extract-plugin, anyway if you still think we have bugs in code, please open new issue with reproducible test repo thanks

@ViieeS Doesn't work, I am getting the following error;

ERROR in Module parse failed: Unexpected token (1:0) You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders

i have the same issue after installing https://github.com/swimlane/ngx-charts
and only in combination with the ngToolsWebpack.AngularCompilerPlugin for aot (is working outside aot)

TypeError: Cannot read property 'replace' of undefined

angular 8

{
                test: /\.css/,
                use: [
                    MiniCssExtractPlugin.loader,
                    'css-loader',
                ],
            },

all the other solutions above didn't work, it needs to be checked against the package causing it

the same bug (in mini-css-extract-plugin) while running aot build.
In dev mode (jit compiler) it works fine

Was this page helpful?
0 / 5 - 0 ratings