From 9cce7578bd54f74e0b53a37de89f64eb78fcad68 Mon Sep 17 00:00:00 2001 From: Nathan Walker Date: Sat, 23 Aug 2025 20:37:59 -0700 Subject: [PATCH] fix: automated tests --- package-lock.json | 42 ++++++ package.json | 1 + packages/webpack5/src/configuration/base.ts | 60 ++++---- .../src/plugins/CompatDefinePlugin.ts | 14 ++ .../src/plugins/PlatformSuffixPlugin.ts | 133 ++++++++++-------- 5 files changed, 160 insertions(+), 90 deletions(-) create mode 100644 packages/webpack5/src/plugins/CompatDefinePlugin.ts diff --git a/package-lock.json b/package-lock.json index 9a70fa89a..71912692c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -75,6 +75,7 @@ "postcss-loader": "^8.0.0", "prettier": "^3.2.5", "sass": "^1.72.0", + "sass-loader": "^16.0.0", "shady-css-parser": "^0.1.0", "terser-webpack-plugin": "^5.0.0", "tree-kill": "^1.2.2", @@ -24218,6 +24219,47 @@ "@parcel/watcher": "^2.4.1" } }, + "node_modules/sass-loader": { + "version": "16.0.5", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-16.0.5.tgz", + "integrity": "sha512-oL+CMBXrj6BZ/zOq4os+UECPL+bWqt6OAC6DWS8Ln8GZRcMDjlJ4JC3FBDuHJdYaFWIdKNIBYmtZtK2MaMkNIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "neo-async": "^2.6.2" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "@rspack/core": "0.x || 1.x", + "node-sass": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0", + "sass": "^1.3.0", + "sass-embedded": "*", + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "node-sass": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, "node_modules/sass/node_modules/chokidar": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.1.tgz", diff --git a/package.json b/package.json index 25b34fe05..ccc97c5e6 100644 --- a/package.json +++ b/package.json @@ -82,6 +82,7 @@ "postcss-loader": "^8.0.0", "prettier": "^3.2.5", "sass": "^1.72.0", + "sass-loader": "^16.0.0", "shady-css-parser": "^0.1.0", "terser-webpack-plugin": "^5.0.0", "tree-kill": "^1.2.2", diff --git a/packages/webpack5/src/configuration/base.ts b/packages/webpack5/src/configuration/base.ts index dcedb4f5c..023183574 100644 --- a/packages/webpack5/src/configuration/base.ts +++ b/packages/webpack5/src/configuration/base.ts @@ -1,10 +1,5 @@ import { extname, relative, resolve } from 'path'; -import { - ContextExclusionPlugin, - DefinePlugin, - HotModuleReplacementPlugin, - BannerPlugin, -} from 'webpack'; +import { ContextExclusionPlugin, HotModuleReplacementPlugin } from 'webpack'; import Config from 'webpack-chain'; import { satisfies } from 'semver'; import { existsSync } from 'fs'; @@ -23,6 +18,7 @@ import { PlatformSuffixPlugin } from '../plugins/PlatformSuffixPlugin'; import { applyFileReplacements } from '../helpers/fileReplacements'; import { addCopyRule, applyCopyRules } from '../helpers/copyRules'; import { WatchStatePlugin } from '../plugins/WatchStatePlugin'; +import { CompatDefinePlugin } from '../plugins/CompatDefinePlugin'; import { applyDotEnvPlugin } from '../helpers/dotEnv'; import { env as _env, IWebpackEnv } from '../index'; import { getValue } from '../helpers/config'; @@ -545,7 +541,7 @@ export default function (config: Config, env: IWebpackEnv = _env): Config { config .plugin('ContextExclusionPlugin|Other_Platforms') .use(ContextExclusionPlugin, [ - new RegExp(`\\.(${otherPlatformsRE})\\.(\\w+)$`), + new RegExp(`\.(${otherPlatformsRE})\.(\w+)$`), ]); // Filter common undesirable warnings @@ -565,31 +561,31 @@ export default function (config: Config, env: IWebpackEnv = _env): Config { ); // todo: refine defaults - config.plugin('DefinePlugin').use(DefinePlugin, [ - { - __DEV__: mode === 'development', - __NS_WEBPACK__: true, - __NS_ENV_VERBOSE__: !!env.verbose, - __NS_DEV_HOST_IPS__: - mode === 'development' ? JSON.stringify(getIPS()) : `[]`, - __CSS_PARSER__: JSON.stringify(getValue('cssParser', 'css-tree')), - __UI_USE_XML_PARSER__: true, - __UI_USE_EXTERNAL_RENDERER__: false, - __COMMONJS__: !!env.commonjs, - __ANDROID__: platform === 'android', - __IOS__: platform === 'ios', - __VISIONOS__: platform === 'visionos', - __APPLE__: platform === 'ios' || platform === 'visionos', - /* for compat only */ 'global.isAndroid': platform === 'android', - /* for compat only */ 'global.isIOS': - platform === 'ios' || platform === 'visionos', - /* for compat only */ 'global.isVisionOS': platform === 'visionos', - process: 'global.process', - - // todo: ?!?! - // profile: '() => {}', - }, - ]); + config.plugin('DefinePlugin').use( + CompatDefinePlugin as any, + [ + { + __DEV__: mode === 'development', + __NS_WEBPACK__: true, + __NS_ENV_VERBOSE__: !!env.verbose, + __NS_DEV_HOST_IPS__: + mode === 'development' ? JSON.stringify(getIPS()) : `[]`, + __CSS_PARSER__: JSON.stringify(getValue('cssParser', 'css-tree')), + __UI_USE_XML_PARSER__: true, + __UI_USE_EXTERNAL_RENDERER__: false, + __COMMONJS__: !!env.commonjs, + __ANDROID__: platform === 'android', + __IOS__: platform === 'ios', + __VISIONOS__: platform === 'visionos', + __APPLE__: platform === 'ios' || platform === 'visionos', + /* for compat only */ 'global.isAndroid': platform === 'android', + /* for compat only */ 'global.isIOS': + platform === 'ios' || platform === 'visionos', + /* for compat only */ 'global.isVisionOS': platform === 'visionos', + process: 'global.process', + }, + ] as any, + ); // enable DotEnv applyDotEnvPlugin(config); diff --git a/packages/webpack5/src/plugins/CompatDefinePlugin.ts b/packages/webpack5/src/plugins/CompatDefinePlugin.ts new file mode 100644 index 000000000..2ed2f39aa --- /dev/null +++ b/packages/webpack5/src/plugins/CompatDefinePlugin.ts @@ -0,0 +1,14 @@ +export class CompatDefinePlugin { + private readonly definitions: Record; + + constructor(definitions: Record) { + this.definitions = definitions || {}; + } + + apply(compiler: any) { + // Use the same webpack instance as the compiler to avoid version mismatches + const wp = compiler?.webpack || require('webpack'); + const DefinePlugin = wp.DefinePlugin || require('webpack').DefinePlugin; + new DefinePlugin(this.definitions).apply(compiler); + } +} diff --git a/packages/webpack5/src/plugins/PlatformSuffixPlugin.ts b/packages/webpack5/src/plugins/PlatformSuffixPlugin.ts index d6b59483d..41a434268 100644 --- a/packages/webpack5/src/plugins/PlatformSuffixPlugin.ts +++ b/packages/webpack5/src/plugins/PlatformSuffixPlugin.ts @@ -37,22 +37,28 @@ export class PlatformSuffixPlugin { // require.context compiler.hooks.contextModuleFactory.tap(id, (cmf) => { // @ts-ignore - cmf.hooks.alternativeRequests.tap(id, (modules, options) => { - const additionalModules = []; - // we are looking for modules that are platform specific (something..ext) - // and we are duplicating them without the platform suffix - // this allows using require.context with non-existent platformless filenames - // but mapped to the platform specific variant (done in the resolver hook below) - for (const module of modules) { - if (platformRE.test(module.request)) { - additionalModules.push({ - ...module, - request: module.request.replace(platformRE, '.'), - }); + const altHook = (cmf as any).hooks?.alternativeRequests; + if (altHook && typeof altHook.tap === 'function') { + altHook.tap(id, (modules: any[], options: any) => { + const additionalModules = []; + // we are looking for modules that are platform specific (something..ext) + // and we are duplicating them without the platform suffix + // this allows using require.context with non-existent platformless filenames + // but mapped to the platform specific variant (done in the resolver hook below) + for (const module of modules) { + if (platformRE.test(module.request)) { + additionalModules.push({ + ...module, + request: module.request.replace(platformRE, '.'), + }); + } } - } - modules.push(...additionalModules); - }); + modules.push(...additionalModules); + }); + } else { + // Hook may be absent on some webpack versions; skip gracefully + // console.log(`[${id}] alternativeRequests hook not available; skipping.`) + } }); compiler.resolverFactory.hooks.resolver @@ -73,57 +79,68 @@ export class PlatformSuffixPlugin { // }); // }) - resolver.hooks.normalResolve.tapAsync( - id, - (request_, resolveContext, callback) => { - for (const platform of this.extensions) { - const { path, request } = request_; - const ext = request && extname(request); - const platformExt = ext ? `.${platform}${ext}` : ''; + const normalResolveHook = (resolver as any).hooks?.normalResolve; + const ensureHook = (name: string) => { + return typeof (resolver as any).ensureHook === 'function' + ? (resolver as any).ensureHook(name) + : (resolver as any).hooks?.[name]; + }; - if (path && request && ext && !request.includes(platformExt)) { - const platformRequest = request.replace(ext, platformExt); - const extPath = resolve(path, platformRequest); + if ( + !normalResolveHook || + typeof normalResolveHook.tapAsync !== 'function' + ) { + // Missing or incompatible hook; skip to avoid crashes + return; + } - // console.log({ - // path, - // request, - // ext, - // extPath - // }) + normalResolveHook.tapAsync(id, (request_, resolveContext, callback) => { + for (const platform of this.extensions) { + const { path, request } = request_; + const ext = request && extname(request); + const platformExt = ext ? `.${platform}${ext}` : ''; - // if a file with the same + a platform suffix exists - // we want to resolve that file instead - if (existsSync(extPath)) { - const message = `resolving "${request}" to "${platformRequest}"`; - const hook = resolver.ensureHook('normalResolve'); - console.log(message); + if (path && request && ext && !request.includes(platformExt)) { + const platformRequest = request.replace(ext, platformExt); + const extPath = resolve(path, platformRequest); - // here we are creating a new resolve object and replacing the path - // with the .. suffix - const obj = { - ...request_, - path: resolver.join(path, platformRequest), - relativePath: - request_.relativePath && - resolver.join(request_.relativePath, platformRequest), - request: undefined, - }; + // console.log({ + // path, + // request, + // ext, + // extPath + // }) - // we call to the actual resolver to do the resolving of this new file - return resolver.doResolve( - hook, - obj, - message, - resolveContext, - callback - ); - } + // if a file with the same + a platform suffix exists + // we want to resolve that file instead + if (existsSync(extPath)) { + const message = `resolving "${request}" to "${platformRequest}"`; + const hook = ensureHook('normalResolve'); + + // here we are creating a new resolve object and replacing the path + // with the .. suffix + const obj = { + ...request_, + path: resolver.join(path, platformRequest), + relativePath: + request_.relativePath && + resolver.join(request_.relativePath, platformRequest), + request: undefined, + }; + + // we call to the actual resolver to do the resolving of this new file + return (resolver as any).doResolve( + hook as any, + obj, + message, + resolveContext, + callback, + ); } } - callback(); } - ); + callback(); + }); // resolver.hooks.rawFile.tap(id, (request, resolveContext, callback) => { // if(request.path && !/\.ios\..+$/.test(request.path)) { // const { ext } = parse(request.path)