antd-mobile是蚂蚁金服出的移动端设计指南和前端框架,它提供了一套基于React的移动端组件库,可以很方便地用来开发体验良好的移动应用。
使用antd-mobile遇到的问题:react-native模块找不到
在阅读了antd-mobile的介绍说明后,使用这一组件库似乎很简单,要做的只是安装和引入组件而已:
安装
$ npm install antd-mobile --save
引入组件
import { Button } from 'antd-mobile/lib/button';
ReactDOM.render(, mountNode);
antd-mobile的介绍说明中推荐使用babel-plugin-import插件来按需加载类库,但为了减少初期使用antd-mobile所面临的复杂度,以上代码采用了最简单的组件引入写法(显式指定组件的路径antd-mobile/lib/button
,并在HTML文件中单独引入CSS样式文件antd-mobile/dist/antd-mobile.min.css
)。
在安装完antd-mobile模块并引入需要的组件后,接下来的一步便是构建整个移动应用。此时,如果项目不是React Native应用,而是Web应用的话,构建过程会报错,显示react-native模块找不到(Error: Cannot resolve module 'react-native'...
)。这个错误无疑是非常令人困惑的:当前所开发的是一个普通的移动端Web项目,与react-native没有任何关系,为什么需要react-native模块?事实上,即使根据报错提示安装react-native模块,在后续的构建过程中也会报一些别的错误,导致构建失败。
进一步的调查发现,问题出在antd-mobile的组件模块设计上。由于antd-mobile被设计为同时支持React Native应用开发和Web应用开发,因此所有的组件都暴露为2个模块文件:index.js
和index.web.js
。其中,index.js
是给React Native开发使用的,而index.web.js
则是给Web开发使用的。由于Browserify和Webpack等打包工具在解析JavaScript模块引入操作时(require
或import
语句),会优先查找.js
后缀名的文件(当不指定模块文件名时,默认文件名即为index.js
),因此即使当前项目与React Native无关,组件模块的引入操作也会导致对react-native的依赖。
找到问题的原因后,解决方案初步考虑有2种:
引入模块时,显式指定模块文件的文件名(
import { Button } from 'antd-mobile/lib/button/index.web';
)。对Browserify或Webpack等打包工具进行配置,更改其模块引入操作时的后缀名优先级,使得
.web.js
文件得以优先使用。
第一种方案比较简单,对代码的改动量也很小。但事实证明,这一方案是行不通的:antd-mobile的组件代码中存在内部组件依赖(如List组件依赖ListItem组件,在List组件的index.web.js
文件中,会出现require('./ListItem')
这样的代码),而这些引入内部组件的操作并未指定具体的模块文件名,因此还是会产生require('./ListItem/index.js')
这样的效果,并最终导致对react-native的依赖。
对于第二种方案,如果是用Webpack打包,则antd-mobile社区有现成的解决方法 — 设定extensions
选项的值,并将.web.js
放在.js
之前即可。但在Browserify中,这一问题该如何解决呢?
使用Browserify遇到的问题:如何自定义模块文件后缀名的优先级?
和Webpack一样,Browserify也提供了一个叫extensions
的配置选项,用于设定模块文件的后缀名及其优先级。但和Webpack不同的是,Browserify中默认的2个模块文件后缀名(.js
和.json
)永远具有最高优先级,即使在extensions
配置选项中设定.web.js
比.js
具有更高的优先级(extensions: ['.web.js', '.js', ...]
)也无济于事。原因在于Browserify源代码中的以下这一行:
mopts.extensions = [ '.js', '.json' ].concat(mopts.extensions || []);
可以看到,无论设定的extensions
值为何,.js
和.json
永远具有最高优先级。那么,在这种情况下如何设定比.js
优先级还要高的模块文件后缀名呢?
在经过一些思索后,发现这个问题只能用比较hack的方式来解决:对于上述计算最终extensions值的操作,修改JavaScript中数组的concat
行为,让mopts.extensions
在[ '.js', '.json' ]
数组之前插入,而不是在其后添加。具体代码为:
var origin_concat = Array.prototype.concat;
Array.prototype.concat = function() {
if (this.length === 2 && this[0] === '.js' && this[1] === '.json') {
return origin_concat.apply(arguments[0], this);
}
return origin_concat.apply(this, arguments);
};
运行以上代码后,就可以通过配置extensions: ['.web.js', ...]
来用Browserify打包antd-mobile开发的Web应用了。
模块抽象:browserify-high-priority-extensions
为了方便使用,上述hack Browserify的代码被抽象为一个模块:browserify-high-priority-extensions
,其意为”让Browserify的extensions选项值具有比默认的后缀名更高的优先级“。使用该模块非常简单:
安装
$ npm install browserify-high-priority-extensions --save-dev
启用extensions高优先级设定
var hpe = require('browserify-high-priority-extensions');
hpe.enable();
启用后,即可通过配置extensions: ['.web.js', ...]
来用Browserify打包antd-mobile开发的Web应用。
取消extensions高优先级设定
当不需要配置extensions
选项高优先级时,可以用以下语句恢复到默认状态:
hpe.disable();