Skip to content

Conversation

@codesankalp
Copy link
Member

@codesankalp codesankalp commented Aug 27, 2025

Checklist

  • I have read the OpenWISP Contributing Guidelines.
  • I have manually tested the changes proposed in this pull request.
  • I have written new test cases for new code and/or updated existing tests for changes to existing code.
  • I have updated the documentation.

Reference to Existing Issue

Closes #392

Description of Changes

Use chunk splitting, dynamic loading and removed unused examples to reduce library size.

#425 (comment)

Copy link
Member

@nemesifier nemesifier left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The main problem I see is that now yarn build produces multiple files, which doesn't make it easy for us to drop the resulting work in OpenWISP. Is this on purpose?

@codesankalp
Copy link
Member Author

The main problem I see is that now yarn build produces multiple files, which doesn't make it easy for us to drop the resulting work in OpenWISP. Is this on purpose?

Yes, there are several reasons for this approach:

  1. Independent library caching - Libraries like ECharts and Leaflet will cache separately, independent of the netjsongraph codebase
  2. Efficient updates - New releases or code updates won't require re-downloading unchanged libraries
  3. Parallel loading - Modern browsers support concurrent chunk downloads, significantly improving load times
  4. Enhanced performance - Overall application performance will improve
  5. Selective loading - Load only required dependencies (network topology needs only ECharts, while map features require Leaflet as well)

The contenthash in filenames will optimize CDN cache management when using cdn (if we are using cdn).

Integration Considerations:

  1. Since we maintain the codebase, updating it to support multiple JavaScript files would be ideal. However, if this isn't feasible, I can make the chunk build process configurable through environment variables.
  2. Else, we could create a netjsongraph-loader.js file that handles parallel chunk loading, requiring only a single script reference in the integration code.

let me know your thoughts!

@nemesifier nemesifier moved this to In progress in 25.09 Release Sep 4, 2025
Copy link
Member

@nemesifier nemesifier left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@codesankalp what do you think about the following?

  1. a result set providing the current output: all separate files
  2. a result set providing the library + echarts
  3. a result set providing the library + echarts + leaflet

Can you add basic explanation about the above?

@codesankalp codesankalp force-pushed the fix/issue-392 branch 4 times, most recently from 68ed674 to 17ffba3 Compare September 8, 2025 14:02
@codesankalp
Copy link
Member Author

New Buid Sizes:

  1. a result set providing the current output: all separate files -> 16.5kb + 324kb + 44.2kb
  2. a result set providing the library + echarts -> 323kb
  3. a result set providing the library + echarts + leaflet -> 366kb

@codesankalp codesankalp force-pushed the fix/issue-392 branch 2 times, most recently from e43988e to 5355c03 Compare September 8, 2025 15:53
@codesankalp codesankalp force-pushed the fix/issue-392 branch 2 times, most recently from 0c99ec8 to b4bbfa9 Compare September 8, 2025 17:23
Copy link
Member

@nemesifier nemesifier left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have the following problem:

yarn install
yarn install v1.22.22
[1/4] Resolving packages...
[2/4] Fetching packages...
error Error: https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.3.tgz: Request failed "404 Not Found"
    at ResponseError.ExtendableBuiltin (/usr/share/yarn/lib/cli.js:696:66)
    at new ResponseError (/usr/share/yarn/lib/cli.js:802:124)
    at Request.<anonymous> (/usr/share/yarn/lib/cli.js:66750:16)
    at Request.emit (node:events:519:28)
    at module.exports.Request.onRequestResponse (/usr/share/yarn/lib/cli.js:142287:10)
    at ClientRequest.emit (node:events:519:28)
    at HTTPParser.parserOnIncomingClient (node:_http_client:716:27)
    at HTTPParser.parserOnHeadersComplete (node:_http_common:117:17)
    at TLSSocket.socketOnData (node:_http_client:558:22)
    at TLSSocket.emit (node:events:519:28)
info Visit https://yarnpkg.com/en/docs/cli/install for documentation about this command.

I see the following line in yarn.lock:

debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@^4.4.1:
  version "4.4.2"
  resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.2.tgz#96b480a7fe47cc04fe57f0bf56e058dba2a001fa"
  integrity sha512-IQeXCZhGRpFiLI3MYlCGLjNssUBiE8G21RMyNH35KFsxIvhrMeh5jXuG82woDZrYX9pgqHs+GF5js2Ducn4y4A==
  dependencies:
    ms "^2.1.3"

The URL https://registry.yarnpkg.com/debug/-/debug-4.4.2.tgz#96b480a7fe47cc04fe57f0bf56e058dba2a001fa returns 404.

I am not sure why the CI build is not complaining about this, but it feels wrong to me to reference a 404 URL in yarn.lock, what do you think?

PS: debug-js/debug#1005

Copy link
Member

@nemesifier nemesifier left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did some changes to the README.

The save on the complete bundle is only ~300KB.
In OpenWISP dashboard we need to load the complete bundle, so reducing 300KB is good but I wonder if we really need 1.3 MB to show geographic and indoor maps.

Is there a way to reduce the imports of echarts so that we only import exactly what we use and let the javascript pruning tools do the work of removing all the JS code that we don't use?

@codesankalp codesankalp force-pushed the fix/issue-392 branch 2 times, most recently from bc058f2 to e6c4b28 Compare September 10, 2025 22:04
@nemesifier nemesifier moved this from In progress to Backlog in 25.09 Release Sep 30, 2025
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@webpack-plugins/inject-leaflet-loader.js`:
- Around line 195-199: The inline-script regex is too restrictive (only allows
an optional type attribute) and misses scripts with other attrs (defer, nonce,
etc.), causing the loader not to be injected; update the matching logic around
the scriptRegex variable to match any <script ...> tag with arbitrary attributes
but no src attribute (e.g. capture attributes with
/<script\b([^>]*?)>\s*([\s\S]*?)\s*<\/script>/i and then ensure you only treat
tags without a src attribute as inline), preserve the full attributes string
when reinserting the modified script, and apply the same change to the other
occurrence around lines 216-219 so all inline scripts are detected and updated
correctly.
- Around line 204-214: The current logic detects module scripts via
scriptTypeMatch/scriptType but still wraps everything in an initMap function
when creating newScript, which breaks module semantics; modify the code around
scriptTypeMatch/scriptType and the newScript construction so that if scriptType
=== "module" you do not wrap originalScript in the initMap function (instead
inject loaderSnippet alongside originalScript or apply a module-compatible
injection), otherwise continue to wrap as before; update references to
loaderSnippet, originalScript and newScript accordingly and ensure module
scripts are emitted unchanged in scope.
♻️ Duplicate comments (1)
webpack-plugins/inject-leaflet-loader.js (1)

137-139: Rename shadowing variable to satisfy Biome lint.

constructor shadows the global identifier and trips lint/suspicious/noShadowRestrictedNames. This was already flagged earlier.

🔧 Suggested fix
-      const HtmlWebpackPlugin = compiler.options.plugins
-        .map((plugin) => plugin.constructor)
-        .find((constructor) => constructor && constructor.name === "HtmlWebpackPlugin");
+      const HtmlWebpackPlugin = compiler.options.plugins
+        .map((plugin) => plugin.constructor)
+        .find((pluginCtor) => pluginCtor && pluginCtor.name === "HtmlWebpackPlugin");
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ba20b6c and 85097e8.

📒 Files selected for processing (1)
  • webpack-plugins/inject-leaflet-loader.js
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{js,ts,css,scss,json}

📄 CodeRabbit inference engine (AGENTS.md)

Format code using openwisp-qa-format command with Python virtualenv enabled and yarn lint:fix (runs eslint --fix and prettier via lint-staged)

Files:

  • webpack-plugins/inject-leaflet-loader.js
🧠 Learnings (4)
📓 Common learnings
Learnt from: codesankalp
Repo: openwisp/netjsongraph.js PR: 425
File: src/js/netjsongraph.render.js:1-26
Timestamp: 2026-01-20T16:51:06.213Z
Learning: In the netjsongraph.js project, imports from `echarts/lib/chart/*/install`, `echarts/lib/component/*/install`, and `echarts/lib/renderer/install*` paths produce better tree-shaking results than the public entry points (`echarts/charts`, `echarts/components`, `echarts/renderers`) in their webpack configuration. The project maintainer has tested both approaches and confirmed the `/lib/*/install` pattern yields smaller bundles.
📚 Learning: 2026-01-20T16:51:06.213Z
Learnt from: codesankalp
Repo: openwisp/netjsongraph.js PR: 425
File: src/js/netjsongraph.render.js:1-26
Timestamp: 2026-01-20T16:51:06.213Z
Learning: In the netjsongraph.js project, imports from `echarts/lib/chart/*/install`, `echarts/lib/component/*/install`, and `echarts/lib/renderer/install*` paths produce better tree-shaking results than the public entry points (`echarts/charts`, `echarts/components`, `echarts/renderers`) in their webpack configuration. The project maintainer has tested both approaches and confirmed the `/lib/*/install` pattern yields smaller bundles.

Applied to files:

  • webpack-plugins/inject-leaflet-loader.js
📚 Learning: 2026-01-04T08:00:40.936Z
Learnt from: dee077
Repo: openwisp/netjsongraph.js PR: 417
File: src/js/netjsongraph.util.js:1302-1341
Timestamp: 2026-01-04T08:00:40.936Z
Learning: In the netjsongraph.js codebase, the `== null` pattern is intentionally used to check for both null and undefined values in a single comparison, which is a well-established JavaScript idiom and should not be flagged for replacement with strict equality checks.

Applied to files:

  • webpack-plugins/inject-leaflet-loader.js
📚 Learning: 2026-01-20T00:42:32.369Z
Learnt from: dee077
Repo: openwisp/netjsongraph.js PR: 417
File: src/js/netjsongraph.core.js:132-145
Timestamp: 2026-01-20T00:42:32.369Z
Learning: In the netjsongraph.js codebase, node IDs are guaranteed not to contain the "~" character, so using "source~target" as a composite key for links in nodeLinkIndex will not collide with node ID keys.

Applied to files:

  • webpack-plugins/inject-leaflet-loader.js
🧬 Code graph analysis (1)
webpack-plugins/inject-leaflet-loader.js (1)
webpack.config.js (2)
  • InjectLeafletLoaderPlugin (10-10)
  • HtmlWebpackPlugin (5-5)
🪛 Biome (2.1.2)
webpack-plugins/inject-leaflet-loader.js

[error] 139-139: Do not shadow the global "constructor" property.

Consider renaming this variable. It's easy to confuse the origin of variables when they're named after a known global.

(lint/suspicious/noShadowRestrictedNames)

🔇 Additional comments (2)
webpack-plugins/inject-leaflet-loader.js (2)

6-19: LGTM: constructor config is clear and scoped.

isEchartsOnly gating plus an explicit mapExamples list keeps the plugin’s scope predictable.


22-128: LGTM: loader snippet covers Leaflet + plugin sequencing cleanly.

The onload/error handling and plugin gating look solid for the echarts-only case.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
index.html (1)

1-278: ⚠️ Potential issue | 🟡 Minor

Fix Prettier formatting issues to pass CI.

The CI pipeline is failing because this file has code style issues detected by Prettier. Run prettier --write index.html or yarn lint:fix to automatically fix the formatting.

.github/workflows/ci.yml (1)

123-126: ⚠️ Potential issue | 🟡 Minor

Coveralls may receive multiple coverage reports from matrix builds.

The Coveralls step runs for all three matrix entries (dev, prod, echarts-only), but only the dev build runs unit tests and generates coverage data. The prod and echarts-only builds will submit empty or incomplete coverage reports.

Consider restricting Coveralls to only the dev build:

🔧 Suggested fix
       - name: Coveralls
+        if: matrix.build-type == 'dev'
         uses: coverallsapp/github-action@master
         with:
           github-token: ${{ secrets.GITHUB_TOKEN }}
src/js/netjsongraph.core.js (1)

80-179: ⚠️ Potential issue | 🟠 Major

async render() has no await statements, causing immediate Promise resolution without waiting for readiness.

The function is marked async but contains no top‑level await, so it returns a resolved Promise immediately. Tests and future callers that await render() expect the function to wait for paginatedDataParse and onReady completion, but the Promise resolves before these operations finish, breaking the async contract. Either add await to the parse chain or remove async to match actual behavior.

🛠️ Possible fix: await parse chain and return completion Promise
 async render() {
   const [JSONParam, ...resParam] = this.JSONParam;

   this.config.onRender.call(this);
   // Ensure applyUrlFragmentState runs only after onReady has completed,
   // as onReady may perform asynchronous operations
   const onReadyDone = new Promise((resolve) => {
     this.event.once("onReady", async () => {
       try {
         await this.config.onReady.call(this);
       } catch (error) {
         console.error("onReady callback failed:", error);
       }
       resolve();
     });
   });
   this.event.once("onLoad", this.config.onLoad.bind(this));
   this.event.once("applyUrlFragmentState", async () => {
     try {
       await onReadyDone;
     } catch (e) {
       console.error("onReady failed:", e);
     }
     this.utils.applyUrlFragmentState.call(this, this);
   });
-  this.utils.paginatedDataParse
+  await this.utils.paginatedDataParse
     .call(this, JSONParam)
     .then((JSONData) => {
       if (this.utils.isNetJSON(JSONData)) {
         this.type = "netjson";
       } else if (this.utils.isGeoJSON(JSONData)) {
         // Treat GeoJSON as a first-class citizen by converting it once
         // to NetJSON shape while keeping the original for polygon rendering.
         this.type = "geojson";
         // Preserve the original GeoJSON so that non-point geometries (e.g. Polygons)
         // can still be rendered as filled shapes via a separate Leaflet layer later
         // in the rendering pipeline, while the converted NetJSON shape is used for
         // clustering and ECharts overlays.
         this.originalGeoJSON = this.utils.fastDeepCopy(JSONData);
         JSONData = this.utils.geojsonToNetjson(JSONData);
       } else {
         throw new Error("Invalid data format!");
       }

       if (this.utils.isNetJSON(JSONData)) {
         if (JSONData.nodes.length > this.config.maxPointsFetched) {
           this.hasMoreData = true;
         }
         JSONData.nodes.splice(
           this.config.maxPointsFetched - 1,
           JSONData.nodes.length - this.config.maxPointsFetched,
         );
         const nodeSet = new Set();
         // Build a lookup map (this.nodeLinkIndex) for quick access to node or link data.
         // Uses node IDs as keys for nodes and "source~target" as keys for links.
         // This avoids repeated traversal when restoring state from URL fragments.
         this.nodeLinkIndex = {};
         JSONData.nodes.forEach((node) => {
           nodeSet.add(node.id);
           this.nodeLinkIndex[node.id] = node;
         });
         JSONData.links = JSONData.links.filter((link) => {
           if (nodeSet.has(link.source) && nodeSet.has(link.target)) {
             const key = `${link.source}~${link.target}`;
             this.nodeLinkIndex[key] = link;
             return true;
           }
           if (!nodeSet.has(link.source)) {
             console.warn(`Node ${link.source} does not exist!`);
           } else {
             console.warn(`Node ${link.target} does not exist!`);
           }
           return false;
         });
       }
       this.config.prepareData.call(this, JSONData);
       this.data = JSONData;

       if (this.config.dealDataByWorker) {
         this.utils.dealDataByWorker.call(
           this,
           JSONData,
           this.config.dealDataByWorker,
         );
       } else {
         this.data = JSONData;
         this.utils.render();
       }
     })
     .catch((error) => {
       console.error(error);
     });

   if (resParam.length) {
     const renderArray = function _renderArray() {
       resParam.map((file) => this.utils.JSONDataUpdate.call(this, file, false));
     };
     this.JSONParam = [JSONParam];
     this.event.once("renderArray", renderArray.bind(this));
   }
 }
🤖 Fix all issues with AI agents
In @.github/workflows/ci.yml:
- Around line 51-57: Add an explicit permissions block to the GitHub Actions job
named "build" to restrict the GITHUB_TOKEN to only the scopes required for
running tests and collecting coverage; under the build job (where
matrix.build-type is defined) add a permissions section that at minimum sets
contents: read and packages: read and optionally checks: write and actions: read
(adjust to your CI needs) so the workflow no longer inherits broad default token
permissions.
🧹 Nitpick comments (2)
src/js/echarts-leaflet/LeafletCoordSys.js (1)

153-155: Redundant Leaflet availability check.

The check typeof L === "undefined" at line 153 is now redundant. Since L is assigned from getLeaflet() at line 12, and lines 13-15 throw if Leaflet is unavailable, L is guaranteed to be defined when this callback executes.

♻️ Optional cleanup
       ecModel.eachComponent("leaflet", (leafletModel) => {
         const viewportRoot = api.getZr().painter.getViewportRoot();
-        if (typeof L === "undefined") {
-          throw new Error("Leaflet api is not loaded");
-        }
         if (leafletCoordSys) {
           throw new Error("Only one leaflet component can exist");
         }
webpack-plugins/inject-leaflet-loader.js (1)

35-37: Hardcoded CDN URLs with SRI hashes may become stale.

The Leaflet version (1.8.0) and integrity hashes are hardcoded. If the project's Leaflet dependency is updated, these values won't automatically sync. Consider documenting this dependency or extracting the version to a shared constant.

Also applies to: 42-44

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 85097e8 and 0ad4fe2.

⛔ Files ignored due to path filters (3)
  • lib/css/assets/leaflet-measure.png is excluded by !**/*.png
  • public/assets/images/floorplan.png is excluded by !**/*.png
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (25)
  • .github/workflows/ci.yml
  • README.md
  • index.html
  • package.json
  • public/assets/data/airplaneRouteMap.json
  • public/assets/data/netjsongraph-graphGL.json
  • public/example_templates/netjsongraph-graphGL.html
  • public/example_templates/netjsonmap-animation.html
  • public/example_templates/netjsonmap-indoormap.html
  • src/js/echarts-leaflet/LeafletCoordSys.js
  • src/js/echarts-leaflet/LeafletModel.js
  • src/js/echarts-leaflet/LeafletView.js
  • src/js/echarts-leaflet/index.js
  • src/js/leaflet-loader.js
  • src/js/netjsongraph.clients.js
  • src/js/netjsongraph.config.js
  • src/js/netjsongraph.core.js
  • src/js/netjsongraph.geojson.js
  • src/js/netjsongraph.js
  • src/js/netjsongraph.render.js
  • test/browser.test.utils.js
  • test/netjsongraph.duplicateNodes.test.js
  • test/netjsongraph.render.test.js
  • webpack-plugins/inject-leaflet-loader.js
  • webpack.config.js
💤 Files with no reviewable changes (4)
  • test/browser.test.utils.js
  • public/assets/data/airplaneRouteMap.json
  • public/example_templates/netjsongraph-graphGL.html
  • public/example_templates/netjsonmap-animation.html
🚧 Files skipped from review as they are similar to previous changes (8)
  • src/js/netjsongraph.geojson.js
  • src/js/netjsongraph.clients.js
  • public/example_templates/netjsonmap-indoormap.html
  • src/js/leaflet-loader.js
  • package.json
  • src/js/netjsongraph.js
  • src/js/echarts-leaflet/LeafletView.js
  • README.md
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{js,ts,css,scss,json}

📄 CodeRabbit inference engine (AGENTS.md)

Format code using openwisp-qa-format command with Python virtualenv enabled and yarn lint:fix (runs eslint --fix and prettier via lint-staged)

Files:

  • src/js/netjsongraph.core.js
  • src/js/netjsongraph.config.js
  • src/js/echarts-leaflet/LeafletModel.js
  • webpack.config.js
  • test/netjsongraph.duplicateNodes.test.js
  • test/netjsongraph.render.test.js
  • src/js/echarts-leaflet/LeafletCoordSys.js
  • webpack-plugins/inject-leaflet-loader.js
  • src/js/netjsongraph.render.js
  • src/js/echarts-leaflet/index.js
src/**/*.js

📄 CodeRabbit inference engine (AGENTS.md)

Husky pre-commit hooks automatically run lint-staged to format JavaScript files in src/**/*.js with prettier

Files:

  • src/js/netjsongraph.core.js
  • src/js/netjsongraph.config.js
  • src/js/echarts-leaflet/LeafletModel.js
  • src/js/echarts-leaflet/LeafletCoordSys.js
  • src/js/netjsongraph.render.js
  • src/js/echarts-leaflet/index.js
**/*.test.{js,ts}

📄 CodeRabbit inference engine (AGENTS.md)

Write unit tests using Jest with jsdom and run with yarn test

Files:

  • test/netjsongraph.duplicateNodes.test.js
  • test/netjsongraph.render.test.js
🧠 Learnings (11)
📓 Common learnings
Learnt from: codesankalp
Repo: openwisp/netjsongraph.js PR: 425
File: src/js/netjsongraph.render.js:1-26
Timestamp: 2026-01-20T16:51:06.213Z
Learning: In the netjsongraph.js project, imports from `echarts/lib/chart/*/install`, `echarts/lib/component/*/install`, and `echarts/lib/renderer/install*` paths produce better tree-shaking results than the public entry points (`echarts/charts`, `echarts/components`, `echarts/renderers`) in their webpack configuration. The project maintainer has tested both approaches and confirmed the `/lib/*/install` pattern yields smaller bundles.
📚 Learning: 2026-01-20T00:56:36.062Z
Learnt from: dee077
Repo: openwisp/netjsongraph.js PR: 417
File: src/js/netjsongraph.core.js:132-145
Timestamp: 2026-01-20T00:56:36.062Z
Learning: In the netjsongraph.js codebase, the data model does not support parallel links (multiple links between the same source and target). Ensure nodeLinkIndex is keyed by a single 'source~target' pair and that adding a link does not overwrite existing links for that pair. If parallel links are ever required, the data model must be changed (e.g., allow an array of links per 'source~target' key) rather than storing only one link.

Applied to files:

  • src/js/netjsongraph.core.js
  • src/js/netjsongraph.config.js
  • test/netjsongraph.duplicateNodes.test.js
  • test/netjsongraph.render.test.js
  • src/js/netjsongraph.render.js
📚 Learning: 2026-01-20T16:50:56.414Z
Learnt from: codesankalp
Repo: openwisp/netjsongraph.js PR: 425
File: src/js/netjsongraph.render.js:1-26
Timestamp: 2026-01-20T16:50:56.414Z
Learning: In netjsongraph.js, prefer importing from echarts/lib/chart/*/install, echarts/lib/component/*/install, and echarts/lib/renderer/install* rather than the public entry points (echarts/charts, echarts/components, echarts/renderers) to improve tree-shaking and reduce bundle size. Apply this pattern across JS files in src (update imports accordingly) and verify by comparing bundle sizes between the install-path imports and the public-entry imports.

Applied to files:

  • src/js/netjsongraph.core.js
  • src/js/netjsongraph.config.js
  • src/js/echarts-leaflet/LeafletModel.js
  • src/js/echarts-leaflet/LeafletCoordSys.js
  • src/js/netjsongraph.render.js
  • src/js/echarts-leaflet/index.js
📚 Learning: 2026-01-22T22:37:11.271Z
Learnt from: CR
Repo: openwisp/netjsongraph.js PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-22T22:37:11.271Z
Learning: Run QA checks using `./run-qa-checks` before committing code

Applied to files:

  • .github/workflows/ci.yml
📚 Learning: 2026-01-22T22:37:11.271Z
Learnt from: CR
Repo: openwisp/netjsongraph.js PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-22T22:37:11.271Z
Learning: Applies to test/netjsongraph.browser.test.js : Write browser tests in test/netjsongraph.browser.test.js using Chrome/ChromeDriver and run with `yarn test test/netjsongraph.browser.test.js`

Applied to files:

  • .github/workflows/ci.yml
  • test/netjsongraph.duplicateNodes.test.js
  • test/netjsongraph.render.test.js
📚 Learning: 2026-01-22T22:37:11.271Z
Learnt from: CR
Repo: openwisp/netjsongraph.js PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-22T22:37:11.271Z
Learning: Applies to **/*.test.{js,ts} : Write unit tests using Jest with jsdom and run with `yarn test`

Applied to files:

  • .github/workflows/ci.yml
📚 Learning: 2026-01-20T16:51:06.213Z
Learnt from: codesankalp
Repo: openwisp/netjsongraph.js PR: 425
File: src/js/netjsongraph.render.js:1-26
Timestamp: 2026-01-20T16:51:06.213Z
Learning: In the netjsongraph.js project, imports from `echarts/lib/chart/*/install`, `echarts/lib/component/*/install`, and `echarts/lib/renderer/install*` paths produce better tree-shaking results than the public entry points (`echarts/charts`, `echarts/components`, `echarts/renderers`) in their webpack configuration. The project maintainer has tested both approaches and confirmed the `/lib/*/install` pattern yields smaller bundles.

Applied to files:

  • webpack.config.js
  • test/netjsongraph.duplicateNodes.test.js
  • test/netjsongraph.render.test.js
  • webpack-plugins/inject-leaflet-loader.js
📚 Learning: 2026-01-04T08:00:40.936Z
Learnt from: dee077
Repo: openwisp/netjsongraph.js PR: 417
File: src/js/netjsongraph.util.js:1302-1341
Timestamp: 2026-01-04T08:00:40.936Z
Learning: In the netjsongraph.js codebase, the `== null` pattern is intentionally used to check for both null and undefined values in a single comparison, which is a well-established JavaScript idiom and should not be flagged for replacement with strict equality checks.

Applied to files:

  • test/netjsongraph.duplicateNodes.test.js
  • webpack-plugins/inject-leaflet-loader.js
  • src/js/netjsongraph.render.js
📚 Learning: 2026-01-27T19:09:23.929Z
Learnt from: nemesifier
Repo: openwisp/netjsongraph.js PR: 473
File: src/js/netjsongraph.render.js:433-468
Timestamp: 2026-01-27T19:09:23.929Z
Learning: In the netjsongraph.js project, when implementing wheel zoom propagation from the graph container to the canvas: ECharts intercepts wheel events directly on the canvas with preventDefault(), so those events don't bubble to the container. A wheel listener on the container only catches events that occur outside the canvas (in the empty container space), making checks for e.target === canvas unnecessary since canvas events never reach the container listener.

Applied to files:

  • src/js/echarts-leaflet/LeafletCoordSys.js
  • src/js/netjsongraph.render.js
📚 Learning: 2026-01-20T00:42:32.369Z
Learnt from: dee077
Repo: openwisp/netjsongraph.js PR: 417
File: src/js/netjsongraph.core.js:132-145
Timestamp: 2026-01-20T00:42:32.369Z
Learning: In the netjsongraph.js codebase, node IDs are guaranteed not to contain the "~" character, so using "source~target" as a composite key for links in nodeLinkIndex will not collide with node ID keys.

Applied to files:

  • webpack-plugins/inject-leaflet-loader.js
📚 Learning: 2026-01-30T08:31:13.746Z
Learnt from: Dhruv-ub
Repo: openwisp/netjsongraph.js PR: 474
File: src/js/netjsongraph.render.js:347-351
Timestamp: 2026-01-30T08:31:13.746Z
Learning: In src/js/netjsongraph.render.js, treat showMapLabelsAtZoom = 0 as the explicit 'disabled' state (not a valid threshold). Do not rely on generic falsy checks for this flag. Update logic to distinguish between undefined/null (not set), 0 (disabled), and >0 (enabled at a given zoom). For example, use explicit checks such as: if (showMapLabelsAtZoom === 0) { /* labels hidden */ } else if (typeof showMapLabelsAtZoom === 'number' && showMapLabelsAtZoom > 0) { /* show labels at/above this zoom */ } else { /* default behavior or undefined */ }. If needed, document this convention or normalize input (e.g., set undefined to a sensible default) to avoid ambiguity in future reviews.

Applied to files:

  • src/js/netjsongraph.render.js
🧬 Code graph analysis (5)
src/js/netjsongraph.config.js (4)
src/js/leaflet-loader.js (2)
  • L (12-12)
  • getLeaflet (14-38)
src/js/echarts-leaflet/LeafletCoordSys.js (1)
  • L (12-12)
src/js/echarts-leaflet/index.js (1)
  • L (11-11)
src/js/netjsongraph.geojson.js (2)
  • L (136-136)
  • L (140-140)
test/netjsongraph.render.test.js (2)
src/js/netjsongraph.geojson.js (1)
  • map (142-142)
test/netjsongraph.duplicateNodes.test.js (1)
  • NetJSONGraphRender (2-2)
webpack-plugins/inject-leaflet-loader.js (1)
webpack.config.js (2)
  • InjectLeafletLoaderPlugin (10-10)
  • HtmlWebpackPlugin (5-5)
src/js/netjsongraph.render.js (5)
src/js/leaflet-loader.js (2)
  • L (12-12)
  • getLeaflet (14-38)
src/js/echarts-leaflet/LeafletCoordSys.js (1)
  • L (12-12)
src/js/echarts-leaflet/index.js (1)
  • L (11-11)
src/js/netjsongraph.config.js (1)
  • L (370-370)
src/js/netjsongraph.geojson.js (2)
  • L (136-136)
  • L (140-140)
src/js/echarts-leaflet/index.js (5)
src/js/leaflet-loader.js (2)
  • L (12-12)
  • getLeaflet (14-38)
src/js/echarts-leaflet/LeafletCoordSys.js (1)
  • L (12-12)
src/js/netjsongraph.config.js (1)
  • L (370-370)
src/js/echarts-leaflet/LeafletModel.js (1)
  • extendLeafletModel (6-50)
src/js/echarts-leaflet/LeafletView.js (1)
  • extendLeafletView (6-122)
🪛 Biome (2.3.13)
webpack-plugins/inject-leaflet-loader.js

[error] 139-139: Do not shadow the global "constructor" property.

Consider renaming this variable. It's easy to confuse the origin of variables when they're named after a known global.

(lint/suspicious/noShadowRestrictedNames)

🪛 GitHub Actions: netjsongraph.js CI BUILD
index.html

[warning] 1-1: Code style issues found in index.html. Run Prettier with --write to fix.


[error] 1-1: CI process failed with exit code 1 due to formatting check failure. Prettier formatting check did not pass. Run 'prettier --write' to fix code style issues in this file.

🪛 GitHub Check: CodeQL
.github/workflows/ci.yml

[warning] 52-128: Workflow does not contain permissions
Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {{contents: read}}

🔇 Additional comments (19)
src/js/echarts-leaflet/LeafletModel.js (1)

1-50: LGTM!

The refactor to import extendComponentModel directly from echarts/core and remove the echarts parameter is clean and aligns with the PR's modular import strategy. The component definition logic remains unchanged.

src/js/echarts-leaflet/LeafletCoordSys.js (1)

11-15: LGTM!

The dynamic Leaflet loading via getLeaflet() with proper error handling and destructuring is well-implemented. This enables the ECharts-only build variant to work without bundling Leaflet.

src/js/netjsongraph.config.js (1)

366-378: LGTM!

The lazy getter pattern for crs is the right approach since Leaflet may not be available at module load time. Returning null when Leaflet is unavailable allows the ECharts-only build to function correctly.

src/js/echarts-leaflet/index.js (1)

10-21: LGTM!

The graceful early return when Leaflet is unavailable (line 12-16) enables the library to function in graph-only mode without errors. The registration flow is clean and properly chains the model/view extensions before coordinate system registration.

src/js/netjsongraph.render.js (6)

1-26: LGTM!

The modular ECharts imports using the echarts/lib/*/install pattern align with the project's established approach for optimal tree-shaking. Based on learnings, this pattern yields smaller bundles than the public entry points in this project's webpack configuration.


83-92: Tooltip formatter correctly simplified.

The formatter now only handles graph and lines component subtypes, removing the dead graphGL branches that were flagged in previous reviews.


206-211: Graph series simplified to use only "graph" type.

The series type is now always "graph" with a default "force" layout, removing the unnecessary graphGL complexity. This aligns with the PR objective to reduce library size by removing unused features.


515-519: LGTM!

The dynamic Leaflet loading with proper error handling ensures map rendering fails gracefully with a clear error message when Leaflet is unavailable.


733-741: Cluster click handling correctly uses "scatter" type.

The check for cluster clicks now uses params.componentSubType === "scatter", which aligns with the simplified map series type at line 327.


871-871: Export surface narrowed to default-only.

Removing the named exports for echarts and L is appropriate since consumers should use the library's public API rather than accessing internal dependencies directly.

test/netjsongraph.render.test.js (3)

1-3: LGTM! Import updates align with the new module structure.

The imports correctly reflect the module restructuring where NetJSONGraphRender is now a default export and L (Leaflet) is imported separately rather than being re-exported from the render module.


477-481: LGTM! Async test pattern is correct.

The test properly awaits the now-asynchronous render() call and asserts on the expected error behavior.


707-715: LGTM! Prototype isolation prevents test pollution.

Snapshotting and restoring NetJSONGraphRender.prototype between tests is good practice since the setUp function modifies the prototype chain via Object.setPrototypeOf.

test/netjsongraph.duplicateNodes.test.js (1)

2-2: LGTM! Import correctly accesses the default export.

The require(...).default pattern properly retrieves the default export from the restructured module, consistent with the import pattern in other test files.

webpack.config.js (5)

61-65: LGTM! Build type detection enables dual-build strategy.

The function-based configuration cleanly supports both full and echarts-only builds via the BUILD_TYPE environment variable, enabling the size reduction objectives of this PR.


84-88: LGTM! Leaflet externalization reduces echarts-only bundle size.

Externalizing Leaflet as L for echarts-only builds allows consumers to load Leaflet separately or via the dynamic loader, supporting the selective loading objective.


169-171: LGTM! DefinePlugin enables conditional Leaflet bundling.

BUNDLE_LEAFLET allows source code to conditionally include/exclude Leaflet-related code paths based on build type, supporting dead code elimination.


172-204: LGTM! Compression plugins configured for optimal delivery.

Gzip and Brotli compression with aggressive settings (level 9 / quality 11) will significantly reduce transfer sizes for production builds. The threshold of 1024 bytes appropriately skips small files.


93-93: CSS and polyfills are safe with sideEffects: false in this codebase.

CSS files in src/css/ are handled as static assets (copied via webpack's CopyPlugin, lines 155-156) and processed through webpack's CSS loader rules (lines 107-108), not imported as ES6 modules. No side-effectful bare imports were found in the source code. The sideEffects: false setting is intentional and explicitly declared in package.json (line 6), consistent with the project's careful tree-shaking optimization strategy (including usedExports: true, providedExports: true, concatenateModules: true, etc.).

Likely an incorrect or invalid review comment.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
.github/workflows/ci.yml (1)

26-28: ⚠️ Potential issue | 🟡 Minor

Deprecated set-output command usage.

The ::set-output command is deprecated and will eventually be removed. This affects both the qa-checks job (line 28) and build job (line 69).

🔧 Suggested fix
       - name: Get yarn cache directory path
         id: yarn-cache-dir-path
-        run: echo "::set-output name=dir::$(yarn cache dir)"
+        run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT

Apply the same fix to line 69 in the build job.

🧹 Nitpick comments (2)
webpack-plugins/inject-leaflet-loader.js (1)

35-44: Hardcoded Leaflet version may drift from bundled version.

The CDN URLs reference Leaflet 1.8.0, which should match the version specified in package.json. If the bundled version changes, these URLs won't automatically update.

Consider extracting the Leaflet version to a shared constant or reading it from package.json during build to ensure consistency:

// At build time, could inject version from package.json
const LEAFLET_VERSION = "1.8.0";
const LEAFLET_CDN_BASE = `https://unpkg.com/leaflet@${LEAFLET_VERSION}/dist`;
webpack.config.js (1)

70-78: Consider extracting filename logic for clarity.

The nested ternary operators for filename are functional but reduce readability. A helper function or object lookup could make the intent clearer.

♻️ Optional refactor for readability
const getOutputFilename = (isProduction, isEchartsOnly) => {
  const prefix = isEchartsOnly ? "netjsongraph.echarts" : "netjsongraph";
  const hash = isProduction ? ".[contenthash:8]" : "";
  const suffix = isProduction ? ".min.js" : ".js";
  return `${prefix}${hash}${suffix}`;
};

// Then in config:
filename: getOutputFilename(isProduction, isEchartsOnly),
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0ad4fe2 and ac84bae.

📒 Files selected for processing (4)
  • .github/workflows/ci.yml
  • index.html
  • webpack-plugins/inject-leaflet-loader.js
  • webpack.config.js
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{js,ts,css,scss,json}

📄 CodeRabbit inference engine (AGENTS.md)

Format code using openwisp-qa-format command with Python virtualenv enabled and yarn lint:fix (runs eslint --fix and prettier via lint-staged)

Files:

  • webpack.config.js
  • webpack-plugins/inject-leaflet-loader.js
🧠 Learnings (7)
📓 Common learnings
Learnt from: codesankalp
Repo: openwisp/netjsongraph.js PR: 425
File: src/js/netjsongraph.render.js:1-26
Timestamp: 2026-01-20T16:51:06.213Z
Learning: In the netjsongraph.js project, imports from `echarts/lib/chart/*/install`, `echarts/lib/component/*/install`, and `echarts/lib/renderer/install*` paths produce better tree-shaking results than the public entry points (`echarts/charts`, `echarts/components`, `echarts/renderers`) in their webpack configuration. The project maintainer has tested both approaches and confirmed the `/lib/*/install` pattern yields smaller bundles.
📚 Learning: 2026-01-20T16:51:06.213Z
Learnt from: codesankalp
Repo: openwisp/netjsongraph.js PR: 425
File: src/js/netjsongraph.render.js:1-26
Timestamp: 2026-01-20T16:51:06.213Z
Learning: In the netjsongraph.js project, imports from `echarts/lib/chart/*/install`, `echarts/lib/component/*/install`, and `echarts/lib/renderer/install*` paths produce better tree-shaking results than the public entry points (`echarts/charts`, `echarts/components`, `echarts/renderers`) in their webpack configuration. The project maintainer has tested both approaches and confirmed the `/lib/*/install` pattern yields smaller bundles.

Applied to files:

  • webpack.config.js
  • webpack-plugins/inject-leaflet-loader.js
📚 Learning: 2026-01-04T08:00:40.936Z
Learnt from: dee077
Repo: openwisp/netjsongraph.js PR: 417
File: src/js/netjsongraph.util.js:1302-1341
Timestamp: 2026-01-04T08:00:40.936Z
Learning: In the netjsongraph.js codebase, the `== null` pattern is intentionally used to check for both null and undefined values in a single comparison, which is a well-established JavaScript idiom and should not be flagged for replacement with strict equality checks.

Applied to files:

  • webpack-plugins/inject-leaflet-loader.js
📚 Learning: 2026-01-20T00:42:32.369Z
Learnt from: dee077
Repo: openwisp/netjsongraph.js PR: 417
File: src/js/netjsongraph.core.js:132-145
Timestamp: 2026-01-20T00:42:32.369Z
Learning: In the netjsongraph.js codebase, node IDs are guaranteed not to contain the "~" character, so using "source~target" as a composite key for links in nodeLinkIndex will not collide with node ID keys.

Applied to files:

  • webpack-plugins/inject-leaflet-loader.js
📚 Learning: 2026-01-22T22:37:11.271Z
Learnt from: CR
Repo: openwisp/netjsongraph.js PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-22T22:37:11.271Z
Learning: Run QA checks using `./run-qa-checks` before committing code

Applied to files:

  • .github/workflows/ci.yml
📚 Learning: 2026-01-22T22:37:11.271Z
Learnt from: CR
Repo: openwisp/netjsongraph.js PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-22T22:37:11.271Z
Learning: Applies to test/netjsongraph.browser.test.js : Write browser tests in test/netjsongraph.browser.test.js using Chrome/ChromeDriver and run with `yarn test test/netjsongraph.browser.test.js`

Applied to files:

  • .github/workflows/ci.yml
📚 Learning: 2026-01-22T22:37:11.271Z
Learnt from: CR
Repo: openwisp/netjsongraph.js PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-22T22:37:11.271Z
Learning: Applies to **/*.test.{js,ts} : Write unit tests using Jest with jsdom and run with `yarn test`

Applied to files:

  • .github/workflows/ci.yml
🪛 Biome (2.3.13)
webpack-plugins/inject-leaflet-loader.js

[error] 139-139: Do not shadow the global "constructor" property.

Consider renaming this variable. It's easy to confuse the origin of variables when they're named after a known global.

(lint/suspicious/noShadowRestrictedNames)

🪛 GitHub Check: CodeQL
.github/workflows/ci.yml

[warning] 52-129: Workflow does not contain permissions
Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {{contents: read}}

🔇 Additional comments (7)
index.html (1)

1-277: LGTM!

The removal of demo cards for netjsongraph-graphGL.html and netjsonmap-animation.html aligns with the PR objective to remove unused examples and reduce library size. The minor whitespace adjustments are acceptable.

.github/workflows/ci.yml (1)

83-98: Well-structured matrix strategy for multi-variant CI.

The conditional build and server steps correctly route to the appropriate build commands based on matrix.build-type. The fail-fast: false setting ensures all build variants complete even if one fails, which is good for comprehensive testing.

webpack-plugins/inject-leaflet-loader.js (1)

5-20: LGTM on plugin structure and map examples list.

The plugin correctly initializes with configurable options and maintains a clear list of Leaflet-dependent examples. The early return in apply() when !isEchartsOnly is a clean way to skip processing for full builds.

webpack.config.js (4)

22-59: Aggressive Terser options are well-configured for size reduction.

The compression settings with 3 passes, dead code elimination, and various optimization flags align with the PR objective to reduce library size. The pure_getters: true and toplevel: true options enable deep optimizations.


169-171: Good use of DefinePlugin for conditional Leaflet bundling.

The BUNDLE_LEAFLET compile-time constant enables dead code elimination for Leaflet-related code paths in echarts-only builds, supporting the PR's selective loading objective.


172-204: Compression plugins are well-configured.

Both gzip and Brotli compression are set up with appropriate thresholds (1024 bytes) and quality levels. Brotli quality 11 is maximum but acceptable for production builds where build time is less critical than output size.


89-103: No action required — sideEffects: false is correctly configured.

ECharts and zrender both properly declare their side effects in their package.json files, which means webpack respects those declarations even with the global sideEffects: false setting. This allows safe tree-shaking while preserving modules with implicit side effects. Leaflet is already externalized in the webpack configuration (lines 84-88), so it is not bundled and not affected by this setting.

The configuration is sound and follows webpack best practices for maximizing bundle efficiency while respecting module side effects.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

codesankalp and others added 9 commits February 2, 2026 08:02
Should maintain consistent return type (undefined instead of null)
- `InjectLeafletLoaderPlugin` assumes specific HTML structure that may break
- Uses regex to remove/replace script tags which is fragile
@codesankalp codesankalp force-pushed the fix/issue-392 branch 2 times, most recently from a2d6125 to 2caff6c Compare February 2, 2026 02:39
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/js/echarts-leaflet/index.js (1)

41-41: ⚠️ Potential issue | 🟡 Minor

Remove unused default export to fix pipeline failure.

The CI pipeline reports: Knip: Unused export 'default' found in src/js/echarts-leaflet/index.js. Since registerLeafletSystem is only imported and used as a named export in the codebase, this default export is redundant.

🔧 Suggested fix
 registerLeafletSystem.version = "1.0.0";
-
-export default registerLeafletSystem;
package.json (1)

57-93: ⚠️ Potential issue | 🟡 Minor

Remove unused acorn dependency from devDependencies.

acorn (line 61) is not referenced anywhere in the codebase. Remove it or add a Knip ignore rule if intentionally retained.

zrender (line 93) is included in jest transformIgnorePatterns, indicating it may be intentionally configured for proper module resolution despite being a transitive dependency of echarts. Verify with the team before removing.

🤖 Fix all issues with AI agents
In `@src/js/netjsongraph.js`:
- Around line 73-77: registerLeafletSystem currently never returns a value so
the guard lock is set even when registration failed; change
registerLeafletSystem to return true after successfully registering the Leaflet
system and return false when Leaflet is unavailable, then update the caller that
checks isLeafletRegistered so it only assigns isLeafletRegistered = true when
registerLeafletSystem() returns true (the code path just before the existing
init(this.graph.el, ...) call); ensure the boolean return is used to allow
future retries if registration initially failed.
🧹 Nitpick comments (1)
src/js/echarts-leaflet/LeafletCoordSys.js (1)

153-155: Redundant Leaflet availability check — already guarded at function entry.

The check if (typeof L === "undefined") at line 153 is now unreachable. If getLeaflet() returned undefined, line 14 would have already thrown. Consider removing this dead code.

♻️ Suggested fix
     ecModel.eachComponent("leaflet", (leafletModel) => {
       const viewportRoot = api.getZr().painter.getViewportRoot();
-      if (typeof L === "undefined") {
-        throw new Error("Leaflet api is not loaded");
-      }
       if (leafletCoordSys) {
         throw new Error("Only one leaflet component can exist");
       }
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ac84bae and a2d6125.

⛔ Files ignored due to path filters (3)
  • lib/css/assets/leaflet-measure.png is excluded by !**/*.png
  • public/assets/images/floorplan.png is excluded by !**/*.png
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (25)
  • .github/workflows/ci.yml
  • README.md
  • index.html
  • package.json
  • public/assets/data/airplaneRouteMap.json
  • public/assets/data/netjsongraph-graphGL.json
  • public/example_templates/netjsongraph-graphGL.html
  • public/example_templates/netjsonmap-animation.html
  • public/example_templates/netjsonmap-indoormap.html
  • src/js/echarts-leaflet/LeafletCoordSys.js
  • src/js/echarts-leaflet/LeafletModel.js
  • src/js/echarts-leaflet/LeafletView.js
  • src/js/echarts-leaflet/index.js
  • src/js/leaflet-loader.js
  • src/js/netjsongraph.clients.js
  • src/js/netjsongraph.config.js
  • src/js/netjsongraph.core.js
  • src/js/netjsongraph.geojson.js
  • src/js/netjsongraph.js
  • src/js/netjsongraph.render.js
  • test/browser.test.utils.js
  • test/netjsongraph.duplicateNodes.test.js
  • test/netjsongraph.render.test.js
  • webpack-plugins/inject-leaflet-loader.js
  • webpack.config.js
💤 Files with no reviewable changes (4)
  • test/browser.test.utils.js
  • public/example_templates/netjsongraph-graphGL.html
  • public/example_templates/netjsonmap-animation.html
  • public/assets/data/airplaneRouteMap.json
🚧 Files skipped from review as they are similar to previous changes (6)
  • src/js/netjsongraph.geojson.js
  • src/js/leaflet-loader.js
  • src/js/netjsongraph.clients.js
  • README.md
  • public/example_templates/netjsonmap-indoormap.html
  • index.html
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{js,ts,css,scss,json}

📄 CodeRabbit inference engine (AGENTS.md)

Format code using openwisp-qa-format command with Python virtualenv enabled and yarn lint:fix (runs eslint --fix and prettier via lint-staged)

Files:

  • webpack.config.js
  • src/js/netjsongraph.render.js
  • src/js/netjsongraph.config.js
  • src/js/echarts-leaflet/LeafletModel.js
  • src/js/echarts-leaflet/LeafletCoordSys.js
  • webpack-plugins/inject-leaflet-loader.js
  • src/js/echarts-leaflet/index.js
  • src/js/netjsongraph.js
  • src/js/netjsongraph.core.js
  • test/netjsongraph.render.test.js
  • src/js/echarts-leaflet/LeafletView.js
  • test/netjsongraph.duplicateNodes.test.js
  • package.json
src/**/*.js

📄 CodeRabbit inference engine (AGENTS.md)

Husky pre-commit hooks automatically run lint-staged to format JavaScript files in src/**/*.js with prettier

Files:

  • src/js/netjsongraph.render.js
  • src/js/netjsongraph.config.js
  • src/js/echarts-leaflet/LeafletModel.js
  • src/js/echarts-leaflet/LeafletCoordSys.js
  • src/js/echarts-leaflet/index.js
  • src/js/netjsongraph.js
  • src/js/netjsongraph.core.js
  • src/js/echarts-leaflet/LeafletView.js
**/*.test.{js,ts}

📄 CodeRabbit inference engine (AGENTS.md)

Write unit tests using Jest with jsdom and run with yarn test

Files:

  • test/netjsongraph.render.test.js
  • test/netjsongraph.duplicateNodes.test.js
🧠 Learnings (11)
📓 Common learnings
Learnt from: codesankalp
Repo: openwisp/netjsongraph.js PR: 425
File: src/js/netjsongraph.render.js:1-26
Timestamp: 2026-01-20T16:51:06.213Z
Learning: In the netjsongraph.js project, imports from `echarts/lib/chart/*/install`, `echarts/lib/component/*/install`, and `echarts/lib/renderer/install*` paths produce better tree-shaking results than the public entry points (`echarts/charts`, `echarts/components`, `echarts/renderers`) in their webpack configuration. The project maintainer has tested both approaches and confirmed the `/lib/*/install` pattern yields smaller bundles.
📚 Learning: 2026-01-20T16:51:06.213Z
Learnt from: codesankalp
Repo: openwisp/netjsongraph.js PR: 425
File: src/js/netjsongraph.render.js:1-26
Timestamp: 2026-01-20T16:51:06.213Z
Learning: In the netjsongraph.js project, imports from `echarts/lib/chart/*/install`, `echarts/lib/component/*/install`, and `echarts/lib/renderer/install*` paths produce better tree-shaking results than the public entry points (`echarts/charts`, `echarts/components`, `echarts/renderers`) in their webpack configuration. The project maintainer has tested both approaches and confirmed the `/lib/*/install` pattern yields smaller bundles.

Applied to files:

  • webpack.config.js
  • webpack-plugins/inject-leaflet-loader.js
  • test/netjsongraph.render.test.js
  • test/netjsongraph.duplicateNodes.test.js
  • package.json
📚 Learning: 2026-01-20T16:50:56.414Z
Learnt from: codesankalp
Repo: openwisp/netjsongraph.js PR: 425
File: src/js/netjsongraph.render.js:1-26
Timestamp: 2026-01-20T16:50:56.414Z
Learning: In netjsongraph.js, prefer importing from echarts/lib/chart/*/install, echarts/lib/component/*/install, and echarts/lib/renderer/install* rather than the public entry points (echarts/charts, echarts/components, echarts/renderers) to improve tree-shaking and reduce bundle size. Apply this pattern across JS files in src (update imports accordingly) and verify by comparing bundle sizes between the install-path imports and the public-entry imports.

Applied to files:

  • src/js/netjsongraph.render.js
  • src/js/netjsongraph.config.js
  • src/js/echarts-leaflet/LeafletModel.js
  • src/js/echarts-leaflet/LeafletCoordSys.js
  • src/js/echarts-leaflet/index.js
  • src/js/netjsongraph.js
  • src/js/netjsongraph.core.js
  • src/js/echarts-leaflet/LeafletView.js
📚 Learning: 2026-01-04T08:00:40.936Z
Learnt from: dee077
Repo: openwisp/netjsongraph.js PR: 417
File: src/js/netjsongraph.util.js:1302-1341
Timestamp: 2026-01-04T08:00:40.936Z
Learning: In the netjsongraph.js codebase, the `== null` pattern is intentionally used to check for both null and undefined values in a single comparison, which is a well-established JavaScript idiom and should not be flagged for replacement with strict equality checks.

Applied to files:

  • src/js/netjsongraph.render.js
  • webpack-plugins/inject-leaflet-loader.js
  • test/netjsongraph.duplicateNodes.test.js
📚 Learning: 2026-01-27T19:09:23.929Z
Learnt from: nemesifier
Repo: openwisp/netjsongraph.js PR: 473
File: src/js/netjsongraph.render.js:433-468
Timestamp: 2026-01-27T19:09:23.929Z
Learning: In the netjsongraph.js project, when implementing wheel zoom propagation from the graph container to the canvas: ECharts intercepts wheel events directly on the canvas with preventDefault(), so those events don't bubble to the container. A wheel listener on the container only catches events that occur outside the canvas (in the empty container space), making checks for e.target === canvas unnecessary since canvas events never reach the container listener.

Applied to files:

  • src/js/netjsongraph.render.js
  • src/js/echarts-leaflet/LeafletCoordSys.js
  • src/js/netjsongraph.js
📚 Learning: 2026-01-20T00:56:36.062Z
Learnt from: dee077
Repo: openwisp/netjsongraph.js PR: 417
File: src/js/netjsongraph.core.js:132-145
Timestamp: 2026-01-20T00:56:36.062Z
Learning: In the netjsongraph.js codebase, the data model does not support parallel links (multiple links between the same source and target). Ensure nodeLinkIndex is keyed by a single 'source~target' pair and that adding a link does not overwrite existing links for that pair. If parallel links are ever required, the data model must be changed (e.g., allow an array of links per 'source~target' key) rather than storing only one link.

Applied to files:

  • src/js/netjsongraph.render.js
  • src/js/netjsongraph.config.js
  • src/js/netjsongraph.core.js
  • test/netjsongraph.render.test.js
  • test/netjsongraph.duplicateNodes.test.js
📚 Learning: 2026-01-30T08:31:13.746Z
Learnt from: Dhruv-ub
Repo: openwisp/netjsongraph.js PR: 474
File: src/js/netjsongraph.render.js:347-351
Timestamp: 2026-01-30T08:31:13.746Z
Learning: In src/js/netjsongraph.render.js, treat showMapLabelsAtZoom = 0 as the explicit 'disabled' state (not a valid threshold). Do not rely on generic falsy checks for this flag. Update logic to distinguish between undefined/null (not set), 0 (disabled), and >0 (enabled at a given zoom). For example, use explicit checks such as: if (showMapLabelsAtZoom === 0) { /* labels hidden */ } else if (typeof showMapLabelsAtZoom === 'number' && showMapLabelsAtZoom > 0) { /* show labels at/above this zoom */ } else { /* default behavior or undefined */ }. If needed, document this convention or normalize input (e.g., set undefined to a sensible default) to avoid ambiguity in future reviews.

Applied to files:

  • src/js/netjsongraph.render.js
📚 Learning: 2026-01-22T22:37:11.271Z
Learnt from: CR
Repo: openwisp/netjsongraph.js PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-22T22:37:11.271Z
Learning: Run QA checks using `./run-qa-checks` before committing code

Applied to files:

  • .github/workflows/ci.yml
📚 Learning: 2026-01-22T22:37:11.271Z
Learnt from: CR
Repo: openwisp/netjsongraph.js PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-22T22:37:11.271Z
Learning: Applies to test/netjsongraph.browser.test.js : Write browser tests in test/netjsongraph.browser.test.js using Chrome/ChromeDriver and run with `yarn test test/netjsongraph.browser.test.js`

Applied to files:

  • .github/workflows/ci.yml
  • test/netjsongraph.render.test.js
  • test/netjsongraph.duplicateNodes.test.js
  • package.json
📚 Learning: 2026-01-22T22:37:11.271Z
Learnt from: CR
Repo: openwisp/netjsongraph.js PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-22T22:37:11.271Z
Learning: Applies to **/*.test.{js,ts} : Write unit tests using Jest with jsdom and run with `yarn test`

Applied to files:

  • .github/workflows/ci.yml
  • package.json
📚 Learning: 2026-01-20T00:42:32.369Z
Learnt from: dee077
Repo: openwisp/netjsongraph.js PR: 417
File: src/js/netjsongraph.core.js:132-145
Timestamp: 2026-01-20T00:42:32.369Z
Learning: In the netjsongraph.js codebase, node IDs are guaranteed not to contain the "~" character, so using "source~target" as a composite key for links in nodeLinkIndex will not collide with node ID keys.

Applied to files:

  • webpack-plugins/inject-leaflet-loader.js
🧬 Code graph analysis (6)
src/js/netjsongraph.render.js (3)
src/js/leaflet-loader.js (2)
  • L (12-12)
  • getLeaflet (14-38)
src/js/echarts-leaflet/LeafletCoordSys.js (1)
  • L (12-12)
src/js/netjsongraph.geojson.js (2)
  • L (136-136)
  • L (140-140)
src/js/netjsongraph.config.js (4)
src/js/leaflet-loader.js (2)
  • L (12-12)
  • getLeaflet (14-38)
src/js/echarts-leaflet/LeafletCoordSys.js (1)
  • L (12-12)
src/js/echarts-leaflet/index.js (1)
  • L (11-11)
src/js/netjsongraph.geojson.js (2)
  • L (136-136)
  • L (140-140)
src/js/echarts-leaflet/LeafletCoordSys.js (2)
src/js/leaflet-loader.js (2)
  • L (12-12)
  • getLeaflet (14-38)
src/js/echarts-leaflet/index.js (1)
  • L (11-11)
webpack-plugins/inject-leaflet-loader.js (1)
webpack.config.js (2)
  • InjectLeafletLoaderPlugin (10-10)
  • HtmlWebpackPlugin (5-5)
src/js/netjsongraph.js (1)
src/js/echarts-leaflet/index.js (1)
  • registerLeafletSystem (10-37)
src/js/echarts-leaflet/LeafletView.js (1)
src/js/echarts-leaflet/LeafletCoordSys.js (3)
  • mapOffset (126-126)
  • mapOffset (132-132)
  • api (113-113)
🪛 Biome (2.3.13)
webpack-plugins/inject-leaflet-loader.js

[error] 142-142: Do not shadow the global "constructor" property.

Consider renaming this variable. It's easy to confuse the origin of variables when they're named after a known global.

(lint/suspicious/noShadowRestrictedNames)

🪛 GitHub Actions: netjsongraph.js CI BUILD
src/js/echarts-leaflet/index.js

[error] 1-1: Knip: Unused export 'default' found in src/js/echarts-leaflet/index.js.

package.json

[error] 1-1: Knip: Unused dependency 'zrender' detected in package.json.


[error] 1-1: Knip: Unused devDependency 'acorn' detected in package.json.

🪛 GitHub Check: CodeQL
.github/workflows/ci.yml

[warning] 52-129: Workflow does not contain permissions
Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {{contents: read}}

🔇 Additional comments (9)
src/js/netjsongraph.core.js (1)

80-80: The async keyword doesn't make render() awaitable for completion.

The method is marked async but doesn't await the paginatedDataParse promise chain (lines 105-171). Callers who await render() will resolve immediately rather than when rendering actually completes.

If the intent is to let callers wait for full render completion, consider returning and awaiting the promise:

🔧 Suggested approach
  async render() {
    const [JSONParam, ...resParam] = this.JSONParam;
    // ... setup code ...
-   this.utils.paginatedDataParse
+   await this.utils.paginatedDataParse
      .call(this, JSONParam)
      .then((JSONData) => {
        // ...
      })
      .catch((error) => {
        console.error(error);
      });
    // ...
  }

Alternatively, if the current fire-and-forget behavior is intentional, consider documenting this or removing async to avoid misleading callers.

src/js/echarts-leaflet/LeafletModel.js (1)

1-17: LGTM!

Clean refactor removing the echarts parameter in favor of direct import from echarts/core. This aligns well with the modular architecture introduced by this PR.

src/js/echarts-leaflet/LeafletView.js (1)

1-7: LGTM!

The refactor to import extendComponentView and getInstanceByDom directly from echarts/core is correct. Removing the echarts and L parameters simplifies the API while maintaining functionality through the model's getLeaflet() method.

src/js/echarts-leaflet/LeafletCoordSys.js (1)

11-25: LGTM on the runtime Leaflet loading pattern.

The destructuring of Leaflet components (Layer, DomUtil, Projection, etc.) from the dynamically loaded instance is a clean approach that enables the echarts-only build while maintaining full functionality when Leaflet is available.

src/js/echarts-leaflet/index.js (1)

10-21: LGTM on the graceful Leaflet handling.

The early return when Leaflet is unavailable (lines 12-16) cleanly enables graph-only rendering without throwing errors, while still registering the coordinate system when Leaflet is present.

src/js/netjsongraph.config.js (1)

366-378: LGTM on the dynamic CRS resolution.

The getter-based crs property elegantly defers Leaflet loading until actually needed, returning null when Leaflet is unavailable. This supports the echarts-only build while maintaining full functionality for map rendering.

src/js/netjsongraph.render.js (3)

1-26: LGTM on the modular ECharts imports.

The echarts/lib/*/install import pattern aligns with project learnings confirming this approach produces better tree-shaking results than the public entry points. The use() registration covers all required components for both graph and map rendering.

Based on learnings: "In netjsongraph.js, prefer importing from echarts/lib/chart//install, echarts/lib/component//install, and echarts/lib/renderer/install* rather than the public entry points."


515-519: LGTM on the runtime Leaflet loading in mapRender.

Clean error handling when Leaflet is unavailable, with targeted destructuring of only the needed Leaflet utilities (circleMarker, latLngBounds). This pattern is consistent with other files in the PR.


206-207: LGTM on the series type updates.

The changes from graphGL to graph (line 206) and to scatter (line 327) correctly reflect the removal of echarts-gl dependency, aligning with the PR's goal of reducing library size.

Also applies to: 327-327

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

- Update CI to use matrix strategy for 'dev' (unit+browser), 'prod' (browser), and 'echarts-only' (browser) builds.
- Remove unsafe Terser optimizations to ensure stability.
- Fix 'InjectLeafletLoaderPlugin' regex to prevent accidental removal of plugin scripts.
- Use 'http-server' via npx in CI for serving production builds.

Signed-off-by: Sankalp <[email protected]>
@codesankalp
Copy link
Member Author

codesankalp commented Feb 2, 2026

I reviewed and done some improvements, see the commit description for more info.

I really want to merge this but unfortunately some examples break:

Other issues I see:

  • The command to build the echarts only variant is not used in the CI at all, if it breaks in the future we won't notice, can we find a way to run also this command and verify if it's working fine?

I tried and leaflet plugins was working for me. can you please tell where it was breaking for you?

I am testing echarts only build like this:

yarn build:echarts-only
npx --yes http-server dist -p 8080

Also, I have added separate CI matrix to test both complete and echarts only build where we are running browser tests.

"ignore": ["examples/**", "lib/js/**", "public/**", "scripts/**"],
"ignoreBinaries": ["wait-on"]
"ignoreBinaries": ["wait-on"],
"ignoreDependencies": ["leaflet"]
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is optional now.

@nemesifier
Copy link
Member

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Feb 2, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai bot added the dependencies Pull requests that update a dependency file label Feb 2, 2026
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
index.html (1)

123-131: ⚠️ Potential issue | 🟡 Minor

Make mobile cards truly full-width by sizing the card container.

width: 100% on the <a> alone won’t necessarily expand the .cards flex item, so the mobile layout may still hug content width. Consider setting the card width in the media query.

Proposed tweak
 `@media` (max-width: 768px) {
   main {
     flex-direction: column;
     align-items: center;
   }

+  .cards {
+    width: 100%;
+    max-width: 100%;
+  }
+
   .cards a {
     width: 100%;
   }
 }
.github/workflows/ci.yml (1)

26-29: ⚠️ Potential issue | 🟡 Minor

Update deprecated set-output command.

The ::set-output command is deprecated. Use $GITHUB_OUTPUT environment file instead.

🔧 Suggested fix
     - name: Get yarn cache directory path
       id: yarn-cache-dir-path
-      run: echo "::set-output name=dir::$(yarn cache dir)"
+      run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT

Also applies to line 69.

🤖 Fix all issues with AI agents
In `@package.json`:
- Around line 5-6: The package.json "main" entry points to a non-existent
index.js; fix by either adding a top-level index.js that re-exports the
library's public API (matching what built bundles export) or update the "main"
field to the actual build output (e.g., "dist/netjsongraph.js" or the production
filename pattern). Modify package.json "main" to the chosen bundle or create an
index.js that imports/exports from the build output (ensure symbols exported by
your library remain identical), and update any build/publish scripts if needed
so the referenced file exists after npm publish.

In `@webpack.config.js`:
- Around line 89-103: The optimization config sets sideEffects: false which
disables webpack's side-effects-based tree-shaking; change sideEffects to true
inside the optimization object (where usedExports, providedExports,
concatenateModules, etc. are set) so webpack will respect the package.json
"sideEffects" flags and drop unused modules—update the optimization.sideEffects
property from false to true and re-run the build to validate minification via
getMinimizers still behaves as expected.
🧹 Nitpick comments (1)
webpack-plugins/inject-leaflet-loader.js (1)

38-46: Consider extracting CDN version to a constant or reading from package.json.

The Leaflet version 1.8.0 is hardcoded in the CDN URLs. If the version in package.json is updated, these URLs would become stale. Consider extracting the version to a constant at the top of the file or reading it from package.json.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2caff6c and 1670b7b.

📒 Files selected for processing (9)
  • .github/workflows/ci.yml
  • index.html
  • knip.json
  • package.json
  • src/js/echarts-leaflet/index.js
  • src/js/leaflet-loader.js
  • src/js/netjsongraph.js
  • webpack-plugins/inject-leaflet-loader.js
  • webpack.config.js
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/js/leaflet-loader.js
  • src/js/netjsongraph.js
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{js,ts,css,scss,json}

📄 CodeRabbit inference engine (AGENTS.md)

Format code using openwisp-qa-format command with Python virtualenv enabled and yarn lint:fix (runs eslint --fix and prettier via lint-staged)

Files:

  • webpack.config.js
  • knip.json
  • webpack-plugins/inject-leaflet-loader.js
  • src/js/echarts-leaflet/index.js
  • package.json
src/**/*.js

📄 CodeRabbit inference engine (AGENTS.md)

Husky pre-commit hooks automatically run lint-staged to format JavaScript files in src/**/*.js with prettier

Files:

  • src/js/echarts-leaflet/index.js
🧠 Learnings (8)
📓 Common learnings
Learnt from: codesankalp
Repo: openwisp/netjsongraph.js PR: 425
File: src/js/netjsongraph.render.js:1-26
Timestamp: 2026-01-20T16:51:06.213Z
Learning: In the netjsongraph.js project, imports from `echarts/lib/chart/*/install`, `echarts/lib/component/*/install`, and `echarts/lib/renderer/install*` paths produce better tree-shaking results than the public entry points (`echarts/charts`, `echarts/components`, `echarts/renderers`) in their webpack configuration. The project maintainer has tested both approaches and confirmed the `/lib/*/install` pattern yields smaller bundles.
📚 Learning: 2026-01-20T16:51:06.213Z
Learnt from: codesankalp
Repo: openwisp/netjsongraph.js PR: 425
File: src/js/netjsongraph.render.js:1-26
Timestamp: 2026-01-20T16:51:06.213Z
Learning: In the netjsongraph.js project, imports from `echarts/lib/chart/*/install`, `echarts/lib/component/*/install`, and `echarts/lib/renderer/install*` paths produce better tree-shaking results than the public entry points (`echarts/charts`, `echarts/components`, `echarts/renderers`) in their webpack configuration. The project maintainer has tested both approaches and confirmed the `/lib/*/install` pattern yields smaller bundles.

Applied to files:

  • webpack.config.js
  • webpack-plugins/inject-leaflet-loader.js
  • package.json
📚 Learning: 2026-01-04T08:00:40.936Z
Learnt from: dee077
Repo: openwisp/netjsongraph.js PR: 417
File: src/js/netjsongraph.util.js:1302-1341
Timestamp: 2026-01-04T08:00:40.936Z
Learning: In the netjsongraph.js codebase, the `== null` pattern is intentionally used to check for both null and undefined values in a single comparison, which is a well-established JavaScript idiom and should not be flagged for replacement with strict equality checks.

Applied to files:

  • webpack-plugins/inject-leaflet-loader.js
📚 Learning: 2026-01-20T00:42:32.369Z
Learnt from: dee077
Repo: openwisp/netjsongraph.js PR: 417
File: src/js/netjsongraph.core.js:132-145
Timestamp: 2026-01-20T00:42:32.369Z
Learning: In the netjsongraph.js codebase, node IDs are guaranteed not to contain the "~" character, so using "source~target" as a composite key for links in nodeLinkIndex will not collide with node ID keys.

Applied to files:

  • webpack-plugins/inject-leaflet-loader.js
📚 Learning: 2026-01-20T16:50:56.414Z
Learnt from: codesankalp
Repo: openwisp/netjsongraph.js PR: 425
File: src/js/netjsongraph.render.js:1-26
Timestamp: 2026-01-20T16:50:56.414Z
Learning: In netjsongraph.js, prefer importing from echarts/lib/chart/*/install, echarts/lib/component/*/install, and echarts/lib/renderer/install* rather than the public entry points (echarts/charts, echarts/components, echarts/renderers) to improve tree-shaking and reduce bundle size. Apply this pattern across JS files in src (update imports accordingly) and verify by comparing bundle sizes between the install-path imports and the public-entry imports.

Applied to files:

  • src/js/echarts-leaflet/index.js
📚 Learning: 2026-01-22T22:37:11.271Z
Learnt from: CR
Repo: openwisp/netjsongraph.js PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-22T22:37:11.271Z
Learning: Applies to test/netjsongraph.browser.test.js : Write browser tests in test/netjsongraph.browser.test.js using Chrome/ChromeDriver and run with `yarn test test/netjsongraph.browser.test.js`

Applied to files:

  • package.json
  • .github/workflows/ci.yml
📚 Learning: 2026-01-22T22:37:11.271Z
Learnt from: CR
Repo: openwisp/netjsongraph.js PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-22T22:37:11.271Z
Learning: Applies to **/*.test.{js,ts} : Write unit tests using Jest with jsdom and run with `yarn test`

Applied to files:

  • package.json
  • .github/workflows/ci.yml
📚 Learning: 2026-01-22T22:37:11.271Z
Learnt from: CR
Repo: openwisp/netjsongraph.js PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-22T22:37:11.271Z
Learning: Run QA checks using `./run-qa-checks` before committing code

Applied to files:

  • .github/workflows/ci.yml
🧬 Code graph analysis (2)
webpack.config.js (2)
test/netjsongraph.duplicateNodes.test.js (1)
  • require (1-1)
test/netjsonWorker.test.js (1)
  • require (1-1)
src/js/echarts-leaflet/index.js (5)
src/js/leaflet-loader.js (2)
  • L (12-12)
  • getLeaflet (14-38)
src/js/netjsongraph.config.js (1)
  • L (370-370)
src/js/echarts-leaflet/LeafletCoordSys.js (1)
  • L (12-12)
src/js/echarts-leaflet/LeafletModel.js (1)
  • extendLeafletModel (6-50)
src/js/echarts-leaflet/LeafletView.js (1)
  • extendLeafletView (6-122)
🪛 Biome (2.3.13)
webpack-plugins/inject-leaflet-loader.js

[error] 142-142: Do not shadow the global "constructor" property.

Consider renaming this variable. It's easy to confuse the origin of variables when they're named after a known global.

(lint/suspicious/noShadowRestrictedNames)

🪛 GitHub Check: CodeQL
.github/workflows/ci.yml

[warning] 52-129: Workflow does not contain permissions
Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {{contents: read}}

🔇 Additional comments (11)
index.html (1)

245-249: The "Nodes expand or fold" demo ships correctly.

The netjsongraph-nodeExpand.html file exists at the referenced path (./examples/netjsongraph-nodeExpand.html), so the link will not 404. No changes needed.

knip.json (1)

5-6: LGTM!

The ignoreDependencies addition for leaflet is appropriate since it's now an optional peer dependency that's dynamically loaded at runtime. This prevents knip from incorrectly flagging it as unused.

.github/workflows/ci.yml (1)

119-121: LGTM!

Browser tests running unconditionally across all matrix build types (dev, prod, echarts-only) ensures the echarts-only build is properly exercised by CI, addressing the review concern about detecting future breakage.

src/js/echarts-leaflet/index.js (2)

10-16: LGTM!

The early return pattern with getLeaflet(true) (suppressing errors) gracefully enables graph-only rendering when Leaflet is unavailable. This aligns well with the echarts-only build strategy.


1-5: LGTM!

The imports are correctly structured. The echarts/core import for registerCoordinateSystem and registerAction is appropriate since these are core registration APIs, not chart/component/renderer modules where the /lib/*/install pattern applies for tree-shaking. Based on learnings, the /lib/*/install pattern is specifically for chart, component, and renderer imports.

webpack-plugins/inject-leaflet-loader.js (1)

57-127: LGTM!

The plugin loading logic with waitForLeaflet polling (5-second timeout) and coordinated onload/onerror handlers ensures robust initialization. The error handlers appropriately prevent blocking initialization if a plugin fails to load.

package.json (2)

89-99: LGTM!

The dependency structure is now correct:

  • leaflet in devDependencies for development/building
  • leaflet as optional peerDependency for consumers who need map features
  • Core dependencies reduced to only echarts and kdbush

This ensures echarts-only consumers won't install Leaflet by default while preserving full build capability.


6-6: sideEffects: false is correctly configured and safe to use.

The concern about import-time side effects breaking module registration is unfounded. The registerLeafletSystem() function is imported but not called at module load time—it's explicitly invoked only when initializeECharts() runs during construction. The echarts registrations (registerCoordinateSystem, registerAction) occur inside the function body, not on import. No CSS imports or other import-time side effects exist that would be affected by tree-shaking.

webpack.config.js (3)

61-88: LGTM!

The build configuration cleanly handles both build variants:

  • isEchartsOnly flag derived from BUILD_TYPE environment variable
  • Conditional filenames with contenthash for production caching
  • Leaflet externalized as "L" for echarts-only builds, aligning with CDN loading

22-59: LGTM!

The TerserPlugin configuration with aggressive compression options (3 passes, dead code elimination, unused removal) should produce well-optimized production bundles. The hoist_vars: false setting is appropriate as hoisting variables can cause issues in some scenarios.


169-171: LGTM!

The DefinePlugin correctly sets BUNDLE_LEAFLET to true for full builds and false for echarts-only builds, aligning with the conditional logic in src/js/leaflet-loader.js (lines 22-24) that decides whether to require("leaflet") or use window.L.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment on lines +5 to +6
"main": "index.js",
"sideEffects": false,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify index.js exists and check its contents
if [ -f "index.js" ]; then
  echo "index.js exists:"
  cat index.js
else
  echo "WARNING: index.js does not exist at package root"
  echo "Searching for potential entry points:"
  fd -t f 'index.js' --max-depth 2
fi

Repository: openwisp/netjsongraph.js

Length of output: 154


🏁 Script executed:

#!/bin/bash
# List files at package root
echo "=== Package root contents ==="
ls -la | grep -E '^-'

echo ""
echo "=== Directory structure (top level) ==="
ls -la | head -20

echo ""
echo "=== Search for all index files ==="
fd -t f 'index\.' --max-depth 3

echo ""
echo "=== Check if there's a dist or build directory ==="
ls -la | grep -E '^d' | grep -v '^\.'

Repository: openwisp/netjsongraph.js

Length of output: 3683


🏁 Script executed:

#!/bin/bash
# Check lib directory
echo "=== Contents of lib/ ==="
find lib -type f -name '*.js' -o -name '*.ts' | head -20

echo ""
echo "=== Contents of src/ ==="
find src -type f -name '*.js' -o -name '*.ts' | head -20

echo ""
echo "=== Current package.json main field ==="
grep -A 2 '"main"' package.json

echo ""
echo "=== package.json excerpt (lines 1-20) ==="
head -20 package.json

Repository: openwisp/netjsongraph.js

Length of output: 1556


🏁 Script executed:

#!/bin/bash
# Check webpack.config.js for output configuration
echo "=== webpack.config.js output config ==="
grep -A 10 "output:" webpack.config.js

echo ""
echo "=== Check if there's any dist or build directory already ==="
ls -la | grep -E '^d' | awk '{print $NF}'

Repository: openwisp/netjsongraph.js

Length of output: 765


The main entry point does not exist and must be fixed before publishing.

The package.json points to "main": "index.js", but this file does not exist at the package root. npm consumers will fail to import this package. The webpack configuration outputs to dist/ with filenames like netjsongraph.js (dev) or netjsongraph.[contenthash:8].min.js (production).

Either create an index.js entry point that re-exports the library's public API, or update the main field to point to the correct build output location (e.g., dist/netjsongraph.js for production builds).

🤖 Prompt for AI Agents
In `@package.json` around lines 5 - 6, The package.json "main" entry points to a
non-existent index.js; fix by either adding a top-level index.js that re-exports
the library's public API (matching what built bundles export) or update the
"main" field to the actual build output (e.g., "dist/netjsongraph.js" or the
production filename pattern). Modify package.json "main" to the chosen bundle or
create an index.js that imports/exports from the build output (ensure symbols
exported by your library remain identical), and update any build/publish scripts
if needed so the referenced file exists after npm publish.

Comment on lines +89 to +103
optimization: {
minimize: isProduction,
minimizer: getMinimizers(isProduction),
usedExports: true,
sideEffects: false,
providedExports: true,
concatenateModules: true,
flagIncludedChunks: true,
mangleExports: true,
removeAvailableModules: true,
removeEmptyChunks: true,
mergeDuplicateChunks: true,
innerGraph: true,
moduleIds: "deterministic",
},
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

webpack optimization.sideEffects true false meaning documentation

💡 Result:

In webpack, optimization.sideEffects controls whether webpack will drop entire modules/files during tree shaking when it believes importing them isn’t needed (because the import has “no side effects”). This works together with the "sideEffects" field in a package’s package.json (and module.rules[].sideEffects). [1]

  • optimization.sideEffects: true (default)
    webpack will use side-effects information and can also analyze code to mark some modules as side‑effect‑free, allowing it to skip those modules when their exports aren’t used. [1]

  • optimization.sideEffects: false
    Disables this optimization: webpack will not try to skip modules based on side-effects detection/flags (so it’s less aggressive about pruning). [1]

  • optimization.sideEffects: "flag"
    webpack will only use the manual flags (package.json "sideEffects" and rule.sideEffects) and will not analyze source code for side effects. This is the default behavior in non‑production builds. [1]

Related (often confused) concept: the "sideEffects" field in a package’s package.json is the hint you (as a library/package author) provide:

  • "sideEffects": false means “all files in this package are side‑effect‑free,” enabling webpack to drop unused files/imports (but this can be wrong for things like CSS/polyfills). [2]

Sources: webpack optimization config (optimization.sideEffects) [1], webpack tree-shaking guide (package.json "sideEffects") [2].


Change sideEffects: false to sideEffects: true to enable tree-shaking optimization.

In webpack's optimization.sideEffects, setting it to false disables tree-shaking: webpack will not skip modules based on side-effects detection. Setting it to true (the default) enables webpack to use side-effects information and analyze code to drop unused modules. Given the other aggressive optimizations in this config (usedExports: true, providedExports: true), this should be true to properly leverage the "sideEffects" field from package.json.

🔧 Suggested fix
   optimization: {
     minimize: isProduction,
     minimizer: getMinimizers(isProduction),
     usedExports: true,
-    sideEffects: false,
+    sideEffects: true,
     providedExports: true,
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
optimization: {
minimize: isProduction,
minimizer: getMinimizers(isProduction),
usedExports: true,
sideEffects: false,
providedExports: true,
concatenateModules: true,
flagIncludedChunks: true,
mangleExports: true,
removeAvailableModules: true,
removeEmptyChunks: true,
mergeDuplicateChunks: true,
innerGraph: true,
moduleIds: "deterministic",
},
optimization: {
minimize: isProduction,
minimizer: getMinimizers(isProduction),
usedExports: true,
sideEffects: true,
providedExports: true,
concatenateModules: true,
flagIncludedChunks: true,
mangleExports: true,
removeAvailableModules: true,
removeEmptyChunks: true,
mergeDuplicateChunks: true,
innerGraph: true,
moduleIds: "deterministic",
},
🤖 Prompt for AI Agents
In `@webpack.config.js` around lines 89 - 103, The optimization config sets
sideEffects: false which disables webpack's side-effects-based tree-shaking;
change sideEffects to true inside the optimization object (where usedExports,
providedExports, concatenateModules, etc. are set) so webpack will respect the
package.json "sideEffects" flags and drop unused modules—update the
optimization.sideEffects property from false to true and re-run the build to
validate minification via getMinimizers still behaves as expected.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Pull requests that update a dependency file enhancement javascript Pull requests that update Javascript code

Projects

Status: In progress

Development

Successfully merging this pull request may close these issues.

[change] Reduce library size

3 participants