/
githubmirror
/
webpack
Обзор
Документация
Войти
/
githubmirror
/
webpack
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
test/WatchTestCases.template.js
484 строки
14 KB
Alexander Akait
feat: reduce generated runtime code via method shorthand, spread, and Object.hasOwn (#21188)
16 июн 2026, 12:05
Не верифицирован
16 июн 2026, 12:05
8596a28
Код
Авторство
О чём код?
"use strict"; require("./helpers/warmup-webpack"); /** @typedef {Record<string, EXPECTED_ANY>} Env */ /** @typedef {{ testPath: string, srcPath: string }} TestOptions */ /** * @typedef {object} SuiteConfig * @property {string} name suite name * @property {EXPECTED_ANY=} experiments experiments overrides * @property {EXPECTED_ANY=} optimization optimization overrides */ /** * @typedef {object} WatchTestConfig * @property {((i: EXPECTED_ANY, options: EXPECTED_ANY) => string)=} findBundle * @property {boolean=} noTests */ const path = require("path"); const fs = require("graceful-fs"); /** @type {{ sync: (p: string) => void, (p: string, cb: (err: EXPECTED_ANY) => void): void }} */ const rimraf = require("rimraf"); const { parseResource } = require("../lib/util/identifier"); const checkArrayExpectation = require("./checkArrayExpectation"); const { TestRunner } = require("./harness/runner"); const createLazyTestEnv = require("./helpers/createLazyTestEnv"); const deprecationTracking = require("./helpers/deprecationTracking"); const prepareOptions = require("./helpers/prepareOptions"); const { remove } = require("./helpers/remove"); const supportsObjectHasOwn = require("./helpers/supportsObjectHasOwn"); const supportsOptionalChaining = require("./helpers/supportsOptionalChaining"); /** * @param {string} src src * @param {string} dest dest * @param {boolean} initial is initial? */ function copyDiff(src, dest, initial) { if (!fs.existsSync(dest)) fs.mkdirSync(dest); const files = fs.readdirSync(src); for (const filename of files) { const srcFile = path.join(src, filename); const destFile = path.join(dest, filename); const directory = fs.statSync(srcFile).isDirectory(); if (directory) { copyDiff(srcFile, destFile, initial); } else { const content = fs.readFileSync(srcFile); if (/^DELETE\s*$/.test(content.toString("utf8"))) { fs.unlinkSync(destFile); } else if (/^DELETE_DIRECTORY\s*$/.test(content.toString("utf8"))) { rimraf.sync(destFile); } else { fs.writeFileSync(destFile, content); if (initial) { const longTimeAgo = Date.now() - 1000 * 60 * 60 * 24; fs.utimesSync( destFile, Date.now() - longTimeAgo, Date.now() - longTimeAgo ); } } } } } /** * @param {SuiteConfig} config suite config */ const describeCases = (config) => { describe(config.name, () => { beforeAll(() => { let dest = path.join(__dirname, "js"); if (!fs.existsSync(dest)) fs.mkdirSync(dest); dest = path.join(__dirname, "js", `${config.name}-src`); if (!fs.existsSync(dest)) fs.mkdirSync(dest); }); if (process.env.NO_WATCH_TESTS) { // eslint-disable-next-line jest/no-disabled-tests it.skip("long running tests excluded", () => {}); return; } const casesPath = path.join(__dirname, "watchCases"); const categories = fs.readdirSync(casesPath).map((cat) => ({ name: cat, tests: fs .readdirSync(path.join(casesPath, cat)) .filter((folder) => !folder.includes("_")) .filter((testName) => { const testDirectory = path.join(casesPath, cat, testName); const filterPath = path.join(testDirectory, "test.filter.js"); if (fs.existsSync(filterPath) && !require(filterPath)(config)) { // eslint-disable-next-line jest/no-disabled-tests, jest/valid-describe-callback describe.skip(testName, () => it("filtered", () => {})); return false; } return true; }) .sort() })); for (const category of categories) { // eslint-disable-next-line jest/prefer-hooks-on-top, jest/no-duplicate-hooks beforeAll(() => { const dest = path.join( __dirname, "js", `${config.name}-src`, category.name ); if (!fs.existsSync(dest)) fs.mkdirSync(dest); }); describe(category.name, () => { for (const testName of category.tests) { describe(testName, () => { const tempDirectory = path.join( __dirname, "js", `${config.name}-src`, category.name, testName ); const testDirectory = path.join(casesPath, category.name, testName); /** @type {{ name: string, done?: boolean, stats?: import("../").Stats, it?: EXPECTED_ANY, getNumberOfTests?: () => number }[]} */ const runs = fs .readdirSync(testDirectory) .sort() .filter((name) => fs.statSync(path.join(testDirectory, name)).isDirectory() ) .map((name) => ({ name })); beforeAll((done) => { rimraf(tempDirectory, done); }); it(`${testName} should compile`, (done) => { const outputDirectory = path.join( __dirname, "js", config.name, category.name, testName ); rimraf.sync(outputDirectory); let options = {}; const configPath = path.join(testDirectory, "webpack.config.js"); if (fs.existsSync(configPath)) { options = prepareOptions(require(configPath), { testPath: outputDirectory, srcPath: tempDirectory }); } const applyConfig = ( /** @type {import("../").Configuration} */ options, /** @type {number} */ idx ) => { if (!options.mode) options.mode = "development"; if (!options.context) options.context = tempDirectory; if (!options.entry) options.entry = "./index.js"; if (!options.target) options.target = "async-node"; if (!options.output) options.output = {}; if (!options.output.environment) { options.output.environment = {}; } if ( options.output.environment.optionalChaining === undefined && !supportsOptionalChaining() ) { // generated runtime runs in this Node.js process; avoid `?.` on Node < 14 options.output.environment.optionalChaining = false; } if ( options.output.environment.hasOwn === undefined && !supportsObjectHasOwn() ) { // generated runtime runs in this Node.js process; avoid `Object.hasOwn` on Node < 16.9 options.output.environment.hasOwn = false; } if (options.output.clean === undefined) { options.output.clean = true; } if (!options.output.path) options.output.path = outputDirectory; if (typeof options.output.pathinfo === "undefined") { options.output.pathinfo = true; } if (!options.output.filename) { options.output.filename = `bundle${ options.experiments && options.experiments.outputModule ? ".mjs" : ".js" }`; } if ( options.cache && /** @type {import("../").FileCacheOptions} */ (options.cache) .type === "filesystem" ) { const cacheDirectory = path.join(tempDirectory, ".cache"); /** @type {import("../").FileCacheOptions} */ ( options.cache ).cacheDirectory = cacheDirectory; /** @type {import("../").FileCacheOptions} */ ( options.cache ).name = `config-${idx}`; } if (config.experiments) { if (!options.experiments) options.experiments = {}; for (const key of Object.keys(config.experiments)) { if ( /** @type {EXPECTED_ANY} */ (options.experiments)[key] === undefined ) { /** @type {EXPECTED_ANY} */ (options.experiments)[key] = config.experiments[key]; } } } if (config.optimization) { if (!options.optimization) options.optimization = {}; for (const key of Object.keys(config.optimization)) { if ( /** @type {EXPECTED_ANY} */ (options.optimization)[ key ] === undefined ) { /** @type {EXPECTED_ANY} */ (options.optimization)[key] = config.optimization[key]; } } } }; if (Array.isArray(options)) { for (const [idx, item] of options.entries()) { applyConfig(item, idx); } } else { applyConfig(options, 0); } const state = {}; let runIdx = 0; let waitMode = false; let run = runs[runIdx]; /** @type {string | null | undefined} */ let triggeringFilename; let lastHash = ""; const currentWatchStepModule = require("./helpers/currentWatchStep"); /** @type {(err?: Error | null) => void} */ let compilationFinished = /** @type {EXPECTED_ANY} */ (done); /** @type {{ step: string | undefined }} */ ( currentWatchStepModule ).step = run.name; copyDiff(path.join(testDirectory, run.name), tempDirectory, true); setTimeout(() => { const deprecationTracker = deprecationTracking.start(); const webpack = require(".."); const compiler = webpack(options); compiler.hooks.invalid.tap( "WatchTestCasesTest", (filename, _mtime) => { triggeringFilename = filename; } ); compiler.watch( { aggregateTimeout: 1000 }, async (err, stats) => { if (err) return compilationFinished(err); if (!stats) { return compilationFinished( new Error("No stats reported from Compiler") ); } if (stats.hash === lastHash) return; lastHash = stats.hash; if (run.done && lastHash !== stats.hash) { return compilationFinished( new Error( `Compilation changed but no change was issued ${lastHash} != ${stats.hash} (run ${runIdx})\n` + `Triggering change: ${triggeringFilename}` ) ); } if (waitMode) return; run.done = true; run.stats = stats; if (err) return compilationFinished(err); const statOptions = { preset: "verbose", cached: true, cachedAssets: true, cachedModules: true, colors: false }; fs.mkdirSync(outputDirectory, { recursive: true }); fs.writeFileSync( path.join( outputDirectory, `stats.${runs[runIdx] && runs[runIdx].name}.txt` ), stats.toString(statOptions), "utf8" ); const jsonStats = stats.toJson({ errorDetails: true }); if ( checkArrayExpectation( path.join(testDirectory, run.name), jsonStats, "error", "Error", options, compilationFinished ) ) { return; } if ( checkArrayExpectation( path.join(testDirectory, run.name), jsonStats, "warning", "Warning", options, compilationFinished ) ) { return; } /** @type {WatchTestConfig} */ let testConfig = { findBundle(_, options) { const ext = path.extname( parseResource(options.output.filename).path ); return `./bundle${ext}`; } }; try { // try to load a test file testConfig = Object.assign( testConfig, require(path.join(testDirectory, "test.config.js")) ); } catch (_err) { // empty } if (testConfig.noTests) { return process.nextTick(compilationFinished); } const { results } = TestRunner.runBundles({ optionsArr: Array.isArray(options) ? options : [options], outputDirectory, testConfig: { ...testConfig, evaluateScriptOnAttached: true }, category, testName, setupRunner: ({ runner }) => { runner.mergeModuleScope({ it: run.it, beforeEach: _beforeEach, afterEach: _afterEach, STATS_JSON: jsonStats, STATE: state, WATCH_STEP: run.name }); }, getBundlePaths: (i, options) => /** @type {NonNullable<WatchTestConfig["findBundle"]>} */ ( testConfig.findBundle )(i, options) }); await Promise.all(results); if ( /** @type {() => number} */ (run.getNumberOfTests)() < 1 ) { return compilationFinished( new Error("No tests exported by test case") ); } /** @type {EXPECTED_ANY} */ (run.it)( "should compile the next step", (/** @type {(err?: Error | null) => void} */ done) => { runIdx++; if (runIdx < runs.length) { run = runs[runIdx]; waitMode = true; setTimeout(() => { waitMode = false; compilationFinished = done; /** @type {{ step: string | undefined }} */ ( currentWatchStepModule ).step = run.name; copyDiff( path.join(testDirectory, run.name), tempDirectory, false ); }, 1500); } else { const deprecations = deprecationTracker(); if ( checkArrayExpectation( testDirectory, { deprecations }, "deprecation", "Deprecation", options, done ) ) { compiler.close(() => {}); return; } compiler.close(done); } }, 45000 ); compilationFinished(); } ); }, 300); }, 45000); for (const run of runs) { const { it: _it, getNumberOfTests } = createLazyTestEnv( 10000, run.name ); run.it = _it; run.getNumberOfTests = getNumberOfTests; it(`${run.name} should allow to read stats`, (done) => { if (run.stats) { run.stats.toString({ all: true }); run.stats = undefined; } done(); }); } // eslint-disable-next-line jest/prefer-hooks-on-top afterAll(() => { remove(tempDirectory); }); const { it: _it, beforeEach: _beforeEach, afterEach: _afterEach } = createLazyTestEnv(10000); }); } }); } }); }; // eslint-disable-next-line jest/no-export module.exports.describeCases = describeCases;