かもメモ

自分の落ちた落とし穴に何度も落ちる人のメモ帳

TypeScript で SCSS の変数を使いたい!にハマる 😇

SCSS のブレイクポイントの変数を React で使いたかったけど読み込ませるのにハマったのでメモ。

要件

SCSS の変数を JS で読み込んで使う

検索して出てきたサイトを参考に次のように作成しました。

Webpack の設定

// webpack.config.js
module.exports = {
  // ...
  module: {
    rules: [
      {
        test: /\.(css|scss|sass)$/,
        use: ["style-loader", "css-loader", "sass-loader"],
      },
    ]
  },
}

JS で使いたい変数の scss ファイル

// _variables.scss
$breakpoints: (
  sm: '576px',
  md: '768px',
  lg: '992px',
);

// JS で読み込むための設定を追加
:export {
  BreakPoints: $breakpoints
}

SCSS の変数を使う JS

import BreakPoints from './styles/_variables.scss';
const { sm, md, lg } = BreakPoints;

Error: isn't a valid CSS value なエラー

サイトを表示してみると次のようなエラーが表示されていました。

SassError: (sm: '576px', md: '768px', lg: '992px') isn't a valid CSS value.
>> $grid-breakpoints: (
   -------------------^

😇

Sass 構文をチェックできる SassMeister | The Sass Playground! に SCSS ファイルの内容を貼ってみると、BreakPoints: $breakpoints の部分でエラーになっているようでした。

:exportCSS Modules の書き方で、 Sass の構文ではないから Sass のコンパイルでエラーになっていた

Unfortunately the SASS/SCSS documentation does not mention anything about export, and as such, other than guessing and solving it through trial and error, your best bet is to migrate to SCSS and implement the solution that is widely used (but also not documented).
Personally, I like to import my SCSS variables, and then, export them to JS.
NOTE: It is vitally important that _export.scss is only utilised in JS files, and is not imported in SCSS files. Importing _export.scss into your SCSS files will result in a) unecessary style exports in your CSS, and b) duplications of these style exports throughout every compiled SCSS file

cf. vue.js - Cannot Export Sass Variables into Javascript - Stack Overflow

導入しようとしていたサイトは Sass を別途コンパイルしていたので、コンパイルするファイルの中に CSS Modules の構文が入っていたのが原因で Sass のコンパイルがコケてしまいエラーになっていたのが原因でした。

JS で import する SCSS を別のファイルにする

Sass のコンパイルに含まれない JS で使うための export 用の SCSS ファイルを作ってしまえばOK

_export-variables.scss

// JS で使いたい変数のあるファイルを import する
@import "./_variables.scss";

:export {
  BreakPoints: $breakpoints
}

wbpack でコンパイルエラー

これで Sass コンパイルのエラーはなくなった筈… と思ったらまたエラーが表示されました。ぐぬぬぬぬぬ…
😇 😇

Error in webpack compile, details follow below:
…
Module build failed (from ./node_modules/sass-loader/dist/cjs.js):
SassError: (sm: '576px', md: '768px', lg: '992px') isn't a valid CSS value.
>> $grid-breakpoints: (
   -------------------^

下半分は同じエラーのですが、今回はどうやら sass-loader でうまく処理ができなかったっぽい感じです。

map はそのまま CSS Module で export できないっぽい

map な $breakpoints をそのまま JS に渡せばいい感じに Object にしてくれると思ったのですが、どうやらそうではなく map なままだとエラーになってしまうみたいです。

/ Define all colours /
$theme-colours: (
  some-color: #000,
  another-color: #000,
  third-color: #000,
  fourth-color: #000
)
@each $color, $value in $theme-colours {
  :export{
    $color: $value;
  }
}

cf. https://stackoverflow.com/questions/56523785/exporting-scss-variables-to-js-auto-looping-variables

map を変改して個別の変数にしてしまいます。
_export-variables.scss

// JS で使いたい変数のあるファイルを import する
@import "./_variables.scss";

@each $size, $value in $breakpoints {
  :export {
    #{$size}: $value;
  }
}

これでコンパイルエラーは出なくなりました!


import した scss が空オブジェクト {} になってしまう

もろもろ エラーは出なくなったのですが、今度は import してきた値の中身が空オブジェクト {} になってしまう問題が発生しました…
😇 😇 😇 なんもわからん…

import BreakPoints from './styles/_export-variables.scss';
console.log(BreakPoints);
// => {}

This is not a sass-loader issue, you just forgot to turn on CSS Modules in the css-loader.
cf. i got a empty object when import a sass file. · Issue #206 · webpack-contrib/sass-loader · GitHub

webpack の css-loader に modules オプションが必要だった

webpack の設定の問題だったようです

webpack.config.js

module.exports = {
  // ...
  module: {
    rules: [
      {
        test: /\.(css|scss|sass)$/,
        // "css-loader" を "css-loader?modules" に変更
        use: ["style-loader", "css-loader?modules", "sass-loader"],
      },
    ]
  },
}

これで JS で import した scss のオブジェクトに変数が入るようになりました!

import BreakPoints from './styles/_export-variables.scss';
console.log(BreakPoints);
// => { sm: '576px', md: '768px', lg: '992px', …  }

css-loaderはdefaultがglobal scopeで :local(.className) または :local{...}~ と宣言した場合にclassNameがハッシュ化されexportされる。 css-loaderの設定にmodulesオプションを渡すとdefaultでセレクタがscoped cssの対象となりハッシュ化され、逆に:global` を宣言した場合にglobal scopeとして処理される。
cf. css-loaderでscoped cssを手に入れる - Qiita

:export で囲っていると modules オプションがないと、変数が取得できない形に変換されてしまっていたという事?
ちょっと理由が理解しきれてないです。


TypeScript の型の問題

無事 SCSS の変数を JS で読み込んで扱えるようになったのですが、プロジェクトは TypeScript なので今度は読み込んだ SCSS 変数の型の問題が発生です。

import BreakPoints from './styles/_export-variables.scss';

BreakPoints.sm;
// Property 'sm' does not exist on type 'typeof import("*.scss")'

インポートしたスタイルのプロパティを使おうとしたら型エラーになりました。
😇 😇 😇 😇 知ってた…

型定義ファイルの作成

そもそも型定義がないので、型定義ファイルを作成します。
型定義ファイルは、{import するファイル名}.d.ts という名前で作ればOKっぽい。
今回は _export-variables.scss ファイルなので、型定義ファイルは _export-variables.scss.d.ts とすれば良いみたい。

/types/styles/_export-variables.scss.d.ts

declare module "*.scss" {
  type BreakpointsType = {
    sm: string;
    md: string;
    lg: string;
  };

  const BreakPoints: BreakpointsType;
  export default BreakPoints;
}
import BreakPoints from './styles/_export-variables.scss';
BreakPoints.sm; // 👌

ESLint import/no-default-export error

コレで大丈夫かな。と、思ったらら ESLint で default import を禁止にしている import/no-default-export error… 😇 😇 😇 😇 😇

型定義ファイルの export default BreakPoints; この部分が問題のようです。
とは言え、SCSS からのインポートはデフォルトなので、export BreakPoints, import { BreakPoints } from './styles/_export-variables.scss' とすると BreakPointsundefined になってしまいます。

export = を使う

export default されているものをインポートする時のimport Express = require('express') ( インポート代入 ) の逆みたいな感じで、 export default XXXexport = XXX (エクポート代入) と書き換えることができるようです。

/types/styles/_export-variables.scss.d.ts

declare module "*.scss" {
  type BreakpointsType = {
    sm: string;
    md: string;
    lg: string;
  };

  const BreakPoints: BreakpointsType;
  export = BreakPoints; // 👌
}

 
単に ESLint を誤魔化しているだけな気もしますが、これでコンパイルエラー、TypeScript / ESLint のエラーがなくなり、TypeScript で SCSS の変数が JS として扱えるようになりました!
₍ ᐢ. ̫ .ᐢ ₎👌 ヤッタネ

😇 😇 😇 😇 😇 1日潰れた
おわり。


[参考]

プログラミングTypeScript ―スケールするJavaScriptアプリケーション開発

プログラミングTypeScript ―スケールするJavaScriptアプリケーション開発

  • 作者:Boris Cherny
  • 発売日: 2020/03/16
  • メディア: 単行本(ソフトカバー)

Paint it, Black (GRAPHICTION BOOKS)

Paint it, Black (GRAPHICTION BOOKS)

  • 作者:よむ
  • 発売日: 2020/09/28
  • メディア: 大型本
がんばれ俺ちゃん