Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ignore Angular2 component stylesheets with ExtractTextPlugin in webpack

I am using the html-webpack-plugin plugin to inject <script> tags into a template file for the chunks that webpack generates. I also have a few global stylesheets that I want to dynamically add to the <head> section of my HTML template. For that, I am also using the html-webpack-plugin as follows (the app entry/chunk is the relevant one in this case).

module.exports = {
    entry: {
        'polyfills': './ts/polyfills.ts',
        'vendor': './ts/vendor.ts',
        'app': './ts/app.ts'
    },

    module: {
        loaders: [
            {
                test: /\.ts$/,
                loaders: ['ts', 'angular2-template-loader']
            },
            {
                test: /\.html$/,
                loader: 'html-loader'
            },
            {
                test: /\.css$/,
                loaders: ['to-string-loader', ExtractTextPlugin.extract('style-loader', 'css-loader')]
            }
        ]
    },

    plugins: [
        new ExtractTextPlugin('css/[name].[contenthash].css'),

        new webpack.ProvidePlugin({
            bootstrap: 'bootstrap'
        }),

        new webpack.optimize.CommonsChunkPlugin({
            name: 'app',
            filename: 'js/[name].[chunkhash].min.js',
            chunks: ['app']
        }),

        new webpack.optimize.CommonsChunkPlugin({
            name: 'vendor',
            filename: 'js/[name].[chunkhash].min.js',
            chunks: ['vendor']
        }),

        new webpack.optimize.CommonsChunkPlugin({
            name: 'polyfills',
            filename: 'js/[name].[chunkhash].min.js',
            chunks: ['polyfills']
        }),

        new HtmlWebpackPlugin({
            template: 'my-template.html',
            filename: 'my-template.html',
            inject: 'body',
            hash: false
        }),
    ]
};

Below is the app.ts file, where I have added a require statement for each stylesheet that I would like to include in my template (they are being merged together into a single stylesheet).

require('main.css');
require('misc.css');

import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app.module';

platformBrowserDynamic().bootstrapModule(AppModule);

Essentially this works well, but the catch is that I am using this configuration for an Angular2 application, which is composed of components which may have associated stylesheets, like in this example.

@Component({
    styleUrls: [
        'my-stylesheet.css'
    ]
})
export class MyComponent { ... }

Building the application would normally result in a ton of HTTP calls for these stylesheets for the components, so to solve this, I have pulled the stylesheets directly into the components by using angular2-template-loader. This loader works as follows.

The angular2-template-loader searches for templateUrl and styleUrls declarations inside of the Angular 2 Component metadata and replaces the paths with the corresponding require statement.

The problem with this is that besides the two stylesheets that are added to the app.ts entrypoint, webpack also includes every stylesheet that is referenced from my Angular2 components. Because it goes through the dependencies of the application and finds the require statements added by angular2-template-loader. This is undesirable, because I want those stylesheets to be local to the components they belong to, i.e. be inline in the JS.

Therefore I need a way to only include the stylesheets that I require directly in my entrypoints, perhaps by somehow excluding the component stylesheets. The problem is that in both cases, the file have the .css extension, so they both pass the test for the extract-text-plugin. I took a look at the documentation of the loaders/plugins, but I couldn't find anything that would solve my problem.

So what I want is to add a app.[contenthash].css file to my template file (this part works), but excluding the stylesheets referenced in my Angular2 components (included because of angular2-template-loader).

How can I prevent these Angular2 component stylesheets from being included in the stylesheet that is added to my HTML template?

like image 338
ba0708 Avatar asked Oct 06 '16 19:10

ba0708


2 Answers

You could give webpack a hint to differentiate the CSS files by using two separate name patterns. Say you name all files that should be included by angular2-template-loader with suffix .tl (short for template loader, e.g. mycss.tl.css) and the other ones with suffix .pt (short for platform, e.g. main.pt.css), then you can write the webpack config as follows:

module: {
        loaders: [
            {
                test: /\.ts$/,
                loaders: ['ts', 'angular2-template-loader']
            },
            {
                test: /\.pt\.css$/,
                loaders: ['to-string-loader', ExtractTextPlugin.extract('style-loader', 'css-loader')]
            },
            {
                test: /\.tl\.css$/,
                loaders: ['style-loader', 'css-loader']
            }
        ]
    }

Your component definition then looks like

@Component({
    styleUrls: [
        'my-stylesheet.tl.css'
    ]
})
export class MyComponent { ... }

whereas in app.ts you place

require('main.pt.css');
require('misc.pt.css');

import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app.module';

platformBrowserDynamic().bootstrapModule(AppModule);

angular2-template-loader then still wraps the css paths with require calls but based on your config you can have a different set of loaders for the css files.

like image 45
dotcs Avatar answered Oct 18 '22 20:10

dotcs


Let's say you have the following folder structure:

enter image description here

So all my components are located inside app folder.

main.ts

require('main.css');
require('misc.css');

import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app.module';

platformBrowserDynamic().bootstrapModule(AppModule);

/app/app.component.ts

@Component({
  selector: 'my-app',
  template: `<h1>My Angular 2 App</h1>`,
  styleUrls: ['my-stylesheet.css']
})
export class AppComponent {}

Then your solution might look like:

webpack.config.js

{
  test: /\.css$/,
  exclude: /app\\.+\.css$/, <== add this (exclude all styles from app folder)
  loaders: ['to-string-loader', ExtractTextPlugin.extract('style-loader', 'css-loader')]
},
{
  test: /app\\.+\.css$/,
  loader: 'raw'  <== use raw loader for these files
}
like image 192
yurzui Avatar answered Oct 18 '22 21:10

yurzui