かもメモ

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

React create-react-app で作ったアプリの bundle サイズを確認したい

SPA の速度カイゼンやパッケージやファイルのサイズを可視化してみたい事が多々あります。

今回は create-react-app で作成したアプリでバンドルサイズを可視化してみたのメモ。

create-react-app v3 以降は --stats オプションが廃止され webpack bundle analyzer は使えなくなっていた

バンドルサイズ可視化で調べると出てくる webpack-bundle-analyzer を使う方法を見つけて試していましした。

$ yarn build --state && npx webpack-bundle-analyzer ./build/bundle-stats.json
# …
Compiled successfully.
#...
Couldn't read webpack bundle stats from "/Users/kikiki/app/frontend/build/bundle-stats.json":
Error: ENOENT: no such file or directory, open '/Users/kikiki/app/frontend/build/bundle-stats.json'
error Command failed with exit code 1.

--state オプションを付けてビルドすれば ./build/bundle-stats.json が作成されて webpack-bundle-analyzer で見ることができるというものでしたが、create-react-app v3 以降では --status オプションが廃止されていて bundle-stats.json が作成されなくなっていました。
ejict して webpack の設定自前で弄りたくはない…

Source map explorer を使う

v3 以降では公式に記載されている Source map explorer を使うのが良さそうです。

パッケージをインストール

$yarn add -D source-map-explorer

package.json に npm scripts を追加

"scripts": {
  "start": "PORT=8888 react-scripts start",
  "build": "react-scripts build",
  // ...
  "analyze": "yarn build && source-map-explorer 'build/static/js/*.js'"
}

実行

$ yarn analyze

ビルドが完了したら build/static/js/ 内に出力された js ファイルを元に解析した結果をブラウザで表示してくれます。

f:id:kikiki-kiki:20201019162901p:plain source-map-explorer は見た目がめちゃめちゃシンプルです

それでも webpack-bundle-analyzer が使いたい!

source-map-explorer は見た目がめちゃめちゃシンプルなのでやっぱり webpack-bundle-analyzer を使いたい場合

cra-bundle-analyzer というパッケージを使えば eject して webpack の設定を自分で頑張らなくても webpack-bundle-analyzer が使えるようになるようです。

$ yarn add -D cra-bundle-analyzer
$ npx cra-bundle-analyzer

f:id:kikiki-kiki:20201019162929p:plain

解析が終わると./build/report.html という静的ファイルが作成されて自動的にブラウザで開くようになっており、そこでカラフルな webpack-bundle-analyzer の表示で見ることができるようになりました!
サイズの大きな webpack-bundle-analyzer はlocalnにはインストールせずに npx で実行しているみたいなので、カラフルな表示で見たい場合は cra-bundle-analyzer を使うと良さそうです!

おわり


[参考]

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 一個で大丈夫そう。


[参考]

エンブん早わかり…