使用外部插件react应用程序

IT技术 reactjs typescript webpack module parcel
2021-04-29 19:07:26

我正在构建一个使用 Parcel 或 Webpack 捆绑的 React 应用程序。
该应用程序应该能够嵌入
由第三方开发并作为现代 javascript module托管在其他地方的外部 React 组件

// https://example.com/scripts/hello-plugin.js
import React from 'react';

export default class HelloPlugin extends React.Component {
    render() {
        return "Hello from external plugin!";
    }
}

主机应用程序使用异步导入来加载这些组件,例如:

// createAsyncComponent.tsx
import * as React from 'react';
import { asyncComponent } from 'react-async-component';

export default function createAsyncComponent(url: string) {
    return asyncComponent({
        resolve: () => import(url).then(component => component.default),
        LoadingComponent: () => <div>Loading {url}....</div>,
        ErrorComponent: ({ error }) => <div>Couldn't load {url}: {error.message}</div>,
    })
}

但看起来捆绑器不允许将任意 url 作为外部 javascript module导入。

Webpack 发出构建警告:“依赖项的请求是一个表达式”并且导入不起作用。Parcel 不报告任何错误,但在运行时发生 import(url) 时失败。

Webpack 作者推荐使用 scriptjs 或 little-loader 来加载外部脚本。
一个从任意 URL 加载 UMD 组件的工作示例,如下所示:

public componentDidMount() {
    // expose dependencies as globals
    window["React"] = React;
    window["PropTypes"] = PropTypes;

    // async load of remote UMD component
    $script(this.props.url, () => {
        const target = window[this.props.name];
        if (target) {
            this.setState({
                Component: target,
                error: null,
            })
        } else {
            this.setState({
                Component: null,
                error: `Cannot load component at ${this.props.url}`,
            })
        }
    });
}

此外,我在一年前看到了一个类似的问题,其中建议的方法还涉及通过窗口对象传递变量。

但我想避免使用全局变量,因为大多数现代浏览器都支持开箱即用的module。

我想知道是否有可能。也许,有任何方法可以指示捆绑器我的 import(url) 不是对主机应用程序的代码拆分块的请求,而是对加载外部 Javascript module的请求。

2个回答

在 Webpack 的上下文中,您可以执行以下操作:

import(/* webpackIgnore: true */'https://any.url/file.js')
  .then((response) => {
    response.main({ /* stuff from app plugins need... */ });
  });

然后你的插件文件会有类似的东西......

const main = (args) => console.log('The plugin was started.');
export { main };
export default main;

请注意,您可以在插件的初始化(即在插件中调用main时)将应用程序运行时的内容发送到插件,这样您就不会最终依赖于全局变量。

您可以免费缓存,因为 Webpack 会记住(缓存)给定的 URL 已经加载,因此后续调用导入该 URL 将立即解析。

注意:这似乎适用于 Chrome、Safari 和 firefox,但不适用于 Edge。我从不费心在 IE 或其他浏览器中进行测试。

我已经尝试在插件端使用 UMD 格式执行相同类型的加载,但这似乎不适用于 Webpack 加载内容的方式。事实上,有趣的是,声明为全局变量的变量并没有出现在运行时的 window 对象中。你必须明确地做window.aGlobalValue = ...才能在全局范围内获得一些东西。

显然你也可以在你的应用程序中使用 requirejs - 或类似的 - 然后让你的插件遵循该 API。

听听 Webpack 作者。你(还)不能做你想用 Webpack 做的事情。

您将不得不遵循他建议的路线。