2017.3.4
2017.6.6

webpack環境で外出しにしたCSSファイルを読み込む方法

主題の通り、webpack環境下でのCSSファイルの読み込み方法についてです。webpackに慣れている人はそうでもないかもしれませんが、不慣れな自分は結構ハマりました。

Angular2でのスタイルの指定方法

Angular2でスタイルを指定する場合、大きく分けて、コンポーネントファイル(.ts)に直接書くか、別途、CSSファイルに書き出すかの2通りがあるかと思います。

① .tsファイルに直接書く場合

コンポーネントファイル(test.component.ts)


@Component({
  selector: 'test',
  styles: [`
    h1 { color: #f00; display:flex;}
  `],
  template: `<h1>テスト</h1>`
})

② 別途、CSSファイルに書き出す場合

コンポーネントファイル(test.component.ts)


@Component({
  selector: 'test',
  styleUrls: ['./test.component.css'],
  template: `<h1>テスト</h1>`
})

CSSファイル(test.component.css)


h1 { color: #f00; display: flex; }

こちらの記事(webpackでPostCSSを使い、Autoprefixerでベンダープレフィックスを自動付与させる)で設定したPostCSSを利用した場合、上記の①と②で以下のような挙動になります。

① ベンダープレフィックスが自動付与されない(コンポーネントファイルに文字列として直接埋め込んでいるため、webpackでPostCSSが適用されない)。

② ブラウザのコンソールに以下のようなエラーが出る(原因は...分かりません)。


Unhandled Promise rejection: Failed to load test.component.css ; 
Zone: <root> ; 
Task: Promise.then ; 
Value: Failed to load test.component.css undefined

解決方法

一応、公式サイトに方法(Webpack: an introduction - ts - GUIDE)は載っていますが、初心者にはなかなか理解できません。ちょっとでも齧った後じゃないとさっぱりですね。

ここでは自分が最終的に落ち着いた方法を掲載しておきます。

まずは、コンポーネントファイル(test.component.ts)を以下のようにします。


@Component({
  selector: 'test',
  styles: [require('./test.component.css')],
  template: `<h1>テスト</h1>`
})

続いて、CSSファイルはそのまま(test.component.css)。


h1 { color: #f00; display: flex; }

最初は上記の対応だけでいいと思っていたのですが、これだけでは動作しません。さらに「webpack.config.js」を以下のように設定します。


module.exports = {
  entry: './src/entry.js',
  output: {
    path: './dist',
    publicPath: '/',
    filename: 'bundle.js'
  },
  module: {
    rules: [
      // コンポーネント用(styles: [require('./dashboard.component.css')] 形式)
      {
        test: /\.css$/,
        exclude: /styles\.css/,
        use: [
          'raw-loader',
          'postcss-loader'
        ]
      },
      // 通常用(グローバル用のCSSファイルとして「styles.css」を作成して読み込んでいる場合)
      {
        test: /\.css$/,
        include: /styles\.css/,
        use: [
          'style-loader',
          {
            loader: 'css-loader',
            options: {
              importLoaders: 1,
              minimize: true
            }
          },
          'postcss-loader'
        ]
      },
    ],
  },
  resolve: {
    extensions: ['.js', '.css']
  },
};

なにが問題だったかというと、コンポーネント用(styles: [require('./dashboard.component.css')] 形式)での出力と、グローバル用としてtsファイルで単純に require('./styles.css') とした場合での出力のさせ方が違うという点です。

コンポーネント用としてCSSファイルを読み込む場合は、あくまで文字列として取得しなければいけませんが、style-loaderやcss-loaderを経由させてしまうと余計な情報が付与されてしまうため、postcss-loaderの後にraw-loaderを適用するような上記の設定にしないといけないようです。(useで指定したloaderは後ろの方から先に実行されます)

公式のサイトでは、includeとexcludeでappフォルダ直下のファイルかどうかという判定をしているようなので、そちらの方が便利そうです。

なお、以下のように「toString()」を利用した場合、raw-loaderを設定しなくともエラーが出ないので一見は大丈夫そうに見えるのですが、本来の動作であるスタイルのカプセル化が適用されず、グローバルのスタイルとして配置されてしまうので止めた方がいいです。


@Component({
  selector: 'test',
  styles: [require('./test.component.css').toString()],
  template: `<h1>テスト</h1>`
})

Angular】関連記事