不下载npm包到本地项目,如何动态加载npm包?

Smile丶
  • 18

问题描述

目前想做一个根据配置json来生成页面的项目,倾向于使用react来构建这个项目。
大体是想完成这样的一件事情,从后端获取一个json对象,前端根据这个json对象渲染对应的组件

{
  page:{
    width: 1920,
    height: 1080
  },
  components: [
    'Button': {
      attr: {
        width: 100,
        height: 20,
      },
      config: {
        content: '测试按钮',
      }
    },
    'Table':{
      attr: {
        width: 200,
        height:100,
      },
      data: 'xxx',
    }
  ]
}

前端拿到这样一个json,在页面渲染一个宽100,高20,内容为"测试按钮"的Button组件,渲染一个宽200,高100的表格。
目前的实现思路是通过import()来动态引入组件,

import('Button').then()

但问题是如果有100个npm的react组件,那100个组件都需要下载到项目里面,并且会存在版本问题,比如说有些页面在用Button组件的0.1版本,有些页面需要用Button组件的0.3版本,就没办法去区分。

所以说知道能不能 在 代码中 去动态引入npm react组件包,并且使用。或者把单独的react组件以src的方式引入到项目中?

看到过别人的方案,是采用的js编写的组件,该组件会生成相应的dom结构以及css以及js的逻辑,动态加载一个js就可以了,但我想用react的方式去编写组件,所以提出上面的问题。

或者有其他更好的技术方案吗?

回复
阅读 1.5k
3 个回答

systemjs简单得实现了下,response为了写起来方便改一下,url对应组件地址,name对应组件名,systemjs我也是现学现卖

<!DOCTYPE html>
<html lang="en">

<head>
  <title>Test</title>
  <meta charset="utf-8" />
  <meta name="viewport" content="minimum-scale=1, initial-scale=1, width=device-width" />
  <script src="https://cdn.jsdelivr.net/npm/systemjs/dist/system.min.js"></script>
  <script src="https://unpkg.com/react@latest/umd/react.development.js" crossorigin="anonymous"></script>
  <script src="https://unpkg.com/react-dom@latest/umd/react-dom.development.js"></script>
  <link rel="stylesheet" href="https://unpkg.com/antd@4.15.5/dist/antd.compact.css" >
</head>

<body>
  <div id="root"></div>
  <script>
    // 响应返回结果
    /*---------------------------------------------test1---------------------------------------------*/
    const response = {
      packages:
      {
        MaterialUI: {
          url: "https://unpkg.com/@material-ui/core@latest/umd/material-ui.development.js",
          components: {
            button: {
              name: "MaterialUI.Button",
              attr: {
                variant: 'contained',
                color: 'primary'
              },
              config: {
                content: 'Material Button'
              }
            },
          }
        },
        antd: {
          url: "https://unpkg.com/antd@4.15.5/dist/antd.min.js",
          components: {
            button: {
              name: "antd.Button",
              attr: {
                type: "primary"
              },
              config: {
                content: 'Antd Button'
              }
            }
          }
        }
      }
    }
    /*---------------------------------------------test2---------------------------------------------*/
    // const response = {
    //   packages:
    //   {
    //     MaterialUI: {
    //       url: "https://unpkg.com/@material-ui/core@latest/umd/material-ui.development.js",
    //       components: {
    //         switch: {
    //           name: "MaterialUI.Switch",
    //           attr: {
    //             checked: true
    //           },
    //           config: {
    //           }
    //         },
    //       }
    //     },
    //     antd: {
    //       url: "https://unpkg.com/antd@4.15.5/dist/antd.min.js",
    //       components: {
    //         switch: {
    //           name: "antd.Switch",
    //           attr: {
    //             checkedChildren: "开启",
    //             unCheckedChildren: "关闭",
    //             defaultChecked: true
    //           },
    //           config: {
    //           }
    //         }
    //       }
    //     }
    //   }
    // }
    /*---------------------------------------------END---------------------------------------------*/

    const REMOTE = {
      packages: Object.keys(response.packages),
      components: []
    }
    const imports = {}
    REMOTE.packages.map((item) => {
      const package = response.packages[item];
      imports[item] = package.url;
      REMOTE.components.push(Object.keys(package.components))
    })

    // 添加systemjs-importmap的script
    const script1 = document.createElement('script');
    script1.type = 'systemjs-importmap';
    script1.text = `           
     {
        "imports": ${JSON.stringify(imports)}
     }`;
    document.head.appendChild(script1);

  </script>

  <script>
    System.register(REMOTE.packages, function (_export, _context) {

      // 解析response成React component
      function App() {
        const items = REMOTE.components.map((componentsArray, index) => {
          const package = REMOTE.packages[index];
          return componentsArray.map((item) => {
            const componet = response.packages[package].components[item];
            const TagName = new Function(`return ${componet.name}`)();

            return React.createElement(TagName, componet.attr, componet.config.content);
          })
        })
        return React.createElement("div", null, items);
      }

      return {
        execute: function () {
          ReactDOM.render(
            React.createElement(App, null),
            document.querySelector('#root'),
          );
        }
      }
    })

  </script>
</body>

</html>

是不是可以用js document.createElement创建script标签,src到cdn链接来解决呢?例如:
<script crossorigin src="https://unpkg.com/react@17/umd/react.development.js"></script>

你可以把多项目共同使用的组件封装成一个包放进一个单独的仓库,然后在其它项目中使用
npm i -S git+ssh://git@git.com:your-component/component.git 来安装或更新

使用方法和你用其他组件是一样的:
import { ComponentOne } from your-component

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
宣传栏