かもメモ

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

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
  • メディア: 大型本
がんばれ俺ちゃん

React create-react-app したアプリで 開発時 (development) と build する時 (production) とで env を切り替えたい

create-react-app で作った React アプリで yarn start での開発時と yarn build でビルドした時に使う環境変数を切り替えたい。

.env のファイル名で自動的に切り替わる

create-react-app で作られたファイルにある .gitignore を見ると次のように設定されていました。

# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local

どうやら次のように読み込まれる設定になっており、より細かい指定のファイルが有ればそちらが優先されるようになっているようです。

  • .env … 開発時 / ビルド時両方で使用される (デフォルト)
  • .env.local … local 環境で使用される (どの環境でも読み込まれるっぽい)
  • .env.development, .env.production, .env.test … Environment モードに合わせて使用される
  • .env.development.local, .env.production.local, .env.test.local … local 環境での実行時のEnvironment モードに合わせて使用される

開発用は .env.development.local, ビルド用は .env.production.local の設定ファイルを作成しておけばOK

yarn start 時には .env.development.local が読み込まれる。
ローカル環境で yarn build してしまう場合はビルド用の設定は .env.production.local を作成しておけばOK。

CI などでビルドする場合は、ビルドが実行される環境に .env.env.production を作成しておけば良さそう。
場合分けが少ないなら .env.loacl 一個で大丈夫そう。


[参考]

エンブん早わかり…

Serverless で再 deploy したらエラーになった

一度 AWS に deploy してた Serverless 化したアプリを再デプロイしようとしたらエラーになってしまったのメモ。
$ sls deploy で再デプロイしようとしたらエラーになってロールバックされてしまった。

$ sls deploy
# …
Serverless Error ---------------------------------------
# … エラー内容また取れたら書く

AWS の cloudwatch からデプロイエラーの内容を見るとどうやら DynamoDB の部分でエラーが起こっているっぽい。
serverless.yml の DynamoDB の設定部分に DeletionPolicy: Retain があると削除には IAM のアクセス権限がないとダメっぽい。

Serverless deploy で作られていた DynamoDB が削除されて作り直されるわけではなさそうですが、 IAM のアクセス権限が無いのが問題っぽい雰囲気

DynamoDB を操作する IAM role が足りなかった

Serverless deploy で使ってる IAM ユーザーに AWS のコンソールから AmazonDynamoDBFullAccess の権限をつければOK。
権限を付けて再度 $ sls deploy コマンドを実行したら問題なくデプロイできた。


おわり。


[参考]