TypescriptにSCSSをimportした上に型定義を使う


はじめに

Reactを使う時、css-loaderで:local(.className)または:local{...}と宣言した場合にclassNameがハッシュ化されexportされる。
それらのモジュールをimportする時、vscode(またはその他IDE)のIntelliSenseを使えると大変便利だと思う。
その方法をメモします。

やり方

フォルダ構成


+- demo-app/
   +- package.json
   +- tsconfig.json
   +- webpack.config.js
   +- html/
     +- index.html
   +- scripts/
     +- foo.tsx
   +- styles/
     +- foo.scss
     +- foo.scss.d.ts

css-loaderの導入

割愛します。
一応webpack.config.jsextensionsの領域に下記の拡張子を追加するのを忘れずに。

webpack.config.js
resolve: {
  extensions: [... ".sass", ".scss", ".css"]
}

スタイルの記入

styles/foo.scss
// ここはGlobal Style
* {
  color: royalblue;
}

// ここはScroped Style
:local {
  .bar {
    background-color: aquamarine;
  }
  .baz {
    font-size: 2em;
  }
}

スタイルの仮導入

.d.tsを作成しなくても、tsファイルの中で下記の記述のようにrequire()を使えば、別に導入できなくもないですが、TSスタイルではないので、ちゃんとimportできるようにする。

scripts/foo.tsx
const style = require('styles/foo')

型定義ファイル.d.tsの作成

TSにJSファイルを導入する時と同じように、ファイル名.d.tsを作成すればOK。
記述の仕方はこちらを参考していた。

styles/foo.scss.d.ts
declare module '*.scss' {
  const content: {
    bar: string;
    baz: string;
  };
  export default content;
}

TSにimport

scripts/foo.tsx
import * as React from 'react'
import * as ReactDOM from 'react-dom'

import styles from 'styles/foo.scss' // 拡張子省略不可

const Hello: React.FC<{}> = props => (
  <div className={styles.bar}>Hello World!
  </div>
)

document.addEventListener('DOMContentLoaded', () => {
  ReactDOM.render(
    <Hello />,
    document.body.appendChild(document.createElement('div')),
  )
})

問題なければ、変数stylesを呼ぶ時、定義したclassはIntelliSenseに表示される。

結果

結果はこんな感じになる



ここのclass名はコンパイルする度に変わる。

注意点

上記foo.scss.d.tsの書き方だと、下記の構文でのみ読み込める

import styles from 'styles/foo.scss'

以下のexport default contentを修正すれば、別の書き方でimportできる

declare module '*.scss' {
  const content: {
    bar: string;
    baz: string;
  };
- export default content;
+ export = content;
}
import * as styles from 'styles/foo.scss'
// または
import {foo, bar} from 'styles/foo.scss'

ちょっとスマートな使い方

前述のやり方だと、1つ.scssファイルに1つ.d.tsファイルを作らなきゃいけなくなるので、スタイルファイルが多い場合は大変見づらい。
styles/配下にindex.d.tsを作って、先ほどstyles/foo.scssの内容を移動すればいいと思う。

styles/index.d.ts
declare module 'styles/foo.scss' {
  const content: {
    bar: string;
    baz: string;
  };
  export = content;
}

declare module 'styles/bar.scss' {
  const content: {
    baz: string;
  };
  export = content;
}

こう記述すると、より綺麗にまとまると思う

余談

railsのwebpackerを使う時、デフォルトはjs/tsファイルのみwatchするが、下記の記述をconfig/application.rbに追加すれば、cssファイル(とscssファイル)もwatchしてくれる。

config/application.rb
Webpacker::Compiler.watched_paths += %w(PATH/TO/STYLES/**/*.css PATH/TO/STYLES/**/*.scss)

詳しい使い方はここを参照してください。

参考

https://qiita.com/nullabletypo/items/d3ac5f15f2405f9ac3fd
https://stackoverflow.com/questions/40382842/cant-import-css-scss-modules-typescript-says-cannot-find-module
https://qiita.com/terrierscript/items/56d2cc15f76df50dfee7