@fishx/remote-component v1.0.0-alpha.1
@fishx/remote-component
FishX
提供了远程组件,用户提供一个 url
,即可加载远程组件。
基本用法
用户可以提供一个远程组件,比如:
const HelloWorld = ({ name }) => <div>Hello {name}!</div>
远程组件经过编译打包后,就可以在想要加载远程组件的地方使用:
import React from 'react'
import { RemoteComponent } from '@fishx/remote-component'
const RemoteHelloWorld = ({ name }) => (
<RemoteComponent url="http://localhost:8080/main.js" name={name} />
)
const HelloWorld = (props) => <div>Hello{props.name}</div>
export default () => {
return (
<>
<HelloWorld name="Local" />
<RemoteHelloWorld name="Remote" />
</>
)
}
远程组件的打包
fishx
提供了两种方式来对远程组件进行打包。
一种是如果在 fishx
工程里,可以使用 fishx
自带的 fishx remote
命令进行远程组件的打包,
FishX
提供了 fishx remote
命令行来执行远程组件的打包。
fishx remote 进行打包
使用方法
fishx
工程下的package.json
的scriptes
中增加一条命令,用来打包远程组件。
{
"scripts": {
"build:remote-components": "fishx remote"
}
}
- 在
fishx
工程下的fishx.config.ts
配置文件中增加打包远程组件的配置。
fishx.config.ts
remoteComponents: {
// 需要打包的远程组件,注意打包的远程组件必须要放在 src/components 目录下,或者可以使用@别名来指代src目录
entry: [
'example', // 或者 '@/components/example', 这两个路径等效
'example1'
],
externals: {
react: "react",
classnames: 'classnames',
'@fishx/utils': 'fishx/utils',
'@whalecloud/fdx': 'whalecloud/fdx'
},
outputDir: 'remoteComponents',
devtool: 'source-map',
}
参数介绍:
- entry:指定打包的入口
打包的入口约定是以 src/components/xxx
下的 index.js
文件作为入口的,entry 是一个字符串数组,指定哪些远程 portlet 需要被打包,比如这里的配置指定了 src/components/example.js
和 src/components/example1.js
两个文件作为打包入口。
也可以使用@别名来指定打包路径,比如 @/widgets/example
比如打包时的入口是 src/widgets/example
。
- externals:指定远程组件打包时需要排除的依赖包
- outputDir:指定远程 portlet 打包后的输出目录,默认会输出到
build/remoteComponents
目录下 - devtool:指定 sourcemap 级别,默认为
source-map
- 配置完毕后先执行
npm run build
再执行npm run build:remote-components
命令就可以在build
目录下看到打包后生成的远程组件资源。
远程组件作为子组件集成到主工程中,打包的时候最好剔除掉主工程中已经包含的模块,比如 @whalecloud/fdx
、react
、classnames
等包。这样打包后生成的产物体积可以大大缩小,避免加载冗余模块,提高模块加载效率。
集成多个组件
fishx remote
通过 --mode=pages
参数,可以通过一个 url
加载多个远程组件,使用的时候通过给 RemoteComponent
组件传递 componentName
参数,选择需要加载的组件。
--mode=pages
模式下的打包会读取 fishx 的路由配置文件,因此,componentName
的值就是路由的 path。
const RemoteHelloWorld = ({ name }) => (
<RemoteComponent url="http://localhost:8080/main.js" componentName="demo" />
)
远程组件编写可以参考这里:http://gitlab.iwhalecloud.com/fish-x/fishx/tree/develop/sample/fishx-remote-component-complex
使用 minifish 来进行打包
开发步骤
通过 yarn create fishx widget-name
选择微件模板项目进行创建。
D:\code>yarn create fishx widget03
yarn create v1.22.17
[1/4] Resolving packages...
[2/4] Fetching packages...
[3/4] Linking dependencies...
[4/4] Building fresh packages...
success Installed "create-fishx@1.1.12" with binaries:
- create-fishx
create-fishx version: 1.1.12
? Choose a template type (Use arrow keys)
FDX4.0 模板
FDX3.0 模板
fishx-mobile模板
预置主题的fishx模板
> 微件模板
使用说明
minfish 提供了三种打包模式:开发模式、打包模式、预览模式
- 开发模式
执行 npm run start
,会进入开发模式,这种模式下可以方便用户进行本地开发调试。
- 打包模式
执行 npm run build:remote
,会生成远程组件打包后的 js 资源,输出到 build 目录下,并且会上传到服务器,通过 https://fish.iwhalecloud.com/fishx-widget-preview/#/ 网站可以预览您开发的远程组件,其中工程下 package.json 中的 name 字段很重要,必须要保持唯一,上传的目录是以此名称进行区分的。
- 预览模式
执行 npm run build:preview
会将远程组件进行打包,输出到 preview 目录下,此目录可以部署到服务器进行预览。
远程组件的使用
常规用法
- 首先需要在
FishX
工程的 src 目录下创建一个remote-component.config.js
文件,用来指定远程组件打包中剔除的模块
remote-component.config.js
module.exports = {
resolve: {
react: require('react'),
classnames: require('classnames'),
'@fishx/rest': require('@fishx/rest'),
antd: require('antd'),
lodash: require('lodash'),
'@fishx/i18n': require('@fishx/i18n'),
'@fishx/router': require('@fishx/router'),
'@whalecloud/fdx': require('@whalecloud/fdx'),
},
}
- 使用 @fishx/remote-component 加载远程组件
import React from 'react'
import { RemoteComponent } from '@fishx/remote-component'
const RemoteHelloWorld = ({ name }) => (
<RemoteComponent url="http://localhost:8080/main.js" name={name} />
)
const HelloWorld = (props) => <div>Hello{props.name}</div>
export default () => {
return (
<>
<HelloWorld name="Local" />
<RemoteHelloWorld name="Remote" />
</>
)
}
说明:url 地址可以使用 http-server 在远程组件打包之后的 dist 目录中启动一个 http-server 服务,比如: http-server --cors -p 8080
手工配置 remote-component.config.js
如果不想在 src
目录下指定 remote-component.config.js
配置文件,也可以使用以下方式来指定 remote-component.config.js
src/components/RemoteComponent.js
import { createRemoteComponent, createRequires } from '@fishx/remote-component'
import { resolve } from '../../remote-component.config.js'
const requires = createRequires(resolve)
export const RemoteComponent = createRemoteComponent({ requires })
正常使用:
import React from 'react'
import { RemoteComponent } from './components/RemoteComponent'
const url = 'http://localhost:8080/main.js'
const RemoteHelloWorld = (props) => <RemoteComponent url={url} {...props} />
export default () => {
return <RemoteHelloWorld name="Remote" />
}
使用 Render 属性
使用 render
属性可以获取组件渲染过程中的更多状态,比如是否出现 error
。
import React from "react";
import { RemoteComponent } from "./components/RemoteComponent";
const url = "http://localhost:8080/main.js";
const HelloWorld = props =>
<RemoteHelloWorld
url={url}
render={({ err, Component }) =>
err ? <div>{err.toString()}</div> : <Component {...props} />
}
/>
);
export default () => {
return (
<RemoteHelloWorld name="Remote" />
)
}
使用 React Hooks
import { createRequires, createUseRemoteComponent } from '@fishx/remote-component'
import { resolve } from '../../remote-component.config.js'
const url = 'http://localhost:8080/main.js'
const requires = createRequires(resolve)
const useRemoteComponent = createUseRemoteComponent({ requires })
const HelloWorld = (props) => {
const [loading, err, Component] = useRemoteComponent(url)
if (loading) {
return <div>Loading...</div>
}
if (err != null) {
return <div>Unknown Error: {err.toString()}</div>
}
return <Component {...props} />
}
远程组件中使用国际化
远程组件中使用国际化的原理非常简单:
远程组件集成到主系统后,主系统每次切换多语言都会将系统当前语言存储到 cookie
中,同时会刷新一次浏览器,因此,在子组件的 componentDidMount
或者 useEffect
中先从 cookie
中获取一下系统当前的多语言,然后根据当前的语言类型从 js 对象(存储远程组件的国际化翻译文件)中就可以获取对应的国际化文本翻译。
使用方法
子工程中引入@fishx/utils 包
import { cookieUtils } from '@fishx/utils' const { getCookie } = cookieUtils
在
webpack
配置文件的externals
字段中排除掉@fishx/utils
,因为主系统已经安装了@fishx/utils
,无需重复安装externals: { '@fishx/utils': '@fishx/utils' }
在远程组件的
componentDidMount
或者useEffect
中读取cookie
中的国际化语言const [textHello, setTextHello] = useState('') useEffect(() => { const currentLang = getCookie('userLocale') || 'zh-CN' setTextHello(locales[currentLang]['hello']) }, [])
远程组件中使用
const RemoteComponent = (props) => { const [textHello, setTextHello] = useState('') useEffect(() => { const currentLang = getCookie('userLocale') || 'zh-CN' setTextHello(locales[currentLang]['hello']) }, []) return <div>{textHello} Remote World!</div> }
在主工程的
remote-component.config.js
文件中加上@fishx/utils
包module.exports = { resolve: { '@fishx/utils': require('@fishx/utils'), }, }
远程组件中使用 dva
远程组件中支持使用 dva 状态管理,状态管理的 model 可以放在远程组件侧,当主系统中开启了 dva 插件后,会在 window 上提供一个window.g_app
对象,通过这个全局变量我们可以实现 model 的动态注册。
远程组件示例:
import React from 'react'
import { connect } from 'react-redux'
import model from './models/todo'
class Example extends React.Component {
state = {
ready: false,
}
componentDidMount() {
if (window.g_app) {
window.g_app.model(model) // 判断window上是否存在window.g_app对象,存在的话就动态注入远程组件的model
this.setState({
ready: true,
})
}
}
render() {
const { remoteExample } = this.props
const { ready } = this.state
return ready ? <div>{remoteExample.greeting}</div> : null
}
}
export default connect(({ remoteExample }) => ({
remoteExample,
}))(Example)
完整示例可以参考这里:http://gitlab.iwhalecloud.com/fish-x/fishx/tree/develop/sample/fishx-remote-component-main
UMD模块
远程组件支持通过 UMD
模块规范引入并使用,命名为 window.ReactComponent,提供的方法有以下:
- createRemoteComponent:创建远程组件
- createRequires: 为远程组件提供所需依赖模块
加载远程组件库
加载远程组件库,html
文件中添加如下代码:
<script src="https://www.unpkg.com/@fishx/remote-component@1.0.0-alpha.1/dist/remote-component.min.js"></script>
使用远程组件库
首先声明远程组件。 createRemoteComponent 方法的参数为远程组件所需的外部依赖,假设 RemoteComponent 打包时剔除了 react 和 fdx 两个依赖项,则声明时提供对应的依赖。
const RemoteComponent = window.RemoteComponent.createRemoteComponent({
requires: window.RemoteComponent.createRequires({
react: React,
'whalecloud/fdx': window.fdx,
}),
})
声明之后就可使用远程组件了
JSX 语法风格
export default (props) => <RemoteComponent url="http://www.example.com/my-component.js" {...props} >;
无法使用 JSX 时,也可利用 React.createElement
来使用远程组件。
export default (props) => React.createElement(RemoteComponent, Object.assign({ url: url }, props))
FAQ
- 引用
lodash
之后,全局window
对象上面多了window._
方法。
出现这种问题的原因主要是因为全局引用了 lodash
导致的,需要检查一下业务代码中是否全量引入了 lodash
,比如下面几种用法。
错误用法:
import { get } from 'lodash'
或者:
import lodash from 'lodash'
正确用法:
import get from 'lodash/get'
如果排除了业务代码导致的,需要检查一下 remote-component.config.js
配置文件,是否全量引入了 lodash
。
- 在远程组件中使用
dva
的时候,远程组件打包的时候一定要排除掉react-redux
这个包,然后在remote-component.config.js
配置文件中将react-redux
加入依赖列表,因为远程组件需要和主系统共享一个store
对象,否则会报错。