react-native-bundle-discovery 2.3.0 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/webpack.js +118 -40
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-bundle-discovery",
3
- "version": "2.3.0",
3
+ "version": "2.4.0",
4
4
  "main": "index.js",
5
5
  "repository": "git@github.com:retyui/react-native-bundle-discovery.git",
6
6
  "author": "David <4661784+retyui@users.noreply.github.com>",
package/webpack.js CHANGED
@@ -5,6 +5,7 @@ const chalk = require("chalk");
5
5
 
6
6
  const { parseBundle } = require("./parseUtils.js");
7
7
  const NAME = require("./package.json").name;
8
+ const packageRegex = /(?:^|\/)node_modules\/((?:@[^\/]+\/)?[^\/]+)/g;
8
9
 
9
10
  /**
10
11
  * Simple Webpack/Rspack plugin
@@ -47,6 +48,7 @@ class BundleDiscoveryPlugin {
47
48
  all: false,
48
49
  source: true,
49
50
  });
51
+
50
52
  // Format the stats into the desired structure
51
53
  const statsJson = this.formatStat(
52
54
  json,
@@ -83,49 +85,82 @@ class BundleDiscoveryPlugin {
83
85
  }
84
86
  }
85
87
 
86
- getPackageName(modulePath) {
87
- if (!modulePath) {
88
- return null;
89
- }
90
- const parts = modulePath.split("node_modules");
91
- if (parts.length < 2) {
92
- return null;
93
- }
94
- // WARN: Won't work with `pnpm's nested layout`
95
- const packagePath = parts[1].split(path.sep).filter(Boolean);
96
- if (packagePath.length === 0) {
97
- return null;
98
- }
99
- if (packagePath[0].startsWith("@")) {
100
- return `${packagePath[0]}/${packagePath[1]}`;
101
- }
102
- return packagePath[0];
103
- }
104
- getAbsolutePath(packageName, modulePath) {
105
- if (!packageName) {
106
- return null;
88
+ getPkgVersionFromPath(path) {
89
+ try {
90
+ // Extract the segment right before node_modules in `.pnpm/.../node_modules/`
91
+ const matches = [...path.matchAll(/\.pnpm\/([^/]+)\/node_modules/g)];
92
+ if (!matches.length) return null;
93
+
94
+ const lastPnpmFolder = matches[matches.length - 1][1];
95
+
96
+ // Match the version pattern `@<version>` right before optional peer dep suffix (`_`) or string end
97
+ const versionMatch = lastPnpmFolder.match(/@([^/_]+)(?:_|$)/);
98
+
99
+ return versionMatch ? versionMatch[1] : null;
100
+ } catch {
101
+ return "";
107
102
  }
108
- return modulePath.substring(
109
- 0,
110
- modulePath.indexOf(packageName) + packageName.length,
111
- );
112
103
  }
104
+
113
105
  getPackages(statsJson) {
114
- const packagesMap = statsJson.modules.reduce((allPackages, mdl) => {
115
- const name = this.getPackageName(mdl.nameForCondition);
116
- if (!name) {
117
- return allPackages;
106
+ const packagesMap = new Map();
107
+
108
+ for (const mdl of statsJson.modules) {
109
+ const filePath = mdl.nameForCondition;
110
+ if (!filePath) {
111
+ continue;
112
+ }
113
+
114
+ let match;
115
+ let lastMatch = null;
116
+
117
+ // Reset regex state for global matching
118
+ packageRegex.lastIndex = 0;
119
+
120
+ // Find the LAST valid node_modules/<pkg> pattern in the path
121
+ while ((match = packageRegex.exec(filePath)) !== null) {
122
+ lastMatch = match;
123
+ }
124
+
125
+ if (!lastMatch) continue;
126
+
127
+ const packageName = lastMatch[1];
128
+ const matchIndex = lastMatch.index;
129
+
130
+ // Account for potential leading slash in the regex match
131
+ const prefixOffset = filePath[matchIndex] === "/" ? 1 : 0;
132
+ const cutIndex =
133
+ matchIndex + prefixOffset + "node_modules/".length + packageName.length;
134
+
135
+ const absolutePath = filePath.slice(0, cutIndex);
136
+
137
+ let version = "";
138
+ try {
139
+ version = require(path.join(absolutePath, "package.json")).version;
140
+ } catch (e) {
141
+ const verFromPath = this.getPkgVersionFromPath(absolutePath);
142
+ if (verFromPath) {
143
+ version = verFromPath;
144
+ } else {
145
+ if (e?.code === "MODULE_NOT_FOUND") {
146
+ console.warn(`[${NAME}] getPackages: unexpected path resolution`, {
147
+ filePath,
148
+ absolutePath,
149
+ });
150
+ } else {
151
+ throw e;
152
+ }
153
+ }
118
154
  }
119
- const absolutePath = this.getAbsolutePath(name, mdl.nameForCondition);
120
- if (!allPackages.has(absolutePath)) {
121
- allPackages.set(absolutePath, {
122
- name,
123
- absolutePath,
124
- version: require(path.join(absolutePath, "package.json")).version,
155
+
156
+ if (!packagesMap.has(absolutePath)) {
157
+ packagesMap.set(absolutePath, {
158
+ name: packageName,
159
+ absolutePath: absolutePath,
160
+ version: version,
125
161
  });
126
162
  }
127
- return allPackages;
128
- }, new Map());
163
+ }
129
164
  return Array.from(packagesMap.values());
130
165
  }
131
166
  getEntryFile(statsJson, rootFolder) {
@@ -166,10 +201,15 @@ class BundleDiscoveryPlugin {
166
201
  statsJson,
167
202
  compilerOptions,
168
203
  );
204
+
169
205
  return {
206
+ kind: "webpack",
170
207
  date: statsJson.builtAt || Date.now(),
171
208
  entryPoint: this.getEntryFile(statsJson, compilerOptions.context),
172
- rootFolder: compilerOptions.context,
209
+ rootFolder:
210
+ this.findCommonRoot(
211
+ statsJson.modules.map((m) => m.nameForCondition).filter(Boolean),
212
+ ) || compilerOptions.context,
173
213
  // envs: {},
174
214
  transformOptions: {
175
215
  // customTransformOptions: {},
@@ -195,7 +235,7 @@ class BundleDiscoveryPlugin {
195
235
  };
196
236
 
197
237
  return {
198
- path: "runtime modules", // Can we rename it?
238
+ path: "__runtime__",
199
239
  source: sourceAndOutput,
200
240
  output: sourceAndOutput,
201
241
  dependencies: [],
@@ -209,7 +249,7 @@ class BundleDiscoveryPlugin {
209
249
  dependencies: modulesDeps.get(mdl.nameForCondition) ?? [],
210
250
  };
211
251
  })
212
- .filter((e) => e !== null),
252
+ .filter((e) => e !== null && e.path),
213
253
  packages: this.getPackages(statsJson),
214
254
  };
215
255
  }
@@ -264,6 +304,44 @@ class BundleDiscoveryPlugin {
264
304
 
265
305
  return importsMap;
266
306
  }
307
+
308
+ findCommonRoot(paths) {
309
+ if (!paths || paths.length === 0) return "";
310
+ if (paths.length === 1) {
311
+ const lastSlash = paths[0].lastIndexOf("/");
312
+ return lastSlash === -1 ? "" : paths[0].slice(0, lastSlash);
313
+ }
314
+
315
+ const first = paths[0];
316
+ let maxLen = first.length;
317
+
318
+ // Short-circuit scanning loop across all paths
319
+ for (let i = 1; i < paths.length; i++) {
320
+ const current = paths[i];
321
+ const limit = Math.min(maxLen, current.length);
322
+ let j = 0;
323
+
324
+ // Fast character match using engine string optimizations
325
+ while (j < limit && first.charCodeAt(j) === current.charCodeAt(j)) {
326
+ j++;
327
+ }
328
+
329
+ maxLen = j;
330
+ if (maxLen === 0) return "";
331
+ }
332
+
333
+ // Exact match down to the root or full path segment
334
+ if (maxLen === first.length) {
335
+ const lastSlash = first.lastIndexOf("/");
336
+ return lastSlash === -1 ? "" : first.slice(0, lastSlash);
337
+ }
338
+
339
+ // Truncate to the last valid '/' boundary within maxLen
340
+ const commonSlice = first.slice(0, maxLen);
341
+ const lastSlash = commonSlice.lastIndexOf("/");
342
+
343
+ return lastSlash === -1 ? "" : commonSlice.slice(0, lastSlash);
344
+ }
267
345
  }
268
346
 
269
347
  module.exports = { BundleDiscoveryPlugin };