@rsdoctor/core 0.1.0-beta → 0.1.1

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.
package/README.md CHANGED
@@ -1,11 +1,18 @@
1
- # Rsdoctor
1
+ # Rsdoctor Core
2
2
 
3
- Rsdoctor is a tool for diagnosing and analyzing the build process and build artifacts to help developers quickly identify and solve problems.
3
+ This is the core package of Rsdoctor, providing core tools and analysis capabilities for Rsdoctor plugins.
4
4
 
5
- It also supports Webpack and Rspack builders, as well as various build frameworks, such as Rsbuild.
5
+ ## features
6
+
7
+ - Rsdoctor is a one-stop tool for diagnosing and analyzing the build process and build artifacts.
8
+ - Rsdoctor is a tool that supports Webpack and Rspack build analysis.
9
+ - Rsdoctor is an analysis tool that can display the time-consuming and behavioral details of the compilation.
10
+ - Rsdoctor is a tool that provides bundle Diff and other anti-degradation capabilities simultaneously.
6
11
 
7
12
  ## Documentation
8
13
 
14
+ https://rsdoctor.dev/
15
+
9
16
  ## Contributing
10
17
 
11
18
  Please read the [Contributing Guide](https://github.com/web-infra-dev/rsdoctor/blob/main/CONTRIBUTING.md).
@@ -1,7 +1,12 @@
1
1
  /// <reference types="node" />
2
2
  import type { Common, Plugin } from '@rsdoctor/types';
3
3
  import { SourceMapInput as WebpackSourceMapInput } from '../../../types';
4
- export declare function loadLoaderModule(p: string, cwd?: string): {
4
+ export declare function parsePathQueryFragment(str: string): {
5
+ path: string;
6
+ query: string;
7
+ fragment: string;
8
+ };
9
+ export declare function loadLoaderModule(loaderPath: string, cwd?: string): {
5
10
  default: Plugin.LoaderDefinition<Common.PlainObject, {}>;
6
11
  pitch: Plugin.PitchLoaderDefinitionFunction;
7
12
  raw: boolean | void;
@@ -9,4 +14,5 @@ export declare function loadLoaderModule(p: string, cwd?: string): {
9
14
  export declare function getLoaderOptions<T>(loaderContext: Plugin.LoaderContext<T>): T;
10
15
  export declare function extractLoaderName(loaderPath: string, cwd?: string): string;
11
16
  export declare function mapEachRules<T extends Plugin.BuildRuleSetRule>(rules: T[], callback: (rule: T) => T): T[];
17
+ export declare function changeBuiltinLoader<T extends Plugin.BuildRuleSetRule>(rules: T[], loaderName: string, appendRules: (rule: T, index: number) => T): T[];
12
18
  export declare function createLoaderContextTrap(this: Plugin.LoaderContext<Common.PlainObject>, final: (err: Error | null | undefined, res: string | Buffer | null, sourceMap?: WebpackSourceMapInput) => void): Plugin.LoaderContext<Common.PlainObject<any>>;
@@ -28,19 +28,31 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
28
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
29
  var loader_exports = {};
30
30
  __export(loader_exports, {
31
+ changeBuiltinLoader: () => changeBuiltinLoader,
31
32
  createLoaderContextTrap: () => createLoaderContextTrap,
32
33
  extractLoaderName: () => extractLoaderName,
33
34
  getLoaderOptions: () => getLoaderOptions,
34
35
  loadLoaderModule: () => loadLoaderModule,
35
- mapEachRules: () => mapEachRules
36
+ mapEachRules: () => mapEachRules,
37
+ parsePathQueryFragment: () => parsePathQueryFragment
36
38
  });
37
39
  module.exports = __toCommonJS(loader_exports);
38
40
  var import_loader_utils = require("loader-utils");
39
41
  var import_path = __toESM(require("path"));
40
42
  var import_lodash = require("lodash");
41
43
  var import_common = require("@rsdoctor/utils/common");
42
- function loadLoaderModule(p, cwd = process.cwd()) {
43
- const mod = process.env.DOCTOR_TEST ? require(import_path.default.resolve(cwd, p)) : require(require.resolve(p, {
44
+ const PATH_QUERY_FRAGMENT_REGEXP = /^((?:\0.|[^?#\0])*)(\?(?:\0.|[^#\0])*)?(#.*)?$/;
45
+ function parsePathQueryFragment(str) {
46
+ const match = PATH_QUERY_FRAGMENT_REGEXP.exec(str);
47
+ return {
48
+ path: match?.[1].replace(/\0(.)/g, "$1") || "",
49
+ query: match?.[2] ? match[2].replace(/\0(.)/g, "$1") : "",
50
+ fragment: match?.[3] || ""
51
+ };
52
+ }
53
+ function loadLoaderModule(loaderPath, cwd = process.cwd()) {
54
+ const cleanLoaderPath = parsePathQueryFragment(loaderPath).path;
55
+ const mod = process.env.DOCTOR_TEST ? require(import_path.default.resolve(cwd, cleanLoaderPath)) : require(require.resolve(cleanLoaderPath, {
44
56
  paths: [cwd, import_path.default.resolve(cwd, "node_modules")]
45
57
  }));
46
58
  const isESM = mod.__esModule && typeof mod.default === "function";
@@ -73,7 +85,7 @@ function extractLoaderName(loaderPath, cwd = "") {
73
85
  return res;
74
86
  }
75
87
  function mapEachRules(rules, callback) {
76
- return rules.map((rule, i) => {
88
+ return rules.map((rule) => {
77
89
  if (typeof rule === "string") {
78
90
  return callback({
79
91
  loader: rule
@@ -104,6 +116,17 @@ function mapEachRules(rules, callback) {
104
116
  )
105
117
  };
106
118
  }
119
+ if (typeof rule.use === "function") {
120
+ const funcUse = rule.use;
121
+ const newRule = {
122
+ ...rule,
123
+ use: (...args) => {
124
+ const rules2 = funcUse.apply(null, args);
125
+ return mapEachRules(rules2, callback);
126
+ }
127
+ };
128
+ return newRule;
129
+ }
107
130
  if (Array.isArray(rule.use)) {
108
131
  return {
109
132
  ...rule,
@@ -114,9 +137,6 @@ function mapEachRules(rules, callback) {
114
137
  ...rule,
115
138
  use: mapEachRules([rule.use], callback)
116
139
  };
117
- throw new Error(
118
- `webpack.module.rules.use[${i}] parse error: ${rule.use}`
119
- );
120
140
  }
121
141
  if ("rules" in rule && Array.isArray(rule.rules)) {
122
142
  return {
@@ -133,6 +153,68 @@ function mapEachRules(rules, callback) {
133
153
  return rule;
134
154
  });
135
155
  }
156
+ function getLoaderNameMatch(_r, loaderName) {
157
+ return typeof _r === "object" && typeof _r?.loader === "string" && _r.loader === loaderName || typeof _r === "string" && _r === loaderName;
158
+ }
159
+ function changeBuiltinLoader(rules, loaderName, appendRules) {
160
+ return rules.map((rule) => {
161
+ if (!rule || typeof rule === "string")
162
+ return rule;
163
+ if (getLoaderNameMatch(rule, loaderName)) {
164
+ const _rule = {
165
+ ...rule,
166
+ use: [
167
+ {
168
+ loader: rule.loader,
169
+ options: rule.options
170
+ }
171
+ ],
172
+ loader: void 0,
173
+ options: void 0
174
+ };
175
+ return appendRules(_rule, 0);
176
+ }
177
+ if (rule.use) {
178
+ if (Array.isArray(rule.use)) {
179
+ const _index = (0, import_lodash.findIndex)(
180
+ rule.use,
181
+ (_r) => getLoaderNameMatch(_r, loaderName)
182
+ );
183
+ if (_index > -1) {
184
+ return appendRules(rule, _index);
185
+ }
186
+ } else if (typeof rule.use === "object" && !Array.isArray(rule.use) && typeof rule.use !== "function") {
187
+ rule.use = [
188
+ {
189
+ ...rule.use
190
+ }
191
+ ];
192
+ return appendRules(rule, 0);
193
+ }
194
+ }
195
+ if ("oneOf" in rule && rule.oneOf) {
196
+ return {
197
+ ...rule,
198
+ oneOf: changeBuiltinLoader(
199
+ rule.oneOf,
200
+ loaderName,
201
+ appendRules
202
+ )
203
+ };
204
+ }
205
+ if ("rules" in rule && rule.rules) {
206
+ return {
207
+ ...rule,
208
+ rules: changeBuiltinLoader(
209
+ rule.rules,
210
+ loaderName,
211
+ appendRules
212
+ )
213
+ };
214
+ }
215
+ return rule;
216
+ });
217
+ }
136
218
  function createLoaderContextTrap(final) {
137
219
  const cb = this.callback;
138
220
  let callback = (...args) => {
@@ -171,7 +253,9 @@ function createLoaderContextTrap(final) {
171
253
  if (options.hasOptions) {
172
254
  return (0, import_lodash.omit)(target.query, [import_common.Loader.LoaderInternalPropertyName]);
173
255
  }
174
- return target.resourceQuery;
256
+ const innerLoaderPath = options?.loader;
257
+ const loaderQuery = parsePathQueryFragment(innerLoaderPath).query;
258
+ return loaderQuery;
175
259
  }
176
260
  }
177
261
  return Reflect.get(target, key, receiver);
@@ -202,9 +286,11 @@ function createLoaderContextTrap(final) {
202
286
  }
203
287
  // Annotate the CommonJS export names for ESM import in node:
204
288
  0 && (module.exports = {
289
+ changeBuiltinLoader,
205
290
  createLoaderContextTrap,
206
291
  extractLoaderName,
207
292
  getLoaderOptions,
208
293
  loadLoaderModule,
209
- mapEachRules
294
+ mapEachRules,
295
+ parsePathQueryFragment
210
296
  });
@@ -92,7 +92,7 @@ const parseBundle = (bundlePath, modulesData) => {
92
92
  if (state.locations)
93
93
  return;
94
94
  const { left, right } = node;
95
- if (left && left.object && left.object.name === "exports" && left.property && left.property.name === "modules" && isModulesHash(right)) {
95
+ if (left?.object && left.object.name === "exports" && left.property && left.property.name === "modules" && isModulesHash(right)) {
96
96
  state.locations = getModulesLocations(right);
97
97
  }
98
98
  },
@@ -36,7 +36,6 @@ var import_lodash = require("lodash");
36
36
  var import_path = __toESM(require("path"));
37
37
  var import_logger = require("@rsdoctor/utils/logger");
38
38
  var import_module_graph = require("../module-graph");
39
- const logger = (0, import_logger.createLogger)();
40
39
  async function getAssetsModulesData(bundleStats, bundleDir, opts) {
41
40
  const { parseBundle = () => ({}) } = opts || {};
42
41
  if ((0, import_lodash.isEmpty)(bundleStats.assets) && !(0, import_lodash.isEmpty)(bundleStats.children)) {
@@ -62,7 +61,7 @@ async function getAssetsModulesData(bundleStats, bundleDir, opts) {
62
61
  if (bundleDir && bundleStats?.assets) {
63
62
  bundlesSources = {};
64
63
  parsedModules = {};
65
- for (const statAsset of bundleStats?.assets) {
64
+ for (const statAsset of bundleStats.assets) {
66
65
  const assetFile = import_path.default.join(bundleDir, statAsset.name);
67
66
  let bundleInfo;
68
67
  const collectedModules = [];
@@ -72,7 +71,7 @@ async function getAssetsModulesData(bundleStats, bundleDir, opts) {
72
71
  } catch (err) {
73
72
  const { code = "", message } = err;
74
73
  const msg = code === "ENOENT" ? "no such file" : message;
75
- process.env.DEVTOOLS_NODE_DEV === "1" && logger.warn(`Error parsing bundle asset "${assetFile}": ${msg}`);
74
+ process.env.DEVTOOLS_NODE_DEV === "1" && import_logger.logger.warn(`Error parsing bundle asset "${assetFile}": ${msg}`);
76
75
  continue;
77
76
  }
78
77
  bundlesSources[statAsset.name] = (0, import_lodash.pick)(bundleInfo, "src", "runtimeSrc");
@@ -81,7 +80,7 @@ async function getAssetsModulesData(bundleStats, bundleDir, opts) {
81
80
  if ((0, import_lodash.isEmpty)(bundlesSources)) {
82
81
  bundlesSources = null;
83
82
  parsedModules = null;
84
- process.env.DEVTOOLS_DEV && logger.warn(
83
+ process.env.DEVTOOLS_DEV && import_logger.logger.warn(
85
84
  "\nNo bundles were parsed. Analyzer will show only original module sizes from stats file.\n"
86
85
  );
87
86
  }
@@ -72,7 +72,7 @@ function getModuleExportsType(module2, moduleGraph, strict = false) {
72
72
  if (moduleGraph) {
73
73
  return module2.getExportsType(moduleGraph, strict);
74
74
  }
75
- const exportsType = module2.buildMeta && module2.buildMeta.exportsType;
75
+ const exportsType = module2.buildMeta?.exportsType;
76
76
  if (!exportsType && !strict) {
77
77
  return "dynamic";
78
78
  }
@@ -63,7 +63,9 @@ class InternalBundlePlugin extends import_base.InternalBasePlugin {
63
63
  }
64
64
  };
65
65
  this.done = async () => {
66
- import_common.Chunks.assetsContents(this.map, this.scheduler.chunkGraph);
66
+ if (this.scheduler.chunkGraph) {
67
+ import_common.Chunks.assetsContents(this.map, this.scheduler.chunkGraph);
68
+ }
67
69
  this.sdk.addClientRoutes([
68
70
  import_types.Manifest.RsdoctorManifestClientRoutes.ModuleGraph,
69
71
  import_types.Manifest.RsdoctorManifestClientRoutes.BundleSize
@@ -128,7 +128,7 @@ class InternalLoaderPlugin extends import_base.InternalBasePlugin {
128
128
  return tap;
129
129
  }
130
130
  };
131
- if (compiler.webpack && compiler.webpack.NormalModule && compiler.webpack.NormalModule.getCompilationHooks) {
131
+ if (compiler.webpack?.NormalModule?.getCompilationHooks) {
132
132
  compiler.webpack.NormalModule.getCompilationHooks(
133
133
  compilation
134
134
  ).loader.intercept(interceptor);
@@ -5,10 +5,10 @@ export declare class InternalSummaryPlugin<T extends Plugin.BaseCompiler> extend
5
5
  private times;
6
6
  private preTimes;
7
7
  private postTimes;
8
- apply(compiler: Plugin.BaseCompiler): void;
8
+ apply(compiler: T): void;
9
9
  private mark;
10
10
  beforeCompile: () => Promise<void>;
11
11
  afterCompile: (compilation: Plugin.BaseCompilation) => Promise<void>;
12
- done: (compiler: Plugin.BaseCompiler) => Promise<void>;
12
+ done: (compiler: T) => Promise<void>;
13
13
  private report;
14
14
  }
@@ -107,7 +107,7 @@ function interceptLoader(rules, loaderPath, options, cwd = process.cwd(), resolv
107
107
  return target;
108
108
  };
109
109
  return import_build.Utils.mapEachRules(rules, (rule) => {
110
- if (rule?.loader && rule.loader.startsWith("builtin:")) {
110
+ if (rule.loader?.startsWith("builtin:")) {
111
111
  return rule;
112
112
  }
113
113
  const opts = {
@@ -171,7 +171,7 @@ async function reportLoader(ctx, start, startHRTime, isPitch, sync, code, err, r
171
171
  file: loaderData[0].resource.path
172
172
  };
173
173
  const sdk = (0, import_sdk.getSDK)();
174
- if (sdk && sdk.reportLoader) {
174
+ if (sdk?.reportLoader) {
175
175
  sdk.reportLoader(loaderData);
176
176
  sdk.reportSourceMap(sourceMapData);
177
177
  return loaderData;
@@ -16,7 +16,7 @@ export declare class Rule<Config = DefaultRuleConfig> implements Linter.RuleMeta
16
16
  get title(): string;
17
17
  get severity(): Linter.Severity;
18
18
  get config(): Config | undefined;
19
- get category(): "bundle" | "compile" | RuleTypes.RuleMessageCategory | "emo";
19
+ get category(): "bundle" | RuleTypes.RuleMessageCategory | "compile" | "emo";
20
20
  setOption(opt: Linter.RuleConfigItem): void;
21
21
  match(level: Linter.Severity): boolean;
22
22
  validate(context: SDK.RuntimeContext): Promise<Linter.ValidateResult>;
@@ -1,7 +1,9 @@
1
1
  import type { Linter as LinterType, Common, Plugin, SDK } from '@rsdoctor/types';
2
2
  import type { RsdoctorSlaveSDK, RsdoctorWebpackSDK } from '@rsdoctor/sdk';
3
- import { ModuleGraph } from '@rsdoctor/graph';
4
- type InternalRules = any;
3
+ import { ChunkGraph, ModuleGraph } from '@rsdoctor/graph';
4
+ import { rules } from "../rules/rules";
5
+ import { RuleData } from '@rsdoctor/types/dist/linter';
6
+ type InternalRules = typeof rules[number] & RuleData[];
5
7
  export interface RsdoctorWebpackPluginOptions<Rules extends LinterType.ExtendRuleData[]> {
6
8
  /** Checker configuration */
7
9
  linter?: LinterType.Options<Rules, InternalRules>;
@@ -65,6 +67,8 @@ export interface RsdoctorPluginInstance<T extends Plugin.BaseCompiler, Rules ext
65
67
  readonly name: string;
66
68
  readonly options: RsdoctorPluginOptionsNormalized<Rules>;
67
69
  readonly sdk: RsdoctorWebpackSDK;
70
+ _modulesGraphApplied?: boolean;
71
+ chunkGraph?: ChunkGraph;
68
72
  modulesGraph: ModuleGraph;
69
73
  ensureModulesChunksGraphApplied(compiler: T): void;
70
74
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rsdoctor/core",
3
- "version": "0.1.0-beta",
3
+ "version": "0.1.1",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/web-infra-dev/rsdoctor",
@@ -51,10 +51,10 @@
51
51
  "semver": "^7.5.4",
52
52
  "source-map": "^0.7.4",
53
53
  "webpack-bundle-analyzer": "^4.9.1",
54
- "@rsdoctor/graph": "0.1.0-beta",
55
- "@rsdoctor/sdk": "0.1.0-beta",
56
- "@rsdoctor/types": "0.1.0-beta",
57
- "@rsdoctor/utils": "0.1.0-beta"
54
+ "@rsdoctor/sdk": "0.1.1",
55
+ "@rsdoctor/graph": "0.1.1",
56
+ "@rsdoctor/types": "0.1.1",
57
+ "@rsdoctor/utils": "0.1.1"
58
58
  },
59
59
  "devDependencies": {
60
60
  "@types/bytes": "3.1.1",
@@ -73,7 +73,7 @@
73
73
  "tslib": "2.4.1",
74
74
  "typescript": "^5.2.2",
75
75
  "webpack": "^5.89.0",
76
- "@rsdoctor/test-helper": "0.1.0-beta"
76
+ "@rsdoctor/test-helper": "0.1.1"
77
77
  },
78
78
  "publishConfig": {
79
79
  "access": "public",