create-react-app项目配置
喵__40c2 人气:0引言
create-react-app(以下简称cra)作为react官方提供的脚手架工具,是目前生成react项目一个非常常用和主流的工具。很多企业级的应用搭建也是基于这个脚手架工具上二次开发。最近这段正好最近学习了webpack打包配置工程化的一些内容,索性就以cra的配置为例,对这段时间的学习做一个总结。
准备工作
首先,我们要用cra创建一个项目。这个没啥好说,有手就行。
create-react-app cra-config-project
这样初始化后创建出来项目的配置信息是隐藏在node_modules中的react-scripts中的。为了更直观的看到配置信息和修改,使用
eject命令将配置弹射出来。
yarn eject
完成后,我们项目配置的目录结构变成这样。
启动命令
打开package.json 文件,在scripts中看到以下三条命令
"scripts": { "start": "node scripts/start.js", "build": "node scripts/build.js", "test": "node scripts/test.js" },
很明显,这分别是项目的启动开发环境,构建,测试的命令。我们重点看一下scripts中开发和构建的脚本。
start.js
在大概115行的位置,我们看到这样一段代码
const devServer = new WebpackDevServer(serverConfig, compiler); // Launch WebpackDevServer. devServer.startCallback(() => { ...
很明显,这就是启动开发服务器的关键代码。在开发环境的时候,我们通过webpack-dev-server来启动一个本地的服务器,然后把随时构建出来的项目放在这个服务器下面运行。实例化这个devServer对象时候传的第一个参数是服务器的配置项,包括端口号,代理,静态资源目录等,具体见https://webpack.docschina.org/configuration/dev-server/;第二个参数是webpack的相关配置。如下所示:
compiler = webpack(config);
build.js
构建脚本直接输出打包结果,自然不再需要启动本地服务。因此在获取了编译结果后,直接运行即可。因此在140行中
compiler.run((err, stats) => { //... }
在代码中我们可以看到构建时,编译过程通过promise封装,对各种错误情况进行了处理。
目录结构
在看具体的配置之前,让我们回到这张图,看一下eject命令都弹射出了哪些配置放到了项目目录中来。
比起初使状态,现在的项目目录中除了装有启动脚本文件的目录scripts外,另外增加的就是config目录。打开config目录,webpack.config.js和webpackDevServer.config.js赫然在目,根据这个文件名我们可以很明显得知,这两个文件一个是webpack的配置,一个是开发服务器devServer 的配置。接下来,我们就可以从这两个文件按图索骥,学习cra的基本配置了。
配置解析
weback.config.js
在webpack.config.js中,默认导出了一个接受一个环境变量作为返回一个配置对象的方法。那传这个环境变量的目的不言而喻,一定有很多配置开发和生产环境是不同的。接下来重头戏来了,让我们来一条一条地学习下react官方对react开发环境是怎么配置的吧。
1.entry
entry: paths.appIndexJs,
也就是 src目录下的index.js,因为cra构建的是单页应用,只有一个入口文件
2.output
output: { path: paths.appBuild, // 打包后文件目录 在config目录中path.js中配置 pathinfo: isEnvDevelopment, // webpack 在 bundle 中是否引入「所包含模块信息」的相关注释 开发环境打开 生产环境关闭 filename: isEnvProduction ? 'static/js/[name].[contenthash:8].js' : isEnvDevelopment && 'static/js/bundle.js',//打包后文件名,生产环境根据name放在不同文件,开发环境放在一个bundle.js文件中 chunkFilename: isEnvProduction ? 'static/js/[name].[contenthash:8].chunk.js' : isEnvDevelopment && 'static/js/[name].chunk.js',//chunk文件名称,生产环境和开发环境的区别是文件名中加上了hash assetModuleFilename: 'static/media/[name].[hash][ext]',//打包后的静态资源目录和文件名规则,如不指定直接放在打包后的根目录中 publicPath: paths.publicUrlOrPath,//打包后的文件部署的url地址 devtoolModuleFilenameTemplate: isEnvProduction ? info => path .relative(paths.appSrc, info.absoluteResourcePath) .replace(/\\/g, '/') : isEnvDevelopment && (info => path.resolve(info.absoluteResourcePath).replace(/\\/g, '/')),//自定义source-map文件数组使用名称 }
3.target
target: ['browserslist'],
构建目标,从 browserslist-config 中推断出平台和 ES 特性 默认是browserslist 如果browserslist不存在,为web(cra项目中browserslist在package.json中)
4.bail
bail: isEnvProduction,
错误出现时是否立即退出,生产环境下打开
5.devtool
devtool: isEnvProduction ? shouldUseSourceMap ? 'source-map' : false : isEnvDevelopment && 'cheap-module-source-map',
生成sourceMap方式,cra配置为生产环境source-map,开发环境为cheap-module-source-map。这两者的区别source-map调试时会显示列信息。devtool的配置有很多种,具体见https://webpack.docschina.org/configuration/devtool/#root
6.cache
cache: { type: 'filesystem',//缓存生成的 webpack 模块和 chunk,来改善构建速度 开发环境下默认为type:'memory' 生产环境下关闭 version: createEnvironmentHash(env.raw), cacheDirectory: paths.appWebpackCache,//缓存目录 store: 'pack', buildDependencies: { defaultWebpack: ['webpack/lib/'], config: [__filename], tsconfig: [paths.appTsConfig, paths.appJsConfig].filter(f => fs.existsSync(f) ), }, },
7.optimization 优化项
optimization: { minimize: isEnvProduction, //只在生产环境下开启 minimizer: [ //js TerserPlugin开启代码压缩 new TerserPlugin({ terserOptions: { parse: { ecma: 8, }, compress: { ecma: 5, warnings: false, comparisons: false, inline: 2, }, mangle: { safari10: true, }, keep_classnames: isEnvProductionProfile, keep_fnames: isEnvProductionProfile, output: { ecma: 5, comments: false, ascii_only: true, }, }, }), //css 代码压缩 new CssMinimizerPlugin(), ], },
8.resolve 解析
resolve: { modules: ['node_modules', paths.appNodeModules].concat( modules.additionalModulePaths || [] ),//解析模块时应该搜索的目录 extensions: paths.moduleFileExtensions .map(ext => `.${ext}`) .filter(ext => useTypeScript || !ext.includes('ts')),//如果有多个文件有相同的名字,但后缀名不同时webpack按顺序解析这些后缀名,使用户在引入模块时不带扩展名 alias: { 'react-native': 'react-native-web', ...(isEnvProductionProfile && { 'react-dom$': 'react-dom/profiling', 'scheduler/tracing': 'scheduler/tracing-profiling', }), ...(modules.webpackAliases || {}), },//创建 import 或 require 的别名,来确保模块引入变得更简单。可以给utils之类的文件色之后 plugins: [ new ModuleScopePlugin(paths.appSrc, [ paths.appPackageJson, reactRefreshRuntimeEntry, reactRefreshWebpackPluginRuntimeEntry, babelRuntimeEntry, babelRuntimeEntryHelpers, babelRuntimeRegenerator, ]), ], },//应该使用的额外的解析插件列表
9.performance
performance: false,
关闭了webpack本身的性能提示,cra本身提供了FileSizeReporter来计算和报告文件大小
10. module
终于进入到我们这个比较重要的module配置项,module配置决定了webpack如何解析非js的模块,项目中的各种静态资源,样式文件,乃至于ts tsx jsx等loader配置都是在这个模块中配置。
- source-map loader
shouldUseSourceMap && { enforce: 'pre', exclude: /@babel(?:\/|\\{1,2})runtime/, test: /\.(js|mjs|jsx|ts|tsx|css)$/, loader: require.resolve('source-map-loader'), },
- 静态资源loader
{ test: [/\.avif$/], type: 'asset', mimetype: 'image/avif', parser: { dataUrlCondition: { maxSize: imageInlineSizeLimit, }, }, }, { test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/], type: 'asset', parser: { dataUrlCondition: { maxSize: imageInlineSizeLimit, }, }, }, { test: /\.svg$/, use: [ { loader: require.resolve('@svgr/webpack'), options: { prettier: false, svgo: false, svgoConfig: { plugins: [{ removeViewBox: false }], }, titleProp: true, ref: true, }, }, { loader: require.resolve('file-loader'), options: { name: 'static/media/[name].[hash].[ext]', }, }, ], issuer: { and: [/\.(ts|tsx|js|jsx|md|mdx)$/], }, },
对各种格式的图片,svg文件的处理
- 样式文件loader
{ test: cssRegex, exclude: cssModuleRegex, use: getStyleLoaders({ importLoaders: 1, sourceMap: isEnvProduction ? shouldUseSourceMap : isEnvDevelopment, modules: { mode: 'icss', }, }), sideEffects: true, }, { test: cssModuleRegex, use: getStyleLoaders({ importLoaders: 1, sourceMap: isEnvProduction ? shouldUseSourceMap : isEnvDevelopment, modules: { mode: 'local', getLocalIdent: getCSSModuleLocalIdent, }, }), }, { test: sassRegex, exclude: sassModuleRegex, use: getStyleLoaders( { importLoaders: 3, sourceMap: isEnvProduction ? shouldUseSourceMap : isEnvDevelopment, modules: { mode: 'icss', }, }, 'sass-loader' ), sideEffects: true, }, { test: sassModuleRegex, use: getStyleLoaders( { importLoaders: 3, sourceMap: isEnvProduction ? shouldUseSourceMap : isEnvDevelopment, modules: { mode: 'local', getLocalIdent: getCSSModuleLocalIdent, }, }, 'sass-loader' ), },
对样式文件的处理, 主要是scss和css,cra 为什么没有配置less文件loader呢?开发环境下直接将所有样式注入head中style中,生产环境下结合下面要介绍的miniCssExtractPlugin插件抽出后放入不同css文件。另外,这里cra还对以.module.css 和 .module.sass后缀结尾的文件进行了css module处理,如果开发者需要对样式文件要用modules规则,可以将文件的后缀写成这两种。
11. 插件
- htmlWebpackPlugin
new HtmlWebpackPlugin( Object.assign( {}, { inject: true, template: paths.appHtml, }, isEnvProduction ? { minify: { removeComments: true, collapseWhitespace: true, removeRedundantAttributes: true, useShortDoctype: true, removeEmptyAttributes: true, removeStyleLinkTypeAttributes: true, keepClosingSlash: true, minifyJS: true, minifyCSS: true, minifyURLs: true, }, } : undefined ) ),
没啥好说的,地球人都知道的一个插件,把打包好的js文件注入到html中去,要注意的是在生产环境了开启了移除注释,合并空格一系列优化配置
- InlineChunkHtmlPlugin
isEnvProduction && shouldInlineRuntimeChunk && new InlineChunkHtmlPlugin(HtmlWebpackPlugin, [/runtime-.+[.]js/]),
这个插件辅助将一些chunk出来的模块内联到html中,比如runtime的代码,代码量不大。生产环境下开启
- InterpolateHtmlPlugin
new InterpolateHtmlPlugin(HtmlWebpackPlugin, env.raw),
HtmlWebpackPlugin的辅助插件,可以在html文件中加入变量
- ModuleNotFoundPlugin
- ReactRefreshWebpackPlugin
isEnvDevelopment && shouldUseReactRefresh && new ReactRefreshWebpackPlugin({ overlay: false, }),
热更新 react 组件,开发环境下开启
- MiniCssExtractPlugin
isEnvProduction && new MiniCssExtractPlugin({ filename: 'static/css/[name].[contenthash:8].css', chunkFilename: 'static/css/[name].[contenthash:8].chunk.css', }),
抽离css文件插件,生产环境下开启
- WebpackManifestPlugin
- ForkTsCheckerWebpackPlugin
useTypeScript && new ForkTsCheckerWebpackPlugin({ async: isEnvDevelopment, typescript: { typescriptPath: resolve.sync('typescript', { basedir: paths.appNodeModules, }), configOverwrite: { compilerOptions: { sourceMap: isEnvProduction ? shouldUseSourceMap : isEnvDevelopment, skipLibCheck: true, inlineSourceMap: false, declarationMap: false, noEmit: true, incremental: true, tsBuildInfoFile: paths.appTsBuildInfoFile, }, }, context: paths.appPath, diagnosticOptions: { syntactic: true, }, mode: 'write-references', }, issue: { include: [ { file: '../**/src/**/*.{ts,tsx}' }, { file: '**/src/**/*.{ts,tsx}' }, ], exclude: [ { file: '**/src/**/__tests__/**' }, { file: '**/src/**/?(*.){spec|test}.*' }, { file: '**/src/setupProxy.*' }, { file: '**/src/setupTests.*' }, ], }, logger: { infrastructure: 'silent', }, }),
强制ts类型检查,如果项目使用了typescript编写的话使用
- webpack.definePlugin
new webpack.DefinePlugin(env.stringified),
wepack内置插件,在浏览器环境中定义环境变量
- webpack.ignorePlugin
new webpack.IgnorePlugin({ resourceRegExp: /^\.\/locale$/, contextRegExp: /moment$/, }),
wepack内置插件,可以在打包时有选择的忽略一些内容,这里的配置是在打包moment的时候忽略moment的本地化内容
isEnvDevelopment && new CaseSensitivePathsPlugin(),
解决为了解决mac系统中文件名大小写不敏感导致的打包不报错的问题,详见https://github.com/facebook/create-react-app/issues/240
结语
对于工程化经验特别少的开发者来说,webpack的配置浩如烟海,宛如一本百科全书让人望而兴叹。但是掌握webpack可以说是前端开发者进阶的必经之路。在学习的过程中,可以自己多搞一些demo,多去尝试和实践,就会渐渐的对它熟悉起来。之后,笔者计划对webpack打包的性能优化从配置项的各个维度做一个总结,请拭目以待。
加载全部内容