How to import CSS from node_modules in webpack angular2 app
Asked Answered
F

3

75

Let's say that we start with the following starter pack: https://github.com/angularclass/angular2-webpack-starter

After npm install and npm run start everything works fine.

I want to add an external css module, for example bootstrap 4's css (and only the css). (I know that bootstrap has a bootstrap-loader, but now I'm asking for general solution, so please think about bootstrap 4 here as it could be any other css module that is available via npm).

I install bootstrap via npm: npm install [email protected] --save

First I thought that it is enough to add import 'bootstrap/dist/css/bootstrap.css'; to the vendor.browser.ts file.

But it isn't.

What should I do to have a proper solution?

Solutions I'm NOT asking for:

  1. "Copy the external css module to the assets folder, and use it from there"
    • I'm looking for a solution that works together with npm package.
  2. "Use bootstrap-loader for webpack"
    • As I described above, I'm looking for a general solution, bootstrap is only an example here.
  3. "Use another stack"
    • I'm looking for a solution in the exact starter pack that I've mentioned above.
Foulup answered 16/10, 2016 at 15:12 Comment(2)
Are you getting any error?Cinquefoil
This is officially the best SO thread I've ever been on. Stellar question and two equally stellar answers. Thanks all.Cottonweed
N
83

You won't be able to import any css to your vendors file using that stack, without making some changes.

Why? Well because this line:

import 'bootstrap/dist/css/bootstrap.css';

It's only importing your css as string, when in reality what you want is your vendor css in a style tag. If you check config/webpack.commons.js you will find this rule:

 {
   test: /\.css$/,
   loaders: ['to-string-loader', 'css-loader']
 },

This rule allows your components to import the css files, basically this:

@Component({
  selector: 'app',
  encapsulation: ViewEncapsulation.None,
  styleUrls: [
    './app.component.css' // this why you import css as string
  ],

In the AppComponent there's no encapsulation, because of this line encapsulation: ViewEncapsulation.None, which means any css rules will be applied globally to your app. So you can import the bootstrap styles in your app component:

@Component({
  selector: 'app',
  encapsulation: ViewEncapsulation.None,
  styleUrls: [
    './app.component.css',
    '../../node_modules/bootstrap/dist/css/bootstrap.css'
  ],

But if you insist in importing to your vendor.ts then you will need to install a new loader, npm i style-loader --save-dev this will allow webpack to inject css to your page. Then you need to create a specific rule, on your webpack.common.js and change the existing one:

 { //this rule will only be used for any vendors
   test: /\.css$/,
   loaders: ['style-loader', 'css-loader'],
   include: [/node_modules/]
 },
 {
   test: /\.css$/,
   loaders: ['to-string-loader', 'css-loader'],
   exclude: [/node_modules/] //add this line so we ignore css coming from node_modules
 },

The firs rule will be only applied when you try to import css, from any package inside node_modules the second rule will be applied to any css that you import from outside the node_modules

Niacin answered 16/10, 2016 at 17:14 Comment(6)
Thanks dude, this is a kind of perfect answer I was looking for. Both solution works well. One thing that I didn't know is that it's possible to target node_modules from styleUrls. Is it also a preferred thing to do?Foulup
There's no right or wrong, either them are valid, but if in the future you plan on having your vendor.css in a separated file then, the last option is better.Niacin
note that the angular2-webpack-starter has already style-loader in its package.json (which means that no additional npm install is needed to use it in this example)Foulup
@Foulup true, but it's bot being used anywhere, so they might remove it in the future. So will leave in the answer, just in case, for future readers.Niacin
How to make my Angular app treat installed library's import @import "~dist/3rd_party_lib/blabla.css as node_modules/dist/3rd_party_lib/blabla.css, not as https://localhost:4200/~/dist/3rd_party_lib/blabla.css?Nessi
This is the best solution i found. Moreover, you can import you leaflet css file under any scss or css file like: @import "leaflet/dist/leaflet.css"; while compiling, angular will import directy from node_modules folderSeating
V
110

It is possible by using @import '~bootstrap/dist/css/bootstrap.css'; on the styles.css file. (Note the ~)

Edit: How it works - The '~' is an alias set on the webpack config pointing to the assets folder... simple as that..

Edit 2: Example on how to configure webpack with the '~' alias... this should go on the webpack config file (usually webpack.config.js)...

// look for the "resolve" property and add the following...
// you might need to require the asset like '~/bootsrap/...'
resolve: {
  alias: {
    '~': path.resolve('./node_modules')
  }
}
Volscian answered 2/3, 2017 at 4:19 Comment(7)
This worked for me and I really like the idea of keeping my css files contained so I didnt want to use the ViewEncapsulation.none deal. But how does this work?Tania
As far as I know is something on the compiler, but since I'm not an expert, I'll leave this to someone who knows better to explain how it works...Volscian
I tried this but get an error: GET localhost:55490/~bootstrap/css/bootstrap.min.css 404 (Not Found)Minstrelsy
For those who want to understand how does this work, here is a nice read blog.angularindepth.com/…Bibcock
This did not used to work in Angular 6 but it works for Angular 7. I can't see anything in the changelog that explains why, though. Does anybody know?Stipule
@bubble as I explained in the edit, it only works because it's configured in webpack. Probabbly it was not correctly set up on your angular 6 project. I'll edit my answer again with some example...Volscian
This does not work angular 13Smorgasbord
N
83

You won't be able to import any css to your vendors file using that stack, without making some changes.

Why? Well because this line:

import 'bootstrap/dist/css/bootstrap.css';

It's only importing your css as string, when in reality what you want is your vendor css in a style tag. If you check config/webpack.commons.js you will find this rule:

 {
   test: /\.css$/,
   loaders: ['to-string-loader', 'css-loader']
 },

This rule allows your components to import the css files, basically this:

@Component({
  selector: 'app',
  encapsulation: ViewEncapsulation.None,
  styleUrls: [
    './app.component.css' // this why you import css as string
  ],

In the AppComponent there's no encapsulation, because of this line encapsulation: ViewEncapsulation.None, which means any css rules will be applied globally to your app. So you can import the bootstrap styles in your app component:

@Component({
  selector: 'app',
  encapsulation: ViewEncapsulation.None,
  styleUrls: [
    './app.component.css',
    '../../node_modules/bootstrap/dist/css/bootstrap.css'
  ],

But if you insist in importing to your vendor.ts then you will need to install a new loader, npm i style-loader --save-dev this will allow webpack to inject css to your page. Then you need to create a specific rule, on your webpack.common.js and change the existing one:

 { //this rule will only be used for any vendors
   test: /\.css$/,
   loaders: ['style-loader', 'css-loader'],
   include: [/node_modules/]
 },
 {
   test: /\.css$/,
   loaders: ['to-string-loader', 'css-loader'],
   exclude: [/node_modules/] //add this line so we ignore css coming from node_modules
 },

The firs rule will be only applied when you try to import css, from any package inside node_modules the second rule will be applied to any css that you import from outside the node_modules

Niacin answered 16/10, 2016 at 17:14 Comment(6)
Thanks dude, this is a kind of perfect answer I was looking for. Both solution works well. One thing that I didn't know is that it's possible to target node_modules from styleUrls. Is it also a preferred thing to do?Foulup
There's no right or wrong, either them are valid, but if in the future you plan on having your vendor.css in a separated file then, the last option is better.Niacin
note that the angular2-webpack-starter has already style-loader in its package.json (which means that no additional npm install is needed to use it in this example)Foulup
@Foulup true, but it's bot being used anywhere, so they might remove it in the future. So will leave in the answer, just in case, for future readers.Niacin
How to make my Angular app treat installed library's import @import "~dist/3rd_party_lib/blabla.css as node_modules/dist/3rd_party_lib/blabla.css, not as https://localhost:4200/~/dist/3rd_party_lib/blabla.css?Nessi
This is the best solution i found. Moreover, you can import you leaflet css file under any scss or css file like: @import "leaflet/dist/leaflet.css"; while compiling, angular will import directy from node_modules folderSeating
B
24

So here is a way to import various CSS files using the angular-cli which I find the most convenient.

Basically, you can refer to the CSS files (order is important if you will be overriding them) in the config and angular-cli will take care of the rest. For instance, you might want to include a couple of styles from node-modules, which can be done as follows:

"styles": [
    "../node_modules/font-awesome/css/font-awesome.min.css",
    "../node_modules/primeng/resources/primeng.min.css",
    "styles.css"
  ]

A sample full-config might look like this:

.angular-cli.json

{
  "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
  "project": {
    "name": "my-angular-app"
  },
  "apps": [
    {
      "root": "src",
      "outDir": "dist",
      "assets": [
        "assets",
        "favicon.ico"
      ],
      "index": "index.html",
      "main": "main.ts",
      "polyfills": "polyfills.ts",
      "test": "test.ts",
      "tsconfig": "tsconfig.app.json",
      "testTsconfig": "tsconfig.spec.json",
      "prefix": "app",
      "styles": [
        "../node_modules/font-awesome/css/font-awesome.min.css",
        "../node_modules/primeng/resources/primeng.min.css",
        "styles.css"
      ],
      "scripts": [],
      "environmentSource": "environments/environment.ts",
      "environments": {
        "dev": "environments/environment.ts",
        "prod": "environments/environment.prod.ts"
      }
    }
  ],
  "e2e": {
    "protractor": {
      "config": "./protractor.conf.js"
    }
  },
  "lint": [
    {
      "project": "src/tsconfig.app.json",
      "exclude": "**/node_modules/**"
    },
    {
      "project": "src/tsconfig.spec.json",
      "exclude": "**/node_modules/**"
    },
    {
      "project": "e2e/tsconfig.e2e.json",
      "exclude": "**/node_modules/**"
    }
  ],
  "test": {
    "karma": {
      "config": "./karma.conf.js"
    }
  },
  "defaults": {
    "styleExt": "scss",
    "component": {}
  }
}
Bibcock answered 2/3, 2018 at 17:52 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.