diff --git a/packages/rspack/jest.config.js b/packages/rspack/jest.config.js index 1ca92e555dc..e154f842144 100644 --- a/packages/rspack/jest.config.js +++ b/packages/rspack/jest.config.js @@ -6,6 +6,7 @@ const config = { testMatch: [ "/tests/*.test.ts", "/tests/*.basictest.ts", + "/tests/*.basictest.js", "/tests/*.longtest.ts", "/tests/*.unittest.ts", "/tests/copyPlugin/*.test.js", diff --git a/packages/rspack/src/rspack.ts b/packages/rspack/src/rspack.ts index 9d586cfa95d..b7d5f11a959 100644 --- a/packages/rspack/src/rspack.ts +++ b/packages/rspack/src/rspack.ts @@ -103,9 +103,17 @@ function rspack( options: MultiRspackOptions | RspackOptions, callback?: Callback | Callback ) { - asArray(options).every(opts => { - validate(opts, rspackOptions); - }); + try { + for (let o of asArray(options)) { + validate(o, rspackOptions); + } + } catch (e) { + if (e instanceof Error) { + callback?.(e); + return; + } + throw e; + } const create = () => { if (isMultiRspackOptions(options)) { const compiler = createMultiCompiler(options); @@ -139,10 +147,8 @@ function rspack( } else { const { compiler, watch } = create(); if (watch) { - util.deprecate( - () => {}, - "A 'callback' argument needs to be provided to the 'rspack(options, callback)' function when the 'watch' option is set. There is no way to handle the 'watch' option without a callback." - )(); + util.deprecate(() => {}, + "A 'callback' argument needs to be provided to the 'rspack(options, callback)' function when the 'watch' option is set. There is no way to handle the 'watch' option without a callback.")(); } return compiler; } diff --git a/packages/rspack/tests/ConfigCase.template.ts b/packages/rspack/tests/ConfigCase.template.ts deleted file mode 100644 index 475d3dbe57f..00000000000 --- a/packages/rspack/tests/ConfigCase.template.ts +++ /dev/null @@ -1,647 +0,0 @@ -"use strict"; -import { Configuration, StatsValue, rspack } from "../src"; -import { - ensureRspackConfigNotExist, - ensureWebpackConfigExist, - isValidTestCaseDir -} from "./utils"; - -const path = require("path"); -const fs = require("graceful-fs"); -const vm = require("vm"); -const { URL, pathToFileURL, fileURLToPath } = require("url"); -const rimraf = require("rimraf"); -const checkArrayExpectation = require("./checkArrayExpectation"); -const createLazyTestEnv = require("./helpers/createLazyTestEnv"); -const deprecationTracking = require("./helpers/deprecationTracking"); -const FakeDocument = require("./helpers/FakeDocument"); -const CurrentScript = require("./helpers/CurrentScript"); - -const prepareOptions = require("./helpers/prepareOptions"); -const captureStdio = require("./helpers/captureStdio"); -const asModule = require("./helpers/asModule"); -const filterInfraStructureErrors = require("./helpers/infrastructureLogErrors"); -// fake define -const define = function (...args) { - const factory = args.pop(); - factory(); -}; - -const PATH_QUERY_FRAGMENT_REGEXP = - /^((?:\0.|[^?#\0])*)(\?(?:\0.|[^#\0])*)?(#.*)?$/; - -// In webpack, it is declared in ../lib/util/identifier -const parseResource = str => { - const match = PATH_QUERY_FRAGMENT_REGEXP.exec(str)!; - return { - resource: str, - path: match[1].replace(/\0(.)/g, "$1"), - query: match[2] ? match[2].replace(/\0(.)/g, "$1") : "", - fragment: match[3] || "" - }; -}; - -const casesPath = path.join(__dirname, "configCases"); -const categories = fs - .readdirSync(casesPath) - .filter(isValidTestCaseDir) - .map(cat => { - return { - name: cat, - tests: fs - .readdirSync(path.join(casesPath, cat)) - .filter(isValidTestCaseDir) - .filter(folder => - fs.lstatSync(path.join(casesPath, cat, folder)).isDirectory() - ) - .sort() - }; - }); - -const createLogger = appendTarget => { - return { - log: l => appendTarget.push(l), - debug: l => appendTarget.push(l), - trace: l => appendTarget.push(l), - info: l => appendTarget.push(l), - warn: console.warn.bind(console), - error: console.error.bind(console), - logTime: () => {}, - group: () => {}, - groupCollapsed: () => {}, - groupEnd: () => {}, - profile: () => {}, - profileEnd: () => {}, - clear: () => {}, - status: () => {} - }; -}; - -export const describeCases = config => { - describe(config.name, () => { - let stderr; - beforeEach(() => { - stderr = captureStdio(process.stderr, true); - }); - afterEach(() => { - stderr.restore(); - }); - - for (const category of categories) { - // eslint-disable-next-line no-loop-func - describe(category.name, () => { - for (const testName of category.tests) { - // eslint-disable-next-line no-loop-func - const testDirectory = path.join(casesPath, category.name, testName); - - ensureRspackConfigNotExist(testDirectory); - ensureWebpackConfigExist(testDirectory); - - const configFile = path.join(testDirectory, "webpack.config.js"); - describe(testName, function () { - const filterPath = path.join(testDirectory, "test.filter.js"); - if ( - fs.existsSync(filterPath) && - (typeof require(filterPath)() === "string" || - !require(filterPath)()) - ) { - describe.skip(testName, () => { - it("filtered", () => {}); - }); - return; - } - const infraStructureLog = []; - const outBaseDir = path.join(__dirname, "js"); - const testSubPath = path.join(config.name, category.name, testName); - // const outputDirectory = path.join(outBaseDir, testSubPath); - const outputDirectory = path.join(testDirectory, "dist"); - const cacheDirectory = path.join(outBaseDir, ".cache", testSubPath); - let options, optionsArr, testConfig; - - beforeAll(() => { - options = {}; - if (fs.existsSync(configFile)) { - options = prepareOptions(require(configFile), { - testPath: outputDirectory - }); - } - optionsArr = [].concat(options); - optionsArr.forEach((options, idx) => { - if (!options.context) options.context = testDirectory; - if (!options.mode) options.mode = "development"; - // if (!options.optimization) options.optimization = {}; - // if (options.optimization.minimize === undefined) - // options.optimization.minimize = false; - // if (options.optimization.minimizer === undefined) { - // options.optimization.minimizer = [ - // new (require("terser-webpack-plugin"))({ - // parallel: false - // }) - // ]; - // } - if (!options.entry) - options.entry = { - main: "./" - }; - if (!options.target) options.target = "node"; - if (!options.output) options.output = {}; - 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" + - // idx + - // (options.experiments && options.experiments.outputModule - // ? ".mjs" - // : ".js"); - // if (config.cache) { - // options.cache = { - // cacheDirectory, - // name: `config-${idx}`, - // ...config.cache - // }; - // options.infrastructureLogging = { - // debug: true, - // console: createLogger(infraStructureLog) - // }; - // } - // if (!options.snapshot) options.snapshot = {}; - // if (!options.snapshot.managedPaths) { - // options.snapshot.managedPaths = [ - // path.resolve(__dirname, "../node_modules") - // ]; - // } - }); - testConfig = { - findBundle: function (i: number, options: Configuration) { - const ext = path.extname( - parseResource(options.output!.filename).path - ); - if ( - fs.existsSync( - path.join(options.output!.path, "bundle" + i + ext) - ) - ) { - return "./bundle" + i + ext; - } - return "./main.js"; - }, - timeout: 30000 - }; - try { - // try to load a test file - testConfig = Object.assign( - testConfig, - require(path.join(testDirectory, "test.config.js")) - ); - } catch (e) { - // ignored - } - // if (testConfig.timeout) setDefaultTimeout(testConfig.timeout); - }); - afterAll(() => { - // cleanup - options = undefined; - optionsArr = undefined; - testConfig = undefined; - }); - beforeAll(() => { - rimraf.sync(cacheDirectory); - }); - const handleFatalError = (err, done) => { - const fakeStats = { - errors: [ - { - message: err.message, - stack: err.stack - } - ] - }; - if ( - checkArrayExpectation( - testDirectory, - fakeStats, - "error", - "Error", - done - ) - ) { - return; - } - // Wait for uncaught errors to occur - setTimeout(done, 200); - return; - }; - it(`${testName} should compile`, done => { - // console.info("running:", testName); - // console.time(testName); - rimraf.sync(outputDirectory); - fs.mkdirSync(outputDirectory, { recursive: true }); - infraStructureLog.length = 0; - const deprecationTracker = deprecationTracking.start(); - const onCompiled = (err, stats) => { - const deprecations = deprecationTracker(); - if (err) { - return handleFatalError(err, done); - } - const statOptions: StatsValue = { - preset: "verbose", - colors: false - }; - fs.mkdirSync(outputDirectory, { recursive: true }); - // fs.writeFileSync( - // path.join(outputDirectory, "stats.txt"), - // stats.toString(statOptions), - // "utf-8" - // ); - const jsonStats = stats.toJson({ - errorDetails: true - }); - // fs.writeFileSync( - // path.join(outputDirectory, "stats.json"), - // JSON.stringify(jsonStats, null, 2), - // "utf-8" - // ); - if ( - checkArrayExpectation( - testDirectory, - jsonStats, - "error", - "Error", - done - ) - ) { - return; - } - if ( - checkArrayExpectation( - testDirectory, - jsonStats, - "warning", - "Warning", - done - ) - ) { - return; - } - const infrastructureLogging = stderr.toString(); - if (infrastructureLogging) { - return done( - new Error( - "Errors/Warnings during build:\n" + infrastructureLogging - ) - ); - } - if ( - checkArrayExpectation( - testDirectory, - { deprecations }, - "deprecation", - "Deprecation", - done - ) - ) { - return; - } - const infrastructureLogErrors = filterInfraStructureErrors( - infraStructureLog, - { - run: 3, - options - } - ); - if ( - infrastructureLogErrors.length && - checkArrayExpectation( - testDirectory, - { infrastructureLogs: infrastructureLogErrors }, - "infrastructureLog", - "infrastructure-log", - "InfrastructureLog", - done - ) - ) { - return; - } - - let filesCount = 0; - if (testConfig.noTests) return process.nextTick(done); - if (testConfig.beforeExecute) testConfig.beforeExecute(); - const results = []; - for (let i = 0; i < optionsArr.length; i++) { - const options = optionsArr[i]; - const bundlePath = testConfig.findBundle(i, optionsArr[i]); - if (bundlePath) { - filesCount++; - const document = new FakeDocument(outputDirectory); - const globalContext = { - console: console, - expect: expect, - setTimeout: setTimeout, - clearTimeout: clearTimeout, - document, - getComputedStyle: - document.getComputedStyle.bind(document), - location: { - href: "https://test.cases/path/index.html", - origin: "https://test.cases", - toString() { - return "https://test.cases/path/index.html"; - } - } - }; - - const requireCache = Object.create(null); - const esmCache = new Map(); - const esmIdentifier = `${category.name}-${testName}-${i}`; - const baseModuleScope = { - console: console, - it: _it, - beforeEach: _beforeEach, - afterEach: _afterEach, - expect, - jest, - __STATS__: jsonStats, - nsObj: m => { - Object.defineProperty(m, Symbol.toStringTag, { - value: "Module" - }); - return m; - } - }; - - let runInNewContext = false; - if ( - options.target === "web" || - options.target === "webworker" - ) { - // @ts-ignore - baseModuleScope.window = globalContext; - // @ts-ignore - baseModuleScope.self = globalContext; - // @ts-ignore - baseModuleScope.URL = URL; - // @ts-ignore - baseModuleScope.Worker = - require("./helpers/createFakeWorker")({ - outputDirectory - }); - runInNewContext = true; - } - if (testConfig.moduleScope) { - testConfig.moduleScope(baseModuleScope); - } - const esmContext = vm.createContext(baseModuleScope, { - name: "context for esm" - }); - - // eslint-disable-next-line no-loop-func - const _require = ( - currentDirectory, - options, - module, - esmMode, - parentModule - ) => { - if (testConfig === undefined) { - throw new Error( - `_require(${module}) called after all tests from ${category.name} ${testName} have completed` - ); - } - if (Array.isArray(module) || /^\.\.?\//.test(module)) { - let content; - let p; - let subPath = ""; - if (Array.isArray(module)) { - p = path.join(currentDirectory, ".array-require.js"); - content = `module.exports = (${module - .map(arg => { - return `require(${JSON.stringify(`./${arg}`)})`; - }) - .join(", ")});`; - } else { - p = path.join(currentDirectory, module); - content = fs.readFileSync(p, "utf-8"); - const lastSlash = module.lastIndexOf("/"); - let firstSlash = module.indexOf("/"); - - if (lastSlash !== -1 && firstSlash !== lastSlash) { - if (firstSlash !== -1) { - let next = module.indexOf("/", firstSlash + 1); - let dir = module.slice(firstSlash + 1, next); - - while (dir === ".") { - firstSlash = next; - next = module.indexOf("/", firstSlash + 1); - dir = module.slice(firstSlash + 1, next); - } - } - - subPath = module.slice( - firstSlash + 1, - lastSlash + 1 - ); - } - } - const isModule = - // p.endsWith(".mjs") && - options.experiments && - options.experiments.outputModule; - - if (isModule) { - if (!vm.SourceTextModule) - throw new Error( - "Running this test requires '--experimental-vm-modules'.\nRun with 'node --experimental-vm-modules node_modules/jest-cli/bin/jest'." - ); - let esm = esmCache.get(p); - if (!esm) { - esm = new vm.SourceTextModule(content, { - identifier: esmIdentifier + "-" + p, - url: pathToFileURL(p).href + "?" + esmIdentifier, - context: esmContext, - initializeImportMeta: (meta, module) => { - meta.url = pathToFileURL(p).href; - }, - importModuleDynamically: async ( - specifier, - module - ) => { - const result = await _require( - path.dirname(p), - options, - specifier, - "evaluated", - module - ); - return await asModule(result, module.context); - } - }); - esmCache.set(p, esm); - } - if (esmMode === "unlinked") return esm; - return (async () => { - await esm.link( - async (specifier, referencingModule) => { - return await asModule( - await _require( - path.dirname( - referencingModule.identifier - ? referencingModule.identifier.slice( - esmIdentifier.length + 1 - ) - : fileURLToPath(referencingModule.url) - ), - options, - specifier, - "unlinked", - referencingModule - ), - referencingModule.context, - true - ); - } - ); - // node.js 10 needs instantiate - if (esm.instantiate) esm.instantiate(); - await esm.evaluate(); - if (esmMode === "evaluated") return esm; - const ns = esm.namespace; - return ns.default && ns.default instanceof Promise - ? ns.default - : ns; - })(); - } else { - if (p in requireCache) { - return requireCache[p].exports; - } - const m = { - exports: {} - }; - requireCache[p] = m; - const moduleScope = { - ...baseModuleScope, - require: _require.bind( - null, - path.dirname(p), - options - ), - importScripts: url => { - expect(url).toMatch( - /^https:\/\/test\.cases\/path\// - ); - // @ts-ignore - _require( - outputDirectory, - options, - `.${url.slice( - "https://test.cases/path".length - )}` - ); - }, - module: m, - exports: m.exports, - __dirname: path.dirname(p), - __filename: p, - _globalAssign: { expect }, - define - }; - if (testConfig.moduleScope) { - testConfig.moduleScope(moduleScope); - } - if (!runInNewContext) - content = `Object.assign(global, _globalAssign);\n ${content}`; - const args = Object.keys(moduleScope); - const argValues = args.map(arg => moduleScope[arg]); - const code = `(function(${args.join( - ", " - )}) {${content}\n})`; - - let oldCurrentScript = document.currentScript; - document.currentScript = new CurrentScript(subPath); - const fn = runInNewContext - ? vm.runInNewContext(code, globalContext, p) - : vm.runInThisContext(code, p); - fn.call( - testConfig.nonEsmThis - ? testConfig.nonEsmThis(module) - : m.exports, - ...argValues - ); - document.currentScript = oldCurrentScript; - return m.exports; - } - } else if ( - testConfig.modules && - module in testConfig.modules - ) { - return testConfig.modules[module]; - } else { - return require(module.startsWith("node:") - ? module.slice(5) - : module); - } - }; - if (Array.isArray(bundlePath)) { - for (const bundlePathItem of bundlePath) { - results.push( - // @ts-ignore - _require( - outputDirectory, - options, - "./" + bundlePathItem - ) - ); - } - } else { - results.push( - // @ts-ignore - _require(outputDirectory, options, bundlePath) - ); - } - } - } - // give a free pass to compilation that generated an error - if ( - !jsonStats.errors.length && - filesCount !== optionsArr.length - ) { - return done( - new Error( - "Should have found at least one bundle file per webpack config" - ) - ); - } - Promise.all(results) - .then(() => { - if (testConfig.afterExecute) testConfig.afterExecute(); - for (const key of Object.keys(global)) { - if (key.includes("webpack")) delete global[key]; - } - if (getNumberOfTests() < filesCount) { - return done(new Error("No tests exported by test case")); - } - done(); - }) - .catch(done); - }; - try { - rspack(optionsArr, onCompiled); - } catch (err) { - onCompiled(err, undefined); - } - } /* 30000 */); - - const { - it: _it, - beforeEach: _beforeEach, - afterEach: _afterEach, - setDefaultTimeout, - getNumberOfTests - } = createLazyTestEnv(10000); - }); - } - }); - } - }); -}; diff --git a/packages/rspack/tests/ConfigTestCases.basictest.js b/packages/rspack/tests/ConfigTestCases.basictest.js new file mode 100644 index 00000000000..8c8e0933c66 --- /dev/null +++ b/packages/rspack/tests/ConfigTestCases.basictest.js @@ -0,0 +1,5 @@ +const { describeCases } = require("./ConfigTestCases.template"); + +describeCases({ + name: "ConfigTestCases" +}); diff --git a/packages/rspack/tests/ConfigTestCases.template.js b/packages/rspack/tests/ConfigTestCases.template.js new file mode 100644 index 00000000000..da78300ad32 --- /dev/null +++ b/packages/rspack/tests/ConfigTestCases.template.js @@ -0,0 +1,766 @@ +"use strict"; + +require("./helpers/warmup-webpack"); + +const path = require("path"); +const fs = require("graceful-fs"); +const vm = require("vm"); +const { URL, pathToFileURL, fileURLToPath } = require("url"); +const rimraf = require("rimraf"); +const checkArrayExpectation = require("./checkArrayExpectation"); +const createLazyTestEnv = require("./helpers/createLazyTestEnv"); +const deprecationTracking = require("./helpers/deprecationTracking"); +const FakeDocument = require("./helpers/FakeDocument"); +const CurrentScript = require("./helpers/CurrentScript"); + +const prepareOptions = require("./helpers/prepareOptions"); +const { parseResource } = require("./lib/util/identifier"); +const captureStdio = require("./helpers/captureStdio"); +const asModule = require("./helpers/asModule"); +const filterInfraStructureErrors = require("./helpers/infrastructureLogErrors"); + +const casesPath = path.join(__dirname, "configCases"); +const categories = fs.readdirSync(casesPath).map(cat => { + return { + name: cat, + tests: fs + .readdirSync(path.join(casesPath, cat)) + .filter(folder => !folder.startsWith("_")) + .sort() + }; +}); + +const createLogger = appendTarget => { + return { + log: l => appendTarget.push(l), + debug: l => appendTarget.push(l), + trace: l => appendTarget.push(l), + info: l => appendTarget.push(l), + warn: console.warn.bind(console), + error: console.error.bind(console), + logTime: () => {}, + group: () => {}, + groupCollapsed: () => {}, + groupEnd: () => {}, + profile: () => {}, + profileEnd: () => {}, + clear: () => {}, + status: () => {} + }; +}; + +const TIMEOUT = 10_000; + +const describeCases = config => { + describe(config.name, () => { + let stderr; + beforeEach(() => { + stderr = captureStdio(process.stderr, true); + }); + afterEach(() => { + if (stderr) { + stderr.restore(); + } + }); + jest.setTimeout(TIMEOUT); + + for (const category of categories) { + // eslint-disable-next-line no-loop-func + describe(category.name, () => { + for (const testName of category.tests) { + // eslint-disable-next-line no-loop-func + describe(testName, function () { + const testDirectory = path.join(casesPath, category.name, testName); + const filterPath = path.join(testDirectory, "test.filter.js"); + if (fs.existsSync(filterPath) && !require(filterPath)()) { + describe.skip(testName, () => { + it("filtered", () => {}); + }); + return; + } + const infraStructureLog = []; + const outBaseDir = path.join(__dirname, "js"); + const testSubPath = path.join(config.name, category.name, testName); + const outputDirectory = path.join(outBaseDir, testSubPath); + const cacheDirectory = path.join(outBaseDir, ".cache", testSubPath); + let options, optionsArr, testConfig; + beforeAll(() => { + expect(() => { + options = prepareOptions( + require(path.join(testDirectory, "webpack.config.js")), + { testPath: outputDirectory } + ); + }).not.toThrow(); + optionsArr = [].concat(options); + optionsArr.forEach((options, idx) => { + if (!options.context) options.context = testDirectory; + if (!options.mode) options.mode = "production"; + if (!options.optimization) options.optimization = {}; + if (options.optimization.minimize === undefined) + options.optimization.minimize = false; + if (!options.experiments) { + options.experiments = {}; + } + if (!options.experiments.rspackFuture) { + options.experiments.rspackFuture = { + newTreeshaking: true + }; + } + // CHANGE: use rspack internal swc minimizer + // if (options.optimization.minimizer === undefined) { + // options.optimization.minimizer = [ + // new (require("terser-webpack-plugin"))({ + // parallel: false + // }) + // ]; + // } + if (!options.entry) options.entry = "./index.js"; + if (!options.target) options.target = "async-node"; + if (!options.output) options.output = {}; + if (!options.devtool) options.devtool = false; + if (options.cache === undefined) options.cache = false; + if (!options.output.path) options.output.path = outputDirectory; + // CHANGE: rspack does not support `pathinfo` yet. + // if (typeof options.output.pathinfo === "undefined") + // options.output.pathinfo = true; + if (!options.output.filename) + options.output.filename = + "bundle" + + idx + + (options.experiments && options.experiments.outputModule + ? ".mjs" + : ".js"); + if (config.cache) { + options.cache = { + cacheDirectory, + name: `config-${idx}`, + ...config.cache + }; + options.infrastructureLogging = { + debug: true, + console: createLogger(infraStructureLog) + }; + } + if (!options.snapshot) options.snapshot = {}; + // CHANGE: rspack does not support `snapshot.managedPaths` yet + // if (!options.snapshot.managedPaths) { + // options.snapshot.managedPaths = [ + // path.resolve(__dirname, "../node_modules") + // ]; + // } + }); + testConfig = { + findBundle: function (i, options) { + const ext = path.extname( + parseResource(options.output.filename).path + ); + if ( + fs.existsSync( + path.join(options.output.path, "bundle" + i + ext) + ) + ) { + return "./bundle" + i + ext; + } + }, + timeout: TIMEOUT + }; + try { + // try to load a test file + testConfig = Object.assign( + testConfig, + require(path.join(testDirectory, "test.config.js")) + ); + } catch (e) { + // ignored + } + if (testConfig.timeout) setDefaultTimeout(testConfig.timeout); + }); + afterAll(() => { + // cleanup + options = undefined; + optionsArr = undefined; + testConfig = undefined; + }); + beforeAll(() => { + rimraf.sync(cacheDirectory); + }); + const handleFatalError = (err, done) => { + const fakeStats = { + errors: [ + { + message: err.message, + stack: err.stack + } + ] + }; + if ( + checkArrayExpectation( + testDirectory, + fakeStats, + "error", + "Error", + done + ) + ) { + return; + } + // Wait for uncaught errors to occur + setTimeout(done, 200); + return; + }; + if (config.cache) { + it( + `${testName} should pre-compile to fill disk cache (1st)`, + done => { + rimraf.sync(outputDirectory); + fs.mkdirSync(outputDirectory, { recursive: true }); + infraStructureLog.length = 0; + const deprecationTracker = deprecationTracking.start(); + require("@rspack/core").rspack(options, err => { + deprecationTracker(); + const infrastructureLogging = stderr.toString(); + if (infrastructureLogging) { + return done( + new Error( + "Errors/Warnings during build:\n" + + infrastructureLogging + ) + ); + } + const infrastructureLogErrors = filterInfraStructureErrors( + infraStructureLog, + { + run: 1, + options + } + ); + if ( + infrastructureLogErrors.length && + checkArrayExpectation( + testDirectory, + { infrastructureLogs: infrastructureLogErrors }, + "infrastructureLog", + "infrastructure-log", + "InfrastructureLog", + done + ) + ) { + return; + } + if (err) return handleFatalError(err, done); + done(); + }); + }, + TIMEOUT + ); + it( + `${testName} should pre-compile to fill disk cache (2nd)`, + done => { + rimraf.sync(outputDirectory); + fs.mkdirSync(outputDirectory, { recursive: true }); + infraStructureLog.length = 0; + const deprecationTracker = deprecationTracking.start(); + require("..")(options, (err, stats) => { + deprecationTracker(); + if (err) return handleFatalError(err, done); + const { errorsCount } = stats.toJson({ + all: false, + modules: true, + errorsCount: true + }); + if (errorsCount === 0) { + const infrastructureLogging = stderr.toString(); + if (infrastructureLogging) { + return done( + new Error( + "Errors/Warnings during build:\n" + + infrastructureLogging + ) + ); + } + const allModules = children + ? children.reduce( + (all, { modules }) => all.concat(modules), + modules || [] + ) + : modules; + if ( + allModules.some( + m => m.type !== "cached modules" && !m.cached + ) + ) { + return done( + new Error( + `Some modules were not cached:\n${stats.toString({ + all: false, + modules: true, + modulesSpace: 100 + })}` + ) + ); + } + } + const infrastructureLogErrors = filterInfraStructureErrors( + infraStructureLog, + { + run: 2, + options + } + ); + if ( + infrastructureLogErrors.length && + checkArrayExpectation( + testDirectory, + { infrastructureLogs: infrastructureLogErrors }, + "infrastructureLog", + "infrastructure-log", + "InfrastructureLog", + done + ) + ) { + return; + } + done(); + }); + }, + TIMEOUT + ); + } + it( + `${testName} should compile`, + done => { + rimraf.sync(outputDirectory); + fs.mkdirSync(outputDirectory, { recursive: true }); + infraStructureLog.length = 0; + const deprecationTracker = deprecationTracking.start(); + const onCompiled = (err, stats) => { + const deprecations = deprecationTracker(); + if (err) return handleFatalError(err, done); + const statOptions = { + preset: "verbose", + colors: false + }; + fs.mkdirSync(outputDirectory, { recursive: true }); + fs.writeFileSync( + path.join(outputDirectory, "stats.txt"), + stats.toString(statOptions), + "utf-8" + ); + const jsonStats = stats.toJson({ + errorDetails: true + }); + fs.writeFileSync( + path.join(outputDirectory, "stats.json"), + JSON.stringify(jsonStats, null, 2), + "utf-8" + ); + if ( + checkArrayExpectation( + testDirectory, + jsonStats, + "error", + "Error", + done + ) + ) { + return; + } + if ( + checkArrayExpectation( + testDirectory, + jsonStats, + "warning", + "Warning", + done + ) + ) { + return; + } + const infrastructureLogging = stderr.toString(); + if (infrastructureLogging) { + return done( + new Error( + "Errors/Warnings during build:\n" + + infrastructureLogging + ) + ); + } + if ( + checkArrayExpectation( + testDirectory, + { deprecations }, + "deprecation", + "Deprecation", + done + ) + ) { + return; + } + const infrastructureLogErrors = filterInfraStructureErrors( + infraStructureLog, + { + run: 3, + options + } + ); + if ( + infrastructureLogErrors.length && + checkArrayExpectation( + testDirectory, + { infrastructureLogs: infrastructureLogErrors }, + "infrastructureLog", + "infrastructure-log", + "InfrastructureLog", + done + ) + ) { + return; + } + + let filesCount = 0; + + if (testConfig.noTests) return process.nextTick(done); + if (testConfig.beforeExecute) testConfig.beforeExecute(); + const results = []; + for (let i = 0; i < optionsArr.length; i++) { + const options = optionsArr[i]; + const bundlePath = testConfig.findBundle(i, optionsArr[i]); + if (bundlePath) { + filesCount++; + const document = new FakeDocument(outputDirectory); + const globalContext = { + console: console, + expect: expect, + setTimeout: setTimeout, + clearTimeout: clearTimeout, + document, + getComputedStyle: + document.getComputedStyle.bind(document), + location: { + href: "https://test.cases/path/index.html", + origin: "https://test.cases", + toString() { + return "https://test.cases/path/index.html"; + } + } + }; + + const requireCache = Object.create(null); + const esmCache = new Map(); + const esmIdentifier = `${category.name}-${testName}-${i}`; + const baseModuleScope = { + console: console, + it: _it, + beforeEach: _beforeEach, + afterEach: _afterEach, + expect, + jest, + __STATS__: jsonStats, + nsObj: m => { + Object.defineProperty(m, Symbol.toStringTag, { + value: "Module" + }); + return m; + } + }; + + let runInNewContext = false; + if ( + options.target === "web" || + options.target === "webworker" + ) { + baseModuleScope.window = globalContext; + baseModuleScope.self = globalContext; + baseModuleScope.URL = URL; + baseModuleScope.Worker = + require("./helpers/createFakeWorker")({ + outputDirectory + }); + runInNewContext = true; + } + if (testConfig.moduleScope) { + testConfig.moduleScope(baseModuleScope); + } + const esmContext = vm.createContext(baseModuleScope, { + name: "context for esm" + }); + + // eslint-disable-next-line no-loop-func + const _require = ( + currentDirectory, + options, + module, + esmMode, + parentModule + ) => { + if (testConfig === undefined) { + throw new Error( + `_require(${module}) called after all tests from ${category.name} ${testName} have completed` + ); + } + if (Array.isArray(module) || /^\.\.?\//.test(module)) { + let content; + let p; + let subPath = ""; + if (Array.isArray(module)) { + p = path.join( + currentDirectory, + ".array-require.js" + ); + content = `module.exports = (${module + .map(arg => { + return `require(${JSON.stringify(`./${arg}`)})`; + }) + .join(", ")});`; + } else { + p = path.join(currentDirectory, module); + content = fs.readFileSync(p, "utf-8"); + const lastSlash = module.lastIndexOf("/"); + let firstSlash = module.indexOf("/"); + + if (lastSlash !== -1 && firstSlash !== lastSlash) { + if (firstSlash !== -1) { + let next = module.indexOf("/", firstSlash + 1); + let dir = module.slice(firstSlash + 1, next); + + while (dir === ".") { + firstSlash = next; + next = module.indexOf("/", firstSlash + 1); + dir = module.slice(firstSlash + 1, next); + } + } + + subPath = module.slice( + firstSlash + 1, + lastSlash + 1 + ); + } + } + const isModule = + p.endsWith(".mjs") && + options.experiments && + options.experiments.outputModule; + + if (isModule) { + if (!vm.SourceTextModule) + throw new Error( + "Running this test requires '--experimental-vm-modules'.\nRun with 'node --experimental-vm-modules node_modules/jest-cli/bin/jest'." + ); + let esm = esmCache.get(p); + if (!esm) { + esm = new vm.SourceTextModule(content, { + identifier: esmIdentifier + "-" + p, + url: + pathToFileURL(p).href + "?" + esmIdentifier, + context: esmContext, + initializeImportMeta: (meta, module) => { + meta.url = pathToFileURL(p).href; + }, + importModuleDynamically: async ( + specifier, + module + ) => { + const result = await _require( + path.dirname(p), + options, + specifier, + "evaluated", + module + ); + return await asModule(result, module.context); + } + }); + esmCache.set(p, esm); + } + if (esmMode === "unlinked") return esm; + return (async () => { + await esm.link( + async (specifier, referencingModule) => { + return await asModule( + await _require( + path.dirname( + referencingModule.identifier + ? referencingModule.identifier.slice( + esmIdentifier.length + 1 + ) + : fileURLToPath(referencingModule.url) + ), + options, + specifier, + "unlinked", + referencingModule + ), + referencingModule.context, + true + ); + } + ); + // node.js 10 needs instantiate + if (esm.instantiate) esm.instantiate(); + await esm.evaluate(); + if (esmMode === "evaluated") return esm; + const ns = esm.namespace; + return ns.default && ns.default instanceof Promise + ? ns.default + : ns; + })(); + } else { + if (p in requireCache) { + return requireCache[p].exports; + } + const m = { + exports: {} + }; + requireCache[p] = m; + const moduleScope = { + ...baseModuleScope, + require: _require.bind( + null, + path.dirname(p), + options + ), + importScripts: url => { + expect(url).toMatch( + /^https:\/\/test\.cases\/path\// + ); + _require( + outputDirectory, + options, + `.${url.slice( + "https://test.cases/path".length + )}` + ); + }, + module: m, + exports: m.exports, + __dirname: path.dirname(p), + __filename: p, + _globalAssign: { expect } + }; + if (testConfig.moduleScope) { + testConfig.moduleScope(moduleScope); + } + if (!runInNewContext) + content = `Object.assign(global, _globalAssign); ${content}`; + const args = Object.keys(moduleScope); + const argValues = args.map(arg => moduleScope[arg]); + const code = `(function(${args.join( + ", " + )}) {${content}\n})`; + + let oldCurrentScript = document.currentScript; + document.currentScript = new CurrentScript(subPath); + const fn = runInNewContext + ? vm.runInNewContext(code, globalContext, p) + : vm.runInThisContext(code, p); + fn.call( + testConfig.nonEsmThis + ? testConfig.nonEsmThis(module) + : m.exports, + ...argValues + ); + document.currentScript = oldCurrentScript; + return m.exports; + } + } else if ( + testConfig.modules && + module in testConfig.modules + ) { + return testConfig.modules[module]; + } else { + return require(module.startsWith("node:") + ? module.slice(5) + : module); + } + }; + + try { + if (Array.isArray(bundlePath)) { + for (const bundlePathItem of bundlePath) { + results.push( + _require( + outputDirectory, + options, + "./" + bundlePathItem + ) + ); + } + } else { + results.push( + _require(outputDirectory, options, bundlePath) + ); + } + } catch (err) { + // Abort early if `_require` throws + return handleFatalError(err, done); + } + } + } + // give a free pass to compilation that generated an error + if ( + !jsonStats.errors.length && + filesCount !== optionsArr.length + ) { + return done( + new Error( + "Should have found at least one bundle file per webpack config" + ) + ); + } + Promise.all(results) + .then(() => { + if (testConfig.afterExecute) testConfig.afterExecute(); + for (const key of Object.keys(global)) { + if (key.includes("webpack")) delete global[key]; + } + if (getNumberOfTests() < filesCount) { + return done( + new Error( + "No tests exported by test case" + + getNumberOfTests() + + " " + + filesCount + ) + ); + } + done(); + }) + .catch(done); + }; + if (config.cache) { + try { + const compiler = require("..")(options); + compiler.run(err => { + if (err) return handleFatalError(err, done); + compiler.run((error, stats) => { + compiler.close(err => { + if (err) return handleFatalError(err, done); + onCompiled(error, stats); + }); + }); + }); + } catch (e) { + handleFatalError(e, done); + } + } else { + require("..")(options, onCompiled); + } + }, + TIMEOUT + ); + + const { + it: _it, + beforeEach: _beforeEach, + afterEach: _afterEach, + setDefaultTimeout, + getNumberOfTests + } = createLazyTestEnv(TIMEOUT); + }); + } + }); + } + }); +}; + +exports.describeCases = describeCases; diff --git a/packages/rspack/tests/TestCases.template.js b/packages/rspack/tests/TestCases.template.js new file mode 100644 index 00000000000..cb9e5d8ba6c --- /dev/null +++ b/packages/rspack/tests/TestCases.template.js @@ -0,0 +1,543 @@ +"use strict"; + +// require("./helpers/warmup-webpack"); +const path = require("path"); +const fs = require("graceful-fs"); +const vm = require("vm"); +const { pathToFileURL, URL } = require("url"); +const rimraf = require("rimraf"); +const checkArrayExpectation = require("./checkArrayExpectation"); +const createLazyTestEnv = require("./helpers/createLazyTestEnv"); +const deprecationTracking = require("./helpers/deprecationTracking"); +const captureStdio = require("./helpers/captureStdio"); +const asModule = require("./helpers/asModule"); +const filterInfraStructureErrors = require("./helpers/infrastructureLogErrors"); + +const casesPath = path.join(__dirname, "cases"); +let categories = fs.readdirSync(casesPath); +categories = categories.map(cat => { + return { + name: cat, + tests: fs + .readdirSync(path.join(casesPath, cat)) + .filter(folder => folder.indexOf("_") < 0) + }; +}); + +const createLogger = appendTarget => { + return { + log: l => appendTarget.push(l), + debug: l => appendTarget.push(l), + trace: l => appendTarget.push(l), + info: l => appendTarget.push(l), + warn: console.warn.bind(console), + error: console.error.bind(console), + logTime: () => {}, + group: () => {}, + groupCollapsed: () => {}, + groupEnd: () => {}, + profile: () => {}, + profileEnd: () => {}, + clear: () => {}, + status: () => {} + }; +}; + +const describeCases = config => { + describe(config.name, () => { + let stderr; + beforeEach(() => { + stderr = captureStdio(process.stderr, true); + }); + afterEach(() => { + stderr.restore(); + }); + categories.forEach(category => { + describe(category.name, function () { + jest.setTimeout(20000); + + category.tests + .filter(test => { + const testDirectory = path.join(casesPath, category.name, test); + const filterPath = path.join(testDirectory, "test.filter.js"); + if (fs.existsSync(filterPath) && !require(filterPath)(config)) { + describe.skip(test, () => { + it("filtered", () => {}); + }); + return false; + } + return true; + }) + .forEach(testName => { + let infraStructureLog = []; + + describe(testName, () => { + const testDirectory = path.join( + casesPath, + category.name, + testName + ); + const outputDirectory = path.join( + __dirname, + "js", + config.name, + category.name, + testName + ); + const cacheDirectory = path.join( + __dirname, + "js/.cache", + config.name, + category.name, + testName + ); + let testConfig = {}; + const testConfigPath = path.join(testDirectory, "test.config.js"); + if (fs.existsSync(testConfigPath)) { + testConfig = require(testConfigPath); + } + const TerserPlugin = require("terser-webpack-plugin"); + const terserForTesting = new TerserPlugin({ + parallel: false + }); + let options = { + ...testConfig, + context: casesPath, + entry: "./" + category.name + "/" + testName + "/", + target: config.target || "async-node", + devtool: config.devtool, + mode: config.mode || "none", + optimization: config.mode + ? { + emitOnErrors: true, + minimizer: [terserForTesting], + ...testConfig.optimization + } + : { + removeAvailableModules: true, + removeEmptyChunks: true, + mergeDuplicateChunks: true, + flagIncludedChunks: true, + sideEffects: true, + providedExports: true, + usedExports: true, + mangleExports: true, + emitOnErrors: true, + concatenateModules: + !!testConfig?.optimization?.concatenateModules, + innerGraph: true, + // CHANGE: size is not supported yet + // moduleIds: "size", + // chunkIds: "size", + moduleIds: "named", + chunkIds: "named", + minimizer: [terserForTesting], + ...config.optimization + }, + performance: { + hints: false + }, + node: { + __dirname: "mock", + __filename: "mock" + }, + cache: config.cache && { + cacheDirectory, + ...config.cache + }, + output: { + pathinfo: "verbose", + path: outputDirectory, + filename: config.module ? "bundle.mjs" : "bundle.js" + }, + resolve: { + modules: ["web_modules", "node_modules"], + mainFields: [ + "webpack", + "browser", + "web", + "browserify", + "main" + ], + aliasFields: ["browser"], + extensions: [".webpack.js", ".web.js", ".js", ".json"] + }, + resolveLoader: { + modules: [ + "web_loaders", + "web_modules", + "node_loaders", + "node_modules" + ], + mainFields: ["webpackLoader", "webLoader", "loader", "main"], + extensions: [ + ".webpack-loader.js", + ".web-loader.js", + ".loader.js", + ".js" + ] + }, + module: { + rules: [ + { + test: /\.coffee$/, + loader: "coffee-loader" + }, + { + test: /\.pug/, + loader: "pug-loader" + }, + { + test: /\.wat$/i, + loader: "wast-loader", + type: "webassembly/async" + } + ] + }, + plugins: (config.plugins || []) + .concat(testConfig.plugins || []) + .concat(function () { + this.hooks.compilation.tap("TestCasesTest", compilation => { + [ + // CHANGE: the follwing hooks are not supported yet, so comment it out + // "optimize", + // "optimizeModules", + // "optimizeChunks", + // "afterOptimizeTree", + // "afterOptimizeAssets" + ].forEach(hook => { + compilation.hooks[hook].tap("TestCasesTest", () => + compilation.checkConstraints() + ); + }); + }); + }), + experiments: { + asyncWebAssembly: true, + topLevelAwait: true, + backCompat: false, + // CHANGE: Rspack enables `css` by default. + // Turning off here to fallback to webpack's default css processing logic. + + rspackFuture: testConfig?.experiments?.rspackFuture ?? { + newTreeshaking: true + }, + css: false, + ...(config.module ? { outputModule: true } : {}) + }, + infrastructureLogging: config.cache && { + debug: true, + console: createLogger(infraStructureLog) + } + }; + const cleanups = []; + afterAll(() => { + options = undefined; + testConfig = undefined; + for (const fn of cleanups) fn(); + }); + beforeAll(done => { + rimraf(cacheDirectory, done); + }); + if (config.cache) { + it( + `${testName} should pre-compile to fill disk cache (1st)`, + done => { + const oldPath = options.output.path; + options.output.path = path.join( + options.output.path, + "cache1" + ); + infraStructureLog.length = 0; + const deprecationTracker = deprecationTracking.start(); + const webpack = require("@rspack/core").rspack; + webpack(options, err => { + deprecationTracker(); + options.output.path = oldPath; + if (err) return done(err); + const infrastructureLogErrors = + filterInfraStructureErrors(infraStructureLog, { + run: 1, + options + }); + if ( + infrastructureLogErrors.length && + checkArrayExpectation( + testDirectory, + { infrastructureLogs: infrastructureLogErrors }, + "infrastructureLog", + "infrastructure-log", + "InfrastructureLog", + done + ) + ) { + return; + } + done(); + }); + }, + testConfig.timeout || 60000 + ); + it( + `${testName} should pre-compile to fill disk cache (2nd)`, + done => { + const oldPath = options.output.path; + options.output.path = path.join( + options.output.path, + "cache2" + ); + infraStructureLog.length = 0; + const deprecationTracker = deprecationTracking.start(); + const webpack = require("@rspack/core").rspack; + webpack(options, err => { + deprecationTracker(); + options.output.path = oldPath; + if (err) return done(err); + const infrastructureLogErrors = + filterInfraStructureErrors(infraStructureLog, { + run: 2, + options + }); + if ( + infrastructureLogErrors.length && + checkArrayExpectation( + testDirectory, + { infrastructureLogs: infrastructureLogErrors }, + "infrastructureLog", + "infrastructure-log", + "InfrastructureLog", + done + ) + ) { + return; + } + done(); + }); + }, + testConfig.cachedTimeout || testConfig.timeout || 10000 + ); + } + it( + testName + " should compile", + done => { + infraStructureLog.length = 0; + const webpack = require("@rspack/core").rspack; + const compiler = webpack(options); + const run = () => { + const deprecationTracker = deprecationTracking.start(); + compiler.run((err, stats) => { + const deprecations = deprecationTracker(); + if (err) return done(err); + const infrastructureLogErrors = + filterInfraStructureErrors(infraStructureLog, { + run: 3, + options + }); + if ( + infrastructureLogErrors.length && + checkArrayExpectation( + testDirectory, + { infrastructureLogs: infrastructureLogErrors }, + "infrastructureLog", + "infrastructure-log", + "InfrastructureLog", + done + ) + ) { + return; + } + compiler.close(err => { + if (err) return done(err); + const statOptions = { + preset: "verbose", + colors: false, + modules: true, + reasonsSpace: 1000 + }; + fs.mkdirSync(outputDirectory, { recursive: true }); + fs.writeFileSync( + path.join(outputDirectory, "stats.txt"), + stats.toString(statOptions), + "utf-8" + ); + const jsonStats = stats.toJson({ + errorDetails: true, + modules: false, + assets: false, + chunks: false + }); + if ( + checkArrayExpectation( + testDirectory, + jsonStats, + "error", + "Error", + done + ) + ) { + return; + } + if ( + checkArrayExpectation( + testDirectory, + jsonStats, + "warning", + "Warning", + done + ) + ) { + return; + } + const infrastructureLogging = stderr.toString(); + if (infrastructureLogging) { + done( + new Error( + "Errors/Warnings during build:\n" + + infrastructureLogging + ) + ); + } + + expect(deprecations).toEqual(config.deprecations || []); + + Promise.resolve().then(done); + }); + }); + }; + if (config.cache) { + // pre-compile to fill memory cache + const deprecationTracker = deprecationTracking.start(); + compiler.run(err => { + deprecationTracker(); + if (err) return done(err); + run(); + }); + } else { + run(); + } + }, + testConfig.cachedTimeout || + testConfig.timeout || + (config.cache ? 20000 : 60000) + ); + + it( + testName + " should load the compiled tests", + done => { + const esmContext = vm.createContext({ + it: _it, + expect, + process, + global, + URL, + Buffer, + setTimeout, + setImmediate, + nsObj: function (m) { + Object.defineProperty(m, Symbol.toStringTag, { + value: "Module" + }); + return m; + } + }); + cleanups.push(() => (esmContext.it = undefined)); + function _require(module, esmMode) { + if (module.startsWith("./")) { + const p = path.join(outputDirectory, module); + const content = fs.readFileSync(p, "utf-8"); + if (p.endsWith(".mjs")) { + let esm; + try { + esm = new vm.SourceTextModule(content, { + identifier: p, + context: esmContext, + initializeImportMeta: (meta, module) => { + meta.url = pathToFileURL(p).href; + }, + importModuleDynamically: async ( + specifier, + module + ) => { + const result = await _require( + specifier, + "evaluated" + ); + return await asModule(result, module.context); + } + }); + cleanups.push(() => (esmContext.it = undefined)); + } catch (e) { + console.log(e); + e.message += `\nwhile parsing ${p}`; + throw e; + } + if (esmMode === "unlinked") return esm; + return (async () => { + await esm.link(async (specifier, module) => { + return await asModule( + await _require(specifier, "unlinked"), + module.context, + true + ); + }); + // node.js 10 needs instantiate + if (esm.instantiate) esm.instantiate(); + await esm.evaluate(); + if (esmMode === "evaluated") return esm; + const ns = esm.namespace; + return ns.default && ns.default instanceof Promise + ? ns.default + : ns; + })(); + } else { + const fn = vm.runInThisContext( + "(function(require, module, exports, __dirname, __filename, it, expect) {" + + "global.expect = expect;" + + 'function nsObj(m) { Object.defineProperty(m, Symbol.toStringTag, { value: "Module" }); return m; }' + + content + + "\n})", + p + ); + const m = { + exports: {}, + webpackTestSuiteModule: true + }; + fn.call( + m.exports, + _require, + m, + m.exports, + outputDirectory, + p, + _it, + expect + ); + return m.exports; + } + } else return require(module); + } + _require.webpackTestSuiteRequire = true; + Promise.resolve() + .then(() => _require("./" + options.output.filename)) + .then(() => { + if (getNumberOfTests() === 0) + return done( + new Error("No tests exported by test case") + ); + done(); + }, done); + }, + 10000 + ); + + const { it: _it, getNumberOfTests } = createLazyTestEnv( + testConfig.timeout || 10000 + ); + }); + }); + }); + }); + }); +}; + +exports.describeCases = describeCases; diff --git a/packages/rspack/tests/__snapshots__/configCase.test.ts.snap b/packages/rspack/tests/__snapshots__/ConfigTestCases.basictest.js.snap similarity index 77% rename from packages/rspack/tests/__snapshots__/configCase.test.ts.snap rename to packages/rspack/tests/__snapshots__/ConfigTestCases.basictest.js.snap index 889a08e43e3..aa248c097fa 100644 --- a/packages/rspack/tests/__snapshots__/configCase.test.ts.snap +++ b/packages/rspack/tests/__snapshots__/ConfigTestCases.basictest.js.snap @@ -1,48 +1,48 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`configCases builtins css-auto exported tests css/auto can handle css module correctly 1`] = ` +exports[`ConfigTestCases builtins css-auto exported tests css/auto can handle css module correctly 1`] = ` { - "style": "index-module-css__style", + "style": "_5c2d5551d80e7ce1ba5f", } `; -exports[`configCases builtins css-modules-composes exported tests css modules composes 1`] = ` +exports[`ConfigTestCases builtins css-modules-composes exported tests css modules composes 1`] = ` { - "simple-bar": "index-css__simple-bar imported-simple-css__imported-simple", - "simple-foo": "index-css__simple-foo imported-simple-css__imported-simple", + "simple-bar": "_0e180c50fc723d21893f e45545294690ac1f111b", + "simple-foo": "_64c341554d21f0338d15 e45545294690ac1f111b", } `; -exports[`configCases builtins css-modules-composes-preprocessers exported tests css modules with css preprocessers 1`] = ` +exports[`ConfigTestCases builtins css-modules-composes-preprocessers exported tests css modules with css preprocessers 1`] = ` { - "class": "index-css__class less-file-less__lessClass", - "ghi": "index-css__ghi", - "other": "index-css__other scss-file-scss__scssClass", - "otherClassName": "index-css__otherClassName globalClassName", + "class": "df80f3b0f4e4a348c22a _4658e34db45b67e69001", + "ghi": "b40af806355ae2a77e71", + "other": "_5c221b4c6b52ae1c3f04 _64a6a8c8c9df5bc70d58", + "otherClassName": "e8f69e8c0cf8a7addae3 globalClassName", } `; -exports[`configCases builtins css-modules-composes-sass exported tests css modules in scss 1`] = ` +exports[`ConfigTestCases builtins css-modules-composes-sass exported tests css modules in scss 1`] = ` { - "bar": "index-scss__bar foo-scss__foo", + "bar": "ced11c340af4be27ae9a df69808451aa79e0bb0e", } `; -exports[`configCases builtins css-modules-dedupe exported tests css modules dedupe 1`] = ` +exports[`ConfigTestCases builtins css-modules-dedupe exported tests css modules dedupe 1`] = ` { - "backButton": "source-css__backButton buttons-secondary-button-css__secondaryButton buttons-button-css__button", - "nextButton": "source-css__nextButton buttons-primary-button-css__primaryButton buttons-button-css__button", + "backButton": "e584739154dc3b504aac _7b415a244cf0005470b1 _4370a95c3b5bb2e782b3", + "nextButton": "ef4e163a567c697d018d _2188a2bc321e703ef769 _4370a95c3b5bb2e782b3", } `; -exports[`configCases builtins css-modules-exports-only exported tests css modules exportsOnly 1`] = ` +exports[`ConfigTestCases builtins css-modules-exports-only exported tests css modules exportsOnly 1`] = ` { - "simple-bar": "index-css__simple-bar imported-simple-css__imported-simple", - "simple-foo": "index-css__simple-foo imported-simple-css__imported-simple", + "simple-bar": "_0e180c50fc723d21893f e45545294690ac1f111b", + "simple-foo": "_64c341554d21f0338d15 e45545294690ac1f111b", } `; -exports[`configCases builtins css-modules-local-ident-name-hash exported tests css modules localIdentName with hash 1`] = ` +exports[`ConfigTestCases builtins css-modules-local-ident-name-hash exported tests css modules localIdentName with hash 1`] = ` { "#": "_2d1f13069a34dadb8112", "##": "ae8301fd6f3598539bfd", @@ -95,7 +95,7 @@ exports[`configCases builtins css-modules-local-ident-name-hash exported tests c } `; -exports[`configCases builtins css-modules-local-ident-name-path exported tests css modules localIdentName with path 1`] = ` +exports[`ConfigTestCases builtins css-modules-local-ident-name-path exported tests css modules localIdentName with path 1`] = ` { "#": "src-index__#", "##": "src-index__##", @@ -148,85 +148,69 @@ exports[`configCases builtins css-modules-local-ident-name-path exported tests c } `; -exports[`configCases builtins css-modules-locals-convention-camelCase exported tests css modules localsConvention with camelCase 1`] = ` +exports[`ConfigTestCases builtins css-modules-locals-convention-camelCase exported tests css modules localsConvention with camelCase 1`] = ` { - "btn--info_is-disabled_1": "index-css__btn--info_is-disabled_1", - "btn-info_is-disabled": "index-css__btn-info_is-disabled", - "btnInfoIsDisabled": "index-css__btn-info_is-disabled", - "btnInfoIsDisabled1": "index-css__btn--info_is-disabled_1", - "fooBar": "index-css__foo_bar", - "foo_bar": "index-css__foo_bar", - "simple": "index-css__simple", + "btn--info_is-disabled_1": "be5d658eb22137a006e0", + "btn-info_is-disabled": "_779fe26757a96701fe9b", + "btnInfoIsDisabled": "_779fe26757a96701fe9b", + "btnInfoIsDisabled1": "be5d658eb22137a006e0", + "fooBar": "f6ab18360b31088c39ba", + "foo_bar": "f6ab18360b31088c39ba", + "simple": "a883f99892916cc2d8fd", } `; -exports[`configCases builtins css-modules-locals-convention-camelCaseOnly exported tests css modules localsConvention with camelCaseOnly 1`] = ` +exports[`ConfigTestCases builtins css-modules-locals-convention-camelCaseOnly exported tests css modules localsConvention with camelCaseOnly 1`] = ` { - "btnInfoIsDisabled": "index-css__btn-info_is-disabled", - "btnInfoIsDisabled1": "index-css__btn--info_is-disabled_1", - "fooBar": "index-css__foo_bar", - "simple": "index-css__simple", + "btnInfoIsDisabled": "_779fe26757a96701fe9b", + "btnInfoIsDisabled1": "be5d658eb22137a006e0", + "fooBar": "f6ab18360b31088c39ba", + "simple": "a883f99892916cc2d8fd", } `; -exports[`configCases builtins css-modules-locals-convention-dashes exported tests css modules localsConvention with dashes 1`] = ` +exports[`ConfigTestCases builtins css-modules-locals-convention-dashes exported tests css modules localsConvention with dashes 1`] = ` { - "btn--info_is-disabled_1": "index-css__btn--info_is-disabled_1", - "btn-info-is-disabled": "index-css__btn-info_is-disabled", - "btn-info-is-disabled-1": "index-css__btn--info_is-disabled_1", - "btn-info_is-disabled": "index-css__btn-info_is-disabled", - "foo-bar": "index-css__foo_bar", - "foo_bar": "index-css__foo_bar", - "simple": "index-css__simple", + "btn--info_is-disabled_1": "be5d658eb22137a006e0", + "btn-info-is-disabled": "_779fe26757a96701fe9b", + "btn-info-is-disabled-1": "be5d658eb22137a006e0", + "btn-info_is-disabled": "_779fe26757a96701fe9b", + "foo-bar": "f6ab18360b31088c39ba", + "foo_bar": "f6ab18360b31088c39ba", + "simple": "a883f99892916cc2d8fd", } `; -exports[`configCases builtins css-modules-pseudo exported tests css modules pseudo syntax 1`] = ` +exports[`ConfigTestCases builtins css-modules-pseudo exported tests css modules pseudo syntax 1`] = ` { - "bar": "index-css__bar", - "bav": "index-css__bav", - "foo": "index-css__foo", - "four": "index-css__four", - "one": "index-css__one", - "three": "index-css__three", - "two": "index-css__two", + "bar": "cacc1496e2b72da608be", + "bav": "db3f1cc506f178423160", + "foo": "e19c45648c68713ed25f", + "four": "f182e3b95198a17baa9e", + "one": "_6f07ddfd0780cc1659ae", + "three": "_3f1fe9cfd1b2534d3f7f", + "two": "eea2bec7b16040ba9d36", } `; -exports[`configCases builtins css-modules-simple exported tests css modules simple test 1`] = ` +exports[`ConfigTestCases builtins css-modules-simple exported tests css modules simple test 1`] = ` { - "style": "index-module-css__style", + "style": "_5c2d5551d80e7ce1ba5f", } `; -exports[`configCases chunk-index available-modules-order-index exported tests should compile 1`] = ` -"/* #region "./m.css" */ -/* -- type: css -*/ -.m { +exports[`ConfigTestCases chunk-index available-modules-order-index exported tests should compile 1`] = ` +".m { color: red; } -/* #endregion "./m.css" */ - -/* #region "./n.css" */ -/* -- type: css -*/ .n { color: blue; } -/* #endregion "./n.css" */ - " `; -exports[`configCases css urls exported tests css urls should works 1`] = ` -"/* #region "./urls.css" */ -/* -- type: css -*/ -/* @supports (background-image: url("unknown.png")) { +exports[`ConfigTestCases css urls exported tests css urls should works 1`] = ` +"/* @supports (background-image: url("unknown.png")) { div { a195: url("img.png"); } @@ -278,14 +262,12 @@ div { 50% 50%/191px no-repeat; } -/* #endregion "./urls.css" */ - " `; -exports[`configCases plugins chunk-modules exported tests chunk-modules 1`] = ` +exports[`ConfigTestCases plugins chunk-modules exported tests chunk-modules 1`] = ` { - "async": { + "515": { "entryModules": [], "modules": [ { @@ -295,7 +277,7 @@ exports[`configCases plugins chunk-modules exported tests chunk-modules 1`] = ` }, ], }, - "main": { + "909": { "entryModules": [ { "context": "", diff --git a/packages/rspack/tests/configCase.test.ts b/packages/rspack/tests/configCase.test.ts deleted file mode 100644 index bea33f1abdd..00000000000 --- a/packages/rspack/tests/configCase.test.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { describeCases } from "./ConfigCase.template"; -describeCases({ - name: "configCases", - casePath: "configCases" -}); diff --git a/packages/rspack/tests/configCases/loader/issue-4838/a.js b/packages/rspack/tests/configCases/asset/data-url-condition/img.png similarity index 100% rename from packages/rspack/tests/configCases/loader/issue-4838/a.js rename to packages/rspack/tests/configCases/asset/data-url-condition/img.png diff --git a/packages/rspack/tests/configCases/asset/data-url-condition/index.js b/packages/rspack/tests/configCases/asset/data-url-condition/index.js index bf57175b1be..d984261976f 100644 --- a/packages/rspack/tests/configCases/asset/data-url-condition/index.js +++ b/packages/rspack/tests/configCases/asset/data-url-condition/index.js @@ -1,10 +1,5 @@ -import fs from "fs"; -import path from "path"; - -import LARGE from "./large.png"; +import IMG from "./img.png"; it("should inline the content if `rule.type` is sat to `asset` and the size of the asset doesn't exceeds the `dataUrlCondition.maxSize`", () => { - const png = fs.readFileSync(path.join(__dirname, "../large.png")); - expect(png.length <= 100 * 1024).toBeTruthy(); - expect(LARGE.startsWith("data:image/png;base64,")).toBeTruthy(); + expect(IMG.startsWith("data:image/png;base64,")).toBeTruthy(); }); diff --git a/packages/rspack/tests/configCases/asset/data-url-condition/large.png b/packages/rspack/tests/configCases/asset/data-url-condition/large.png deleted file mode 100644 index b74b839e2b8..00000000000 Binary files a/packages/rspack/tests/configCases/asset/data-url-condition/large.png and /dev/null differ diff --git a/packages/rspack/tests/configCases/asset/issue#2283/img.png b/packages/rspack/tests/configCases/asset/issue#2283/img.png deleted file mode 100644 index b74b839e2b8..00000000000 Binary files a/packages/rspack/tests/configCases/asset/issue#2283/img.png and /dev/null differ diff --git a/packages/rspack/tests/configCases/asset/issue#2283/index.js b/packages/rspack/tests/configCases/asset/issue#2283/index.js deleted file mode 100644 index 0db7ae71f87..00000000000 --- a/packages/rspack/tests/configCases/asset/issue#2283/index.js +++ /dev/null @@ -1,9 +0,0 @@ -import fs from "fs"; -require("./img.png"); - -it("should create the same asset as input", () => { - const png = fs.readdirSync(__dirname).find(file => file.endsWith(".png")); - expect(fs.readFileSync(__dirname + "/" + png)).toEqual( - fs.readFileSync(__dirname + "/../" + "img.png") - ); -}); diff --git a/packages/rspack/tests/configCases/loader/issue-4838/b.js b/packages/rspack/tests/configCases/asset/rspack-issue-2283/img.png similarity index 100% rename from packages/rspack/tests/configCases/loader/issue-4838/b.js rename to packages/rspack/tests/configCases/asset/rspack-issue-2283/img.png diff --git a/packages/rspack/tests/configCases/asset/rspack-issue-2283/index.js b/packages/rspack/tests/configCases/asset/rspack-issue-2283/index.js new file mode 100644 index 00000000000..04623200133 --- /dev/null +++ b/packages/rspack/tests/configCases/asset/rspack-issue-2283/index.js @@ -0,0 +1,7 @@ +import fs from "fs"; + +it("should create the same asset as input", () => { + expect(fs.readFileSync(__dirname + "/" + require("./img.png"), "utf-8")).toBe( + "" + ); +}); diff --git a/packages/rspack/tests/configCases/asset/issue#2283/webpack.config.js b/packages/rspack/tests/configCases/asset/rspack-issue-2283/webpack.config.js similarity index 100% rename from packages/rspack/tests/configCases/asset/issue#2283/webpack.config.js rename to packages/rspack/tests/configCases/asset/rspack-issue-2283/webpack.config.js diff --git a/packages/rspack/tests/configCases/builtin-swc-loader/issue-4597/index.js b/packages/rspack/tests/configCases/builtin-swc-loader/issue-4597/index.js index f6e6d524feb..f135786a83c 100644 --- a/packages/rspack/tests/configCases/builtin-swc-loader/issue-4597/index.js +++ b/packages/rspack/tests/configCases/builtin-swc-loader/issue-4597/index.js @@ -3,6 +3,7 @@ import fs from "fs"; it("should generate css successfully", () => { const dir = fs.readdirSync(__dirname); - expect(dir).toStrictEqual(["main.css", "main.js"]); + expect(dir.includes("bundle0.js")).toBeTruthy(); + expect(dir.includes("bundle0.css")).toBeTruthy(); expect(Button).toBe("button"); }); diff --git a/packages/rspack/tests/configCases/builtin-swc-loader/issue-4597/node_modules/aaaaa/es/index.js b/packages/rspack/tests/configCases/builtin-swc-loader/issue-4597/node_modules/aaaaa/es/index.js index 30df6a6d7ce..572dc5f080d 100644 --- a/packages/rspack/tests/configCases/builtin-swc-loader/issue-4597/node_modules/aaaaa/es/index.js +++ b/packages/rspack/tests/configCases/builtin-swc-loader/issue-4597/node_modules/aaaaa/es/index.js @@ -1 +1 @@ -import {default as Button} from './button'; \ No newline at end of file +import {default as Button} from 'aaaaa/es/button'; diff --git a/packages/rspack/tests/configCases/builtin-swc-loader/react-refresh-false/webpack.config.js b/packages/rspack/tests/configCases/builtin-swc-loader/react-refresh-false/webpack.config.js index f8de9ff7dbb..bc22007f080 100644 --- a/packages/rspack/tests/configCases/builtin-swc-loader/react-refresh-false/webpack.config.js +++ b/packages/rspack/tests/configCases/builtin-swc-loader/react-refresh-false/webpack.config.js @@ -1,4 +1,5 @@ module.exports = { + entry: "./index.jsx", resolve: { extensions: ["...", ".ts", ".tsx", ".jsx"] }, diff --git a/packages/rspack/tests/configCases/builtin-swc-loader/react-runtime-automic/webpack.config.js b/packages/rspack/tests/configCases/builtin-swc-loader/react-runtime-automic/webpack.config.js index d6293800418..70da0008114 100644 --- a/packages/rspack/tests/configCases/builtin-swc-loader/react-runtime-automic/webpack.config.js +++ b/packages/rspack/tests/configCases/builtin-swc-loader/react-runtime-automic/webpack.config.js @@ -1,4 +1,5 @@ module.exports = { + entry: "./index.jsx", resolve: { extensions: ["...", ".ts", ".tsx", ".jsx"] }, diff --git a/packages/rspack/tests/configCases/builtin-swc-loader/react-runtime-classic/webpack.config.js b/packages/rspack/tests/configCases/builtin-swc-loader/react-runtime-classic/webpack.config.js index da00820a97c..516a16ff058 100644 --- a/packages/rspack/tests/configCases/builtin-swc-loader/react-runtime-classic/webpack.config.js +++ b/packages/rspack/tests/configCases/builtin-swc-loader/react-runtime-classic/webpack.config.js @@ -1,4 +1,5 @@ module.exports = { + entry: "./index.jsx", resolve: { extensions: ["...", ".ts", ".tsx", ".jsx"] }, diff --git a/packages/rspack/tests/configCases/builtin-swc-loader/source-map/index.js b/packages/rspack/tests/configCases/builtin-swc-loader/source-map/index.js index 976f3d340f0..690ab0d80ef 100644 --- a/packages/rspack/tests/configCases/builtin-swc-loader/source-map/index.js +++ b/packages/rspack/tests/configCases/builtin-swc-loader/source-map/index.js @@ -6,7 +6,7 @@ it("should generate correct sourceMap", async () => { const source = fs.readFileSync(__filename + ".map", "utf-8"); const map = JSON.parse(source); const sourceContent = fs.readFileSync( - path.resolve(__dirname, "../a.ts"), + __dirname + "/" + require("!!./a.ts?resource"), "utf-8" ); expect(map.sources).toContain("webpack:///./a.ts"); diff --git a/packages/rspack/tests/configCases/builtin-swc-loader/source-map/source.txt b/packages/rspack/tests/configCases/builtin-swc-loader/source-map/source.txt new file mode 100644 index 00000000000..ec127c18f7a --- /dev/null +++ b/packages/rspack/tests/configCases/builtin-swc-loader/source-map/source.txt @@ -0,0 +1,4 @@ +let foo: string = "f1"; +let bar: string = "b1"; +let baz: string = "b2"; +const boo = "abc"; diff --git a/packages/rspack/tests/configCases/builtin-swc-loader/source-map/webpack.config.js b/packages/rspack/tests/configCases/builtin-swc-loader/source-map/webpack.config.js index f5a8f1d99d4..a6f5d288115 100644 --- a/packages/rspack/tests/configCases/builtin-swc-loader/source-map/webpack.config.js +++ b/packages/rspack/tests/configCases/builtin-swc-loader/source-map/webpack.config.js @@ -22,6 +22,13 @@ module.exports = { } ], type: "javascript/auto" + }, + { + resourceQuery: /resource/, + type: "asset/resource", + generator: { + filename: "source.txt" + } } ] } diff --git a/packages/rspack/tests/configCases/builtin-swc-loader/styled-components/index.jsx b/packages/rspack/tests/configCases/builtin-swc-loader/styled-components/index.jsx index 44e5565cab4..5f63de1c27d 100644 --- a/packages/rspack/tests/configCases/builtin-swc-loader/styled-components/index.jsx +++ b/packages/rspack/tests/configCases/builtin-swc-loader/styled-components/index.jsx @@ -2,11 +2,9 @@ import { Container } from "./Button"; import { Container as Container2 } from "./Button2"; it("styled components", () => { - expect(Container.displayName).toMatch("Button__Container"); expect(Container.styledComponentId).toMatch( /^Button__Container-rspack-test__/ ); - expect(Container2.displayName).toMatch("Button2__Container"); expect(Container2.styledComponentId).toMatch( /^Button2__Container-rspack-test__/ ); diff --git a/packages/rspack/tests/configCases/builtin-swc-loader/styled-components/webpack.config.js b/packages/rspack/tests/configCases/builtin-swc-loader/styled-components/webpack.config.js index 11bd6cb711c..ec413d6b090 100644 --- a/packages/rspack/tests/configCases/builtin-swc-loader/styled-components/webpack.config.js +++ b/packages/rspack/tests/configCases/builtin-swc-loader/styled-components/webpack.config.js @@ -1,5 +1,6 @@ /** @type {import('@rspack/core').Configuration}*/ module.exports = { + entry: "./index.jsx", resolve: { alias: { "@xstyled/styled-components": "styled-components" diff --git a/packages/rspack/tests/configCases/builtins/banner/test.config.js b/packages/rspack/tests/configCases/builtins/banner/test.config.js new file mode 100644 index 00000000000..89647663bee --- /dev/null +++ b/packages/rspack/tests/configCases/builtins/banner/test.config.js @@ -0,0 +1,5 @@ +module.exports = { + findBundle: (i, options) => { + return ["main.js"]; + } +}; diff --git a/packages/rspack/tests/configCases/builtins/banner/webpack.config.js b/packages/rspack/tests/configCases/builtins/banner/webpack.config.js index 8ba2dc0b748..b339a5ed7a3 100644 --- a/packages/rspack/tests/configCases/builtins/banner/webpack.config.js +++ b/packages/rspack/tests/configCases/builtins/banner/webpack.config.js @@ -5,10 +5,16 @@ module.exports = { main: "./index", a: "./a" }, + target: "node", output: { + filename: "[name].js", + chunkFilename: "[name].js", assetModuleFilename: "[name][ext]" }, devtool: "source-map", + optimization: { + chunkIds: "named" + }, module: { rules: [ { diff --git a/packages/rspack/tests/configCases/builtins/css-auto/index.js b/packages/rspack/tests/configCases/builtins/css-auto/index.js index 127a8e08744..461efcae309 100644 --- a/packages/rspack/tests/configCases/builtins/css-auto/index.js +++ b/packages/rspack/tests/configCases/builtins/css-auto/index.js @@ -8,6 +8,6 @@ it("css/auto can handle css module correctly", () => { it("css/auto can handle css correctly", () => { require("./index.css"); - const css = fs.readFileSync(path.resolve(__dirname, "main.css"), "utf-8"); + const css = fs.readFileSync(path.resolve(__dirname, "bundle0.css"), "utf-8"); expect(css.includes("aliceblue")).toBe(true); }); diff --git a/packages/rspack/tests/configCases/builtins/html-entry-order/test.config.js b/packages/rspack/tests/configCases/builtins/html-entry-order/test.config.js new file mode 100644 index 00000000000..89647663bee --- /dev/null +++ b/packages/rspack/tests/configCases/builtins/html-entry-order/test.config.js @@ -0,0 +1,5 @@ +module.exports = { + findBundle: (i, options) => { + return ["main.js"]; + } +}; diff --git a/packages/rspack/tests/configCases/builtins/html-entry-order/webpack.config.js b/packages/rspack/tests/configCases/builtins/html-entry-order/webpack.config.js index 60fcb1f39ec..566b16b81b8 100644 --- a/packages/rspack/tests/configCases/builtins/html-entry-order/webpack.config.js +++ b/packages/rspack/tests/configCases/builtins/html-entry-order/webpack.config.js @@ -6,6 +6,9 @@ module.exports = { polyfill: "./polyfill.js", main: "./index.js" }, + output: { + filename: "[name].js" + }, plugins: [ new rspack.HtmlRspackPlugin({ template: "./index.html" diff --git a/packages/rspack/tests/configCases/builtins/html-inject/index.js b/packages/rspack/tests/configCases/builtins/html-inject/index.js index 1d427ecf57e..0dd22ce1dd1 100644 --- a/packages/rspack/tests/configCases/builtins/html-inject/index.js +++ b/packages/rspack/tests/configCases/builtins/html-inject/index.js @@ -5,7 +5,7 @@ it("body-index.html inject", () => { const htmlPath = path.join(__dirname, "./body-index.html"); const htmlContent = fs.readFileSync(htmlPath, "utf-8"); expect( - htmlContent.includes('') + htmlContent.includes('') ).toBe(true); }); @@ -13,28 +13,28 @@ it("head-index.html inject", () => { const htmlPath = path.join(__dirname, "./head-index.html"); const htmlContent = fs.readFileSync(htmlPath, "utf-8"); expect( - htmlContent.includes('') + htmlContent.includes('') ).toBe(true); }); it("true-blocking-index.html inject", () => { const htmlPath = path.join(__dirname, "./true-blocking-index.html"); const htmlContent = fs.readFileSync(htmlPath, "utf-8"); - expect(htmlContent.includes('')).toBe( - true - ); + expect( + htmlContent.includes('') + ).toBe(true); }); it("true-defer-index.html inject", () => { const htmlPath = path.join(__dirname, "./true-defer-index.html"); const htmlContent = fs.readFileSync(htmlPath, "utf-8"); expect( - htmlContent.includes('') + htmlContent.includes('') ).toBe(true); }); it("false-index.html inject", () => { const htmlPath = path.join(__dirname, "./false-index.html"); const htmlContent = fs.readFileSync(htmlPath, "utf-8"); - expect(htmlContent.includes('