Conflicting dependencies

Hi,

I am building a perspective component module that uses apex chart as a dependencies. Everything works fine when I installed the module to the gateway, however, the chart fails to render if the Kyvis-Labs apex charts components module is also installed on the same gateway.

I think the error is due to the different versions used in the two module as I am using the latest version of apexcharts 4.3.0 but the Kyvis-Labs is using version 3.54.1. When i use the version 3.54.1 in my module, the chart renders fine.

I've tried isolating the dependencies but running into some bottlenecks.

I didn't include apexcharts in the externals of webpack so the library is bundled which I can see from my output file. I don't want to depend on the apexchart installed in the Kyvis-Labs library because the user may not have that module installed.

The following is how the apex charts is imported in the output file

const apexcharts_1 = __importDefault(__webpack_require__(/*! apexcharts */ "../../node_modules/apexcharts/dist/apexcharts.common.js"));

I've noticed that the Kyvis-Labs component sets the ApexCharts globally
window["ApexCharts"] = ApexCharts; which I'm not sure might be causing conflicts with the dependencies? I did try setting window["ApexCharts"] = ApexCharts; in my module as well but that doesn't fix the issue. The Kyvis-Labs library component seems to render fine but my chart component fail to render.

Am I missing steps here?

You want the opposite of this; you explicitly don’t want to collide with the ApexCharts implementation provided by the KyvisLabs module.

Maybe something here, maybe output.uniquename (I don’t use webpack :face_vomiting:)?

1 Like

I wonder if the error was not caused by global version collision because setting the unique name doesn't help as I still get the same error which should solve the problem according to the docs :thinking:

For more information the module installs fine it's just when I am using the component and triggers chart.render() then it'll error and fail to render chart

Post some code/configs? :man_shrugging:

mesh-oee/web/packages/client/package.json

  "dependencies": {
    "@inductiveautomation/perspective-client": "2.1.16",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "apexcharts": "4.3.0"
  },

mesh-oee/web/packages/client/webpack.config.js

const config = {
...

  output: {
    library: [LibName], // name as it will be accessible by on the webpack when linked as a script
    path: path.join(__dirname, "dist"),
    filename: `${LibName}.js`,
    libraryTarget: "umd",
    umdNamedDefine: true,
  },
}
...
 resolve: {
    extensions: [".jsx", ".js", ".ts", ".tsx", ".d.ts", ".css", ".scss"],
    modules: [
      path.resolve(__dirname, "../../node_modules"), // look at the local as well as shared node modules when resolving dependencies
      path.resolve(__dirname, "node_modules"), // Local node_modules in /web
    ],
  }
...
 

mesh-oee/web/pacakages/client/typescript/components/MESHOEESummary.tsx

import ApexCharts from "apexcharts";
...
  private chartRef: React.RefObject<HTMLDivElement> = React.createRef();
  private chart: ApexCharts | null = null;
...
 if (!this.chart && this.chartRef.current) {
      this.chart = new ApexCharts(this.chartRef.current, {
        series: chartSeries,
        chart: {
          type: "line",
          height: "50%",
          width: "100%",
          useCanvas: true,
          stacked: true,
          toolbar: { show: false },
          animations: { enabled: false }, // Turn off animations for better performance
          zoom: { enabled: false },
        },
        grid: { show: false },
        xaxis: {
          type: "datetime",
          labels: {
            datetimeUTC: false,
            format: "HH:mm",
          },
        },
        stroke: {
          curve: "smooth",
          width: [2, 2, 0, 0],
        },
        yaxis: [
          {
            show: false,
            max: 100,
            min: 0,
            seriesName: ["Speed (%)", "Yield (%)"],
          },
          {
            show: false,
            opposite: true,
            max: 100,
            min: 0,
            seriesName: ["Available (%)", "Not Available (%)"],
          },
        ],
        tooltip: {
          enabled: true,
          x: { format: "HH:mm" },
        },
        plotOptions: {
          bar: {
            columnWidth: "100%",
            borderRadius: 0,
            stacked: true,
          },
        },
        dataLabels: { enabled: false },
      });
      this.chart.render().catch((e) => {
        console.log(e, "error init chart");
      });
    }

Chart render fine without the ApexCharts Module from Kyvis Labs installed but with it installed during the this.chart.render() call it throws an error saying TypeError: t.put is not a function error init chart

Do you have any externals or globals configured? Showing this full file would be the most useful.

Here's the full file

/**
 * Webpack build configuration file.  Uses generic configuration that is appropriate for development.  Depending on
 * the needs of your module, you'll likely want to add appropriate 'production' configuration to this file in order
 * do do things such as minify, postcss, etc.
 *
 * To learn more about webpack, visit https://webpack.js.org/
 */

const webpack = require("webpack"),
  path = require("path"),
  fs = require("fs"),
  MiniCssExtractPlugin = require("mini-css-extract-plugin"),
  AfterBuildPlugin = require("@fiverr/afterbuild-webpack-plugin");

const LibName = "MESHOEEComponents";

// function that copies the result of the webpack from the dist/ folder into the  generated-resources folder which
// ultimately gets included in a 'web.jar'.  This jar is included in the module's gateway scope, and its contents are
// accessible as classpath resources just as if they were included in the gateway jar itself.
function copyToResources() {
  const generatedResourcesDir = path.resolve(
    __dirname,
    "../../../",
    "gateway/src/main/resources/mounted/"
  );
  const jsToCopy = path.resolve(__dirname, "dist/", `${LibName}.js`);
  const cssToCopy = path.resolve(__dirname, "dist/", `${LibName}.css`);
  const jSResourcePath = path.resolve(generatedResourcesDir, `${LibName}.js`);
  const cssResourcePath = path.resolve(generatedResourcesDir, `${LibName}.css`);

  const toCopy = [
    { from: jsToCopy, to: jSResourcePath },
    { from: cssToCopy, to: cssResourcePath },
  ];

  // if the desired folder doesn't exist, create it
  if (!fs.existsSync(generatedResourcesDir)) {
    fs.mkdirSync(generatedResourcesDir, { recursive: true });
  }

  toCopy.forEach((file) => {
    console.log(`copying ${file} into ${generatedResourcesDir}...`);

    try {
      fs.access(file.from, fs.constants.R_OK, (err) => {
        if (!err) {
          fs.createReadStream(file.from).pipe(fs.createWriteStream(file.to));
        } else {
          console.log(
            `Error when attempting to copy ${file.from} into ${file.to}`
          );
        }
      });
    } catch (err) {
      console.error(err);
      // rethrow to fail build
      throw err;
    }
  });
}

const config = {
  // define our entry point, from which we build our source tree for bundling
  entry: {
    MESHOEEComponents: path.join(
      __dirname,
      "./typescript/mesh-oee-client-component.ts"
    ),
  },

  output: {
    library: [LibName], // name as it will be accessible by on the webpack when linked as a script
    path: path.join(__dirname, "dist"),
    filename: `${LibName}.js`,
    libraryTarget: "umd",
    umdNamedDefine: true,
  },

  // Enable sourcemaps for debugging webpack's output.  Should be changed for production builds.
  devtool: "source-map",

  resolve: {
    extensions: [".jsx", ".js", ".ts", ".tsx", ".d.ts", ".css", ".scss"],
    modules: [
      path.resolve(__dirname, "../../node_modules"), // look at the local as well as shared node modules when resolving dependencies
      path.resolve(__dirname, "node_modules"), // Local node_modules in /web
    ],
  },

  module: {
    rules: [
      {
        test: /\.tsx?$/,
        use: {
          loader: "ts-loader",
          options: {
            transpileOnly: false,
          },
        },
        exclude: /node_modules/,
      },
      {
        test: /\.css$|.scss$/,
        use: [
          MiniCssExtractPlugin.loader,
          {
            loader: "css-loader",
            options: {
              // tells css-loader not to treat `url('/some/path')` as things that need to resolve at build time
              // in other words, the url value is simply passed-through as written in the css/sass
              url: false,
            },
          },
          {
            loader: "sass-loader",
          },
        ],
      },
    ],
  },
  plugins: [
    new AfterBuildPlugin(function (stats) {
      copyToResources();
    }),
    // pulls CSS out into a single file instead of dynamically inlining it
    new MiniCssExtractPlugin({
      filename: "[name].css",
    }),
  ],

  // IMPORTANT -- this tells the webpack build tooling "don't include these things as part of the webpack bundle".
  // They are 'provided' 'externally' via perspective/ignition at runtime, and we don't want multiple copies in the
  // browser.  Any libraries used that are also used in perspective should be excluded.
  externals: {
    react: "React",
    "react-dom": "ReactDOM",
    mobx: "mobx",
    "mobx-react": "mobxReact",
    "@inductiveautomation/perspective-client": "PerspectiveClient",
  },
  optimization: {
    splitChunks: {
      cacheGroups: {
        styles: {
          name: "styles",
          test: /\.css$/,
          chunks: "all",
          enforce: true,
        },
      },
    },
  },
};

module.exports = () => config;

Tried a few more things but still have no luck on this unfortunately. But here's a summary of what I tried.

Instead of installing Kyvis Lab apex chart module and my module which uses apex charts, I've created a second module that also uses apex chart to do more testing.

When I set the version of apexcharts in the two module to be the same (either v3.54.1 or v.4.3.0), chart renders fine, but when they are different, the chart in the module that uses v4.3.0 fails to render, and the other one renders fine. Since I get the same kind of error in my own modules, i suspect it has nothing to do with Kyvis Lab setting ApexCharts globally.

The import statement of apexcharts in the js file generated by the webpack is as follows

const apexcharts_1 = __importDefault(__webpack_require__(/*! apexcharts */ "../../node_modules/apexcharts/dist/apexcharts.common.js"));

I'm wondering if in theory it is possible to have two modules bundling their own apexcharts and using different version without causing conflicts with how perspective components runs in ignition designer - I don't know much about this so I could be wrong.