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

  • create-react-appが利用しているreact-scriptは、webpackで何をしているのか
  • eject コマンドで挿入される設定ファイル群はreact-scriptのものなので、設定を変更するときの参考に

対象


publicPathpublicUrl

const publicPath = isEnvProduction
  ? paths.servedPath
  : isEnvDevelopment && '/';
const shouldUseRelativeAssetPaths = publicPath === './';

const publicUrl = isEnvProduction
  ? publicPath.slice(0, -1)
  : isEnvDevelopment && '';
  • ビルドしたファイル群がどこにデプロイされるか
  • 開発時は相対パス
  • index.htmlcssファイルのコンテンツへのパスの制御
  • publicPath のデフォルトはWebサーバのルートディレクト
  • publicUrl のデフォルトは空文字
  • 共に config/paths.js で定義

getStyleLoaders

const getStyleLoaders = (cssOptions, preProcessor) => {
  ...
  return loaders;
};
  • cssのローダーを返す関数
  • 開発時は style-loader を、ビルド時は MiniCssExtractPlugin を適用
    • ビルド時はcssファイルが生成される
  • postcss-loaderoptions でpostcssのプラグイン等を設定
  • preProcessor (sass-loaderとか) が渡された場合、loadersに追加

entry

[
  isEnvDevelopment &&
    require.resolve('react-dev-utils/webpackHotDevClient'),
  paths.appIndexJs,
 ]

output

devtoolModuleFilenameTemplate

{
  devtoolModuleFilenameTemplate: isEnvProduction
    ? info =>
        path
          .relative(paths.appSrc, info.absoluteResourcePath)
          .replace(/\\/g, '/')
    : isEnvDevelopment &&
      (info => path.resolve(info.absoluteResourcePath).replace(/\\/g, '/')),
}

optimization

TerserPlugin

new TeserPlugin({
  parse: { ecma: 8 },
  compress: { ecma: 5 },
  mangle: { safari10: true },
  output: { ecma: 5 },
})

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]),
]

module

strictExportPresence

{ strictExportPresence: true }
  • エクスポートが不足している場合にエラーにする

module.rules

requireEnsure

{ parser: { requireEnsure: false } },
  • 標準仕様にない require.ensure を無効化

module.rules

eslint-loader

{
  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-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 },
      ],
    ],
  },
}

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 オプションを有効にしている
  • getLocalIdentcssのクラス名の生成方法を指定するオプション

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)

plugins

ModuleNotFoundPlugin

new ModuleNotFoundPlugin(paths.appPath)

plugins

WatchMissingNodeModulesPlugin

isEnvDevelopment &&
  new WatchMissingNodeModulesPlugin(paths.appNodeModules)

plugins

WorkboxWebpackPlugin

isEnvProduction &&
  new WorkboxWebpackPlugin.GenerateSW({
    clientsClaim: true,
    exclude: [/\.map$/, /asset-manifest\.json$/],
    importWorkboxFrom: 'cdn',
    navigateFallback: publicUrl + '/index.html',
    navigateFallbackBlacklist: [
      new RegExp('^/_'),
      new RegExp('/[^/]+\\.[^/]+$'),
    ],
  })

plugins

ForkTsCheckerWebpackPlugin

useTypeScript &&
  new ForkTsCheckerWebpackPlugin({
    tsconfig: paths.appTsConfig,
    formatter: typescriptFormatter,
  })