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
@coderabbitai
Copy link

coderabbitai bot commented Jan 20, 2026

📝 Walkthrough

Walkthrough

This PR splits builds into Full and ECharts-only variants, adds a runtime Leaflet loader module, refactors ECharts usage to modular imports from echarts/core, moves Leaflet to an optional peer dependency, makes NetJSONGraph.render asynchronous, removes several example pages and a large data asset, updates package.json scripts/deps, introduces a webpack multi-build config with compression and bundle analysis, and adds an InjectLeafletLoaderPlugin to inject runtime Leaflet loading for echarts-only HTML outputs.

Sequence Diagram(s)

sequenceDiagram
    participant Dev as Developer
    participant Webpack as Webpack Build
    participant Plugin as InjectLeafletLoaderPlugin
    participant Output as HTML Output
    participant Browser as Browser Runtime
    participant Loader as getLeaflet
    participant ECharts as ECharts (core)
    participant Leaflet as Leaflet

    Note over Dev,Webpack: Build Time (echarts-only)
    Dev->>Webpack: yarn build:echarts-only
    Webpack->>Webpack: set BUILD_TYPE=echarts-only
    Webpack->>Plugin: apply plugin (process HTML)
    Plugin->>Output: remove hardcoded Leaflet assets
    Plugin->>Output: inject loader snippet wrapping example scripts
    Webpack->>Output: emit modified HTML

    Note over Browser,Loader: Runtime
    Browser->>Output: load page
    Output->>Loader: execute injected loader snippet
    Loader->>Leaflet: attempt load (window.L or CDN)
    alt Leaflet available
        Leaflet->>Browser: provide API
    else Leaflet missing
        Loader-->>Browser: return undefined (suppress error optional)
    end
    Browser->>ECharts: call registerLeafletSystem() (runtime)
    ECharts->>Loader: getLeaflet() for coord system / CRS
    Browser->>Output: execute original example script which may initialize graphs/maps
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title '[change] Reduce library size #392' directly and clearly relates to the main objective of the pull request, which is to reduce build output size through chunk splitting, dynamic loading, and removal of unused examples.
Description check ✅ Passed The PR description covers the key points: checklist items completed, issue reference (#392), and core changes (chunk splitting, dynamic loading, removing unused examples). However, it lacks detailed explanation of technical implementation, build configuration changes, and file removals.
Linked Issues check ✅ Passed The PR successfully addresses issue #392 requirements: removes unused examples (netjsongraph-graphGL.html, netjsonmap-animation.html), implements chunk splitting and dynamic loading via webpack config changes, and provides selective dependency inclusion (build:full vs build:echarts-only).
Out of Scope Changes check ✅ Passed The PR contains changes aligned with stated objectives: build configuration refactoring, example removal, dynamic Leaflet loading implementation, and package.json updates for build variants. Minor formatting changes in index.html (quote standardization) are tangential but contribute to code quality.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

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 (1)
src/js/netjsongraph.core.js (1)

80-142: Async method doesn't return or await its promise chain.

The render() method is marked async but doesn't return or await the promise from paginatedDataParse. This means calling await render() will resolve immediately without waiting for data processing to complete, making the async keyword misleading.

Proposed fix - return the promise chain
   async render() {
     const [JSONParam, ...resParam] = this.JSONParam;

     this.config.onRender.call(this);
     this.event.once("onReady", this.config.onReady.bind(this));
     this.event.once("onLoad", this.config.onLoad.bind(this));
-    this.utils.paginatedDataParse
+    await this.utils.paginatedDataParse
       .call(this, JSONParam)
       .then((JSONData) => {
         // ... existing logic
       })
       .catch((error) => {
         console.error(error);
       });

Or alternatively, convert the .then() chain to try/catch with await for consistency with the async pattern.

🤖 Fix all issues with AI agents
In `@package.json`:
- Around line 87-97: Remove "leaflet" from the "dependencies" section in
package.json so it is only declared as a peer dependency (and optional via
"peerDependenciesMeta"); update the package.json to leave "leaflet" only under
"peerDependencies" and "peerDependenciesMeta" (keeping "zrender" and other
actual runtime deps in "dependencies") so the echarts-only build will not bundle
Leaflet and consumers must provide it.

In `@src/js/netjsongraph.render.js`:
- Around line 213-214: Remove the now-dead conditional branches that test
params.componentSubType === "graphGL" in the event-handling code (the two
occurrences flagged in netjsongraph.render.js); delete those if/else branches
and any variables or fallback logic that only applied to "graphGL", and ensure
events fall through to the existing "graph" handling path (keep existing
handlers that use the generic graph type). Locate the checks by searching for
params.componentSubType === "graphGL" and remove the entire branch bodies so
only the active graph event logic remains; run the component event flows to
confirm no behavior regressions.
🧹 Nitpick comments (7)
src/js/echarts-leaflet/LeafletView.js (1)

38-39: Consider removing the unused parameter instead of suppressing the lint warning.

The e parameter in moveHandler is never used. Since the function is called directly in zoomHandler() (line 86) without arguments, you could remove it entirely rather than suppressing the warning.

Proposed fix
-      // eslint-disable-next-line no-unused-vars
-      function moveHandler(e) {
+      function moveHandler() {
src/js/echarts-leaflet/LeafletCoordSys.js (1)

76-81: Consider simplifying the nested ternary for readability.

The chained ternary for coordSys resolution works but is somewhat dense. This is acceptable given the /* eslint-disable */ block, but a future refactor could extract this logic.

♻️ Optional: Clearer coordSys resolution
-    const coordSys = leafletModel
-      ? leafletModel.coordinateSystem
-      : seriesModel
-        ? seriesModel.coordinateSystem || // For map.
-          (seriesModel.getReferringComponents("leaflet")[0] || {}).coordinateSystem
-        : null;
+    let coordSys = null;
+    if (leafletModel) {
+      coordSys = leafletModel.coordinateSystem;
+    } else if (seriesModel) {
+      coordSys = seriesModel.coordinateSystem ||
+        (seriesModel.getReferringComponents("leaflet")[0] || {}).coordinateSystem;
+    }
webpack-plugins/inject-leaflet-loader.js (2)

102-104: Rename variable to avoid shadowing global constructor.

The static analysis tool correctly identifies that constructor shadows the global property. Renaming improves clarity.

♻️ Rename shadowed variable
       const HtmlWebpackPlugin = compiler.options.plugins
-        .map((plugin) => plugin.constructor)
-        .find((constructor) => constructor && constructor.name === "HtmlWebpackPlugin");
+        .map((plugin) => plugin.constructor)
+        .find((ctor) => ctor && ctor.name === "HtmlWebpackPlugin");

140-145: Regex-based script removal may be fragile.

The do-while loop removes Leaflet script tags iteratively. This approach works but could be simplified. Consider if a single global replace would suffice.

♻️ Simpler approach using global replace
-        const leafletJSRegex = /<script[^>]*leaflet[^>]*>[\s\S]*?<\/script>/gi;
-        let prev;
-        do {
-          prev = html;
-          html = html.replace(leafletJSRegex, "");
-        } while (html !== prev);
+        // Remove all leaflet script tags in one pass
+        html = html.replace(/<script[^>]*leaflet[^>]*>[\s\S]*?<\/script>/gi, "");

Note: The loop may have been intentional for nested matches, but gi flag handles multiple occurrences.

webpack.config.js (3)

22-56: unsafe* Terser flags can change runtime semantics.

Line 49-55 enables multiple unsafe* options. These can introduce subtle runtime changes (e.g., around -0, getters, or prototype checks). If you keep them for size, please gate behind an explicit env flag and validate the minified build with production tests.

💡 Possible gating to make unsafe options opt-in
 const getMinimizers = (isProduction) => {
   const minimizers = [];
   if (isProduction) {
+    const enableUnsafe = process.env.TERSER_UNSAFE === "true";
     minimizers.push(
       new TerserPlugin({
         extractComments: false,
         parallel: true,
         terserOptions: {
           compress: {
             passes: 3,
             dead_code: true,
             unused: true,
             toplevel: true,
             reduce_vars: true,
             collapse_vars: true,
             evaluate: true,
             conditionals: true,
             sequences: true,
             properties: true,
             booleans: true,
             if_return: true,
             join_vars: true,
             side_effects: true,
             negate_iife: true,
             hoist_funs: true,
             hoist_vars: false,
             pure_getters: true,
-            unsafe: true,
-            unsafe_comps: true,
-            unsafe_Function: true,
-            unsafe_math: true,
-            unsafe_proto: true,
-            unsafe_regexp: true,
-            unsafe_undefined: true,
+            ...(enableUnsafe
+              ? {
+                  unsafe: true,
+                  unsafe_comps: true,
+                  unsafe_Function: true,
+                  unsafe_math: true,
+                  unsafe_proto: true,
+                  unsafe_regexp: true,
+                  unsafe_undefined: true,
+                }
+              : {}),
           },

179-209: Ensure hosting serves the generated .gz/.br assets.

The compression plugins will emit precompressed files, but they won’t help unless your static server is configured to serve them with the proper Content-Encoding headers.


77-88: Consider using publicPath: "auto" for better deployment flexibility.

This library bundle currently uses absolute publicPath: "/", which works for root-level hosting but assumes the library will always be served from the site root. For a reusable library that may be hosted at different paths or from CDNs with custom base URLs, publicPath: "auto" (supported in webpack 5.84.0+) is a more resilient choice that adapts to the runtime context.

Suggested change
-      publicPath: "/",
+      publicPath: "auto",
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 362c9c4 and e0fc528.

⛔ Files ignored due to path filters (2)
  • lib/js/echarts-gl.min.js is excluded by !**/*.min.js
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (25)
  • .prettierrc
  • 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)
  • public/example_templates/netjsongraph-graphGL.html
  • public/example_templates/netjsonmap-animation.html
  • test/browser.test.utils.js
  • public/assets/data/airplaneRouteMap.json
🧰 Additional context used
🧠 Learnings (3)
📚 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.clients.js
  • test/netjsongraph.render.test.js
  • src/js/netjsongraph.render.js
  • test/netjsongraph.duplicateNodes.test.js
  • src/js/netjsongraph.geojson.js
  • src/js/netjsongraph.config.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
  • test/netjsongraph.duplicateNodes.test.js
📚 Learning: 2026-01-20T00:42:26.416Z
Learnt from: dee077
Repo: openwisp/netjsongraph.js PR: 417
File: src/js/netjsongraph.core.js:132-145
Timestamp: 2026-01-20T00:42:26.416Z
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 (11)
webpack-plugins/inject-leaflet-loader.js (1)
webpack.config.js (2)
  • InjectLeafletLoaderPlugin (10-10)
  • HtmlWebpackPlugin (5-5)
test/netjsongraph.render.test.js (2)
src/js/netjsongraph.geojson.js (1)
  • map (142-142)
test/netjsongraph.duplicateNodes.test.js (1)
  • NetJSONGraphRender (2-2)
src/js/leaflet-loader.js (4)
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 (364-364)
src/js/netjsongraph.geojson.js (2)
  • L (136-136)
  • L (140-140)
src/js/netjsongraph.render.js (3)
src/js/echarts-leaflet/index.js (1)
  • L (11-11)
src/js/leaflet-loader.js (2)
  • L (12-12)
  • getLeaflet (14-38)
src/js/netjsongraph.geojson.js (2)
  • L (136-136)
  • L (140-140)
src/js/netjsongraph.geojson.js (4)
src/js/echarts-leaflet/LeafletCoordSys.js (1)
  • L (12-12)
src/js/echarts-leaflet/index.js (1)
  • L (11-11)
src/js/leaflet-loader.js (2)
  • L (12-12)
  • getLeaflet (14-38)
src/js/netjsongraph.config.js (1)
  • L (364-364)
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 (3)
src/js/echarts-leaflet/LeafletCoordSys.js (1)
  • L (12-12)
src/js/leaflet-loader.js (2)
  • L (12-12)
  • getLeaflet (14-38)
src/js/echarts-leaflet/LeafletModel.js (1)
  • extendLeafletModel (6-50)
src/js/echarts-leaflet/LeafletCoordSys.js (3)
src/js/echarts-leaflet/index.js (1)
  • L (11-11)
src/js/leaflet-loader.js (2)
  • L (12-12)
  • getLeaflet (14-38)
src/js/netjsongraph.geojson.js (3)
  • L (136-136)
  • L (140-140)
  • map (142-142)
src/js/netjsongraph.config.js (4)
src/js/echarts-leaflet/LeafletCoordSys.js (1)
  • L (12-12)
src/js/echarts-leaflet/index.js (1)
  • L (11-11)
src/js/leaflet-loader.js (2)
  • L (12-12)
  • getLeaflet (14-38)
src/js/netjsongraph.geojson.js (2)
  • L (136-136)
  • L (140-140)
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.1.2)
webpack-plugins/inject-leaflet-loader.js

[error] 104-104: 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 (31)
.prettierrc (1)

4-5: LGTM!

The printWidth: 88 setting aligns well with common formatting standards (e.g., Python's black formatter) and provides consistency across the project.

README.md (1)

138-173: LGTM!

The new "Building the library" section clearly documents both build variants with appropriate use cases, outputs, and requirements. The documentation aligns well with the PR objectives for size reduction and provides clear guidance for integrators.

public/example_templates/netjsonmap-indoormap.html (1)

13-17: LGTM!

Adding the Leaflet JS script from the CDN is necessary to support the echarts-only build variant. The SRI integrity hash and crossorigin attribute provide proper security for the external resource. This correctly enables the L namespace usage throughout the example.

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

1-2: LGTM!

Importing graphic directly from echarts/core aligns with the tree-shakeable modular ECharts pattern and removes dependency on global echarts references. This is consistent with the PR's broader refactoring approach.


61-61: Correct usage of modular graphic API.

The graphic.Group instantiation properly uses the imported module instead of the previous global reference.


136-144: Correct usage of modular graphic API.

The graphic.Circle instantiation properly uses the imported module for creating client dot overlays.

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

1-2: LGTM!

Importing extendComponentModel directly from echarts/core is the correct modular approach for ECharts 5.x component registration.


6-6: Clean API simplification.

Removing the echarts parameter from the function signature simplifies the API since the required function is now imported directly. This aligns with the broader refactoring to use modular ECharts imports.


17-49: LGTM!

The component model registration correctly uses the imported extendComponentModel function. The model definition with its methods (getLeaflet, setCenterAndZoom, centerOrZoomChanged) and defaultOption remains functionally unchanged.

index.html (3)

36-42: LGTM!

The CSS transition property reformatted to multi-line improves readability, and the quote standardization to double quotes provides consistency.


145-239: Removal of unused example cards aligns with PR objectives.

The removal of netjsongraph-graphGL.html and netjsonmap-animation.html demo cards aligns with issue #392's objective to reduce bundle size by removing unused features and examples.


241-263: LGTM!

The theme toggle script logic is correct. The quote standardization to double quotes is consistent with the formatting changes throughout the file.

package.json (1)

6-13: LGTM!

The sideEffects: false declaration enables tree-shaking optimization, and the new build scripts properly support the dual build variants (full and echarts-only) via the BUILD_TYPE environment variable.

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

2-2: LGTM!

The import pattern correctly retrieves the default export from the refactored module, aligning with the broader change where NetJSONGraphRender is now exported as a default export rather than a named export.

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

136-140: LGTM!

The dynamic Leaflet loading with proper null guard ensures graceful handling when Leaflet is unavailable (echarts-only build). This pattern is consistent with usage in other files like LeafletCoordSys.js and index.js.

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

1-7: LGTM!

The refactor from echarts globals to modular imports (extendComponentView, getInstanceByDom) aligns with echarts 5.x best practices and enables better tree-shaking. The parameterless function signature simplifies the API since dependencies are now imported directly.

src/js/leaflet-loader.js (1)

14-37: LGTM!

The loader correctly implements a caching strategy with conditional loading based on the build type. The suppressError parameter is appropriately used by callers like src/js/echarts-leaflet/index.js to gracefully handle optional Leaflet scenarios. The typeof window check ensures compatibility with SSR/Node environments.

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

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

The updated imports correctly reflect the shift to default exports for NetJSONGraphRender and explicit Leaflet import, matching the broader refactoring in this PR.


705-713: Good test isolation practice.

Preserving and restoring the original prototype in beforeEach/afterEach ensures clustering tests don't leak prototype modifications to other tests.

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

11-24: Clean factory pattern with runtime Leaflet loading.

The early validation and destructuring of Leaflet utilities is well-structured. This ensures the coordinate system is only created when Leaflet is actually available.

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

360-369: Well-designed dynamic CRS getter.

The getter pattern correctly defers Leaflet access until runtime, returning null when Leaflet is unavailable. This enables echarts-only builds to function without Leaflet while preserving full functionality when Leaflet is present.

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

1-26: Excellent modular ECharts integration.

The shift to explicit chart/component/renderer imports from echarts/core enables tree-shaking and significantly reduces bundle size. The use() call registers only what's needed.


464-468: Proper Leaflet validation for map rendering.

Throwing early when Leaflet is unavailable provides a clear error message rather than cryptic runtime failures. The destructuring of circleMarker and latLngBounds is clean.

src/js/netjsongraph.js (3)

9-9: Good one-time registration pattern.

The isLeafletRegistered guard ensures the Leaflet coordinate system is registered exactly once, preventing duplicate registration errors when multiple NetJSONGraph instances are created.

Also applies to: 73-76


77-79: Direct init usage improves tree-shaking.

Using init from echarts/core instead of echarts.init allows bundlers to eliminate unused ECharts code, contributing to the size reduction goal.


172-172: No changes required—this concern does not apply.

The library does not and has not exposed window.echarts or window.L as global objects. Only window.NetJSONGraph is exposed at line 172. The library stores ECharts and Leaflet as instance properties (this.echarts, this.leaflet), while external code is expected to provide Leaflet separately as a global L object when needed (as documented in README.md line 172). There is no breaking change to integrations.

Likely an incorrect or invalid review comment.

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

35-36: Leaflet version is already consistent.

The CDN URLs correctly use Leaflet 1.8.0, which matches the minimum version specified in package.json (^1.8.0). The pinned CDN version prevents unintended updates while allowing flexibility in local dependencies. No changes needed.

webpack.config.js (3)

111-124: CSS + asset/resource rules look solid.

The CSS loader and asset/resource generator setup is straightforward and aligns with the new bundling goals.


3-10: I need the review comment to rewrite. Please provide the review comment within <review_comment> tags so I can proceed with verification and rewriting.


96-101: Review comment is incorrect regarding webpack optimization behavior.

The claim that optimization.sideEffects: false "can drop CSS/side-effect imports" mischaracterizes webpack's behavior. Setting optimization.sideEffects: false disables side-effects-based tree-shaking, meaning webpack will NOT remove modules based on side-effects analysis. This actually reduces the risk of CSS being dropped.

The actual concern lies with "sideEffects": false in package.json (line 6), which marks the entire library as side-effect-free. However, since CSS is loaded through style-loader/css-loader (not direct CSS imports in source), and the webpack config disables side-effects pruning, CSS is protected from tree-shaking removal in the current setup.

Changing the webpack config setting to true would enable side-effects analysis, which combined with the package.json declaration, could actually create the problem described—not fix it.

Likely an incorrect or invalid review comment.

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

10-22: The review comment is based on an incorrect understanding of the code location. The const L = getLeaflet(); in LeafletCoordSys.js is inside the createLeafletCoordSystem() function (line 12), not at module scope (line 11 is the function declaration). Since getLeaflet() is called at runtime when createLeafletCoordSystem() is invoked, the code already supports dynamic Leaflet loading without module-scope caching. No changes are needed.

Likely an incorrect or invalid review comment.

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

@github-project-automation github-project-automation bot moved this from Backlog to In progress in 25.09 Release Jan 20, 2026
Signed-off-by: Sankalp <sankalp.nex@gmail.com>
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

🤖 Fix all issues with AI agents
In `@src/js/netjsongraph.render.js`:
- Around line 1-26: The imports use legacy private paths (echarts/lib/...) which
hurt tree-shaking; replace them with the public entry points and named exports:
import GraphChart, LinesChart, ScatterChart from "echarts/charts"; import
TooltipComponent, TitleComponent, ToolboxComponent, LegendComponent,
GraphicComponent from "echarts/components"; import SVGRenderer, CanvasRenderer
from "echarts/renderers"; keep the existing use([GraphChart, LinesChart,
TooltipComponent, TitleComponent, ToolboxComponent, LegendComponent,
SVGRenderer, CanvasRenderer, ScatterChart, GraphicComponent]) call but remove
the "install as" style and the "echarts/lib/..." paths so the same symbols
(GraphChart, LinesChart, ScatterChart, TooltipComponent, TitleComponent,
ToolboxComponent, LegendComponent, GraphicComponent, SVGRenderer,
CanvasRenderer) are imported from the public modules for proper tree-shaking.
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e0fc528 and 0194a40.

📒 Files selected for processing (2)
  • package.json
  • src/js/netjsongraph.render.js
🚧 Files skipped from review as they are similar to previous changes (1)
  • package.json
🧰 Additional context used
🧠 Learnings (2)
📚 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
📚 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
🧬 Code graph analysis (1)
src/js/netjsongraph.render.js (5)
src/js/echarts-leaflet/index.js (1)
  • L (11-11)
src/js/leaflet-loader.js (2)
  • L (12-12)
  • getLeaflet (14-38)
src/js/netjsongraph.config.js (1)
  • L (364-364)
src/js/echarts-leaflet/LeafletCoordSys.js (1)
  • L (12-12)
src/js/netjsongraph.geojson.js (2)
  • L (136-136)
  • L (140-140)
🔇 Additional comments (7)
src/js/netjsongraph.render.js (7)

203-212: LGTM! Simplified graph series configuration.

Removing the conditional graphGL handling in favor of a consistent type: "graph" with a default "force" layout is a good simplification. This eliminates the echarts-gl dependency while maintaining standard force-directed layout functionality.


325-331: LGTM! Simplified to standard scatter series.

Switching from conditional effectScatter to a plain scatter series type reduces complexity and bundle size by eliminating the animation effects component. This is a reasonable trade-off for the size reduction objectives.


458-462: LGTM! Proper Leaflet availability guard.

The guard correctly checks for Leaflet availability via getLeaflet() and throws a descriptive error early if unavailable. Destructuring circleMarker and latLngBounds immediately after the guard ensures they're available for the rest of the method.


490-504: LGTM! Consistent use of destructured Leaflet utilities.

The circleMarker function is correctly used from the destructured Leaflet instance, maintaining consistency with the dynamic loading pattern.


524-534: LGTM! Proper bounds calculation with destructured latLngBounds.

The latLngBounds factory function is correctly used to create bounds from the node locations array. The [lat, lng] format is valid input for Leaflet's latLngBounds constructor.


675-685: LGTM! Simplified cluster click handling.

The condition now correctly checks only for "scatter" component subtype, aligned with the removal of the effectScatter path. The zoom-to-cluster behavior is preserved.


812-812: Default export is appropriate for the module's API.

The change to a default export aligns with the module's actual public interface. The module only exports NetJSONGraphRender as intended for consumers; echarts and L are internal dependencies not exposed through this module's exports.

Likely an incorrect or invalid review comment.

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

Comment on lines +1 to 26
import {use} from "echarts/core";
import {install as LinesChart} from "echarts/lib/chart/lines/install";
import {install as GraphChart} from "echarts/lib/chart/graph/install";
import {install as ScatterChart} from "echarts/lib/chart/scatter/install";
import {install as TooltipComponent} from "echarts/lib/component/tooltip/install";
import {install as TitleComponent} from "echarts/lib/component/title/install";
import {install as ToolboxComponent} from "echarts/lib/component/toolbox/install";
import {install as LegendComponent} from "echarts/lib/component/legend/install";
import {install as GraphicComponent} from "echarts/lib/component/graphic/install";
import {install as SVGRenderer} from "echarts/lib/renderer/installSVGRenderer";
import {install as CanvasRenderer} from "echarts/lib/renderer/installCanvasRenderer";
import getLeaflet from "./leaflet-loader";
import {addPolygonOverlays} from "./netjsongraph.geojson";

echarts.use([
use([
GraphChart,
EffectScatterChart,
LinesChart,
TooltipComponent,
TitleComponent,
ToolboxComponent,
LegendComponent,
SVGRenderer,
CanvasRenderer,
ScatterChart,
GraphicComponent,
]);
Copy link

@coderabbitai coderabbitai bot Jan 20, 2026

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

🌐 Web query:

echarts 5 tree-shaking recommended import paths echarts/core vs echarts/lib

💡 Result:

Use the tree-shaking API: import from "echarts/core" plus specific subpackages (echarts/charts, echarts/components, echarts/renderers) and call echarts.use([...]). This gives the smallest bundles and is the recommended approach in ECharts 5. The legacy "echarts/lib/..." (or importing the whole "echarts" package) is supported for CommonJS/compat but does not tree-shake as well. [1][2]

References:

  • ECharts handbook — Import / tree-shaking example. [1]
  • v5 upgrade guide — introduces the new tree-shaking API and recommends it over echarts/lib. [2]

🌐 Web query:

echarts official documentation modular imports tree-shaking entry points

💡 Result:

Short summary — how to do modular imports, tree-shaking and which entry points to use in ECharts:

  • Tree-shakable API (recommended): import the core and then only the chart/component/feature/renderer modules you need, then register them with echarts.use([...]). You must import either CanvasRenderer or SVGRenderer. Example pattern:
    import * as echarts from 'echarts/core';
    import { BarChart } from 'echarts/charts';
    import { TitleComponent, TooltipComponent } from 'echarts/components';
    import { CanvasRenderer } from 'echarts/renderers';
    echarts.use([BarChart, TitleComponent, TooltipComponent, CanvasRenderer]);. [1][2]

  • Primary entry points:

    • 'echarts' — legacy/all-in-one bundle (imports everything).
    • 'echarts/core' — minimal core for tree-shaking (use with charts/components/features/renderers).
    • 'echarts/charts' — chart series exports (e.g., BarChart, LineChart).
    • 'echarts/components' — UI/coord/dataset components (e.g., TooltipComponent, GridComponent).
    • 'echarts/features' — extra features (e.g., LabelLayout, UniversalTransition).
    • 'echarts/renderers' — CanvasRenderer or SVGRenderer (one required). [1][2]
  • Notes:

    • ECharts v5+ added this tree-shaking API (since v5.0.1); later versions use ESM by default (check docs for your installed version). [2][1]
    • Use your bundler’s tree-shaking (ESM + production build) to actually eliminate unused modules.

Sources: ECharts handbook — Import ECharts (tree-shaking examples) and v5 upgrade notes. [1] https://echarts.apache.org/handbook/en/basics/import/ [2] https://echarts.apache.org/handbook/en/basics/release-note/v5-upgrade-guide/


Replace echarts/lib/* imports with the official public entry points for proper tree-shaking.

The code currently imports from echarts/lib/* paths, which are legacy and do not tree-shake as effectively as the recommended approach. According to the official echarts 5.x documentation, the tree-shaking API uses public entry points:

Instead of:

import {install as GraphChart} from "echarts/lib/chart/graph/install";

Use:

import {GraphChart} from "echarts/charts";

Similarly, replace echarts/lib/component/* with echarts/components and echarts/lib/renderer/* with echarts/renderers. The overall pattern remains correct (echarts/core + use()), but these specific import paths should be updated to the public entry points for optimal tree-shaking and bundle size reduction.

🤖 Prompt for AI Agents
In `@src/js/netjsongraph.render.js` around lines 1 - 26, The imports use legacy
private paths (echarts/lib/...) which hurt tree-shaking; replace them with the
public entry points and named exports: import GraphChart, LinesChart,
ScatterChart from "echarts/charts"; import TooltipComponent, TitleComponent,
ToolboxComponent, LegendComponent, GraphicComponent from "echarts/components";
import SVGRenderer, CanvasRenderer from "echarts/renderers"; keep the existing
use([GraphChart, LinesChart, TooltipComponent, TitleComponent, ToolboxComponent,
LegendComponent, SVGRenderer, CanvasRenderer, ScatterChart, GraphicComponent])
call but remove the "install as" style and the "echarts/lib/..." paths so the
same symbols (GraphChart, LinesChart, ScatterChart, TooltipComponent,
TitleComponent, ToolboxComponent, LegendComponent, GraphicComponent,
SVGRenderer, CanvasRenderer) are imported from the public modules for proper
tree-shaking.

Copy link
Member Author

Choose a reason for hiding this comment

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

I tried with public entrypoint and tree shaking was not working. With individual import it is working.

Copy link

Choose a reason for hiding this comment

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

🧩 Analysis chain

🌐 Web query:

echarts tree-shaking echarts/lib install imports vs public entry points webpack bundle size

💡 Result:

Short answer: use the tree‑shakable public entry points (echarts/core + echarts/charts, components, features, renderers) and never import the whole package (import 'echarts' or require 'echarts/lib/echarts') if you care about bundle size. That gives the smallest bundles with modern bundlers; pulling the top-level entry or any non‑tree‑shakeable CommonJS path will include most of ECharts. [1][2]

Practical checklist

  • Install: npm install echarts. [2]
  • Preferred imports (tree‑shakable):
    • import * as echarts from 'echarts/core';
    • import { BarChart } from 'echarts/charts';
    • import { TooltipComponent, GridComponent } from 'echarts/components';
    • import { CanvasRenderer } from 'echarts/renderers';
    • echarts.use([...]); — only modules you need. This minimizes bundle size. [1][2]
  • Avoid:
    • import 'echarts' or import * as echarts from 'echarts' — pulls everything. [2]
    • require('echarts/lib/echarts') + require('echarts/lib/chart/...') is the old CommonJS approach and tends to be larger / less effective for modern tree shaking. Use only if you must support older toolchains. [1][8]
  • Note about echarts/src vs echarts/lib: the ES-source under src can produce slightly smaller bundles, but the supported/public API is the tree‑shakable entry points; mixing src/lib is discouraged. [8]
  • Webpack gotchas:
    • Ensure your build targets ESM-aware toolchain (production mode, Terser, sideEffects:false where safe) so tree shaking actually removes unused exports. If you import any non‑tree‑shaken consumer (e.g., a component that imports the whole echarts package), tree shaking will fail and the bundle stays large. Community issues show tree‑shaking can appear ineffective if any module pulls full ECharts or if bundler config isn't ESM‑friendly. [6][7]
    • Always import the renderer(s) you need (CanvasRenderer or SVGRenderer); missing renderer imports can cause accidental inclusion of both or fallback behavior. [1]
  • If you still see a large bundle:
    • Audit imports with webpack-bundle-analyzer to find which import pulls the big code.
    • Check third‑party wrappers (echarts-for-react, ngx-echarts) — use their "core" variants or pass your own echarts instance to avoid them importing the whole package. [9][2]
  • References: ECharts handbook (import & v5 tree‑shaking guide), custom‑build notes, and community reports on bundle-size issues. [1][2][8][6][9]

Sources
[1] ECharts v5 upgrade & tree‑shaking guide (handbook).
[2] ECharts "Import" handbook (tree‑shakable examples).
[6] GitHub issue: ngx‑echarts tree‑shaking / bundle size observations.
[7] Community blog showing effective tree‑shaking steps and results.
[8] ECharts custom build / src vs lib guidance.
[9] echarts‑for‑react docs (core import example).


@codesankalp, thank you for testing both approaches! Your empirical results are what matter most, especially for a bundle-size optimization PR. If the echarts/lib/*/install imports are producing smaller bundles in your specific webpack configuration, then that's the right choice to keep.

While the official docs generally recommend the public entry points (echarts/charts, echarts/components), tree-shaking effectiveness can vary based on bundler version, configuration, and the specific dependency graph. Since you've measured both approaches and confirmed the current pattern works better, please keep the echarts/lib/*/install imports.

Thanks for doing the due diligence on this!


✏️ Learnings added
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 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.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

@github-project-automation github-project-automation bot moved this from Backlog to In progress in 26.05 Release Jan 20, 2026
@nemesifier nemesifier moved this from In progress to Ready for review/testing in 26.05 Release Jan 20, 2026
@nemesifier nemesifier moved this from In progress to Ready for review/testing in OpenWISP Priorities for next releases Jan 20, 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: 1

🤖 Fix all issues with AI agents
In `@webpack-plugins/inject-leaflet-loader.js`:
- Around line 139-144: The map callback currently uses a parameter named
"constructor", shadowing the global and triggering Biome lint; rename that
parameter (e.g., to "PluginConstructor" or "ctor") in the expression that builds
HtmlWebpackPlugin so the find call uses the new name (e.g., PluginConstructor &&
PluginConstructor.name === "HtmlWebpackPlugin") and update any references inside
the callback accordingly to eliminate the shadowing.
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9f2dc24 and ba20b6c.

⛔ Files ignored due to path filters (1)
  • lib/css/assets/leaflet-measure.png is excluded by !**/*.png
📒 Files selected for processing (2)
  • src/js/leaflet-loader.js
  • webpack-plugins/inject-leaflet-loader.js
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/js/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 (2)
webpack.config.js (2)
  • InjectLeafletLoaderPlugin (10-10)
  • HtmlWebpackPlugin (5-5)
test/browser.test.utils.js (1)
  • options (19-19)
🪛 Biome (2.1.2)
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)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Tests and Coverage
🔇 Additional comments (1)
webpack-plugins/inject-leaflet-loader.js (1)

201-225: Constructor shadowing in arrow function at line 142 requires rename.

Line 142 uses constructor as a parameter name, which shadows the built-in constructor property and will likely trigger linting warnings. Rename it to pluginConstructor or similar to avoid conflicts.

Regarding lines 201–225 script wrapping: the regex pattern correctly handles the current HTML files (each has exactly one simple inline script with no extra attributes). However, the approach assumes a single inline script and doesn't tolerate extra attributes (e.g., defer, data-*) or multiline tag opening. If future examples deviate from this pattern—or if inline module scripts with import/export are added—the wrapping may fail or produce invalid syntax. The current implementation works for the existing templates but consider adding an explicit marker attribute (e.g., data-njg-main) to the intended inline script for more robust selection if edge cases emerge.

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

Comment on lines 139 to 144
const HtmlWebpackPlugin = compiler.options.plugins
.map((plugin) => plugin.constructor)
.find(
(constructor) =>
constructor && constructor.name === "HtmlWebpackPlugin",
);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Rename shadowing variable to satisfy Biome lint.

Line 140–143 shadows the global constructor identifier and Biome flags it. Rename the callback parameter to avoid the lint error.

🔧 Suggested fix
-          .map((plugin) => plugin.constructor)
-          .find(
-            (constructor) =>
-              constructor && constructor.name === "HtmlWebpackPlugin",
-          );
+          .map((plugin) => plugin.constructor)
+          .find(
+            (pluginCtor) =>
+              pluginCtor && pluginCtor.name === "HtmlWebpackPlugin",
+          );
🧰 Tools
🪛 Biome (2.1.2)

[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)

🤖 Prompt for AI Agents
In `@webpack-plugins/inject-leaflet-loader.js` around lines 139 - 144, The map
callback currently uses a parameter named "constructor", shadowing the global
and triggering Biome lint; rename that parameter (e.g., to "PluginConstructor"
or "ctor") in the expression that builds HtmlWebpackPlugin so the find call uses
the new name (e.g., PluginConstructor && PluginConstructor.name ===
"HtmlWebpackPlugin") and update any references inside the callback accordingly
to eliminate the shadowing.

@github-project-automation github-project-automation bot moved this from Ready for review/testing to In progress in OpenWISP Priorities for next releases Jan 23, 2026
@github-project-automation github-project-automation bot moved this from Ready for review/testing to In progress in 26.05 Release Jan 23, 2026
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 reviewed and done some improvements, see the commit description for more info.

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

  • leaflet plugins
  • multiple interfaces, this seems to fail also on master
  • node expand or fold (we may remove this and the related functionality that implemetns this, see #492)

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?

unsafe_math: true,
unsafe_proto: true,
unsafe_regexp: true,
unsafe_undefined: true,
Copy link
Member

@nemesifier nemesifier Jan 23, 2026

Choose a reason for hiding this comment

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

Are you sure these unsafe options are really needed?

- `InjectLeafletLoaderPlugin` assumes specific HTML structure that may break
- Uses regex to remove/replace script tags which is fragile
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.

Comment on lines +195 to +199
// Find the script tag and its content (handles <script>, <script type="text/javascript">, and <script type="module">)
const scriptRegex =
/<script(?:\s+type="(?:text\/javascript|module)")?>\s*([\s\S]*?)\s*<\/script>/i;
const match = html.match(scriptRegex);

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Broaden inline-script matching to avoid missed injection.

The regex only matches <script> tags with an optional type and no other attributes. If the HTML adds defer, nonce, or other attrs, the match fails and Leaflet assets are removed without injecting the loader, breaking the page. Consider matching any inline script and preserving all attributes.

🔧 Suggested fix
-        const scriptRegex =
-          /<script(?:\s+type="(?:text\/javascript|module)")?>\s*([\s\S]*?)\s*<\/script>/i;
-        const match = html.match(scriptRegex);
+        const scriptRegex =
+          /<script\b(?![^>]*\bsrc=)([^>]*)>\s*([\s\S]*?)\s*<\/script>/i;
+        const match = html.match(scriptRegex);

         if (match) {
-          const originalScript = match[1];
+          const originalAttrs = match[1] || "";
+          const originalScript = match[2];
           const loaderSnippet = this.getLeafletLoaderSnippet(hasPlugins);
-
-          // Determine the script type from the original tag
-          const scriptTypeMatch = html.match(
-            /<script(\s+type="(text\/javascript|module)")?>/i,
-          );
-          const scriptType =
-            scriptTypeMatch && scriptTypeMatch[2]
-              ? scriptTypeMatch[2]
-              : "text/javascript";
 
           // Wrap the original script in initMap function and add the loader
           const newScript = `${loaderSnippet}\n${originalScript}\n      }`;
 
           html = html.replace(
             scriptRegex,
-            `<script type="${scriptType}">\n      ${newScript}\n    </script>`,
+            `<script${originalAttrs}>\n      ${newScript}\n    </script>`,
           );
         }

Also applies to: 216-219

🤖 Prompt for AI Agents
In `@webpack-plugins/inject-leaflet-loader.js` around lines 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.

Comment on lines +204 to +214
// Determine the script type from the original tag
const scriptTypeMatch = html.match(
/<script(\s+type="(text\/javascript|module)")?>/i,
);
const scriptType =
scriptTypeMatch && scriptTypeMatch[2]
? scriptTypeMatch[2]
: "text/javascript";

// Wrap the original script in initMap function and add the loader
const newScript = `${loaderSnippet}\n${originalScript}\n }`;
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
set -euo pipefail

map_examples=(
  netjson-clustering.html
  netjson-switchGraphMode.html
  netjsonmap-appendData.html
  netjsonmap-appendData2.html
  netjsonmap-indoormap.html
  netjsonmap-multipleTiles.html
  netjsonmap-nodeTiles.html
  netjsonmap-plugins.html
  netjsonmap.html
  njg-geojson.html
)

for f in "${map_examples[@]}"; do
  path=$(fd -a -g "$f" || true)
  if [[ -z "$path" ]]; then
    echo "Not found: $f"
    continue
  fi
  echo "== $path =="
  rg -n '<script' "$path"
  rg -n 'type="module"|import\s|export\s' "$path" || true
done

Repository: openwisp/netjsongraph.js

Length of output: 1380


🏁 Script executed:

cat -n /home/jailuser/git/public/example_templates/netjsonmap-plugins.html | sed -n '20,35p'

Repository: openwisp/netjsongraph.js

Length of output: 680


🏁 Script executed:

cat -n /home/jailuser/git/public/example_templates/netjsonmap-plugins.html | sed -n '28,60p'

Repository: openwisp/netjsongraph.js

Length of output: 1264


🏁 Script executed:

cat -n /home/jailuser/git/public/example_templates/netjsonmap-plugins.html | sed -n '28,100p'

Repository: openwisp/netjsongraph.js

Length of output: 3096


🏁 Script executed:

cat -n /home/jailuser/git/public/example_templates/netjsonmap-plugins.html | grep -E 'import|export|</script>'

Repository: openwisp/netjsongraph.js

Length of output: 259


🏁 Script executed:

cat -n /home/jailuser/git/webpack-plugins/inject-leaflet-loader.js | sed -n '195,225p'

Repository: openwisp/netjsongraph.js

Length of output: 1432


Add guard to skip wrapping module scripts.

The code detects <script type="module"> (line 206) but still wraps the script content in the initMap function (line 214) regardless of type. The example file netjsonmap-plugins.html uses a module script, which will be incorrectly wrapped. Module scripts must not be nested inside functions as they have their own scope. Either skip wrapping for module scripts or use a different injection strategy for them.

🤖 Prompt for AI Agents
In `@webpack-plugins/inject-leaflet-loader.js` around lines 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.

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

Projects

Status: In progress

Development

Successfully merging this pull request may close these issues.

[change] Reduce library size

3 participants