react‑scriptsはwebpackで何をしているのか
2019.01.28.三宅暁
自己紹介
三宅暁(AKIRA‑MIYAKE)
BizteX株式会社開発部デザインチーム
フロントエンドリードプログラマー
JavaScript/HTML/CSS(React/Angular/Vue.js)
UIデザイン(ツール系が得意)
UXデザイン(最近データ分析でRを使い始めました)
サーバレスアーキテクチャ
アカウント
GitHub:AKIRA‑MIYAKE
Twitter:AKIRA@DreamOfEleCat
ブログ:DreamofElectricCat
今日話すこと
create‑react‑appが利用しているreact‑scriptは、webpackで何をしているのか
eject コマンドで挿入される設定ファイル群はreact‑scriptのものなので、設定を
変更するときの参考に
対象
create‑react‑app/packages/react‑scripts/の config/webpack.config.js
eject コマンドで追加されるのもこのファイル
最近の変更(Mergewebpackconfiguration#5722)で単一のファイルに統合された
publicPath と publicUrl
const publicPath = isEnvProduction
? paths.servedPath
: isEnvDevelopment && '/';
const shouldUseRelativeAssetPaths = publicPath === './';
const publicUrl = isEnvProduction
? publicPath.slice(0, -1)
: isEnvDevelopment && '';
ビルドしたファイル群がどこにデプロイされるか
開発時は相対パス
index.html やcssファイルのコンテンツへのパスの制御
publicPath のデフォルトはWebサーバのルートディレクトリ
publicUrl のデフォルトは空文字
共に config/paths.js で定義
getStyleLoaders
const getStyleLoaders = (cssOptions, preProcessor) => {
...
return loaders;
};
cssのローダーを返す関数
開発時は style-loader を、ビルド時は MiniCssExtractPlugin を適用
ビルド時はcssファイルが生成される
postcss-loader の options でpostcssのプラグイン等を設定
preProcessor (sass‑loaderとか)が渡された場合、loadersに追加
entry
[
isEnvDevelopment &&
require.resolve('react-dev-utils/webpackHotDevClient'),
paths.appIndexJs,
]
開発時は react-dev-utils/webpackHotDevClient.js を追加
WebpackDevServerにsocket接続するためのclient
output
devtoolModuleFilenameTemplate
{
devtoolModuleFilenameTemplate: isEnvProduction
? info =>
path
.relative(paths.appSrc, info.absoluteResourcePath)
.replace(//g, '/')
: isEnvDevelopment &&
(info => path.resolve(info.absoluteResourcePath).replace(//g, '/')),
}
source‑mapの出力先の変換
ビルド時はsrcディレクトリをベースとした相対パスを解決
jsフォルダ内にsrcディレクトと同じ構成に見えるように
開発時はローカルの絶対パスに変換
プロジェクトのディレクトリ構成と同じように見えるように
optimization
TerserPlugin
new TeserPlugin({
parse: { ecma: 8 },
compress: { ecma: 5 },
mangle: { safari10: true },
output: { ecma: 5 },
})
webpack-contrib/terser-webpack-plugin を利用
パースはECMA8を適用するが、無効なコードへの変換を防ぐために圧縮と出
力ではECMA5を適用している
Safari10/11におけるloop内のスコープと await のバグのワークアラウンド
の追加
resolve
modules
{
modules: ['node_modules'].concat(
process.env.NODE_PATH.split(path.delimiter).filter(Boolean)
),
}
webpackが探索する node_modules の追加
プロジェクトの node_modules を優先するために、2番目に追加される
resolve
plugins
[
PnpWebpackPlugin,
new ModuleScopePlugin(paths.appSrc, [paths.appPackageJson]),
]
pnp-webpack-plugin の追加
yarnのPlug'n'Playを利用するためのプラグイン
react-dev-utils/ModuleScopePlugin.js の追加
node_modules 以外の src ディレクトリ外部のモジュールへの依存を防止
するプラグイン
module
strictExportPresence
{ strictExportPresence: true }
エクスポートが不足している場合にエラーにする
module.rules
requireEnsure
{ parser: { requireEnsure: false } },
標準仕様にない require.ensure を無効化
module.rules
eslint-loader
通常のloaderの前に実行
eslintの設定として eslint-config-react-app を指定
formatterに react-dev-utils/eslintFormatter を指定
eslintの出力をcreate‑react‑appのコンソールに統合
{
test: /.(js|mjs|jsx)$/,
enforce: 'pre',
use: [
{
options: {
formatter: require.resolve('react-dev-utils/eslintFormatter'),
eslintPath: require.resolve('eslint'),
baseConfig: { extends: [require.resolve('eslint-config-react-app')] },
ignore: false,
useEslintrc: false,
},
loader: require.resolve('eslint-loader'),
},
],
include: paths.appSrc,
}
mdoule.rules
babel-loader (src内のファイルを対象)
{
test: /.(js|mjs|jsx|ts|tsx)$/,
include: paths.appSrc,
loader: require.resolve('babel-loader'),
options: {
customize: require.resolve(
'babel-preset-react-app/webpack-overrides'
),
babelrc: false,
configFile: false,
presets: [require.resolve('babel-preset-react-app')],
plugins: [
[
require.resolve('babel-plugin-named-asset-import'),
{
loaderMap: {
svg: {
ReactComponent: '@svgr/webpack?-svgo![path]',
},
},
},
],
],
},
}
mdoule.rules
babel-loader (src内のファイルを対象)
プリセットとして babel-preset-react-app を指定
babel-plugin-named-asset-import がプラグインに追加される
SVGファイルをReactコンポーネントとして扱うためのプラグイン
babel-preset-react-app/webpack-overrides.js を babel-loader の
customize に設定
BabelMacrosのキャッシュをいい感じにハンドリングしてくれる
babel-preset-react-app
babel-preset-react-app/create.js で定義が行われている
以下のパッケージがデフォルトで含まれる
@babel/preset-env / @babel/preset-react / babel-plugin-macros
/ @babel/plugin-transform-destructuring / @babel/plugin-
proposal-class-properties / @babel/plugin-proposal-object-rest-
spread / @babel/plugin-transform-runtime / @babel/plugin-
syntax-dynamic-import
TypeScriptが有効な時に追加されるもの
@babel/preset-typescript / @babel/plugin-proposal-decorators
flowが有効な時に追加されるもの
@babel/plugin-transform-flow-strip-types
ビルド時に追加されるもの
babel-plugin-transform-react-remove-prop-types
テスト時に追加されるもの
babel-plugin-dynamic-import-node
mdoule.rules
babel-loader (src外のファイルを対象)
{
test: /.(js|mjs)$/,
exclude: /@babel(?:/|{1,2})runtime/,
loader: require.resolve('babel-loader'),
options: {
presets: [
[
require.resolve('babel-preset-react-app/dependencies'),
{ helpers: true },
],
],
},
}
プリセットとして babel-preset-react-app/dependencies.js を指定
@babel/runtime 以外の依存パッケージをトランスパイルする
mdoule.rules
css(グローバル)
{
test: cssRegex,
exclude: cssModuleRegex,
use: getStyleLoaders({
importLoaders: 1,
sourceMap: isEnvProduction && shouldUseSourceMap,
}),
}
cssRegex は /.css$/
cssModuleRegex の /.module.css$/ に該当するファイルは除外
getStyleLoaders 関数でローダを取得
mdoule.rules
css(モジュール)
{
test: cssModuleRegex,
use: getStyleLoaders({
importLoaders: 1,
sourceMap: isEnvProduction && shouldUseSourceMap,
modules: true,
getLocalIdent: getCSSModuleLocalIdentm
}),
}
cssModuleRegex の /.module.css$/ に該当するファイルに適用される
css‑loaderの modules オプションを有効にしている
getLocalIdent はcssのクラス名の生成方法を指定するオプション
react-dev-utils/getCSSModuleLocalIdent.js を利用
module.rules
sass
{
test: sassRegex,
exclude: sassModuleRegex,
use: getStyleLoaders(
{
importLoaders: 2,
sourceMap: isEnvProduction && shouldUseSourceMap,
},
'sass-loader'
),
},
{
test: sassModuleRegex,
use: getStyleLoaders(
{
importLoaders: 2,
sourceMap: isEnvProduction && shouldUseSourceMap,
modules: true,
getLocalIdent: getCSSModuleLocalIdent,
},
'sass-loader'
),
}
基本的にcssと同様でpreProsessorとして sass-loader を指定
plugins
InterpolateHtmlPlugin
new InterpolateHtmlPlugin(HtmlWebpackPlugin, env.raw)
react-dev-utils/InterpolateHtmlPlugin.js を追加
HtmlWebpackPluginの処理の後に、 index.html の変数定義の部分を env.raw
の値で置き換える
plugins
ModuleNotFoundPlugin
new ModuleNotFoundPlugin(paths.appPath)
react-dev-utils/ModuleNotFoundPlugin.js を追加
インポートするモジュールが見つからなかった場合のエラーメッセージを制御
plugins
WatchMissingNodeModulesPlugin
isEnvDevelopment &&
new WatchMissingNodeModulesPlugin(paths.appNodeModules)
開発時に react-dev-utils/WatchMissingNodeModulesPlugin を追加
依存ライブラリがインストールされていない場合に、強制的にインストール
plugins
WorkboxWebpackPlugin
isEnvProduction &&
new WorkboxWebpackPlugin.GenerateSW({
clientsClaim: true,
exclude: [/.map$/, /asset-manifest.json$/],
importWorkboxFrom: 'cdn',
navigateFallback: publicUrl + '/index.html',
navigateFallbackBlacklist: [
new RegExp('^/_'),
new RegExp('/[^/]+.[^/]+$'),
],
})
ビルド時に workbox-webpack-plugin を追加
Workbox はWebアプリケーションのオフラインサポートを追加するGoogleの
JavaScriptライブラリ
詳しくは workbox-webpack-plugin の公式ページを参照
react-scripts/template/src/serviceWorker.js の register() で登録す
ることで、静的ファイルのプリキャッシュを行うことができる
plugins
ForkTsCheckerWebpackPlugin
useTypeScript &&
new ForkTsCheckerWebpackPlugin({
tsconfig: paths.appTsConfig,
formatter: typescriptFormatter,
})
Realytics/fork-ts-checker-webpack-plugin を追加
別プロセスでTypeScriptの型チェックを実行
TypeScriptの利用は tsconfig.json の有無で判定される
formatterに react-dev-utils/typescriptFormatter.js を指定
プラグインの出力をcreate‑react‑appのコンソールに統合

react-scriptsはwebpackで何をしているのか