はじめに
最近ReactやGraphQL, TypeScriptの環境構築をやっているのですがWebpackがあまりに難しすぎて挫折しかけたのでWebpackに対する苦手意識をなくすためにもwebpack.configの内容を1から調べていこうと思います。僕と同じくWebpackの内容の多さに絶望した方の助けになれば幸いです
動作環境
- npm 6.14.5
- node.js 14.3.0
- create-react-app 3.4.1
Ln1-Ln51
usestrict
strictモードの呼び出し
- 一部の問題が起こりやすいコードをエラーとして処理する
- javascriptの処理を高速化する
strictモードについて細かく書くとそれだけで1記事分になりそうなので細かい違いはこちらをご参照ください
constfs=require('fs');constpath=require('path');constwebpack=require('webpack');constresolve=require('resolve');constPnpWebpackPlugin=require('pnp-webpack-plugin');constHtmlWebpackPlugin=require('html-webpack-plugin');constCaseSensitivePathsPlugin=require('case-sensitive-paths-webpack-plugin');constInlineChunkHtmlPlugin=require('react-dev-utils/InlineChunkHtmlPlugin');constTerserPlugin=require('terser-webpack-plugin');constMiniCssExtractPlugin=require('mini-css-extract-plugin');constOptimizeCSSAssetsPlugin=require('optimize-css-assets-webpack-plugin');constsafePostCssParser=require('postcss-safe-parser');constManifestPlugin=require('webpack-manifest-plugin');constInterpolateHtmlPlugin=require('react-dev-utils/InterpolateHtmlPlugin');constWorkboxWebpackPlugin=require('workbox-webpack-plugin');constWatchMissingNodeModulesPlugin=require('react-dev-utils/WatchMissingNodeModulesPlugin');constModuleScopePlugin=require('react-dev-utils/ModuleScopePlugin');constgetCSSModuleLocalIdent=require('react-dev-utils/getCSSModuleLocalIdent');constpaths=require('./paths');constmodules=require('./modules');constgetClientEnvironment=require('./env');constModuleNotFoundPlugin=require('react-dev-utils/ModuleNotFoundPlugin');constForkTsCheckerWebpackPlugin=require('react-dev-utils/ForkTsCheckerWebpackPlugin');consttypescriptFormatter=require('react-dev-utils/typescriptFormatter');constpostcssNormalize=require('postcss-normalize');constappPackageJson=require(paths.appPackageJson);
基本的にインポートなので省略
// Source maps are resource heavy and can cause out of memory issue for large source files.constshouldUseSourceMap=process.env.GENERATE_SOURCEMAP!=='false';
SourceMapを使用するかどうかのフラグ
使用することでWebpackによってコードがまとめられたときにもとのコードの情報が残るようになりデバッグがしやすくなる
// Some apps do not need the benefits of saving a web request, so not inlining the chunk// makes for a smoother build process.constshouldInlineRuntimeChunk=process.env.INLINE_RUNTIME_CHUNK!=='false';
InlineChunkHtmlPluginを使用するかのフラグ
使用することで共通の設定をChunkの中に埋め込み、http通信の数を減らすことができる
constisExtendingEslintConfig=process.env.EXTEND_ESLINT==='true';
Eslintrcを使用するかのフラグ
ただし2020/6/7時点で機能してないと思われる
(こちらに報告あり)
constimageInlineSizeLimit=parseInt(process.env.IMAGE_INLINE_SIZE_LIMIT||'10000');
url-loader使用時に事前読み込みを行うファイルサイズの上限を設定する
事前読み込みを行うことでhttp接続を減らすことができる
// Check if TypeScript is setupconstuseTypeScript=fs.existsSync(paths.appTsConfig);
TypeScriptを使用しているかをチェックしている
paths.jsで設定されているpaths.appTsConfigが存在するかで判定している(初期ではtsconfig.json)
// style files regexesconstcssRegex=/\.css$/;constcssModuleRegex=/\.module\.css$/;constsassRegex=/\.(scss|sass)$/;constsassModuleRegex=/\.module\.(scss|sass)$/;
styleファイルの正規表現をまとめたもの
Ln53-Ln129
module.exports=function(webpackEnv){constisEnvDevelopment=webpackEnv==='development';constisEnvProduction=webpackEnv==='production';
以降はmodule.exportsという関数の定義となる。webpackEnvは開発環境か本番環境かを分ける引数
// Variable used for enabling profiling in Production// passed into alias object. Uses a flag if passed into the build commandconstisEnvProductionProfile=isEnvProduction&&process.argv.includes('--profile');
コードの圧縮時にclassnameや関数名を保存するか、パフォーマンスの計測を可能にするかのフラグを設定する
// We will provide `paths.publicUrlOrPath` to our app// as %PUBLIC_URL% in `index.html` and `process.env.PUBLIC_URL` in JavaScript.// Omit trailing slash as %PUBLIC_URL%/xyz looks better than %PUBLIC_URL%xyz.// Get environment variables to inject into our app.constenv=getClientEnvironment(paths.publicUrlOrPath.slice(0,-1));
環境変数の読み込み。process.envの内容がenvに読み込まれる
なお、process.envの設定はdotenvにより行われているが、dotenvファイルのパスはpaths.dotenvやenv.jsで変更できる
// common function to get style loadersconstgetStyleLoaders=(cssOptions,preProcessor)=>{constloaders=[isEnvDevelopment&&require.resolve('style-loader'),isEnvProduction&&{loader:MiniCssExtractPlugin.loader,// css is located in `static/css`, use '../../' to locate index.html folder// in production `paths.publicUrlOrPath` can be a relative pathoptions:paths.publicUrlOrPath.startsWith('.')?{publicPath:'../../'}:{},},{loader:require.resolve('css-loader'),options:cssOptions,},{// Options for PostCSS as we reference these options twice// Adds vendor prefixing based on your specified browser support in// package.jsonloader:require.resolve('postcss-loader'),options:{// Necessary for external CSS imports to work// https://github.com/facebook/create-react-app/issues/2677ident:'postcss',plugins:()=>[require('postcss-flexbugs-fixes'),require('postcss-preset-env')({autoprefixer:{flexbox:'no-2009',},stage:3,}),// Adds PostCSS Normalize as the reset css with default options,// so that it honors browserslist config in package.json// which in turn let's users customize the target behavior as per their needs.postcssNormalize(),],sourceMap:isEnvProduction&&shouldUseSourceMap,},},].filter(Boolean);if(preProcessor){loaders.push({loader:require.resolve('resolve-url-loader'),options:{sourceMap:isEnvProduction&&shouldUseSourceMap,},},{loader:require.resolve(preProcessor),options:{sourceMap:true,},});}returnloaders;};
style-loaderの設定
・style-loader (develop環境のみ)
CSSをhtmlに埋め込む
・MiniCssExtractPlugin.loader (production環境のみ)
CSSを別ファイルに分離してまとめる
→htmlに埋め込まないためstyle-loaderは不要になる
・css-loader
CSSのメソッドをjavascriptのメソッドに変換する
・postcss-loader
pcssファイルをcssファイルに展開する
postcss-flexbugs-fixes→frexboxの挙動のズレを吸収する
postcss-preset-env→postcss-nestingなどを利用する
postcssNormalize→ブラウザごとのズレを吸収する
・resolve-url-loader
外部からファイルを読み込むときのパスを通す
getStyleLoadersで追加のloaderが指定されているときのみ使う
追加のloaderではsourceMapをtrueにする必要がある
Ln131-Ln195
return{
ここからLn55の関数の返り値となる
mode:isEnvProduction?'production':isEnvDevelopment&&'development',
本番環境か開発環境かを返す
// Stop compilation early in productionbail:isEnvProduction,
エラーが発生したときにコンパイルを早期終了させる
(具体例などがなかったのでちょっとイメージが掴みづらいです...)
devtool:isEnvProduction?shouldUseSourceMap?'source-map':false:isEnvDevelopment&&'cheap-module-source-map',
bundle後のファイルでエラーが発生したときにbundle前のファイルをどう参照するかの設定
Ln33のshouldUseSourceMapもここで使われる
本番環境だともとのコードのままSourceMapが作られますが、開発環境だと少し簡略化された形でSourceMapが作られるようです
(1ファイルが1行で表された形でbundleファイルが作られる...?)
entry:[// Include an alternative client for WebpackDevServer. A client's job is to// connect to WebpackDevServer by a socket and get notified about changes.// When you save a file, the client will either apply hot updates (in case// of CSS changes), or refresh the page (in case of JS changes). When you// make a syntax error, this client will display a syntax error overlay.// Note: instead of the default WebpackDevServer client, we use a custom one// to bring better experience for Create React App users. You can replace// the line below with these two lines if you prefer the stock client:// require.resolve('webpack-dev-server/client') + '?/',// require.resolve('webpack/hot/dev-server'),isEnvDevelopment&&require.resolve('react-dev-utils/webpackHotDevClient'),// Finally, this is your app's code:paths.appIndexJs,// We include the app code last so that if there is a runtime error during// initialization, it doesn't blow up the WebpackDevServer client, and// changing JS code would still trigger a refresh.].filter(Boolean),
読み取りの起点を設定する
初期だとpaths.appIndexJs(src/indexが指定されている)と、
WebpackDevServer(ファイルセーブしたときに再読込する)に接続するためのclient
の2つ
output:{// The build folder.path:isEnvProduction?paths.appBuild:undefined,// Add /* filename */ comments to generated require()s in the output.pathinfo:isEnvDevelopment,// There will be one main bundle, and one file per asynchronous chunk.// In development, it does not produce real files.filename:isEnvProduction?'static/js/[name].[contenthash:8].js':isEnvDevelopment&&'static/js/bundle.js',// TODO: remove this when upgrading to webpack 5futureEmitAssets:true,// There are also additional JS chunk files if you use code splitting.chunkFilename:isEnvProduction?'static/js/[name].[contenthash:8].chunk.js':isEnvDevelopment&&'static/js/[name].chunk.js',// webpack uses `publicPath` to determine where the app is being served from.// It requires a trailing slash, or the file assets will get an incorrect path.// We inferred the "public path" (such as / or /my-project) from homepage.publicPath:paths.publicUrlOrPath,// Point sourcemap entries to original disk location (format as URL on Windows)devtoolModuleFilenameTemplate:isEnvProduction?info=>path.relative(paths.appSrc,info.absoluteResourcePath).replace(/\\/g,'/'):isEnvDevelopment&&(info=>path.resolve(info.absoluteResourcePath).replace(/\\/g,'/')),// Prevents conflicts when multiple webpack runtimes (from different apps)// are used on the same page.jsonpFunction:`webpackJsonp${appPackageJson.name}`,// this defaults to 'window', but by setting it to 'this' then// module chunks which are built will work in web workers as well.globalObject:'this',},
出力内容の設定
・path: どこにファイルを作成するか
・pathinfo: 使用したファイルをコメントで表示するか
・filename: 生成するファイルの名前
・futureEmitAssets:
Tells webpack to use the future version of asset emitting logic, which allows freeing memory of assets after emitting. It could break plugins which assume that assets are still readable after they were emitted.
ドキュメントより抜粋。常に最新のものを取得することで内部にデータを持たないようにするって感じだと解釈。ただ何を指すのかピンと来ない
※この機能はwebpack5.0以降ではデフォルトになるようです
・chunkFileName: ファイルをchunkで区切ったときのchunkのファイル名
・publicPath: bundleファイルのアップロード先
・devtoolModuleFilenameTemplate: sourceMapの名前の付け方
・jsonpFunction: 外部のデータを取得するのに使われるjsonpの設定
・globalObject: bundle後のコードでの即時関数の第一引数。Node.jsを使う場合デフォルトだとエラーになるのでthisにしないといけない
Ln196-Ln273
optimization:{
以降はバンドル後のコードの最適化についての設定を表している
minimize:isEnvProduction,
コードの最小化を行うかを判断するフラグ。trueなら後述のminimizerによりコードの最小化が行われる
minimizer:[
以降最小化の方法を指定するminimizerに関する設定となる
デフォルトではTenserPlugin, OptimizeCSSAssetsPluginの2つが用意されている
// This is only used in production modenewTerserPlugin({terserOptions:{parse:{// We want terser to parse ecma 8 code. However, we don't want it// to apply any minification steps that turns valid ecma 5 code// into invalid ecma 5 code. This is why the 'compress' and 'output'// sections only apply transformations that are ecma 5 safe// https://github.com/facebook/create-react-app/pull/4234ecma:8,},compress:{ecma:5,warnings:false,// Disabled because of an issue with Uglify breaking seemingly valid code:// https://github.com/facebook/create-react-app/issues/2376// Pending further investigation:// https://github.com/mishoo/UglifyJS2/issues/2011comparisons:false,// Disabled because of an issue with Terser breaking valid code:// https://github.com/facebook/create-react-app/issues/5250// Pending further investigation:// https://github.com/terser-js/terser/issues/120inline:2,},mangle:{safari10:true,},// Added for profiling in devtoolskeep_classnames:isEnvProductionProfile,keep_fnames:isEnvProductionProfile,output:{ecma:5,comments:false,// Turned on because emoji and regex is not minified properly using default// https://github.com/facebook/create-react-app/issues/2488ascii_only:true,},},sourceMap:shouldUseSourceMap,}),
minimizerの1つ、TerserPluginについての設定
parse: コードをどの型に変換するか。ECMAScript8に変換するように設定されている
※ここでは8だが、他の場所で5に上書き設定される
compress: 圧縮方法についての設定
- ecma: 変換後の型。ここではECMAScript5になっている
- warnings: 記述なし...おそらくwarningを表示するかを表す
- comparisons: 論理系の表現を簡略化する
- inline: 関数を1行に圧縮するかの設定。初期では変数宣言のない関数まで圧縮
mangle: 変数名を短くするなど
- safari10: safari10/11のバグに対応させるかのフラグ
keep_classnames, keep_fnames: classnameなどをそのままにするか
output: 出力内容の設定
- ecma: 変換後の型。ここではECMAScript5になっている
- comments: コメントアウトされた部分を残すか
- ascii_only: 対応する文字の範囲の指定
sourceMap: 圧縮時にsourceMap対応するか(Ln33参照)
// This is only used in production modenewOptimizeCSSAssetsPlugin({cssProcessorOptions:{parser:safePostCssParser,map:shouldUseSourceMap?{// `inline: false` forces the sourcemap to be output into a// separate fileinline:false,// `annotation: true` appends the sourceMappingURL to the end of// the css file, helping the browser find the sourcemapannotation:true,}:false,},cssProcessorPluginOptions:{preset:['default',{minifyFontValues:{removeQuotes:false}}],},}),
minimizerのもう一つ、OptimizeCSSAssetsPluginについての設定
cssProcessorとして使われているcssnanoにオプションを引き渡している
cssProcessorOptions
- parser: safePostCssParserによってCSSが壊れていても読み込むことができる
- map
- inline: sourceMapを同じファイルに作成するか
- annotation: sourceMapのURLをCSSに入れるか
cssProcessorPluginOptions
- minifyFontValues: 文字をサイズの小さい形に変換する
removeQuotesがfalseなのでクオートは省略されない
// Automatically split vendor and commons// https://twitter.com/wSokra/status/969633336732905474// https://medium.com/webpack/webpack-4-code-splitting-chunk-graph-and-the-splitchunks-optimization-be739a861366splitChunks:{chunks:'all',name:false,},
各ファイルの呼び出し回数をもとに各chunkを適切に分解、統合する
chunks: どのタイプのchunkを最適化対象にするか
name: chunkのファイル名(falseの場合変更なし)
// Keep the runtime chunk separated to enable long term caching// https://twitter.com/wSokra/status/969679223278505985// https://github.com/facebook/create-react-app/issues/5358runtimeChunk:{name:entrypoint=>`runtime-${entrypoint.name}`,},
各エントリーポイントで動いているファイルのみでchunkを新しく作成する
name: 新しく作られるchunkのファイル名
終わりに
今回はwebpack.configの前半部分の内容についてどんなことをやっているかの簡単な説明をさせていただきました。残りの部分は1記事にまとまるかわかりませんが今月(2020/08)中には出したいなと考えております
また、調べてもいまいち内容のつかめなかった部分もあったので詳しい方は教えていただけるとありがたいです
参考ページ
この記事を書くにあたっていろいろなサイトを参考にさせていただきました
量の問題で書ききれなかった部分も多いのでもしわかりにくい点があれば以下のページを参考にしていただけると幸いです
全体
https://webpack.js.org/
http://js.studio-kingdom.com/webpack/api/configuration
https://qiita.com/soarflat/items/28bf799f7e0335b68186
strictモード
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Strict_mode
sourceMap, devtool
https://chuckwebtips.hatenablog.com/entry/2016/03/02/000000
https://t-hiroyoshi.github.io/webpack-devtool/
https://webpack.js.org/configuration/devtool/
InlineChunkHtmlPlugin
https://www.npmjs.com/package/html-webpack-inline-chunk-plugin
Eslintrc
https://github.com/facebook/create-react-app/issues/9047
env
https://maku77.github.io/nodejs/env/dotenv.html
MiniCssExtractPlugin
https://reffect.co.jp/html/webpack-4-mini-css-extract-plugin
css-loader, style-loader
https://ics.media/entry/17376/
postcss
https://qiita.com/okumurakengo/items/a10f6fa4b77b5b088cb9
https://unformedbuilding.com/articles/php-based-css-preprocessor-pcss-and-css-crush/
https://qiita.com/naru0504/items/86bc7c6cab22a679553e
https://techacademy.jp/magazine/19732
resolve-url-loader
https://e-joint.jp/907/
entry(webpackHotDevClient)
https://www.slideshare.net/ssuserc9c8d8/reactscriptswebpack-130687608
output.globalObject
https://qiita.com/riversun/items/1da0c0668d0dccdc0460
optimization
https://webpack.js.org/configuration/optimization/
ECMAScript
https://ja.wikipedia.org/wiki/ECMAScript
terserOptions
https://github.com/terser/terser
https://gist.github.com/shqld/d101cae50dd83ab7d3487cdb10b80f4d
cssProcessor
https://github.com/NMFR/optimize-css-assets-webpack-plugin
https://site-builder.wiki/posts/9654
https://cssnano.co/optimisations/minifyfontvalues
splitChunks
https://blog.hiroppy.me/entry/mechanism-of-webpack#SplitChunksPlugin-v4
runtimeChunk
https://webpack.js.org/configuration/optimization/#optimizationruntimechunk