定数は DefinePlugin を使ってどこからでも参照できるようにすればいいと思う

テーマの管理辺りとか特にそう思います。例えばそれは、constants/theme.tsなどにオブジェクトで定義して、それを使うコンポーネントすべてでインポートして使ったりしたりといった具合にです。ただコンポーネントファイルはそれこそ数百ファイル以上になることは普通なので、いくらアウトインポートができるからと言っても流石に効率悪すぎなのではと感じています。

DefinePlugin でオブジェクトそのまま渡す

そこで例えば以下のような Webpack 設定があったとします。このように書いてしまえばコードの中ではインポートは書かずとも、COLOR.accentCOLOR.textと書くだけでそれら定数が使えます。

const webpack = require('webpack');

module.exports = {
  /* 色々 */
  plugins: [
    new webpack.DefinePlugin({
      COLOR: JSON.stringify({
        accent: 'orange',
        text: '#222',
      })
    }),
  ]
}

ちゃんと型定義をする

入っているか分からなくなりそうですが、それはtypes/define/index.d.tsなどに以下のように書きます。これで型も効くし、サジェストも効くので TypeScript に慣れた人には問題もないと思います。ここはアナログになり少し面倒くさいかもしれないですがインポートの方がもっと面倒くさいかなと個人的には思います。

declare const COLOR: {
  accent: string;
  text: string;
}

EnvironmentPlugin で環境変数をコード内に渡す

NodeJSでは、環境変数はprocess.envというオブジェクトの中に入っていますが、この習慣をクライアント側でも使おうということでprocess.env.xxxを定義して使えるようにするのが EnvironmentPlugin の役割です。この EnvironmentPluginwebpackパッケージに一緒に入っています。

const webpack = require('webpack');

module.exports = {
  /* 色々 */
  plugins: [
    new webpack.EnvironmentPlugin({
      NODE_ENV: 'development'
    })
  ],
}

環境変数を渡すか渡さないかで切り替える

これは例えば、

yarn webpack

でビルドすればコード内のprocess.env.NODE_ENV === 'developmentになります。そして今度は、

NODE_ENV=production yarn webpack

のように環境変数を渡してビルドすればprocess.env.NODE_ENV === 'productionになるという感じです。このようにprocess.env.NODE_ENVの値を環境変数を渡すか渡さないかで切り替えることに成功したので、あとはコードの中で条件分岐などで処理を分けるだけですね。

他にどんなものを定義したらいいか

例えばこのサイトで使っている Contentful という CMS は 本番用と開発用でトークンが違うのでこういう別れてるものがメインで使えると思います。他には Stripe などのトークンも別れていますね。

ProvidePlugin を使って import を書く回数を減らす

例えば React を JSX で書いているプロジェクトであれば JSX を使っているファイルでは必ずimport React from 'react';のような書かなければいけません。( JSX 部分は変換されてReact.createElementを使うようになる為)

設定する

ProvidePluginwebpackモジュールに付属しているので新たにインストールする必要はありません。これをpluginsセクションの中に置きます。

{
  // ...略
  plugins: [
    new webpack.ProvidePlugin({
      React: 'react'
    })
  ]
}

ProvidePluginはオブジェクトを引数に取ります。その中にReact: 'react'と置いた場合、「reactモジュールをReactという名前でどこでも使えるようにするよ」という意味になります。JavaScript の場合設定はこれだけでどこでもReactが使えるようになっているはずです。

ちなみに、どこでもはwebpackのビルド対象ファイルだけで、window.Reactという形にはならないので安全です。
対象のファイルをラップする親スコープがあり、そこにreact-WEBPACK-IMPORTED-MODULE-1-defaultのような名前の関数が生えています。この関数は実行後にReactモジュールを返し、これをReactという名前で置く親スコープ内に置くことで使えるようになっているのだと思います。

ESLint を使っている場合はreact/react-in-jsx-scopeが引っかかるようになるかもしれません。これは"react/react-in-jsx-scope": 0と ESLint のファイルに追記して無効化できます。

TypeScript 対応

TypeScript でどこでもReactを使おうとすると、そのままではany型になってしまいます。コード上ではいきなり現れた謎変数な為です。これにはアンビエント宣言(Ambient Declarations)を定義したファイルを作る必要があります。その中身は「どこでも出てくるReactとは何ぞや」です。

アンビエント宣言はd.tsという拡張子で作ります。「どこでも出てくる」は、つまりglobalということなのでd.tsの置き場所はtsconfigincludeパターンに含まれるか、filesの中に置かれていればどこでも大丈夫です。

Reactだけならこれだけです。

import React from 'react';

// global 空間を拡張
declare global {
    const React: typeof React;
}

これでどこでも型付きReactが使えるようになったはずです。

ちなみにこっちは良くわからないですが駄目です。

import * as React from 'react';

declare var React: typeof React;

使った時にこのように警告されます。

'React' refers to a UMD global, but the current file is a module. Consider adding an import instead.

「〜 import instead」この内容だと振り出しに戻るので却下です。なんだか難しいですが、モジュールのグローバル定義はdeclare globalで囲むことにします。

TypeScript の paths 設定が効くようにする

ルートに以下のようなtsconfig.jsonを置くだけではまだ WebPack で使えません。

{
  "baseUrl": ".",
  "paths": {
    "foo": ["src/foo/index.ts"]
  }
}

WebPack 設定にも手を加える

dividab/tsconfig-paths-webpack-pluginを使います。

これをresolve.pluginsに設定してあげます。pluginsではないので注意です。

const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin');

module.exports = {
  resolve: {
    plugins: [new TsconfigPathsPlugin(
      {
        // tsconfig.json はデフォルト
        configFile: 'tsconfig.json'
      }
    )]
  }
}