整个Web页面是基于ReactJS的,js打包用的webpack,现在想在Web页面端实现一种插件机制,可以动态载入第三方写的js插件。这个插件有一个约定的入口,插件被载入后调用该入口函数,插件内部实现渲染逻辑。插件的实现也使用了ReactJS,当然理论上也可以不使用。预期的交互关系是这样的:
1 2 3 4 5 6 7 8 9 10 |
|
在主页面上支持这种插件机制,有点类似一个应用市场,主页面作为应用平台,插件就是应用,用户可以在主页面上选用各种插件。
目前主页面里ReactJS被webpack打包进了bundle.js,如果插件也把ReactjS打包进去,最终在载入插件后,浏览器环境中就会初始化两次ReactJS。而ReactJS是不能被初始化多次的。此外,为了插件编写方便,我把一些可重用的组件打包成一个单独的库,让主页面和插件都去依赖。这个库自然也不能把ReactJS打包进来。何况还有很多三方库,例如underscore、ReactDOM最好也能避免重复打包,从而可以去除重复的内容。所以,这里就涉及到如何在webpack中拆分这些库。
需要解决的问题:
关于第二个问题,我选用了RequireJS,但其实它不是用于我这种场景的,不过我不想自己写一个js载入器。用RequireJS在我这种场景下会带来一些问题:webpack在打包js文件时会检查是否有AMD模块加载器,如果有则会把打包的代码作为AMD模块来加载。对于三方库的依赖就需要做一些适配。
开始做这件事时我是不熟悉RequireJS/AMD的,导致踩了不少坑。过程不表,这里就记录一些关键步骤。
公共组件库及插件是必须要打包为library的,否则没有导出符号:
1 2 3 4 5 6 7 |
|
此外,为了不打包三方库进bundle.js,需要设置:
1 2 3 4 5 |
|
externals
中key为代码中require
或import xxx from 'xxx'
中的名字,value为输出代码中的名字。以上设置后,webpack打包出来的代码类似于:
1 2 3 4 5 6 |
|
了解了RequireJS后就能看懂上面的代码,意思是定义我这里说的插件或公共库为一个模块,其依赖React
及_
模块。
插件及公共库如何编写?
1 2 3 4 5 6 7 8 9 10 11 |
|
入口js中export的内容就会成为这个library被require载入后能拿到的符号。这个库在webpack中引用时同理。注意需要设置库的入口文件:
1 2 |
|
对于本地库,可以通过以下方法在本地使用:
1 2 3 4 |
|
package.json
中也不需要依赖该文件,如果不自己install,也是可以在package.json中依赖的,类似:
1
|
|
经过以上步骤后,在主页面中载入插件打包的bundle.js时,会得到错误,说找不到React模块。我这里并没有完全改造为RequireJS的模块,所以我在页面中是静态引入react的,即:
1 2 |
|
当执行插件后,RequireJS会去重新载入react.js,如果能load成功,就又会导致浏览器环境中出现两份ReactJS,解决方法是:
1 2 3 4 5 6 7 8 9 10 11 |
|
即,因为react被静态引入,就会存在全局变量window.React,所以这里是告诉RequireJS我们定义react
模块就是全局变量React。此时webpack中打出的文件中require(['react'], xx
时,就不会导致RequireJS再去从服务端载入react.js文件。
使用RequireJS后,要动态载入插件,代码就类似于:
1 2 3 |
|
最后,之所以没有把页面全部改造为RequireJS,例如通过require载入主页面,主页面依赖react、公共组件库等,是因为我发现RequireJS的载入顺序与项目中使用的部分界面库有冲突,导致一些<a>
的事件监听丢失(如下拉菜单不可用),根本原因还没找到。