虽然在实际开发过程中,我们经常会使用脚手架来初始化一个项目,而脚手架一般都包含了完善的
webpack
配置,例如create-react-app这个脚手架,其把所有关于构建的内容封装在了react-scripts
包中,在实际开发中,我们只需要运行yarn run start/build/test
即可,它就可以帮我们搞定代码压缩、分隔,jsx
和es6
代码编译到es5
等。
回到webpack
,我们都知道一个完整的webpack
配置必定是要包含入口(entry)、输出(output),可能还需要模块-加载器(loader)来处理不同类型的模块、或是使用插件(plugin)来在构建过程中自定义某些动作。除此之外,还有webpack4
中才引入的用于性能和构建优化的optimization,还有用于开发环境的开发服务器(devServer)。还有不那么常用和深入人心的解析(resolve)。本篇将以react-scripts
包的webpack配置中关于resolve
的使用为基础,介绍如何在实际项目中可能会用到的自定义解析。
他们是怎么写的
首先来看看react-scripts
关于resolve
的配置:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15resolve: {
modules: ['node_modules', paths.appNodeModules].concat(
modules.additionalModulePaths || []
),
extensions: paths.moduleFileExtensions
.map(ext => `.${ext}`)
.filter(ext => useTypeScript || !ext.includes('ts')),
alias: {
'react-native': 'react-native-web',
},
plugins: [
PnpWebpackPlugin,
new ModuleScopePlugin(paths.appSrc, [paths.appPackageJson]),
],
},
modules
属性:其指定了webpack
在进行模块解析时应该搜索的目录,该属性可通过数组的方式指定一系列的路径,默认值是:["node_modules"]
,也就是说,在不指定该属性的情况下,如果我们引入import xx from xx
,则webpack
会默认在根目录的node_modules
目录下查找该模块。上面的配置中指定了额外两种模块解析路径,其分别是path.appNodeModules
和modules.additionalModulePaths
,其中path.appNodeModules
最终指向的是path.resolve(fs.realpathSync(process.cwd()), 'node_modules')
也就是说,在默认情况下,该路径是node_modules
的决定路径。modules.additionalModulePaths
指向一个自定义路径,并通过getAdditionalModulePaths(config)
方法生成该路径,如果config
等于{}
,则返回process.env.NODE_PATH
(经过一些列的处理),否则的话如果config.baseUrl
存在且等于modules.additionalModulePaths
或者appSrc
则返回,否则抛出错误。也就是说,我们可以通过jsconfig.json
来指定baseUrl
属性,并且该属性只能是node_modules
目录或src
目录。extensions
属性:自动解析的确定的扩展,默认值是['.js', '.json']
,也就是说,在默认情况下,我们import ClsA from './clsa'
,可以解析到clsa.js
或是cls.json
。上面的配置重写了extensions
,定义了更多的扩展,并根据当前是否是typescript
项目而是用对应的拓展。paths.moduleFileExtensions
定义如下:1
2
3
4
5
6
7
8
9
10
11
12
13const moduleFileExtensions = [
'web.mjs',
'mjs',
'web.js',
'js',
'web.ts',
'ts',
'web.tsx',
'tsx',
'json',
'web.jsx',
'jsx',
];
如何判别当前项目是typescript
项目呢?useTypeScript
是这样定义的:const useTypeScript = fs.existsSync(paths.appTsConfig);
,很明显,通过判断是否存在paths.appTsConfig
指向的文件也就是tsconfig.json
,如果存在该文件,则表示该项目是中可以引用那些以ts
为后缀的文件而不需要指定扩展名。
- alias属性:创建模块的别名,确保在引入某些模块时可以变得简单。在上面中
alias
的配置是:1
2
3alias: {
'react-native': 'react-native-web',
},
也就是在我们使用名为react-native
的模块时,其默认指向的是react-native-web
模块,例如:1
import { View } from 'react-native';
此时项目中根本没安装react-native
,仅安装了react-native-web
,也就是说react-native
成了react-native-web
的别名。那么问题来了:为什么我们不直接写import { View } from 'react-native-web'
呢?
其实这涉及到React-Native
强调的一次编写,处处使用
,这里的使用并不仅仅是iOS和Android代码共用,而是React-Native
和Web
之间的代码共享。react-native-web
就是这样一个库,它把react-native
实现的组件实现成为Web
组件,并且表现和react-native
组件一致。这样,如果我们拿到的是一份react-native
的代码,添加别名过后,就无需把所有的react-native
全都改成react-native-web
,这样就保证了两端代码的统一。
扯远了,继续。
plugins
属性:如果在配置解析的过程中需要插件的话,就可以在这里指定。上面的代码使用了pnp-webpack-plugin
和react-dev-utils/ModuleScopePlugin
,pnp-webpack-plugin
是为了解决require()
时过多的I/O
操作带来的性能消耗,pnp
思想来自Yarn
团队,目的是为了解决安装和引用依赖效率过低问题。其建立了一张映射表,这张表记录了依赖版本关联和依赖与依赖之间的关联以及依赖存放的位置。有了这张表,就可以跳过繁琐的查找过程直接确定依赖在文件中的位置,从而提高性能。详情见stackoverflow。ModuleScopePlugin
插件的官方解释是:该插件可以确保来自源程序目录(也就是/src
)的相对导入不会使用到外部依赖。new ModuleScopePlugin(paths.appSrc, [paths.appPackageJson])
接收两个参数,分别指定了源程序目录/src
和allowedFiles
指向了package.json
文件。
更多的配置
mainFields
属性:该属性是为了指定在不同环境中,默认使用哪个webpack
的字段作为导入的入口。例如某个模块的package.json
文件中执行了以下入口:1
2
3
4
5{
module: 'index.js',
main: 'build/index.node.js',
browser: 'build/index.js',
}
那么在Node
环境下,默认会使用build/index.node.js
导入,而在浏览器环境中,默认使用build/index.js
导入。
mainFiles
属性:解析目录是默认使用的文件名。这个在实际开发中使用得比较多,例如我们开发的某个页面,文件路径为/src/views/Home/index.js
,那么在实际使用这个页面的时候直接使用import Home from './src/views/Home'
即可,因为mainFiles
默认的配置是:['index']
,这个属性不建议自定义。
resolve
还有很多属性,可以让我们充分自定义整个解析过程,但从react-scripts
的实践上来看,其也是针对某些属性进行了定制,并没有一味的自定义。在对某个属性不是很熟悉并且没有过实践,建议不要盲目的修改。并且resolve
的所有属性都提供了适应绝普通场景的默认值。最后,resolve
使用愉快。
完。