How to make a shareable widget with react 18 (plugin name should be static or shareable in some way)?

Viewed 126

I am developing a shareable widget with client as react 18 and server side as Asp.net mvc core 6 which can be called by using following approach:

When I try to run the npm run build command then I can see the following index.html page in build directory and there are some js, css plugins and images in the build directory.

<!doctype html><html lang="en">
    <head><meta charset="utf-8"/>
    <link rel="icon" href="/favicon.ico"/>
    <meta name="viewport" content="width=device-width,initial-scale=1"/>
    <meta name="theme-color" content="#000000"/>
    <meta name="description" content="Web site created using create-react-app"/>
    <link rel="apple-touch-icon" href="/logo192.png"/>
    <link rel="manifest" href="/manifest.json"/>
    <title>React App</title>
    <script defer="defer" src="/static/js/main.6134db66.js"></script>
    <link href="/static/css/main.073c9b0a.css" rel="stylesheet">
</head>
<body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root">        
    </div>
</body>
</html>

Now my main focus is on calling js, css and image resources if I develop the widget. My widget can be accessed through a js and css plugin including a div element.

Now see following URLs will be dynamically generated each time.

src="/static/js/main.6134db66.js"
href="/static/css/main.073c9b0a.css"

How would I give it to my customers/clients if name 'll be changed after each npm run build.

You can see my client application source below which is calling my widget.

 <!DOCTYPE html>
    <html lang="en">
        <head>
            <meta charset="UTF-8" />
            <meta http-equiv="X-UA-Compatible" content="IE=edge" />
            <meta name="viewport" content="width=device-width, initial-scale=1.0" />
            <title>Document</title>
            <link href="http://localhost:3000/static/css/main.073c9b0a.css" rel="stylesheet"></head>
            <script defer="defer" src="http://localhost:3000/static/js/main.6134db66.js"></script>
        </head>
        <body>
            <div id="root"></div>
        </body>
    </html>

I can place it on one of my server domain but how to keep the plug name same ? I am also curios about my widget images. Will my widget image work correctly or 'll I have do something more for it ?

Any help will be appreciated.

See my package.json

{
    "name": "widget",
    "version": "0.1.0",
    "private": true,   
    "dependencies": {
        "@testing-library/jest-dom": "^5.16.3",
        "@testing-library/react": "^12.1.4",
        "@testing-library/user-event": "^13.5.0",
        "react": "^18.0.0",
        "react-dom": "^18.0.0",
        "react-scripts": "5.0.0",
        "web-vitals": "^2.1.4"
    },
    "scripts": {
        "start": "react-scripts start",
        "build": "react-scripts build",
        "test": "react-scripts test",
        "eject": "react-scripts eject"
    },
    "eslintConfig": {
        "extends": [
            "react-app",
            "react-app/jest"
        ]
    },
    "browserslist": {
        "production": [
            ">0.2%",
            "not dead",
            "not op_mini all"
        ],
        "development": [
            "last 1 chrome version",
            "last 1 firefox version",
            "last 1 safari version"
        ]
    }
}
1 Answers

I faced the same issue and solved it using react-app-rewired on a CRA, so I could modify the webpack config without ejecting the CRA.

You should install react-app-rewired and then configure your config-overrides.js and webpack.config.js like I show below:

// config-overrides.js
module.exports = function override(config, env) {
  //do stuff with the webpack config...
  config = {
    ...config,

    output: {
      ...config.output,
      library: "AwesomeWidget", // Important, the name of the lib to be exported
      libraryTarget: "umd",
      umdNamedDefine: true,
    },
  };

  return config;
};

// webpack.config.js
module.exports = {
  entry: {
    "bundle.js": glob
      .sync("build/static/?(js|css)/main.*.?(js|css)")
      .map((f) => path.resolve(__dirname, f)),
  },
  output: {
    library: "AwesomeWidget",
    libraryTarget: "umd",
    umdNamedDefine: true,
    clean: true,
    path: path.resolve(__dirname, "public/example/static"),
    filename: "bundle.min.js", // Important, the name of the exported bundle, always the same name.
  },
};

After that, you need to set scripts in your package.json, adding build scripts like so:

"scripts": {
    "start": "react-app-rewired start",
    "build": "yarn build:react && yarn build:bundle",
    "build:react": "react-app-rewired build",
    "build:bundle": "webpack --config webpack.config.js",
    "test": "react-app-rewired test",
    "eject": "react-scripts eject"
  },

As you can see, when running npm run build it will create a normal build (build:react) and then it will build using our custom webpack config (build:bundle).

This way, you will have exported a JS file always with the same name in public/example/static/bundle.min.js.

Then, I use this bundle in any HTML like this:

<script
  src="url/to/bundle.min.js"
  type="text/javascript"
></script>

<script type="text/javascript">
  if (!!AwesomeWidget) {
    // use your code here
    AwesomeWidget.customMethod();
  }
</script>

It may be a better way to solve it, hope it works for you.

Related