Webpackのcss loaderでcssファイルをstring型に変換、またはCSS Modules的に扱えるようにする。


モチベーション

nodejs(typescript)でcssを以下のようにstring型で書いていたけど、.cssファイルで書いた方がVSCodeとかでcssの補完も効くよな。。と思い、実験してみました。

css.ts
const css = `
.description {
  overflow: hidden;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  color: #777;
  font-size: 12px;
  line-height: 19px;
  font-weight: 700;
  height: 57px;
}
`
hoge.ts
import css from './css';

const style = document.createElement('style');
style.textContent = css;

Webpackでできそう

色々と方法をググっていたら、webpackの公式ドキュメントで以下のような記述を発見。

(実装方法などは後述)

css-loader
toString
You can also use the css-loader results directly as a string, such as in Angular's component style.
css-loader | webpack

元々broserifyでjsなどbundleをしていましたが、ドキュメントの充実度とstackoverflowでの検索ヒット率でwebpackに乗り換えることを決意しました。

browserifyではbroserify-cssというモジュールでcssをトランスフォーム出来るそうです。(今回は試していません)

環境構築

ディレクトリ構成

root/
    ├ package.json
    ├ node_modules
    ├ tsconfig.json
    ├ webpack.config.js
    ├ sample.html
    ├ yarn.lock
    ├ src/
         ├ index.ts
         ├ css.css
         ├ css.text.css

package.jsonの設定

package.jsonに以下のmoduleを含める。

package.json
  "devDependencies": {
    "@types/node": "^12.11.6",
    "css-loader": "^3.4.2",
    "to-string-loader": "^1.1.6",
    "ts-loader": "^6.2.1",
    "typescript": "^3.6.4",
    "webpack": "^4.42.0",
    "webpack-cli": "^3.3.11"
  }

tsconfig.json

各自の設定で大丈夫かと思われます。

tsconfig.json
{
    "compilerOptions": {
        "noImplicitAny": false,
        "strict": true, 
        "target": "es5",
        "removeComments": true,    
        "rootDir": "./src/",
        "module": "es6",
        "sourceMap": false
    },
    "include": ["src"]
}

webpack.config.jsの設定

今回はmodule.rulesの中で、ファイルの末尾が.cssの場合はモジュールで読み込み、.text.cssのように.textがファイル名に入っている場合は、string形で読み込めるように設定。

webpack.config.js
const helpers = require('./webpack-helpers/helpers');

module.exports = {
  mode: 'production',
  entry: './src/index.ts',
  output: {
    path: __dirname,
    filename: 'bundle.js',
  },
  resolve: {
    extensions: ['.ts', '.js'],
  },
  module: {
    rules: [
      {
        test: /\.ts$/,
        loader: 'ts-loader', //ts-loader使うよ
      },
      {
        test: /\.text\.css$/i,
        use: ['to-string-loader', 'css-loader'],
      },
      {
        test: /\.css$/i,
        exclude: /text/,
        use: [
          'style-loader', 
          {
            loader: 'css-loader',
            options: {
              modules: true,
            }
          }],
      },
    ],
  },
};

通常通りcssを書く。

css.text.css
#target1{
    width: 100px;
    height: 100px;
    background-color: red;
}
css.css
#target2{
    width: 100px;
    height: 100px;
    background-color: red; 
    opacity: 0.5;
}

cssをtsファイルの中で読み込み

typescirptファイル内で以下のようにcssをインポートすると、string型、またはCSS Modulesで扱えるようになる。

index.ts
const exec = (): void => {
  const cssText: string = require('./main.text.css').toString(); // string
  const styles = require('./main.css'); // CSS Modules
  const target1 = document.querySelector('#target1');
  const target2 = document.querySelector('#target2');

  if (target1) {
    target1.textContent = 'fuga';
    const style = document.createElement('style');
    style.textContent = cssText;
    document.body.appendChild(style);
  }

  if (target2) {
    const ptag = document.createElement('p');
    ptag.textContent = 'fugafuga';
    target2.appendChild(ptag);
    target2.id = styles.target2;
  }
};

exec();

注意点としては、cssをインポートする時はimportではなく、requireを使用しなければいけません。
(調査中なので追記をしたいと思います。)

sample.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <div id="target1">
        hoge
    </div>
    <div id="target2">
        hogehoge
    </div>
    <script src="bundle.js"></script>
</body>
</html>

これでwebpackを実行すればファイルがbundleされて、以下のような表示になります。

これでcssファイルで編集できるようになります。

以上。