@rspack/test-tools 1.2.6 → 1.2.7

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/dist/case/diff.js CHANGED
@@ -32,49 +32,36 @@ function createDiffCase(name, src, dist) {
32
32
  dist,
33
33
  steps: [processor]
34
34
  });
35
- beforeAll(async () => {
36
- (0, rimraf_1.rimrafSync)(dist);
37
- await tester.prepare();
38
- });
39
- do {
40
- const prefix = node_path_1.default.basename(name);
41
- describe(`${prefix}:build`, () => {
42
- beforeAll(async () => {
43
- await tester.compile();
44
- });
45
- checkBundleFiles("webpack", node_path_1.default.join(dist, "webpack"), caseConfig.files);
46
- checkBundleFiles("rspack", node_path_1.default.join(dist, "rspack"), caseConfig.files);
35
+ (0, rimraf_1.rimrafSync)(dist);
36
+ const buildTask = tester.compile();
37
+ const prefix = node_path_1.default.basename(name);
38
+ describe(`${prefix}:check`, () => {
39
+ beforeAll(async () => {
40
+ await buildTask;
41
+ compareMap.clear();
42
+ await tester.check(env);
47
43
  });
48
- describe(`${prefix}:check`, () => {
49
- beforeAll(async () => {
50
- compareMap.clear();
51
- await tester.check(env);
52
- });
53
- for (const file of caseConfig.files) {
54
- describe(`Comparing "${file}"`, () => {
55
- let moduleResults = [];
56
- let runtimeResults = [];
57
- beforeAll(() => {
58
- const fileResult = compareMap.get(file);
59
- if (!fileResult) {
60
- throw new Error(`File ${file} has no results`);
61
- }
62
- moduleResults = fileResult.modules;
63
- runtimeResults = fileResult.runtimeModules;
64
- });
65
- if (caseConfig.modules) {
66
- checkCompareResults("modules", () => moduleResults);
67
- }
68
- if (caseConfig.runtimeModules) {
69
- checkCompareResults("runtime modules", () => runtimeResults);
44
+ for (const file of caseConfig.files) {
45
+ describe(`Comparing "${file}"`, () => {
46
+ let moduleResults = [];
47
+ let runtimeResults = [];
48
+ beforeAll(() => {
49
+ const fileResult = compareMap.get(file);
50
+ if (!fileResult) {
51
+ throw new Error(`File ${file} has no results`);
70
52
  }
53
+ moduleResults = fileResult.modules;
54
+ runtimeResults = fileResult.runtimeModules;
71
55
  });
72
- }
73
- const env = (0, createLazyTestEnv_1.default)(1000);
74
- });
75
- } while (tester.next());
76
- afterAll(async () => {
77
- await tester.resume();
56
+ if (caseConfig.modules) {
57
+ checkCompareResults("modules", () => moduleResults);
58
+ }
59
+ if (caseConfig.runtimeModules) {
60
+ checkCompareResults("runtime modules", () => runtimeResults);
61
+ }
62
+ });
63
+ }
64
+ const env = (0, createLazyTestEnv_1.default)(1000);
78
65
  });
79
66
  }
80
67
  function createDiffProcessor(config) {
@@ -113,15 +100,6 @@ function createDiffProcessor(config) {
113
100
  });
114
101
  return [processor, fileCompareMap];
115
102
  }
116
- function checkBundleFiles(name, dist, files) {
117
- describe(`Checking ${name} dist files`, () => {
118
- for (const file of files) {
119
- it(`${name}: ${file} should be generated`, () => {
120
- expect(fs_extra_1.default.existsSync(node_path_1.default.join(dist, file))).toBeTruthy();
121
- });
122
- }
123
- });
124
- }
125
103
  function checkCompareResults(name, getResults) {
126
104
  describe(`Comparing ${name}`, () => {
127
105
  it("should not miss any module", () => {
@@ -1,5 +1,8 @@
1
1
  import { type TCompareModules, type TCompareResult, type TFileCompareResult, type TModuleCompareResult } from "../type";
2
2
  import { type IFormatCodeOptions } from "./format-code";
3
+ declare global {
4
+ var updateSnapshot: boolean;
5
+ }
3
6
  export interface ICompareOptions {
4
7
  modules?: TCompareModules;
5
8
  runtimeModules?: TCompareModules;
@@ -7,7 +10,8 @@ export interface ICompareOptions {
7
10
  renameModule?: (name: string) => string;
8
11
  bootstrap?: boolean;
9
12
  detail?: boolean;
13
+ snapshot?: string;
10
14
  }
11
15
  export declare function compareFile(sourceFile: string, distFile: string, compareOptions: ICompareOptions): TFileCompareResult;
12
- export declare function compareModules(modules: string[], sourceModules: Map<string, string>, distModules: Map<string, string>, compareOptions: ICompareOptions): TModuleCompareResult[];
16
+ export declare function compareModules(modules: string[], sourceModules: Record<string, string>, distModules: Record<string, string>, compareOptions: ICompareOptions): TModuleCompareResult[];
13
17
  export declare function compareContent(sourceContent: string | false, distContent: string | false, compareOptions: ICompareOptions): TCompareResult;
@@ -8,10 +8,12 @@ exports.compareModules = compareModules;
8
8
  exports.compareContent = compareContent;
9
9
  const fs_extra_1 = __importDefault(require("fs-extra"));
10
10
  const jest_diff_1 = require("jest-diff");
11
+ const node_path_1 = __importDefault(require("node:path"));
11
12
  const helper_1 = require("../helper");
12
13
  const type_1 = require("../type");
13
14
  const format_code_1 = require("./format-code");
14
15
  const replace_runtime_module_name_1 = require("./replace-runtime-module-name");
16
+ const WORKSPACE = node_path_1.default.resolve(__dirname, "../../../..");
15
17
  function compareFile(sourceFile, distFile, compareOptions) {
16
18
  const result = {
17
19
  type: type_1.ECompareResultType.Same,
@@ -22,7 +24,9 @@ function compareFile(sourceFile, distFile, compareOptions) {
22
24
  modules: {}
23
25
  };
24
26
  const sourceExists = fs_extra_1.default.existsSync(sourceFile);
25
- const distExists = fs_extra_1.default.existsSync(distFile);
27
+ const distExists = compareOptions.snapshot
28
+ ? fs_extra_1.default.existsSync(compareOptions.snapshot)
29
+ : fs_extra_1.default.existsSync(distFile);
26
30
  if (!sourceExists && !distExists) {
27
31
  result.type = type_1.ECompareResultType.Missing;
28
32
  return result;
@@ -35,27 +39,53 @@ function compareFile(sourceFile, distFile, compareOptions) {
35
39
  result.type = type_1.ECompareResultType.OnlySource;
36
40
  return result;
37
41
  }
38
- const sourceContent = (0, replace_runtime_module_name_1.replaceRuntimeModuleName)(fs_extra_1.default.readFileSync(sourceFile, "utf-8"));
39
- const distContent = (0, replace_runtime_module_name_1.replaceRuntimeModuleName)(fs_extra_1.default.readFileSync(distFile, "utf-8"));
40
- // const compareContentResult = compareContent(sourceContent, distContent);
41
- // result.detail = compareContentResult.detail;
42
- // result.lines = compareContentResult.lines;
43
- result.type = type_1.ECompareResultType.Different;
42
+ function formatModules(modules) {
43
+ const res = {};
44
+ for (const [name, content] of Object.entries(modules)) {
45
+ const renamed = name.replaceAll(node_path_1.default.win32.sep, node_path_1.default.posix.sep);
46
+ if (!renamed.includes("node_modules/css-loader/dist")) {
47
+ res[renamed] = (0, format_code_1.formatCode)(renamed, content, compareOptions.format);
48
+ }
49
+ }
50
+ return res;
51
+ }
52
+ const sourceContent = (0, replace_runtime_module_name_1.replaceRuntimeModuleName)(fs_extra_1.default.readFileSync(sourceFile, "utf-8").replaceAll(WORKSPACE, "__WORKSPACE__"));
44
53
  const sourceModules = (0, helper_1.parseModules)(sourceContent, {
45
54
  bootstrap: compareOptions.bootstrap,
46
55
  renameModule: compareOptions.renameModule
47
56
  });
48
- const distModules = (0, helper_1.parseModules)(distContent, {
49
- bootstrap: compareOptions.bootstrap,
50
- renameModule: compareOptions.renameModule
51
- });
57
+ sourceModules.modules = formatModules(sourceModules.modules);
58
+ sourceModules.runtimeModules = formatModules(sourceModules.runtimeModules);
59
+ let distModules = {
60
+ modules: {},
61
+ runtimeModules: {}
62
+ };
63
+ if (!global.updateSnapshot &&
64
+ compareOptions.snapshot &&
65
+ fs_extra_1.default.existsSync(compareOptions.snapshot)) {
66
+ distModules = JSON.parse(fs_extra_1.default.readFileSync(compareOptions.snapshot, "utf-8"));
67
+ }
68
+ else {
69
+ const distContent = (0, replace_runtime_module_name_1.replaceRuntimeModuleName)(fs_extra_1.default.readFileSync(distFile, "utf-8").replaceAll(WORKSPACE, "__WORKSPACE__"));
70
+ distModules = (0, helper_1.parseModules)(distContent, {
71
+ bootstrap: compareOptions.bootstrap,
72
+ renameModule: compareOptions.renameModule
73
+ });
74
+ distModules.modules = formatModules(distModules.modules);
75
+ distModules.runtimeModules = formatModules(distModules.runtimeModules);
76
+ if (compareOptions.snapshot) {
77
+ fs_extra_1.default.ensureDirSync(node_path_1.default.dirname(compareOptions.snapshot));
78
+ fs_extra_1.default.writeFileSync(compareOptions.snapshot, JSON.stringify(distModules, null, 2));
79
+ }
80
+ }
81
+ result.type = type_1.ECompareResultType.Different;
52
82
  for (const type of ["modules", "runtimeModules"]) {
53
83
  const t = type;
54
84
  let moduleList = [];
55
85
  if (compareOptions[t] === true) {
56
86
  moduleList = [
57
- ...sourceModules[t].keys(),
58
- ...distModules[t].keys()
87
+ ...Object.keys(sourceModules[t]),
88
+ ...Object.keys(distModules[t])
59
89
  ].filter((i, idx, arr) => arr.indexOf(i) === idx);
60
90
  }
61
91
  else if (Array.isArray(compareOptions[t])) {
@@ -71,11 +101,9 @@ function compareFile(sourceFile, distFile, compareOptions) {
71
101
  function compareModules(modules, sourceModules, distModules, compareOptions) {
72
102
  const compareResults = [];
73
103
  for (const name of modules) {
74
- const renamed = (0, replace_runtime_module_name_1.replaceRuntimeModuleName)(name);
75
- const sourceContent = sourceModules.has(renamed) &&
76
- (0, format_code_1.formatCode)(name, sourceModules.get(renamed), compareOptions.format);
77
- const distContent = distModules.has(renamed) &&
78
- (0, format_code_1.formatCode)(name, distModules.get(renamed), compareOptions.format);
104
+ const renamed = (0, replace_runtime_module_name_1.replaceRuntimeModuleName)(name).replaceAll(node_path_1.default.win32.sep, node_path_1.default.posix.sep);
105
+ const sourceContent = sourceModules[renamed];
106
+ const distContent = distModules[renamed];
79
107
  compareResults.push({
80
108
  ...compareContent(sourceContent, distContent, compareOptions),
81
109
  name
@@ -45,6 +45,7 @@ const replace_module_argument_1 = require("./replace-module-argument");
45
45
  const SWC_HELPER_PATH_REG = /^_swc_helpers_[a-zA-Z\d_-]+__WEBPACK_IMPORTED_MODULE_xxx__$/;
46
46
  const CSS_FILE_EXT_REG = /(le|sa|c|sc)ss$/;
47
47
  const INVALID_PATH_REG = /[<>:"/\\|?*.]/g;
48
+ const MODULE_ID_REG = /__WEBPACK_IMPORTED_MODULE_\d+__/;
48
49
  function formatCode(name, raw, options) {
49
50
  const ast = (0, parser_1.parse)(raw, {
50
51
  sourceType: "unambiguous"
@@ -73,7 +74,9 @@ function formatCode(name, raw, options) {
73
74
  },
74
75
  Identifier(path) {
75
76
  if (options.ignoreModuleId) {
76
- path.node.name = path.node.name.replace(/__WEBPACK_IMPORTED_MODULE_\d+__/g, "__WEBPACK_IMPORTED_MODULE_xxx__");
77
+ if (MODULE_ID_REG.test(path.node.name)) {
78
+ path.node.name = "__WEBPACK_IMPORTED_MODULE_xxx__";
79
+ }
77
80
  }
78
81
  if (options.ignoreSwcHelpersPath) {
79
82
  if (SWC_HELPER_PATH_REG.test(path.node.name)) {
@@ -1 +1 @@
1
- export declare function replaceRuntimeModuleName(name: string): string;
1
+ export declare function replaceRuntimeModuleName(content: string): string;
@@ -1,49 +1,52 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.replaceRuntimeModuleName = replaceRuntimeModuleName;
4
+ const RUNTIME_MODULE_REGEX = /(webpack\/runtime\/)([a-z_]+)/g;
5
+ const RUNTIME_MODULE_NAME_REPLACER = {
6
+ auto_public_path: "publicPath",
7
+ public_path: "publicPath",
8
+ async_module: "async module",
9
+ base_uri: "base uri",
10
+ chunk_name: "chunkName",
11
+ compat_get_default_export: "compat get default export",
12
+ compat: "compat",
13
+ create_fake_namespace_object: "create fake namespace object",
14
+ create_script_url: "trusted types script url",
15
+ create_script: "trusted types script",
16
+ define_property_getters: "define property getters",
17
+ ensure_chunk: "ensure chunk",
18
+ get_full_hash: "getFullHash",
19
+ get_trusted_types_policy: "trusted types policy",
20
+ global: "global",
21
+ has_own_property: "hasOwnProperty shorthand",
22
+ load_script: "load script",
23
+ make_namespace_object: "make namespace object",
24
+ nonce: "nonce",
25
+ on_chunk_loaded: "chunk loaded",
26
+ relative_url: "relative url",
27
+ runtime_id: "runtimeId",
28
+ startup_chunk_dependencies: "startup chunk dependencies",
29
+ startup_entrypoint: "startup entrypoint",
30
+ system_context: "__system_context__",
31
+ chunk_prefetch_startup: "startup prefetch",
32
+ chunk_prefetch_trigger: "chunk prefetch trigger",
33
+ chunk_preload_trigger: "chunk preload trigger",
34
+ css_loading: "css loading",
35
+ async_wasm_loading: "wasm loading",
36
+ hot_module_replacement: "hot module replacement",
37
+ readfile_chunk_loading: "readFile chunk loading",
38
+ require_chunk_loading: "require chunk loading",
39
+ import_scripts_chunk_loading: "importScripts chunk loading",
40
+ module_chunk_loading: "import chunk loading",
41
+ export_webpack_runtime: "export webpack runtime",
42
+ jsonp_chunk_loading: "jsonp chunk loading",
43
+ remote: "remotes loading",
44
+ share: "sharing",
45
+ consume_shared: "consumes",
46
+ esm_module_decorator: "harmony module decorator",
47
+ node_module_decorator: "node module decorator"
48
+ };
4
49
  const RUNTIME_MODULE_NAME_MAPPING = {
5
- "webpack/runtime/auto_public_path": "webpack/runtime/publicPath",
6
- "webpack/runtime/public_path": "webpack/runtime/publicPath",
7
- "webpack/runtime/async_module": "webpack/runtime/async module",
8
- "webpack/runtime/base_uri": "webpack/runtime/base uri",
9
- "webpack/runtime/chunk_name": "webpack/runtime/chunkName",
10
- "webpack/runtime/compat_get_default_export": "webpack/runtime/compat get default export",
11
- "webpack/runtime/compat": "webpack/runtime/compat",
12
- "webpack/runtime/create_fake_namespace_object": "webpack/runtime/create fake namespace object",
13
- "webpack/runtime/create_script_url": "webpack/runtime/trusted types script url",
14
- "webpack/runtime/create_script": "webpack/runtime/trusted types script",
15
- "webpack/runtime/define_property_getters": "webpack/runtime/define property getters",
16
- "webpack/runtime/ensure_chunk": "webpack/runtime/ensure chunk",
17
- "webpack/runtime/get_full_hash": "webpack/runtime/getFullHash",
18
- "webpack/runtime/get_trusted_types_policy": "webpack/runtime/trusted types policy",
19
- "webpack/runtime/global": "webpack/runtime/global",
20
- "webpack/runtime/has_own_property": "webpack/runtime/hasOwnProperty shorthand",
21
- "webpack/runtime/load_script": "webpack/runtime/load script",
22
- "webpack/runtime/make_namespace_object": "webpack/runtime/make namespace object",
23
- "webpack/runtime/nonce": "webpack/runtime/nonce",
24
- "webpack/runtime/on_chunk_loaded": "webpack/runtime/chunk loaded",
25
- "webpack/runtime/relative_url": "webpack/runtime/relative url",
26
- "webpack/runtime/runtime_id": "webpack/runtime/runtimeId",
27
- "webpack/runtime/startup_chunk_dependencies": "webpack/runtime/startup chunk dependencies",
28
- "webpack/runtime/startup_entrypoint": "webpack/runtime/startup entrypoint",
29
- "webpack/runtime/system_context": "webpack/runtime/__system_context__",
30
- "webpack/runtime/chunk_prefetch_startup": "webpack/runtime/startup prefetch",
31
- "webpack/runtime/chunk_prefetch_trigger": "webpack/runtime/chunk prefetch trigger",
32
- "webpack/runtime/chunk_preload_trigger": "webpack/runtime/chunk preload trigger",
33
- "webpack/runtime/css_loading": "webpack/runtime/css loading",
34
- "webpack/runtime/async_wasm_loading": "webpack/runtime/wasm loading",
35
- "webpack/runtime/hot_module_replacement": "webpack/runtime/hot module replacement",
36
- "webpack/runtime/readfile_chunk_loading": "webpack/runtime/readFile chunk loading",
37
- "webpack/runtime/require_chunk_loading": "webpack/runtime/require chunk loading",
38
- "webpack/runtime/import_scripts_chunk_loading": "webpack/runtime/importScripts chunk loading",
39
- "webpack/runtime/module_chunk_loading": "webpack/runtime/import chunk loading",
40
- "webpack/runtime/export_webpack_runtime": "webpack/runtime/export webpack runtime",
41
- "webpack/runtime/jsonp_chunk_loading": "webpack/runtime/jsonp chunk loading",
42
- "webpack/runtime/remote": "webpack/runtime/remotes loading",
43
- "webpack/runtime/share": "webpack/runtime/sharing",
44
- "webpack/runtime/consume_shared": "webpack/runtime/consumes",
45
- "webpack/runtime/esm_module_decorator": "webpack/runtime/harmony module decorator",
46
- "webpack/runtime/node_module_decorator": "webpack/runtime/node module decorator",
47
50
  // module name with parameters
48
51
  "webpack/runtime/get_chunk_filename": "webpack/runtime/get $1 chunk filename",
49
52
  "webpack/runtime/get_main_filename": "webpack/runtime/get $1 filename",
@@ -54,13 +57,15 @@ const RUNTIME_MODULE_PARAM_REGEX = {
54
57
  "webpack/runtime/get_main_filename": /webpack\/runtime\/get_main_filename\/([\w.\-_\s]+)(\*\/)?/g,
55
58
  "webpack/runtime/chunk_prefetch_function": /webpack\/runtime\/chunk_prefetch_function\/([\w.\-_\s]+)(\*\/)?/g
56
59
  };
57
- function replaceRuntimeModuleName(name) {
58
- return Object.entries(RUNTIME_MODULE_NAME_MAPPING).reduce((name, [rspackName, webpackName]) => {
60
+ function replaceRuntimeModuleName(content) {
61
+ let res = content.replace(RUNTIME_MODULE_REGEX, (_, $1, $2) => `${$1}${RUNTIME_MODULE_NAME_REPLACER[$2] || $2}`);
62
+ res = Object.entries(RUNTIME_MODULE_NAME_MAPPING).reduce((res, [rspackName, webpackName]) => {
59
63
  if (RUNTIME_MODULE_PARAM_REGEX[rspackName]) {
60
- return name.replace(RUNTIME_MODULE_PARAM_REGEX[rspackName], (_, $1, $2) => {
64
+ return res.replace(RUNTIME_MODULE_PARAM_REGEX[rspackName], (_, $1, $2) => {
61
65
  return webpackName.replace("$1", $1.trim()) + ($2 ? " */" : "");
62
66
  });
63
67
  }
64
- return name.split(rspackName).join(webpackName);
65
- }, name);
68
+ return res.replaceAll(rspackName, webpackName);
69
+ }, res);
70
+ return res;
66
71
  }
@@ -2,6 +2,6 @@ export declare function parseModules(content: string, options?: {
2
2
  bootstrap?: boolean;
3
3
  renameModule?: (name: string) => string;
4
4
  }): {
5
- modules: Map<string, string>;
6
- runtimeModules: Map<string, string>;
5
+ modules: Record<string, string>;
6
+ runtimeModules: Record<string, string>;
7
7
  };
@@ -35,14 +35,14 @@ function isValidModule(name) {
35
35
  return true;
36
36
  }
37
37
  function parseModules(content, options = {}) {
38
- const modules = new Map();
39
- const runtimeModules = new Map();
38
+ const modules = {};
39
+ const runtimeModules = {};
40
40
  let currentPosition = 0;
41
41
  if (options.bootstrap) {
42
42
  // parse bootstrap code
43
43
  const bootstrap = getStringBetween(content, 0, BOOTSTRAP_SPLIT_LINE, BOOTSTRAP_SPLIT_LINE);
44
44
  if (bootstrap.result) {
45
- runtimeModules.set("webpack/runtime/bootstrap", bootstrap.result);
45
+ runtimeModules["webpack/runtime/bootstrap"] = bootstrap.result;
46
46
  }
47
47
  }
48
48
  // parse module & runtime module code
@@ -56,11 +56,11 @@ function parseModules(content, options = {}) {
56
56
  ? options.renameModule(moduleName)
57
57
  : moduleName;
58
58
  if (moduleName.startsWith("webpack/runtime")) {
59
- runtimeModules.set(renamedModuleName, moduleContent.result);
59
+ runtimeModules[renamedModuleName] = moduleContent.result;
60
60
  }
61
61
  else {
62
62
  if (isValidModule(moduleName)) {
63
- modules.set(renamedModuleName, moduleContent.result);
63
+ modules[renamedModuleName] = moduleContent.result;
64
64
  }
65
65
  }
66
66
  currentPosition = moduleContent.remain;
@@ -13,13 +13,16 @@ class DiffProcessor {
13
13
  constructor(options) {
14
14
  this.options = options;
15
15
  this.hashes = [];
16
- this.webpack = new basic_1.BasicProcessor({
17
- defaultOptions: context => this.getDefaultOptions(type_1.ECompilerType.Webpack, context.getSource(), node_path_1.default.join(context.getDist(), type_1.ECompilerType.Webpack)),
18
- compilerType: type_1.ECompilerType.Webpack,
19
- name: type_1.ECompilerType.Webpack,
20
- configFiles: ["webpack.config.js", "rspack.config.js"],
21
- runable: false
22
- });
16
+ this.webpack = null;
17
+ if (global.updateSnapshot) {
18
+ this.webpack = new basic_1.BasicProcessor({
19
+ defaultOptions: context => this.getDefaultOptions(type_1.ECompilerType.Webpack, context.getSource(), node_path_1.default.join(context.getDist(), type_1.ECompilerType.Webpack)),
20
+ compilerType: type_1.ECompilerType.Webpack,
21
+ name: type_1.ECompilerType.Webpack,
22
+ configFiles: ["webpack.config.js", "rspack.config.js"],
23
+ runable: false
24
+ });
25
+ }
23
26
  this.rspack = new basic_1.BasicProcessor({
24
27
  defaultOptions: context => this.getDefaultOptions(type_1.ECompilerType.Rspack, context.getSource(), node_path_1.default.join(context.getDist(), type_1.ECompilerType.Rspack)),
25
28
  compilerType: type_1.ECompilerType.Rspack,
@@ -29,24 +32,32 @@ class DiffProcessor {
29
32
  });
30
33
  }
31
34
  async config(context) {
32
- await this.webpack.config(context);
35
+ if (this.webpack) {
36
+ await this.webpack.config(context);
37
+ }
33
38
  await this.rspack.config(context);
34
39
  }
35
40
  async compiler(context) {
36
- await this.webpack.compiler(context);
41
+ if (this.webpack) {
42
+ await this.webpack.compiler(context);
43
+ }
37
44
  await this.rspack.compiler(context);
38
45
  }
39
46
  async build(context) {
40
- await this.webpack.build(context);
47
+ if (this.webpack) {
48
+ await this.webpack.build(context);
49
+ }
41
50
  await this.rspack.build(context);
42
51
  }
43
52
  async check(env, context) {
44
- const webpackCompiler = context.getCompiler(type_1.ECompilerType.Webpack);
45
- const webpackStats = webpackCompiler.getStats();
46
- //TODO: handle chunk hash and content hash
47
- webpackStats?.hash && this.hashes.push(webpackStats?.hash);
48
- if (!this.options.errors) {
49
- env.expect(webpackStats?.hasErrors()).toBe(false);
53
+ if (this.webpack) {
54
+ const webpackCompiler = context.getCompiler(type_1.ECompilerType.Webpack);
55
+ const webpackStats = webpackCompiler.getStats();
56
+ //TODO: handle chunk hash and content hash
57
+ webpackStats?.hash && this.hashes.push(webpackStats?.hash);
58
+ if (!this.options.errors) {
59
+ env.expect(webpackStats?.hasErrors()).toBe(false);
60
+ }
50
61
  }
51
62
  const rspackCompiler = context.getCompiler(type_1.ECompilerType.Rspack);
52
63
  const rspackStats = rspackCompiler.getStats();
@@ -56,16 +67,19 @@ class DiffProcessor {
56
67
  env.expect(rspackStats?.hasErrors()).toBe(false);
57
68
  }
58
69
  const dist = context.getDist();
70
+ const snapshot = context.getSource("__snapshot__");
59
71
  for (const file of this.options.files) {
60
72
  const rspackDist = node_path_1.default.join(dist, type_1.ECompilerType.Rspack, file);
61
73
  const webpackDist = node_path_1.default.join(dist, type_1.ECompilerType.Webpack, file);
74
+ const snapshotDist = node_path_1.default.join(snapshot, file.replace(/\.js$/, ".json"));
62
75
  const result = (0, compare_1.compareFile)(rspackDist, webpackDist, {
63
76
  modules: this.options.modules,
64
77
  runtimeModules: this.options.runtimeModules,
65
78
  format: this.createFormatOptions(),
66
79
  renameModule: this.options.renameModule,
67
80
  bootstrap: this.options.bootstrap,
68
- detail: this.options.detail
81
+ detail: this.options.detail,
82
+ snapshot: snapshotDist
69
83
  });
70
84
  if (typeof this.options.onCompareFile === "function") {
71
85
  this.options.onCompareFile(file, result);
@@ -84,8 +84,19 @@ class WatchProcessor extends multi_1.MultiTaskProcessor {
84
84
  return cached;
85
85
  };
86
86
  })();
87
+ const getStringStats = (() => {
88
+ let cached = null;
89
+ return () => {
90
+ if (!cached) {
91
+ cached = stats.toString({
92
+ logging: "verbose"
93
+ });
94
+ }
95
+ return cached;
96
+ };
97
+ })();
87
98
  if (checkStats.length > 1) {
88
- if (!checkStats(this._watchOptions.stepName, getJsonStats())) {
99
+ if (!checkStats(this._watchOptions.stepName, getJsonStats(), getStringStats())) {
89
100
  throw new Error("stats check failed");
90
101
  }
91
102
  }
@@ -3,7 +3,8 @@ export declare class BasicRunnerFactory<T extends ECompilerType> implements TRun
3
3
  protected name: string;
4
4
  protected context: ITestContext;
5
5
  constructor(name: string, context: ITestContext);
6
+ protected createStatsGetter(): () => TCompilerStatsCompilation<T>;
6
7
  create(file: string, compilerOptions: TCompilerOptions<T>, env: ITestEnv): ITestRunner;
7
8
  protected getRunnerKey(file: string): string;
8
- protected createRunner(file: string, stats: TCompilerStatsCompilation<T>, compilerOptions: TCompilerOptions<T>, env: ITestEnv): ITestRunner;
9
+ protected createRunner(file: string, stats: () => TCompilerStatsCompilation<T>, compilerOptions: TCompilerOptions<T>, env: ITestEnv): ITestRunner;
9
10
  }
@@ -9,17 +9,29 @@ class BasicRunnerFactory {
9
9
  this.name = name;
10
10
  this.context = context;
11
11
  }
12
+ createStatsGetter() {
13
+ const compiler = this.context.getCompiler(this.name);
14
+ const statsGetter = (() => {
15
+ let cached = null;
16
+ return () => {
17
+ if (cached) {
18
+ return cached;
19
+ }
20
+ cached = compiler.getStats().toJson({
21
+ errorDetails: true
22
+ });
23
+ return cached;
24
+ };
25
+ })();
26
+ return statsGetter;
27
+ }
12
28
  create(file, compilerOptions, env) {
13
29
  const key = this.getRunnerKey(file);
14
30
  const exists = this.context.getRunner(key);
15
31
  if (exists) {
16
32
  return exists;
17
33
  }
18
- const compiler = this.context.getCompiler(this.name);
19
- const stats = compiler.getStats().toJson({
20
- errorDetails: true
21
- });
22
- const runner = this.createRunner(file, stats, compilerOptions, env);
34
+ const runner = this.createRunner(file, this.createStatsGetter(), compilerOptions, env);
23
35
  this.context.setRunner(key, runner);
24
36
  return runner;
25
37
  }
@@ -41,6 +53,7 @@ class BasicRunnerFactory {
41
53
  return new web_1.WebRunner({
42
54
  ...runnerOptions,
43
55
  runInNewContext: true,
56
+ cachable: true,
44
57
  dom: this.context.getValue(this.name, "documentType") || type_1.EDocumentType.Fake
45
58
  });
46
59
  }
@@ -23,7 +23,8 @@ class CacheRunnerFactory extends basic_1.BasicRunnerFactory {
23
23
  dom: this.context.getValue(this.name, "documentType") ||
24
24
  type_1.EDocumentType.JSDOM,
25
25
  env,
26
- stats,
26
+ stats: this.createStatsGetter(),
27
+ cachable: false,
27
28
  name: this.name,
28
29
  runInNewContext: false,
29
30
  testConfig: {
@@ -61,7 +61,7 @@ class HotStepRunnerFactory extends hot_1.HotRunnerFactory {
61
61
  const runner = new web_1.WebRunner({
62
62
  dom: this.context.getValue(this.name, "documentType") || type_1.EDocumentType.JSDOM,
63
63
  env,
64
- stats,
64
+ stats: this.createStatsGetter(),
65
65
  name: this.name,
66
66
  runInNewContext: false,
67
67
  testConfig: {
@@ -75,6 +75,7 @@ class HotStepRunnerFactory extends hot_1.HotRunnerFactory {
75
75
  return moduleScope;
76
76
  }
77
77
  },
78
+ cachable: true,
78
79
  source,
79
80
  dist,
80
81
  compilerOptions
@@ -57,7 +57,7 @@ class HotRunnerFactory extends basic_1.BasicRunnerFactory {
57
57
  return new web_1.WebRunner({
58
58
  dom: this.context.getValue(this.name, "documentType") || type_1.EDocumentType.JSDOM,
59
59
  env,
60
- stats,
60
+ stats: this.createStatsGetter(),
61
61
  name: this.name,
62
62
  runInNewContext: false,
63
63
  testConfig: {
@@ -71,6 +71,7 @@ class HotRunnerFactory extends basic_1.BasicRunnerFactory {
71
71
  return moduleScope;
72
72
  }
73
73
  },
74
+ cachable: true,
74
75
  source,
75
76
  dist,
76
77
  compilerOptions
@@ -3,7 +3,7 @@ import { BasicRunnerFactory } from "./basic";
3
3
  export declare class MultipleRunnerFactory<T extends ECompilerType> extends BasicRunnerFactory<T> {
4
4
  protected runned: Set<string>;
5
5
  protected getRunnerKey(file: string): string;
6
- protected createRunner(file: string, stats: TCompilerStatsCompilation<T>, compilerOptions: TCompilerOptions<T>, env: ITestEnv): ITestRunner;
6
+ protected createRunner(file: string, stats: () => TCompilerStatsCompilation<T>, compilerOptions: TCompilerOptions<T>, env: ITestEnv): ITestRunner;
7
7
  protected getFileIndexHandler(file: string): {
8
8
  getIndex: () => number[];
9
9
  flagIndex: () => Set<string>;
@@ -16,7 +16,7 @@ class MultipleRunnerFactory extends basic_1.BasicRunnerFactory {
16
16
  const multiCompilerOptions = this.context.getValue(this.name, "multiCompilerOptions") || [];
17
17
  const { getIndex, flagIndex } = this.getFileIndexHandler(file);
18
18
  const [index] = getIndex();
19
- const runner = super.createRunner(file, stats.children[index], multiCompilerOptions[index], env);
19
+ const runner = super.createRunner(file, () => stats().children[index], multiCompilerOptions[index], env);
20
20
  flagIndex();
21
21
  return runner;
22
22
  }
@@ -1,5 +1,5 @@
1
1
  import type { ECompilerType, ITestEnv, ITestRunner, TCompilerOptions, TCompilerStatsCompilation } from "../type";
2
2
  import { BasicRunnerFactory } from "./basic";
3
3
  export declare class NormalRunnerFactory<T extends ECompilerType> extends BasicRunnerFactory<T> {
4
- protected createRunner(file: string, stats: TCompilerStatsCompilation<T>, compilerOptions: TCompilerOptions<T>, env: ITestEnv): ITestRunner;
4
+ protected createRunner(file: string, stats: () => TCompilerStatsCompilation<T>, compilerOptions: TCompilerOptions<T>, env: ITestEnv): ITestRunner;
5
5
  }
@@ -7,6 +7,7 @@ class NormalRunnerFactory extends basic_1.BasicRunnerFactory {
7
7
  createRunner(file, stats, compilerOptions, env) {
8
8
  return new normal_1.NormalRunner({
9
9
  env,
10
+ cachable: true,
10
11
  name: this.name,
11
12
  runInNewContext: false,
12
13
  testConfig: this.context.getTestConfig(),
@@ -2,13 +2,14 @@ import type { ECompilerType, ITestEnv, ITestRunner, TCompilerOptions, TCompilerS
2
2
  import type { IBasicGlobalContext, IBasicModuleScope, TBasicRunnerFile, TModuleObject, TRunnerRequirer } from "../type";
3
3
  export interface IBasicRunnerOptions<T extends ECompilerType> {
4
4
  env: ITestEnv;
5
- stats?: TCompilerStatsCompilation<T>;
5
+ stats?: () => TCompilerStatsCompilation<T>;
6
6
  name: string;
7
7
  runInNewContext?: boolean;
8
8
  testConfig: TTestConfig<T>;
9
9
  source: string;
10
10
  dist: string;
11
11
  compilerOptions: TCompilerOptions<T>;
12
+ cachable?: boolean;
12
13
  }
13
14
  export declare abstract class BasicRunner<T extends ECompilerType = ECompilerType.Rspack> implements ITestRunner {
14
15
  protected _options: IBasicRunnerOptions<T>;
@@ -24,6 +24,7 @@ const getSubPath = (p) => {
24
24
  }
25
25
  return "";
26
26
  };
27
+ const cached = new Map();
27
28
  class BasicRunner {
28
29
  constructor(_options) {
29
30
  this._options = _options;
@@ -59,8 +60,13 @@ class BasicRunner {
59
60
  return (this.globalContext || {})[name];
60
61
  }
61
62
  getFile(modulePath, currentDirectory) {
63
+ const cacheKey = `${currentDirectory}|${modulePath}`;
64
+ if (this._options.cachable && cached.has(cacheKey)) {
65
+ return cached.get(cacheKey);
66
+ }
67
+ let res = null;
62
68
  if (Array.isArray(modulePath)) {
63
- return {
69
+ res = {
64
70
  path: node_path_1.default.join(currentDirectory, ".array-require.js"),
65
71
  content: `module.exports = (${modulePath
66
72
  .map(arg => {
@@ -70,22 +76,25 @@ class BasicRunner {
70
76
  subPath: ""
71
77
  };
72
78
  }
73
- if (isRelativePath(modulePath)) {
79
+ else if (isRelativePath(modulePath)) {
74
80
  const p = node_path_1.default.join(currentDirectory, modulePath);
75
- return {
81
+ res = {
76
82
  path: p,
77
83
  content: node_fs_1.default.readFileSync(p, "utf-8"),
78
84
  subPath: getSubPath(modulePath)
79
85
  };
80
86
  }
81
- if (node_path_1.default.isAbsolute(modulePath)) {
82
- return {
87
+ else if (node_path_1.default.isAbsolute(modulePath)) {
88
+ res = {
83
89
  path: modulePath,
84
90
  content: node_fs_1.default.readFileSync(modulePath, "utf-8"),
85
91
  subPath: "absolute_path"
86
92
  };
87
93
  }
88
- return null;
94
+ if (this._options.cachable && res) {
95
+ cached.set(cacheKey, res);
96
+ }
97
+ return res;
89
98
  }
90
99
  preExecute(code, file) { }
91
100
  postExecute(m, file) { }
@@ -36,9 +36,6 @@ class CommonJsRunner extends basic_1.BasicRunner {
36
36
  },
37
37
  ...this._options.env
38
38
  };
39
- if (this._options.stats) {
40
- baseModuleScope.__STATS__ = this._options.stats;
41
- }
42
39
  return baseModuleScope;
43
40
  }
44
41
  createModuleScope(requireFn, m, file) {
@@ -90,11 +87,14 @@ class CommonJsRunner extends basic_1.BasicRunner {
90
87
  requireCache[file.path] = m;
91
88
  const currentModuleScope = this.createModuleScope(this.getRequire(), m, file);
92
89
  if (this._options.testConfig.moduleScope) {
93
- this._options.testConfig.moduleScope(currentModuleScope, this._options.stats);
90
+ this._options.testConfig.moduleScope(currentModuleScope);
94
91
  }
95
92
  if (!this._options.runInNewContext) {
96
93
  file.content = `Object.assign(global, _globalAssign);\n ${file.content}`;
97
94
  }
95
+ if (file.content.includes("__STATS__") && this._options.stats) {
96
+ currentModuleScope.__STATS__ = this._options.stats();
97
+ }
98
98
  const args = Object.keys(currentModuleScope);
99
99
  const argValues = args.map(arg => currentModuleScope[arg]);
100
100
  const code = `(function(${args.join(", ")}) {
@@ -17,7 +17,7 @@ class WatchRunner extends fake_1.FakeDocumentWebRunner {
17
17
  moduleScope.document = this.globalContext.document;
18
18
  moduleScope.STATE = this._watchOptions.state;
19
19
  moduleScope.WATCH_STEP = this._watchOptions.stepName;
20
- moduleScope.STATS_JSON = this._options.stats;
20
+ moduleScope.__STATS__ = this._options.stats;
21
21
  return moduleScope;
22
22
  }
23
23
  run(file) {
@@ -133,7 +133,6 @@ class JSDOMWebRunner extends cjs_1.CommonJsRunner {
133
133
  }
134
134
  };
135
135
  };
136
- moduleScope.STATS = moduleScope.__STATS__;
137
136
  return moduleScope;
138
137
  }
139
138
  createJSDOMRequirer() {
@@ -2,5 +2,6 @@ import type { ECompilerType, ITestEnv, ITestRunner, TCompilerOptions, TCompilerS
2
2
  import { BasicRunnerFactory } from "./basic";
3
3
  export declare class WatchRunnerFactory<T extends ECompilerType> extends BasicRunnerFactory<T> {
4
4
  protected getRunnerKey(file: string): string;
5
- protected createRunner(file: string, stats: TCompilerStatsCompilation<T>, compilerOptions: TCompilerOptions<T>, env: ITestEnv): ITestRunner;
5
+ protected createStatsGetter(): () => TCompilerStatsCompilation<T>;
6
+ protected createRunner(file: string, stats: () => TCompilerStatsCompilation<T>, compilerOptions: TCompilerOptions<T>, env: ITestEnv): ITestRunner;
6
7
  }
@@ -8,6 +8,23 @@ class WatchRunnerFactory extends basic_1.BasicRunnerFactory {
8
8
  const stepName = this.context.getValue(this.name, "watchStepName");
9
9
  return `${this.name}-${stepName}`;
10
10
  }
11
+ createStatsGetter() {
12
+ const compiler = this.context.getCompiler(this.name);
13
+ const stepName = this.context.getValue(this.name, "watchStepName");
14
+ const statsGetter = (() => {
15
+ const cached = {};
16
+ return () => {
17
+ if (cached[stepName]) {
18
+ return cached[stepName];
19
+ }
20
+ cached[stepName] = compiler.getStats().toJson({
21
+ errorDetails: true
22
+ });
23
+ return cached[stepName];
24
+ };
25
+ })();
26
+ return statsGetter;
27
+ }
11
28
  createRunner(file, stats, compilerOptions, env) {
12
29
  this.context.getCompiler(this.name);
13
30
  const stepName = this.context.getValue(this.name, "watchStepName");
@@ -32,6 +49,7 @@ class WatchRunnerFactory extends basic_1.BasicRunnerFactory {
32
49
  stepName,
33
50
  runInNewContext: isWeb,
34
51
  isWeb,
52
+ cachable: false,
35
53
  testConfig: this.context.getTestConfig(),
36
54
  source: this.context.getSource(),
37
55
  dist: this.context.getDist(),
package/dist/type.d.ts CHANGED
@@ -141,7 +141,7 @@ export type TTestConfig<T extends ECompilerType> = {
141
141
  beforeExecute?: () => void;
142
142
  afterExecute?: () => void;
143
143
  moduleScope?: (ms: IBasicModuleScope, stats?: TCompilerStatsCompilation<T>) => IBasicModuleScope;
144
- checkStats?: (stepName: string, stats: TCompilerStatsCompilation<T>) => boolean;
144
+ checkStats?: (stepName: string, jsonStats: TCompilerStatsCompilation<T>, stringStats: String) => boolean;
145
145
  findBundle?: (index: number, options: TCompilerOptions<T>, stepName?: string) => string | string[];
146
146
  bundlePath?: string[];
147
147
  nonEsmThis?: (p: string | string[]) => Object;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rspack/test-tools",
3
- "version": "1.2.6",
3
+ "version": "1.2.7",
4
4
  "license": "MIT",
5
5
  "description": "Test tools for rspack",
6
6
  "main": "dist/index.js",
@@ -97,7 +97,7 @@
97
97
  "react": "^19.0.0",
98
98
  "react-dom": "^19.0.0",
99
99
  "react-refresh": "^0.16.0",
100
- "sass-embedded": "^1.85.0",
100
+ "sass-embedded": "^1.85.1",
101
101
  "sass-loader": "^16.0.5",
102
102
  "source-map": "^0.7.4",
103
103
  "source-map-loader": "^5.0.0",
@@ -106,9 +106,9 @@
106
106
  "typescript": "^5.7.3",
107
107
  "wast-loader": "^1.14.1",
108
108
  "worker-rspack-loader": "^3.1.2",
109
- "@rspack/cli": "1.2.6",
110
- "@rspack/test-tools": "1.2.6",
111
- "@rspack/core": "1.2.6"
109
+ "@rspack/cli": "1.2.7",
110
+ "@rspack/core": "1.2.7",
111
+ "@rspack/test-tools": "1.2.7"
112
112
  },
113
113
  "peerDependencies": {
114
114
  "@rspack/core": ">=1.0.0"