1.0.0-alpha.1 • Published 9 months ago

@fishx/remote-component v1.0.0-alpha.1

Weekly downloads
25
License
MIT
Repository
-
Last release
9 months ago

@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.jsonscriptes 中增加一条命令,用来打包远程组件。
{
  "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',
}

参数介绍:

  1. entry:指定打包的入口

打包的入口约定是以 src/components/xxx 下的 index.js 文件作为入口的,entry 是一个字符串数组,指定哪些远程 portlet 需要被打包,比如这里的配置指定了 src/components/example.jssrc/components/example1.js 两个文件作为打包入口。

也可以使用@别名来指定打包路径,比如 @/widgets/example 比如打包时的入口是 src/widgets/example

  1. externals:指定远程组件打包时需要排除的依赖包
  2. outputDir:指定远程 portlet 打包后的输出目录,默认会输出到 build/remoteComponents 目录下
  3. devtool:指定 sourcemap 级别,默认为 source-map
  • 配置完毕后先执行 npm run build 再执行 npm run build:remote-components 命令就可以在 build 目录下看到打包后生成的远程组件资源。

远程组件作为子组件集成到主工程中,打包的时候最好剔除掉主工程中已经包含的模块,比如 @whalecloud/fdxreactclassnames 等包。这样打包后生成的产物体积可以大大缩小,避免加载冗余模块,提高模块加载效率。

集成多个组件

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

faq1

  • 在远程组件中使用 dva 的时候,远程组件打包的时候一定要排除掉 react-redux 这个包,然后在 remote-component.config.js 配置文件中将 react-redux 加入依赖列表,因为远程组件需要和主系统共享一个 store 对象,否则会报错。