From a7b557ed162166d9bc152b06d8be3169a0a2a176 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Roma=C5=84czyk?= Date: Thu, 12 Sep 2024 15:09:23 +0200 Subject: [PATCH] [BREAKING] feat: support `Rspack` & `Webpack` simultaneously (#720) * feat: rspack migration - part 1 squashed 18.07.2024 * chore: update templates for the rspack * fix: typing of getPublicPath * chore: add types to TesterApp config * chore: use v5-preview templates instead of the ones from main branch * fix: use explicilitly declared cwd in addDependencies task * fix: reliably obtain path to react native * fix: check if resolved path to RN is a dir * refactor: only import NativeDevSettings in dev * refactor: reduce output in dev * feat: add OOT platforms so they are transformed like RN * feat: add RN codegen rules to the setup * chore: update rspack related dependencies * fix: remove unnecessary ts comment * chore: update to rspack 0.7.2 * chore: update to rspack 0.7.3 * chore: update rspack deps to 1.0.0-alpha.5 * fix: typing * chore: fix rspack version misalignment * chore: remove unnecessary ts fix * fix: ts & ios setup * chore: upgrade to rspack 1.0.0 beta * chore: pnpm dedupe * feat: add rspack to federation tester * chore: migrate federation tester configs to rspack * fix: typos * chore: update Podfile.lock for federation tester * fix: tsconfig for repack * fix: miniapp config * refactor: remove extra layer when logging warns and errors in dev * chore: optimize federation tester configs * feat: extract NativeEntryPlugin from RepackTargetPlugin * fix: get HMR working in MF v1 * fix: dont reuse getDevServer * chore: upgrade to rspack 1.0.0 rc1 * feat: migrate OutputPlugin * fix: typing in Compiler * fix: typing errors * refactor: revert changes in init, keep adjusted package names only * refactor: remove unused ensureProjectExists from init * chore: update pnpm lockfile * feat: add DefaultRulesPlugin * chore: update tester-app * fix: hardcode platforms for now * refactor: remove asset type from tester app config * refactor: group command-related stuff together * refactor: separate command related defaults and env * refactor: move things around * WIP: fix HMR * refactor: revert most changes to LoggerPlugin * fix: make HMR work again * fix: tsconfig setup in repack * refactor: cleanup rules * fix: eslint setup * fix: reenable ManifestPlugin * refactor: cleanup in plugins * fix: dont use hardcoded versions in init * refactor: revert async server delegate * fix: init compiler before start * refactor: remove fat from ScriptManager * refactor: trim down fat in RepackTargetPlugin * chore: update tester files * chore: type import in DefaultRules, add todo for filtering out unneeded rules * refactor: use type imports to highlight where rspack is actually imported * refactor: cleanup loaders * remove fs-extra mock * refactor: use compiler.webpack instead of imports from rspack * refactor: update babel config * refactor: cleanup * refactor: align OutputPlugin impl for tests compat * refactor: remove types/jest reference * fix: lint issue in asset loader tests * refactor: remove fs-extra * Revert "remove fs-extra mock" This reverts commit 57bb2bba171840634d8d5666ea9d17ab92a72573. * chore: remove fs utils * chore: fix ts config * refactor: use memfs mock in assets copy processor tests * chore: update memfs to newest version * refactor: ScriptManager nad ChunksToHermesBytecodePlugin tests * refactor: getEnvOptions tests * fix: CodeSigningPlugin tests * fix: MF plugin tests * chore: remove mocking of process.cwd * chore: simplify jest.setup.js * feat: use asset extensions as default when using getAssetExtensionRegexp * fix: assetsLoader tests * chore: silence warning about event listeners * chore: update rspack virtual module plugin * chore: add webpack back as optional peer, make rspack optional peer as well * feat: separate commands for webpack and rspack * fix: commands compat * chore: revert removal of send progress from dev-server * fix: lint & type issues * feat: make loadConfig generic * fix: remove platforms from start options for now * feat: support rspack config files * refactor: extract adb reverse * refactor: extract parseFileUrl * refactor: extract interactions setup * refactor: extract stats writer * chore: remove old TODOs * fix: types in tests * refactor: move options to commands * feat: use exports for commands import * move loaders and plugins to the src * fix: types after move * fix: dynamic module imports in plugins * feat: use package exports for loaders * chore: remove docs dir from files in repack package.json * chore: use rspack in tester-app * fix: loaders imports * refactor: better typing for commands * feat: package exports types * refactor: extract Logger type * v4 bundle * fix: dont pass loaderContext into getOptions * feat: webpack compatible repack target plugin * chore: remove test bundle from git * feat: add log requtsts option to start command * refactor: remove DefaultRules plugin * chore: dual config in tester-app * chore: use require in tester-app instead of top level await * feat: progress plugin for webpack compat * feat: add webpack-compatible react refresh * chore: replace lodash throttle with throttleit * refactor: log progress only on webpack * test: update start.ts test to support rspack * fix: dont serve greet message when silent flag is active * fix: copy env to worker to avoid polluting global env * test: update bundle.ts test to support rspack * chore: use env to switch between bundlers * chore: ignore eslint errors when import callstack repack commands * feat: unstable_evaluateJavascript as a replacement for globalEvalWithSourceUrl * fix: properly obtain sourcemaps in dev with Rspack * refactor: align webpack Compiler with rspack Compiler for maintenace purposes * fix: convert worker data, mark hmr sourcemaps, Compiler typings * fix: metro-compat tests * fix: tests & linting rules in repack * feat: update init for V5 * fix: use adjusted entries for commands in repack-init * chore: restore stable templates * chore: add v5 rspack templates * chore: copy old webpack templates * chore: update webpack templates for v5 * fix: assets loader tests * chore: cleanup in assets loader * feat: add --platform CLI option for start command * chore: use EventEmitter from node in webpack Compiler * chore: better debug for start command test * refactor: rename configPath to bundlerConfigPath * refactor: adjust tests with bundlerConfigPath * chore: update vitest in tester-app * fix: tests in tester-app * chore: separate output in tester app tests * chore: use mts for vitest config * chore: update rspack deps * fix: run start.test using single platform to prevent flakiness * fix: bundle test * chore: narrow node 22 version to 22.6 * chore: pnpm dedupe * chore: add changeset * chore: migrate tester-app configs to mjs * chore: migrate tester-federation configs to mjs, rename to rspack configs * chore: use rspack.config mjs configs in tester-federation * chore: remove webpack-cli command from tester-app * chore: remove unused default platforms * fix: meet type requirements in getEnvOptions tests * chore: use commands/rspack in tester-federation * chore: update flow-remote-types to the newest version * fix: tester-federation setup * fix: non-scaled asset path * fix: swc loading rules fixes * fix: display warnings & error messages on a new line * fix: separate codegen setup for ts * refactor: cleanup in react native loading rules * chore: add inquirer prompts to init * feat: add option to bootstrap new projects with repack-init * chore: use dist dir instead of build to align with the rest of the monorepo * feat: use own version as the default when installing repack * fix: read file instead of relying on imports * fix: use cwd for adding deps * fix: add babel-loader to rspack dependencies * fix: bind dev-server version to repack version * fix: add missing welcome message to webpack * chore: update lockfile * fix: read hot update files from assets cache * fix: bind rspack version to 1.0.3 for alpha --- .changeset/spicy-pumas-whisper.md | 5 + .github/workflows/test.yml | 2 +- .gitignore | 3 + apps/tester-app/.eslintrc.js | 9 +- apps/tester-app/__tests__/bundle.test.ts | 233 +- apps/tester-app/__tests__/rspack.config.mjs | 12 + apps/tester-app/__tests__/start.test.ts | 219 +- apps/tester-app/index.js | 36 +- apps/tester-app/ios/Podfile.lock | 16 +- apps/tester-app/package.json | 12 +- apps/tester-app/react-native.config.js | 6 +- apps/tester-app/rspack.config.mjs | 198 + apps/tester-app/src/App.tsx | 5 - .../DeprecatedRemoteDebuggerContainer.tsx | 16 +- .../NativeDevSettings.d.ts | 6 + apps/tester-app/tsconfig.json | 2 +- .../{vitest.config.js => vitest.config.mts} | 0 apps/tester-app/webpack.config.mjs | 142 +- apps/tester-federation/ios/Podfile.lock | 16 +- apps/tester-federation/package.json | 20 +- apps/tester-federation/react-native.config.js | 2 +- ...ost-app.mjs => rspack.config.host-app.mjs} | 75 +- ...ini-app.mjs => rspack.config.mini-app.mjs} | 77 +- package.json | 6 +- packages/dev-server/package.json | 2 +- packages/dev-server/src/createServer.ts | 10 +- .../src/plugins/symbolicate/Symbolicator.ts | 3 +- .../plugins/wss/servers/WebSocketApiServer.ts | 4 +- .../wss/servers/WebSocketDevClientServer.ts | 11 +- .../plugins/wss/servers/WebSocketHMRServer.ts | 6 +- .../wss/servers/WebSocketMessageServer.ts | 2 +- packages/dev-server/src/types.ts | 3 + packages/init/package.json | 9 +- packages/init/src/bin.ts | 12 +- packages/init/src/index.ts | 16 +- packages/init/src/tasks/addDependencies.ts | 47 +- .../init/src/tasks/checkPackageManager.ts | 8 +- packages/init/src/tasks/checkReactNative.ts | 8 +- .../init/src/tasks/createBundlerConfig.ts | 73 + .../init/src/tasks/createWebpackConfig.ts | 67 - .../init/src/tasks/ensureProjectExists.ts | 67 + .../init/src/tasks/handleReactNativeConfig.ts | 19 +- packages/init/src/tasks/modifyAndroid.ts | 4 +- packages/init/src/tasks/modifyIOS.ts | 4 +- packages/init/tsconfig.json | 3 +- packages/repack/.eslintrc.js | 4 + .../src/main/cpp/NativeScriptLoader.cpp | 21 +- .../android/src/main/cpp/NativeScriptLoader.h | 6 + .../repack/FileSystemScriptLoader.kt | 8 +- .../callstack/repack/RemoteScriptLoader.kt | 6 +- .../callstack/repack/ScriptManagerModule.kt | 13 +- .../android/src/oldarch/ScriptManagerSpec.kt | 1 + .../callstack/repack/NativeScriptLoader.kt | 19 +- .../callstack/repack/NativeScriptLoader.kt | 19 +- packages/repack/assets-loader.js | 4 - packages/repack/babel.config.js | 4 +- packages/repack/commands.d.ts | 27 - packages/repack/ios/ScriptManager.mm | 17 + packages/repack/jest.config.js | 189 - packages/repack/jest.setup.js | 13 +- packages/repack/package.json | 90 +- packages/repack/src/commands/bundle.ts | 118 - .../common/__tests__/getEnvOptions.test.ts} | 27 +- .../common}/adaptFilenameToPlatform.ts | 0 .../src/commands/common/getConfigFilePath.ts | 50 + .../common/getEnvOptions.ts} | 13 +- packages/repack/src/commands/common/index.ts | 8 + .../repack/src/commands/common/loadConfig.ts | 28 + .../src/commands/common/parseFileUrl.ts | 27 + .../src/commands/common/runAdbReverse.ts | 18 + .../src/commands/common/setupInteractions.ts | 38 + .../src/commands/common/setupStatsWriter.ts | 40 + packages/repack/src/commands/consts.ts | 23 + .../{commands.js => src/commands/options.ts} | 114 +- .../repack/src/commands/rspack/Compiler.ts | 308 + packages/repack/src/commands/rspack/bundle.ts | 99 + packages/repack/src/commands/rspack/index.ts | 33 + packages/repack/src/commands/rspack/start.ts | 213 + packages/repack/src/commands/rspack/types.ts | 12 + packages/repack/src/commands/types.ts | 57 + .../commands/utils/getWebpackConfigPath.ts | 37 - .../src/{ => commands}/webpack/Compiler.ts | 171 +- .../src/commands/webpack/CompilerWorker.ts | 125 + .../repack/src/commands/webpack/bundle.ts | 101 + packages/repack/src/commands/webpack/index.ts | 33 + .../src/commands/{ => webpack}/start.ts | 156 +- packages/repack/src/commands/webpack/types.ts | 82 + packages/repack/src/env.ts | 33 +- packages/repack/src/index.ts | 10 +- .../__tests__/__fixtures__/logo.png | Bin .../__tests__/__fixtures__/star@1x.png | Bin .../__tests__/__fixtures__/star@2x.png | Bin .../__tests__/__fixtures__/star@3x.png | Bin .../__snapshots__/assetsLoader.test.ts.snap | 443 + .../__tests__/assetsLoader.test.ts | 286 +- .../loaders/assetsLoader/assetsLoader.ts | 176 +- .../assetsLoader/convertToRemoteAssets.ts | 9 +- .../loaders/assetsLoader/extractAssets.ts | 47 +- .../repack/src/loaders/assetsLoader/index.ts | 4 + .../loaders/assetsLoader/inlineAssets.ts | 35 +- .../loaders/assetsLoader/options.ts | 30 +- .../repack/src/loaders/assetsLoader/types.ts | 29 + .../repack/src/loaders/assetsLoader/utils.ts | 77 + .../src/loaders/flowLoader/flowLoader.ts | 16 + .../repack/src/loaders/flowLoader/index.ts | 4 + .../repack/src/loaders/flowLoader/options.ts | 30 + .../loaders/reactRefreshCompatLoader/index.ts | 4 + .../reactRefreshCompatLoader.ts | 38 + .../src/logging/reporters/ConsoleReporter.ts | 66 +- .../src/logging/reporters/FileReporter.ts | 6 +- packages/repack/src/logging/types.ts | 8 +- .../repack/src/modules/DevServerClient.ts | 16 +- packages/repack/src/modules/EmptyModule.ts | 1 + .../src/modules/InitializeScriptManager.ts | 3 + .../ScriptManager/NativeScriptManager.ts | 4 + .../modules/ScriptManager/ScriptManager.ts | 87 +- .../__tests__/ScriptManager.test.ts | 49 +- .../repack/src/modules/WebpackHMRClient.ts | 89 +- .../src/modules/getDevServerLocation.ts | 5 +- .../ChunksToHermesBytecodePlugin.ts | 35 +- .../ChunksToHermesBytecodePlugin/index.ts | 0 .../utils/composeSourceMaps.ts | 8 +- .../utils/getHermesCLIPath.ts | 0 .../utils/index.ts | 0 .../utils/transformBundleToHermesBytecode.ts | 6 +- .../CodeSigningPlugin/CodeSigningPlugin.ts | 19 +- .../plugins/CodeSigningPlugin/config.ts | 0 .../plugins/CodeSigningPlugin/index.ts | 0 .../repack/src/plugins/DevelopmentPlugin.ts | 90 + .../src/{webpack => }/plugins/LoggerPlugin.ts | 51 +- packages/repack/src/plugins/ManifestPlugin.ts | 34 + .../plugins/ModuleFederationPlugin.ts | 15 +- .../repack/src/plugins/NativeEntryPlugin.ts | 69 + .../plugins/OutputPlugin/OutputPlugin.ts | 70 +- .../plugins/OutputPlugin/config.ts | 0 .../plugins/OutputPlugin/index.ts | 0 .../plugins/OutputPlugin/types.ts | 2 +- .../src/{webpack => }/plugins/RepackPlugin.ts | 60 +- .../RepackTargetPlugin/RepackTargetPlugin.ts | 140 + .../implementation/init.ts | 75 +- .../implementation/loadScript.ts | 1 - .../plugins/RepackTargetPlugin/index.ts | 0 .../ChunksToHermesBytecodePlugin.test.ts | 38 +- .../__tests__/CodeSigningPlugin.test.ts | 30 +- .../__tests__/ModuleFederationPlugin.test.ts | 142 +- .../plugins/__tests__/OutputPlugin.test.ts | 21 +- .../__fixtures__/example.chunk.bundle | 0 .../__fixtures__/example.container.bundle | 0 .../__tests__/__fixtures__/local.chunk.bundle | 0 .../__tests__/__fixtures__/main.bundle | 0 .../__tests__/__fixtures__/testRS256.pem | 0 .../__tests__/__fixtures__/testRS256.pem.pub | 0 .../repack/src/{webpack => }/plugins/index.ts | 2 - .../plugins/utils/AssetsCopyProcessor.ts | 48 +- .../utils/AuxiliaryAssetsCopyProcessor.ts | 18 +- .../__tests__/AssetsCopyProcessor.test.ts | 109 +- .../AuxiliaryAssetsCopyProcessor.test.ts | 28 +- packages/repack/src/rules/index.ts | 3 + packages/repack/src/rules/lazyImports.ts | 82 + .../src/rules/nodeModulesLoadingRules.ts | 81 + .../src/rules/reactNativeCodegenRules.ts | 44 + .../src/rules/reactNativeLoadingRules.ts | 54 + packages/repack/src/types.ts | 118 +- .../repack/src/types/flow-remove-types.d.ts | 19 + .../{modules => types}/runtime-globals.d.ts | 5 +- .../{webpack => }/utils/assetExtensions.ts | 7 +- .../src/{webpack => utils}/federated.ts | 6 +- .../src/{webpack => }/utils/getDirname.ts | 0 .../src/{webpack => }/utils/getPublicPath.ts | 4 +- .../{webpack => }/utils/getResolveOptions.ts | 21 + .../repack/src/{webpack => }/utils/index.ts | 8 +- .../repack/src/webpack/loadWebpackConfig.ts | 32 - .../src/webpack/loaders/assetsLoader/index.ts | 1 - .../src/webpack/loaders/assetsLoader/types.ts | 18 - .../src/webpack/loaders/assetsLoader/utils.ts | 82 - .../AssetsResolverPlugin/AssetResolver.ts | 170 - .../AssetsResolverPlugin.ts | 44 - .../plugins/AssetsResolverPlugin/index.ts | 1 - .../src/webpack/plugins/DevelopmentPlugin.ts | 157 - .../plugins/JavaScriptLooseModePlugin.ts | 66 - .../src/webpack/plugins/ManifestPlugin.ts | 36 - .../RepackTargetPlugin/RepackTargetPlugin.ts | 99 - .../runtime/RepackInitRuntimeModule.ts | 28 - .../runtime/RepackLoadScriptRuntimeModule.ts | 18 - .../plugins/utils/__mocks__/fs-extra.ts | 124 - .../webpack/utils/getInitializationEntries.ts | 73 - packages/repack/src/webpack/webpackWorker.ts | 84 - packages/repack/tsconfig.build.json | 8 + packages/repack/tsconfig.json | 86 +- pnpm-lock.yaml | 8426 ++++++++++------- templates_v5/rspack.config.cjs | 170 + templates_v5/rspack.config.mjs | 168 + templates_v5/webpack.config.cjs | 201 + templates_v5/webpack.config.mjs | 200 + tests/metro-compat/jest.setup.js | 2 + tests/metro-compat/package.json | 2 +- tests/metro-compat/resolver/resolve.ts | 2 +- 197 files changed, 10394 insertions(+), 6940 deletions(-) create mode 100644 .changeset/spicy-pumas-whisper.md create mode 100644 apps/tester-app/__tests__/rspack.config.mjs create mode 100644 apps/tester-app/rspack.config.mjs create mode 100644 apps/tester-app/src/deprecatedRemoteDebugger/NativeDevSettings.d.ts rename apps/tester-app/{vitest.config.js => vitest.config.mts} (100%) rename apps/tester-federation/{webpack.config.host-app.mjs => rspack.config.host-app.mjs} (65%) rename apps/tester-federation/{webpack.config.mini-app.mjs => rspack.config.mini-app.mjs} (64%) create mode 100644 packages/init/src/tasks/createBundlerConfig.ts delete mode 100644 packages/init/src/tasks/createWebpackConfig.ts create mode 100644 packages/init/src/tasks/ensureProjectExists.ts delete mode 100644 packages/repack/assets-loader.js delete mode 100644 packages/repack/commands.d.ts delete mode 100644 packages/repack/src/commands/bundle.ts rename packages/repack/src/{webpack/utils/__tests__/getWebpackEnvOptions.test.ts => commands/common/__tests__/getEnvOptions.test.ts} (80%) rename packages/repack/src/{webpack/utils => commands/common}/adaptFilenameToPlatform.ts (100%) create mode 100644 packages/repack/src/commands/common/getConfigFilePath.ts rename packages/repack/src/{webpack/utils/getWebpackEnvOptions.ts => commands/common/getEnvOptions.ts} (79%) create mode 100644 packages/repack/src/commands/common/index.ts create mode 100644 packages/repack/src/commands/common/loadConfig.ts create mode 100644 packages/repack/src/commands/common/parseFileUrl.ts create mode 100644 packages/repack/src/commands/common/runAdbReverse.ts create mode 100644 packages/repack/src/commands/common/setupInteractions.ts create mode 100644 packages/repack/src/commands/common/setupStatsWriter.ts create mode 100644 packages/repack/src/commands/consts.ts rename packages/repack/{commands.js => src/commands/options.ts} (60%) create mode 100644 packages/repack/src/commands/rspack/Compiler.ts create mode 100644 packages/repack/src/commands/rspack/bundle.ts create mode 100644 packages/repack/src/commands/rspack/index.ts create mode 100644 packages/repack/src/commands/rspack/start.ts create mode 100644 packages/repack/src/commands/rspack/types.ts create mode 100644 packages/repack/src/commands/types.ts delete mode 100644 packages/repack/src/commands/utils/getWebpackConfigPath.ts rename packages/repack/src/{ => commands}/webpack/Compiler.ts (59%) create mode 100644 packages/repack/src/commands/webpack/CompilerWorker.ts create mode 100644 packages/repack/src/commands/webpack/bundle.ts create mode 100644 packages/repack/src/commands/webpack/index.ts rename packages/repack/src/commands/{ => webpack}/start.ts (64%) create mode 100644 packages/repack/src/commands/webpack/types.ts rename packages/repack/src/{webpack => }/loaders/assetsLoader/__tests__/__fixtures__/logo.png (100%) rename packages/repack/src/{webpack => }/loaders/assetsLoader/__tests__/__fixtures__/star@1x.png (100%) rename packages/repack/src/{webpack => }/loaders/assetsLoader/__tests__/__fixtures__/star@2x.png (100%) rename packages/repack/src/{webpack => }/loaders/assetsLoader/__tests__/__fixtures__/star@3x.png (100%) create mode 100644 packages/repack/src/loaders/assetsLoader/__tests__/__snapshots__/assetsLoader.test.ts.snap rename packages/repack/src/{webpack => }/loaders/assetsLoader/__tests__/assetsLoader.test.ts (58%) rename packages/repack/src/{webpack => }/loaders/assetsLoader/assetsLoader.ts (67%) rename packages/repack/src/{webpack => }/loaders/assetsLoader/convertToRemoteAssets.ts (86%) rename packages/repack/src/{webpack => }/loaders/assetsLoader/extractAssets.ts (65%) create mode 100644 packages/repack/src/loaders/assetsLoader/index.ts rename packages/repack/src/{webpack => }/loaders/assetsLoader/inlineAssets.ts (64%) rename packages/repack/src/{webpack => }/loaders/assetsLoader/options.ts (54%) create mode 100644 packages/repack/src/loaders/assetsLoader/types.ts create mode 100644 packages/repack/src/loaders/assetsLoader/utils.ts create mode 100644 packages/repack/src/loaders/flowLoader/flowLoader.ts create mode 100644 packages/repack/src/loaders/flowLoader/index.ts create mode 100644 packages/repack/src/loaders/flowLoader/options.ts create mode 100644 packages/repack/src/loaders/reactRefreshCompatLoader/index.ts create mode 100644 packages/repack/src/loaders/reactRefreshCompatLoader/reactRefreshCompatLoader.ts create mode 100644 packages/repack/src/modules/EmptyModule.ts create mode 100644 packages/repack/src/modules/InitializeScriptManager.ts rename packages/repack/src/{webpack => }/plugins/ChunksToHermesBytecodePlugin/ChunksToHermesBytecodePlugin.ts (83%) rename packages/repack/src/{webpack => }/plugins/ChunksToHermesBytecodePlugin/index.ts (100%) rename packages/repack/src/{webpack => }/plugins/ChunksToHermesBytecodePlugin/utils/composeSourceMaps.ts (86%) rename packages/repack/src/{webpack => }/plugins/ChunksToHermesBytecodePlugin/utils/getHermesCLIPath.ts (100%) rename packages/repack/src/{webpack => }/plugins/ChunksToHermesBytecodePlugin/utils/index.ts (100%) rename packages/repack/src/{webpack => }/plugins/ChunksToHermesBytecodePlugin/utils/transformBundleToHermesBytecode.ts (93%) rename packages/repack/src/{webpack => }/plugins/CodeSigningPlugin/CodeSigningPlugin.ts (87%) rename packages/repack/src/{webpack => }/plugins/CodeSigningPlugin/config.ts (100%) rename packages/repack/src/{webpack => }/plugins/CodeSigningPlugin/index.ts (100%) create mode 100644 packages/repack/src/plugins/DevelopmentPlugin.ts rename packages/repack/src/{webpack => }/plugins/LoggerPlugin.ts (81%) create mode 100644 packages/repack/src/plugins/ManifestPlugin.ts rename packages/repack/src/{webpack => }/plugins/ModuleFederationPlugin.ts (95%) create mode 100644 packages/repack/src/plugins/NativeEntryPlugin.ts rename packages/repack/src/{webpack => }/plugins/OutputPlugin/OutputPlugin.ts (81%) rename packages/repack/src/{webpack => }/plugins/OutputPlugin/config.ts (100%) rename packages/repack/src/{webpack => }/plugins/OutputPlugin/index.ts (100%) rename packages/repack/src/{webpack => }/plugins/OutputPlugin/types.ts (98%) rename packages/repack/src/{webpack => }/plugins/RepackPlugin.ts (80%) create mode 100644 packages/repack/src/plugins/RepackTargetPlugin/RepackTargetPlugin.ts rename packages/repack/src/{webpack/plugins/RepackTargetPlugin/runtime => plugins/RepackTargetPlugin}/implementation/init.ts (56%) rename packages/repack/src/{webpack/plugins/RepackTargetPlugin/runtime => plugins/RepackTargetPlugin}/implementation/loadScript.ts (90%) rename packages/repack/src/{webpack => }/plugins/RepackTargetPlugin/index.ts (100%) rename packages/repack/src/{webpack => }/plugins/__tests__/ChunksToHermesBytecodePlugin.test.ts (79%) rename packages/repack/src/{webpack => }/plugins/__tests__/CodeSigningPlugin.test.ts (91%) rename packages/repack/src/{webpack => }/plugins/__tests__/ModuleFederationPlugin.test.ts (66%) rename packages/repack/src/{webpack => }/plugins/__tests__/OutputPlugin.test.ts (89%) rename packages/repack/src/{webpack => }/plugins/__tests__/__fixtures__/example.chunk.bundle (100%) rename packages/repack/src/{webpack => }/plugins/__tests__/__fixtures__/example.container.bundle (100%) rename packages/repack/src/{webpack => }/plugins/__tests__/__fixtures__/local.chunk.bundle (100%) rename packages/repack/src/{webpack => }/plugins/__tests__/__fixtures__/main.bundle (100%) rename packages/repack/src/{webpack => }/plugins/__tests__/__fixtures__/testRS256.pem (100%) rename packages/repack/src/{webpack => }/plugins/__tests__/__fixtures__/testRS256.pem.pub (100%) rename packages/repack/src/{webpack => }/plugins/index.ts (77%) rename packages/repack/src/{webpack => }/plugins/utils/AssetsCopyProcessor.ts (83%) rename packages/repack/src/{webpack => }/plugins/utils/AuxiliaryAssetsCopyProcessor.ts (65%) rename packages/repack/src/{webpack => }/plugins/utils/__tests__/AssetsCopyProcessor.test.ts (74%) rename packages/repack/src/{webpack => }/plugins/utils/__tests__/AuxiliaryAssetsCopyProcessor.test.ts (55%) create mode 100644 packages/repack/src/rules/index.ts create mode 100644 packages/repack/src/rules/lazyImports.ts create mode 100644 packages/repack/src/rules/nodeModulesLoadingRules.ts create mode 100644 packages/repack/src/rules/reactNativeCodegenRules.ts create mode 100644 packages/repack/src/rules/reactNativeLoadingRules.ts create mode 100644 packages/repack/src/types/flow-remove-types.d.ts rename packages/repack/src/{modules => types}/runtime-globals.d.ts (89%) rename packages/repack/src/{webpack => }/utils/assetExtensions.ts (84%) rename packages/repack/src/{webpack => utils}/federated.ts (96%) rename packages/repack/src/{webpack => }/utils/getDirname.ts (100%) rename packages/repack/src/{webpack => }/utils/getPublicPath.ts (89%) rename packages/repack/src/{webpack => }/utils/getResolveOptions.ts (83%) rename packages/repack/src/{webpack => }/utils/index.ts (51%) delete mode 100644 packages/repack/src/webpack/loadWebpackConfig.ts delete mode 100644 packages/repack/src/webpack/loaders/assetsLoader/index.ts delete mode 100644 packages/repack/src/webpack/loaders/assetsLoader/types.ts delete mode 100644 packages/repack/src/webpack/loaders/assetsLoader/utils.ts delete mode 100644 packages/repack/src/webpack/plugins/AssetsResolverPlugin/AssetResolver.ts delete mode 100644 packages/repack/src/webpack/plugins/AssetsResolverPlugin/AssetsResolverPlugin.ts delete mode 100644 packages/repack/src/webpack/plugins/AssetsResolverPlugin/index.ts delete mode 100644 packages/repack/src/webpack/plugins/DevelopmentPlugin.ts delete mode 100644 packages/repack/src/webpack/plugins/JavaScriptLooseModePlugin.ts delete mode 100644 packages/repack/src/webpack/plugins/ManifestPlugin.ts delete mode 100644 packages/repack/src/webpack/plugins/RepackTargetPlugin/RepackTargetPlugin.ts delete mode 100644 packages/repack/src/webpack/plugins/RepackTargetPlugin/runtime/RepackInitRuntimeModule.ts delete mode 100644 packages/repack/src/webpack/plugins/RepackTargetPlugin/runtime/RepackLoadScriptRuntimeModule.ts delete mode 100644 packages/repack/src/webpack/plugins/utils/__mocks__/fs-extra.ts delete mode 100644 packages/repack/src/webpack/utils/getInitializationEntries.ts delete mode 100644 packages/repack/src/webpack/webpackWorker.ts create mode 100644 packages/repack/tsconfig.build.json create mode 100644 templates_v5/rspack.config.cjs create mode 100644 templates_v5/rspack.config.mjs create mode 100644 templates_v5/webpack.config.cjs create mode 100644 templates_v5/webpack.config.mjs diff --git a/.changeset/spicy-pumas-whisper.md b/.changeset/spicy-pumas-whisper.md new file mode 100644 index 000000000..bef103298 --- /dev/null +++ b/.changeset/spicy-pumas-whisper.md @@ -0,0 +1,5 @@ +--- +'@callstack/repack': major +--- + +Support for Rspack & Webpack simultaneously diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1ea4ce991..f2cb99d57 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -11,7 +11,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - node_version: ['18', '20', '22'] + node_version: ['18', '20', '22.6'] os: [ubuntu-latest] steps: diff --git a/.gitignore b/.gitignore index 64ddb6bf3..c5e97d3b8 100644 --- a/.gitignore +++ b/.gitignore @@ -372,3 +372,6 @@ packages/**/docs # turborepo .turbo/ + +# clinic +**/.clinic diff --git a/apps/tester-app/.eslintrc.js b/apps/tester-app/.eslintrc.js index 4c5bb2ac4..fbd01b969 100644 --- a/apps/tester-app/.eslintrc.js +++ b/apps/tester-app/.eslintrc.js @@ -5,6 +5,10 @@ module.exports = { project: true, tsconfigRootDir: __dirname, }, + rules: { + // eslint doesnt support package exports + 'import/no-unresolved': [2, { ignore: ['^@callstack/repack/commands'] }], + }, overrides: [ { files: ['*.config.{js,mjs,cjs}'], @@ -13,9 +17,4 @@ module.exports = { }, }, ], - settings: { - jest: { - version: 'latest', - }, - }, }; diff --git a/apps/tester-app/__tests__/bundle.test.ts b/apps/tester-app/__tests__/bundle.test.ts index b935bad90..8df65d0de 100644 --- a/apps/tester-app/__tests__/bundle.test.ts +++ b/apps/tester-app/__tests__/bundle.test.ts @@ -2,13 +2,9 @@ import path from 'node:path'; import fs from 'node:fs'; import { globby } from 'globby'; import { describe, it, afterEach, beforeAll, expect } from 'vitest'; -import commands from '@callstack/repack/commands'; +import webpackCommands from '@callstack/repack/commands/webpack'; +import rspackCommands from '@callstack/repack/commands/rspack'; -const [bundle] = commands; -type Config = Parameters[1]; -type Args = Parameters[2]; - -const TMP_DIR = path.join(__dirname, 'out/bundle-test-output'); const REACT_NATIVE_PATH = require.resolve('react-native', { paths: [path.dirname(__dirname)], }); @@ -22,115 +18,140 @@ const REACT_NATIVE_ANDROID_ASSET_PATH = RELATIVE_REACT_NATIVE_PATH.replaceAll( ).replaceAll(/[-.@+]/g, ''); describe('bundle command', () => { - it("should be also available under 'webpack-bundle' alias", () => { - const bundleCommand = commands.find((command) => command.name === 'bundle'); - const webpackBundleCommand = commands.find( - (command) => command.name === 'webpack-bundle' - ); - - expect(bundleCommand).toBeDefined(); - expect(webpackBundleCommand).toBeDefined(); - expect(bundleCommand!.func).toEqual(webpackBundleCommand!.func); - }); - describe.each([ { - platform: 'ios', - assets: [ - 'index.bundle', - 'index.bundle.map', - 'miniapp.chunk.bundle', - 'miniapp.chunk.bundle.map', - 'remote.chunk.bundle', - 'remote.chunk.bundle.map', - 'src_asyncChunks_Async_local_tsx.chunk.bundle', - 'src_asyncChunks_Async_local_tsx.chunk.bundle.map', - 'react-native-bundle-output/main.jsbundle', - 'react-native-bundle-output/main.jsbundle.map', - 'react-native-bundle-output/src_asyncChunks_Async_local_tsx.chunk.bundle', - 'react-native-bundle-output/src_asyncChunks_Async_local_tsx.chunk.bundle.map', - 'assets/src/miniapp/callstack-dark.png', - `assets/${RELATIVE_REACT_NATIVE_PATH}/Libraries/NewAppScreen/components/logo.png`, - 'assets/src/assetsTest/localAssets/webpack.png', - 'assets/src/assetsTest/localAssets/webpack@2x.png', - 'assets/src/assetsTest/localAssets/webpack@3x.png', - 'remote-assets/assets/src/assetsTest/remoteAssets/webpack.png', - 'remote-assets/assets/src/assetsTest/remoteAssets/webpack@2x.png', - 'remote-assets/assets/src/assetsTest/remoteAssets/webpack@3x.png', - 'react-native-bundle-output/assets/src/assetsTest/localAssets/webpack.png', - 'react-native-bundle-output/assets/src/assetsTest/localAssets/webpack@2x.png', - 'react-native-bundle-output/assets/src/assetsTest/localAssets/webpack@3x.png', - `react-native-bundle-output/assets/${RELATIVE_REACT_NATIVE_PATH}/Libraries/NewAppScreen/components/logo.png`, - ], + bundler: 'webpack', + commands: webpackCommands, + configFile: './webpack.config.mjs', }, { - platform: 'android', - assets: [ - 'index.bundle', - 'index.bundle.map', - 'miniapp.chunk.bundle', - 'miniapp.chunk.bundle.map', - 'remote.chunk.bundle', - 'remote.chunk.bundle.map', - 'src_asyncChunks_Async_local_tsx.chunk.bundle', - 'src_asyncChunks_Async_local_tsx.chunk.bundle.map', - `drawable-mdpi/${REACT_NATIVE_ANDROID_ASSET_PATH}_libraries_newappscreen_components_logo.png`, - 'drawable-mdpi/src_assetstest_localassets_webpack.png', - 'drawable-xxhdpi/src_assetstest_localassets_webpack.png', - 'drawable-xhdpi/src_assetstest_localassets_webpack.png', - 'drawable-mdpi/src_miniapp_callstackdark.png', - 'react-native-bundle-output/index.android.bundle', - 'react-native-bundle-output/index.android.bundle.map', - 'react-native-bundle-output/src_asyncChunks_Async_local_tsx.chunk.bundle', - 'react-native-bundle-output/src_asyncChunks_Async_local_tsx.chunk.bundle.map', - `react-native-bundle-output/drawable-mdpi/${REACT_NATIVE_ANDROID_ASSET_PATH}_libraries_newappscreen_components_logo.png`, - 'react-native-bundle-output/drawable-mdpi/src_assetstest_localassets_webpack.png', - 'react-native-bundle-output/drawable-xxhdpi/src_assetstest_localassets_webpack.png', - 'react-native-bundle-output/drawable-xhdpi/src_assetstest_localassets_webpack.png', - 'remote-assets/assets/src/assetsTest/remoteAssets/webpack.png', - 'remote-assets/assets/src/assetsTest/remoteAssets/webpack@2x.png', - 'remote-assets/assets/src/assetsTest/remoteAssets/webpack@3x.png', - ], + bundler: 'rspack', + commands: rspackCommands, + configFile: './rspack.config.mjs', }, - ])('should successfully produce bundle assets', ({ platform, assets }) => { - beforeAll(async () => { - await fs.promises.rm(TMP_DIR, { - recursive: true, - force: true, - }); - }); + ])('using $bundler', ({ bundler, commands, configFile }) => { + const bundleCommand = commands.find((command) => command.name === 'bundle'); + if (!bundleCommand) throw new Error('bundle command not found'); - afterEach(() => { - delete process.env.TEST_WEBPACK_OUTPUT_PATH; - }); + it("should be also available under 'webpack-bundle' alias", () => { + const webpackBundleCommand = commands.find( + (command) => command.name === 'webpack-bundle' + ); - it( - `for ${platform}`, - async () => { - const OUTPUT_DIR = path.join(TMP_DIR, platform); - const config = { - root: path.join(__dirname, '..'), - reactNativePath: path.join(__dirname, '../node_modules/react-native'), - }; - const args = { - platform, - entryFile: 'index.js', - bundleOutput: path.join( - OUTPUT_DIR, - 'react-native-bundle-output', - platform === 'ios' ? 'main.jsbundle' : `index.${platform}.bundle` - ), - dev: false, - webpackConfig: path.join(__dirname, './webpack.config.mjs'), - }; - process.env.TEST_WEBPACK_OUTPUT_DIR = OUTPUT_DIR; + expect(webpackBundleCommand).toBeDefined(); + const { description, func, options } = webpackBundleCommand!; - await bundle.func([''], config as Config, args as Args); + expect(bundleCommand.description).toEqual(description); + expect(bundleCommand.options).toEqual(options); + expect(bundleCommand.func).toEqual(func); + }); - const files = await globby([`**/*`], { cwd: OUTPUT_DIR, dot: true }); - expect(files.sort()).toEqual(assets.sort()); + describe.each([ + { + platform: 'ios', + assets: [ + 'index.bundle', + 'index.bundle.map', + 'miniapp.chunk.bundle', + 'miniapp.chunk.bundle.map', + 'remote.chunk.bundle', + 'remote.chunk.bundle.map', + 'src_asyncChunks_Async_local_tsx.chunk.bundle', + 'src_asyncChunks_Async_local_tsx.chunk.bundle.map', + 'react-native-bundle-output/main.jsbundle', + 'react-native-bundle-output/main.jsbundle.map', + 'react-native-bundle-output/src_asyncChunks_Async_local_tsx.chunk.bundle', + 'react-native-bundle-output/src_asyncChunks_Async_local_tsx.chunk.bundle.map', + 'assets/src/miniapp/callstack-dark.png', + `assets/${RELATIVE_REACT_NATIVE_PATH}/Libraries/NewAppScreen/components/logo.png`, + 'assets/src/assetsTest/localAssets/webpack.png', + 'assets/src/assetsTest/localAssets/webpack@2x.png', + 'assets/src/assetsTest/localAssets/webpack@3x.png', + 'remote-assets/assets/src/assetsTest/remoteAssets/webpack.png', + 'remote-assets/assets/src/assetsTest/remoteAssets/webpack@2x.png', + 'remote-assets/assets/src/assetsTest/remoteAssets/webpack@3x.png', + 'react-native-bundle-output/assets/src/assetsTest/localAssets/webpack.png', + 'react-native-bundle-output/assets/src/assetsTest/localAssets/webpack@2x.png', + 'react-native-bundle-output/assets/src/assetsTest/localAssets/webpack@3x.png', + `react-native-bundle-output/assets/${RELATIVE_REACT_NATIVE_PATH}/Libraries/NewAppScreen/components/logo.png`, + ], }, - 60 * 1000 - ); + { + platform: 'android', + assets: [ + 'index.bundle', + 'index.bundle.map', + 'miniapp.chunk.bundle', + 'miniapp.chunk.bundle.map', + 'remote.chunk.bundle', + 'remote.chunk.bundle.map', + 'src_asyncChunks_Async_local_tsx.chunk.bundle', + 'src_asyncChunks_Async_local_tsx.chunk.bundle.map', + `drawable-mdpi/${REACT_NATIVE_ANDROID_ASSET_PATH}_libraries_newappscreen_components_logo.png`, + 'drawable-mdpi/src_assetstest_localassets_webpack.png', + 'drawable-xxhdpi/src_assetstest_localassets_webpack.png', + 'drawable-xhdpi/src_assetstest_localassets_webpack.png', + 'drawable-mdpi/src_miniapp_callstackdark.png', + 'react-native-bundle-output/index.android.bundle', + 'react-native-bundle-output/index.android.bundle.map', + 'react-native-bundle-output/src_asyncChunks_Async_local_tsx.chunk.bundle', + 'react-native-bundle-output/src_asyncChunks_Async_local_tsx.chunk.bundle.map', + `react-native-bundle-output/drawable-mdpi/${REACT_NATIVE_ANDROID_ASSET_PATH}_libraries_newappscreen_components_logo.png`, + 'react-native-bundle-output/drawable-mdpi/src_assetstest_localassets_webpack.png', + 'react-native-bundle-output/drawable-xxhdpi/src_assetstest_localassets_webpack.png', + 'react-native-bundle-output/drawable-xhdpi/src_assetstest_localassets_webpack.png', + 'remote-assets/assets/src/assetsTest/remoteAssets/webpack.png', + 'remote-assets/assets/src/assetsTest/remoteAssets/webpack@2x.png', + 'remote-assets/assets/src/assetsTest/remoteAssets/webpack@3x.png', + ], + }, + ])('should successfully produce bundle assets', ({ platform, assets }) => { + const TMP_DIR = path.join(__dirname, `out/bundle/${bundler}/${platform}`); + + beforeAll(async () => { + await fs.promises.rm(TMP_DIR, { + recursive: true, + force: true, + }); + }); + + afterEach(() => { + delete process.env.TEST_WEBPACK_OUTPUT_DIR; + }); + + it( + `for ${platform}`, + async () => { + const config = { + root: path.join(__dirname, '..'), + platforms: { ios: {}, android: {} }, + reactNativePath: path.join( + __dirname, + '../node_modules/react-native' + ), + }; + + const args = { + platform, + entryFile: 'index.js', + bundleOutput: path.join( + TMP_DIR, + 'react-native-bundle-output', + platform === 'ios' ? 'main.jsbundle' : `index.${platform}.bundle` + ), + dev: false, + webpackConfig: path.join(__dirname, configFile), + }; + process.env.TEST_WEBPACK_OUTPUT_DIR = TMP_DIR; + + // @ts-ignore + await bundleCommand.func([''], config, args); + + const files = await globby([`**/*`], { cwd: TMP_DIR, dot: true }); + expect(files.sort()).toEqual(assets.sort()); + }, + 60 * 1000 + ); + }); }); }); diff --git a/apps/tester-app/__tests__/rspack.config.mjs b/apps/tester-app/__tests__/rspack.config.mjs new file mode 100644 index 000000000..6303668e0 --- /dev/null +++ b/apps/tester-app/__tests__/rspack.config.mjs @@ -0,0 +1,12 @@ +import baseConfig from '../rspack.config.mjs'; + +export default (env) => { + const config = baseConfig(env); + return { + ...config, + output: { + ...config.output, + path: process.env.TEST_WEBPACK_OUTPUT_DIR, + }, + }; +}; diff --git a/apps/tester-app/__tests__/start.test.ts b/apps/tester-app/__tests__/start.test.ts index c631f0dc1..c78f3ce5b 100644 --- a/apps/tester-app/__tests__/start.test.ts +++ b/apps/tester-app/__tests__/start.test.ts @@ -2,16 +2,12 @@ import path from 'node:path'; import fs from 'node:fs'; import { describe, it, expect, beforeAll, afterAll } from 'vitest'; import getPort from 'get-port'; -import commands from '@callstack/repack/commands'; - -const [, start] = commands; -type Config = Parameters[1]; -type Args = Parameters[2]; +import webpackCommands from '@callstack/repack/commands/webpack'; +import rspackCommands from '@callstack/repack/commands/rspack'; let port: number; let stopServer: () => Promise; -const TMP_DIR = path.join(__dirname, 'out/server-test-output'); const REACT_NATIVE_PATH = require.resolve('react-native', { paths: [path.dirname(__dirname)], }); @@ -21,108 +17,147 @@ const RELATIVE_REACT_NATIVE_PATH = path.relative( ); describe('start command', () => { - it("should be also available under 'webpack-start' alias", () => { - const startCommand = commands.find((command) => command.name === 'start'); - const webpackStartCommand = commands.find( - (command) => command.name === 'webpack-start' - ); - - expect(startCommand).toBeDefined(); - expect(webpackStartCommand).toBeDefined(); - expect(startCommand!.func).toEqual(webpackStartCommand!.func); - }); - describe.each([ { - platform: 'ios', - requests: [ - 'index.bundle.map?platform=ios', - 'index.bundle.map?platform=ios', - 'ios/miniapp.chunk.bundle', - 'ios/miniapp.chunk.bundle.map', - 'ios/remote.chunk.bundle', - 'ios/remote.chunk.bundle.map', - 'ios/src_asyncChunks_Async_local_tsx.chunk.bundle', - 'ios/src_asyncChunks_Async_local_tsx.chunk.bundle.map', - 'assets/src/miniapp/callstack-dark.png?platform=ios', - `assets/${RELATIVE_REACT_NATIVE_PATH}/Libraries/NewAppScreen/components/logo.png?platform=ios`, - ], + bundler: 'webpack', + commands: webpackCommands, + configFile: './webpack.config.mjs', }, { - platform: 'android', - requests: [ - 'index.bundle.map?platform=android', - 'index.bundle.map?platform=android', - 'android/miniapp.chunk.bundle', - 'android/miniapp.chunk.bundle.map', - 'android/remote.chunk.bundle', - 'android/remote.chunk.bundle.map', - 'android/src_asyncChunks_Async_local_tsx.chunk.bundle', - 'android/src_asyncChunks_Async_local_tsx.chunk.bundle.map', - 'assets/src/miniapp/callstack-dark.png?platform=android', - `assets/${RELATIVE_REACT_NATIVE_PATH}/Libraries/NewAppScreen/components/logo.png?platform=android`, - ], + bundler: 'rspack', + commands: rspackCommands, + configFile: './rspack.config.mjs', }, - ])('should successfully produce bundle assets', ({ platform, requests }) => { - beforeAll(async () => { - await fs.promises.rm(TMP_DIR, { - recursive: true, - force: true, - }); - - port = await getPort(); - const config = { - root: path.join(__dirname, '..'), - reactNativePath: path.join(__dirname, '../node_modules/react-native'), - }; - const args = { - port, - silent: true, - logFile: path.join(TMP_DIR, 'server.log'), - webpackConfig: path.join(__dirname, './webpack.config.mjs'), - }; - - const { stop } = await start.func([], config as Config, args as Args); - stopServer = stop; - }); + ])('using $bundler', ({ bundler, commands, configFile }) => { + const startCommand = commands.find((command) => command.name === 'start'); + if (!startCommand) throw new Error('start command not found'); + + it("should be also available under 'webpack-start' alias", () => { + const webpackStartCommand = commands.find( + (command) => command.name === 'webpack-start' + ); - afterAll(async () => { - await stopServer(); + expect(webpackStartCommand).toBeDefined(); + const { description, func, options } = webpackStartCommand!; + + expect(startCommand.description).toEqual(description); + expect(startCommand.options).toEqual(options); + expect(startCommand.func).toEqual(func); }); - it( - `for ${platform}`, - async () => { - let response = await fetch(`http://localhost:${port}/`); - await expect(response.text()).resolves.toEqual( - 'React Native packager is running' + describe.each([ + { + platform: 'ios', + requests: [ + 'index.bundle?platform=ios', + 'index.bundle.map?platform=ios', + 'ios/miniapp.chunk.bundle', + 'ios/miniapp.chunk.bundle.map', + 'ios/remote.chunk.bundle', + 'ios/remote.chunk.bundle.map', + 'ios/src_asyncChunks_Async_local_tsx.chunk.bundle', + 'ios/src_asyncChunks_Async_local_tsx.chunk.bundle.map', + 'assets/src/miniapp/callstack-dark.png?platform=ios', + `assets/${RELATIVE_REACT_NATIVE_PATH}/Libraries/NewAppScreen/components/logo.png?platform=ios`, + ], + }, + { + platform: 'android', + requests: [ + 'index.bundle?platform=android', + 'index.bundle.map?platform=android', + 'android/miniapp.chunk.bundle', + 'android/miniapp.chunk.bundle.map', + 'android/remote.chunk.bundle', + 'android/remote.chunk.bundle.map', + 'android/src_asyncChunks_Async_local_tsx.chunk.bundle', + 'android/src_asyncChunks_Async_local_tsx.chunk.bundle.map', + 'assets/src/miniapp/callstack-dark.png?platform=android', + `assets/${RELATIVE_REACT_NATIVE_PATH}/Libraries/NewAppScreen/components/logo.png?platform=android`, + ], + }, + ])( + 'should successfully produce bundle assets', + ({ platform, requests }) => { + const TMP_DIR = path.join( + __dirname, + `out/start/${bundler}/${platform}` ); - const [bundleRequest, ...assetsRequests] = requests; + beforeAll(async () => { + await fs.promises.rm(TMP_DIR, { + recursive: true, + force: true, + }); - response = await fetch(`http://localhost:${port}/${bundleRequest}`); - expect((await response.text()).length).toBeGreaterThan(100000); + port = await getPort(); - const responses = await Promise.all( - assetsRequests.map((asset) => - fetch(`http://localhost:${port}/${asset}`) - ) - ); + const config = { + root: path.join(__dirname, '..'), + platforms: { ios: {}, android: {} }, + reactNativePath: path.join( + __dirname, + '../node_modules/react-native' + ), + }; + + const args = { + port, + platform, + silent: true, + logFile: path.join(TMP_DIR, 'server.log'), + webpackConfig: path.join(__dirname, configFile), + }; - responses.forEach((response) => { - if (!response.ok) { - console.log(response); - } - expect(response.ok).toBe(true); + // @ts-ignore + const { stop } = await startCommand.func([], config, args); + stopServer = stop; }); - ( - await Promise.all(responses.map((response) => response.text())) - ).forEach((text) => { - expect(text.length).toBeGreaterThan(0); + afterAll(async () => { + await stopServer(); }); - }, - 60 * 1000 + + it( + `for ${platform}`, + async () => { + let response = await fetch(`http://localhost:${port}/`); + await expect(response.text()).resolves.toEqual( + 'React Native packager is running' + ); + + const [bundleRequest, ...assetsRequests] = requests; + + response = await fetch(`http://localhost:${port}/${bundleRequest}`); + + const responseText = await response.text(); + if (responseText.length < 100000) { + console.log(response, responseText); + } + expect(responseText.length).toBeGreaterThan(100000); + + const responses = await Promise.all( + assetsRequests.map((asset) => + fetch(`http://localhost:${port}/${asset}`) + ) + ); + + responses.forEach((response) => { + if (!response.ok) { + console.log(response); + } + expect(response.ok).toBe(true); + }); + + ( + await Promise.all(responses.map((response) => response.text())) + ).forEach((text) => { + expect(text.length).toBeGreaterThan(0); + }); + }, + 60 * 1000 + ); + } ); }); }); diff --git a/apps/tester-app/index.js b/apps/tester-app/index.js index 943d98a2f..c8ace6d94 100644 --- a/apps/tester-app/index.js +++ b/apps/tester-app/index.js @@ -25,28 +25,28 @@ ScriptManager.shared.addResolver((scriptId, _caller) => { }; }); -ScriptManager.shared.on('resolving', (...args) => { - console.log('DEBUG/resolving', ...args); -}); +// ScriptManager.shared.on('resolving', (...args) => { +// console.log('DEBUG/resolving', ...args); +// }); -ScriptManager.shared.on('resolved', (...args) => { - console.log('DEBUG/resolved', ...args); -}); +// ScriptManager.shared.on('resolved', (...args) => { +// console.log('DEBUG/resolved', ...args); +// }); -ScriptManager.shared.on('prefetching', (...args) => { - console.log('DEBUG/prefetching', ...args); -}); +// ScriptManager.shared.on('prefetching', (...args) => { +// console.log('DEBUG/prefetching', ...args); +// }); -ScriptManager.shared.on('loading', (...args) => { - console.log('DEBUG/loading', ...args); -}); +// ScriptManager.shared.on('loading', (...args) => { +// console.log('DEBUG/loading', ...args); +// }); -ScriptManager.shared.on('loaded', (...args) => { - console.log('DEBUG/loaded', ...args); -}); +// ScriptManager.shared.on('loaded', (...args) => { +// console.log('DEBUG/loaded', ...args); +// }); -ScriptManager.shared.on('error', (...args) => { - console.log('DEBUG/error', ...args); -}); +// ScriptManager.shared.on('error', (...args) => { +// console.log('DEBUG/error', ...args); +// }); AppRegistry.registerComponent(appName, () => App); diff --git a/apps/tester-app/ios/Podfile.lock b/apps/tester-app/ios/Podfile.lock index aeea6590b..a76675604 100644 --- a/apps/tester-app/ios/Podfile.lock +++ b/apps/tester-app/ios/Podfile.lock @@ -1,6 +1,6 @@ PODS: - boost (1.83.0) - - callstack-repack (4.1.1): + - callstack-repack (4.3.1): - DoubleConversion - glog - hermes-engine @@ -1188,7 +1188,7 @@ PODS: - React-logger (= 0.74.3) - React-perflogger (= 0.74.3) - React-utils (= 0.74.3) - - ReactNativeHost (0.4.9): + - ReactNativeHost (0.4.10): - DoubleConversion - glog - hermes-engine @@ -1215,7 +1215,7 @@ PODS: - React-Core - React-jsi - ReactTestApp-Resources (1.0.0-dev) - - RNCAsyncStorage (1.23.1): + - RNCAsyncStorage (1.24.0): - DoubleConversion - glog - hermes-engine @@ -1342,7 +1342,7 @@ DEPENDENCIES: - React-runtimescheduler (from `../node_modules/react-native/ReactCommon/react/renderer/runtimescheduler`) - React-utils (from `../node_modules/react-native/ReactCommon/react/utils`) - ReactCommon/turbomodule/core (from `../node_modules/react-native/ReactCommon`) - - "ReactNativeHost (from `../../../node_modules/.pnpm/react-native-test-app@3.8.9_react-native@0.74.3_@babel+core@7.24.0_@babel+preset-env@7.24.0_@_mf2ppxx6gl3lpvmnun7q4fqj7u/node_modules/@rnx-kit/react-native-host`)" + - "ReactNativeHost (from `../../../node_modules/.pnpm/react-native-test-app@3.8.9_react-native@0.74.3_@babel+core@7.24.9_@babel+preset-env@7.24.8_@_5gnl7gn4qdcfat774lcowqlmbu/node_modules/@rnx-kit/react-native-host`)" - ReactTestApp-DevSupport (from `../node_modules/react-native-test-app`) - ReactTestApp-Resources (from `..`) - "RNCAsyncStorage (from `../node_modules/@react-native-async-storage/async-storage`)" @@ -1466,7 +1466,7 @@ EXTERNAL SOURCES: ReactCommon: :path: "../node_modules/react-native/ReactCommon" ReactNativeHost: - :path: "../../../node_modules/.pnpm/react-native-test-app@3.8.9_react-native@0.74.3_@babel+core@7.24.0_@babel+preset-env@7.24.0_@_mf2ppxx6gl3lpvmnun7q4fqj7u/node_modules/@rnx-kit/react-native-host" + :path: "../../../node_modules/.pnpm/react-native-test-app@3.8.9_react-native@0.74.3_@babel+core@7.24.9_@babel+preset-env@7.24.8_@_5gnl7gn4qdcfat774lcowqlmbu/node_modules/@rnx-kit/react-native-host" ReactTestApp-DevSupport: :path: "../node_modules/react-native-test-app" ReactTestApp-Resources: @@ -1480,7 +1480,7 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: boost: d3f49c53809116a5d38da093a8aa78bf551aed09 - callstack-repack: 13c4bb5459eb864c64f3f00052877481da47d777 + callstack-repack: ddc8a6429d0ebfda9806a76eb82fc3f4ffa50a28 DoubleConversion: 76ab83afb40bddeeee456813d9c04f67f78771b5 FBLazyVector: 7e977dd099937dc5458851233141583abba49ff2 fmt: 4c2741a687cc09f0634a2e2c72a838b99f1ff120 @@ -1534,10 +1534,10 @@ SPEC CHECKSUMS: React-runtimescheduler: 0c80752bceb80924cb8a4babc2a8e3ed70d41e87 React-utils: a06061b3887c702235d2dac92dacbd93e1ea079e ReactCommon: f00e436b3925a7ae44dfa294b43ef360fbd8ccc4 - ReactNativeHost: 76f58edded6537ed8a03cfce6327e31d667bd802 + ReactNativeHost: fd9d65f6ee7947f468537d35b3fefe8b2bf546da ReactTestApp-DevSupport: 874fb27318647f43f4243c5f3a47a246db9cec12 ReactTestApp-Resources: 0fa9146ee546fb7447198c96d852a56b8e3bb94a - RNCAsyncStorage: f2add1326156dc313df59d855c11f459059e4ffd + RNCAsyncStorage: 7907926157e7c8d10ddfee0f6b3e9a105e060acf RNSVG: 7b44aa4df2587946e28496adf2e10a16f08c1250 SocketRocket: abac6f5de4d4d62d24e11868d7a2f427e0ef940d SwiftyRSA: 8c6dd1ea7db1b8dc4fb517a202f88bb1354bc2c6 diff --git a/apps/tester-app/package.json b/apps/tester-app/package.json index 6d307455b..5047dae72 100644 --- a/apps/tester-app/package.json +++ b/apps/tester-app/package.json @@ -10,7 +10,6 @@ "start": "react-native webpack-start", "bundle:android": "react-native webpack-bundle --platform android --entry-file index.js --dev=false --bundle-output build/output/android/index.android.bundle --assets-dest build/output/android/res", "bundle:ios": "react-native webpack-bundle --platform ios --entry-file index.js --dev=false --bundle-output build/output/ios/main.jsbundle --assets-dest build/output/ios", - "webpack": "webpack -c webpack.config.js --env platform=android --env mode=production", "test": "vitest run", "serve-remote-assets:android": "adb reverse tcp:9999 tcp:9999 && pnpm http-server -p 9999 build/output/android/remote", "serve-remote-assets:ios": "pnpm http-server -p 9999 build/output/ios/remote" @@ -27,7 +26,11 @@ "@react-native/babel-preset": "0.74.83", "@react-native/eslint-config": "0.74.83", "@react-native/typescript-config": "0.74.83", + "@rsdoctor/rspack-plugin": "^0.4.1", + "@rspack/core": "1.0.1", + "@rspack/plugin-react-refresh": "1.0.0", "@svgr/webpack": "^8.1.0", + "@swc/helpers": "^0.5.11", "@types/jest": "^29.5.12", "@types/react": "^18.2.51", "babel-loader": "^9.1.3", @@ -37,10 +40,9 @@ "globby": "^13.1.2", "http-server": "^14.1.1", "react-native-test-app": "^3.8.9", - "react-native-event-source": "^1.1.0", - "terser-webpack-plugin": "^5.3.3", + "terser-webpack-plugin": "^5.3.10", "typescript": "^5.5.3", - "vitest": "^0.15.1", - "webpack": "^5.91.0" + "vitest": "^2.0.5", + "webpack": "^5.94.0" } } diff --git a/apps/tester-app/react-native.config.js b/apps/tester-app/react-native.config.js index 2f7653e16..ceb40d38d 100644 --- a/apps/tester-app/react-native.config.js +++ b/apps/tester-app/react-native.config.js @@ -1,5 +1,7 @@ const { configureProjects } = require('react-native-test-app'); +const useWebpack = Boolean(process.env.USE_WEBPACK); + module.exports = { project: configureProjects({ android: { @@ -9,5 +11,7 @@ module.exports = { sourceDir: 'ios', }, }), - commands: require('@callstack/repack/commands'), + commands: useWebpack + ? require('@callstack/repack/commands/webpack') + : require('@callstack/repack/commands/rspack'), }; diff --git a/apps/tester-app/rspack.config.mjs b/apps/tester-app/rspack.config.mjs new file mode 100644 index 000000000..46258235a --- /dev/null +++ b/apps/tester-app/rspack.config.mjs @@ -0,0 +1,198 @@ +// @ts-check +import { createRequire } from 'node:module'; +import path from 'node:path'; +import * as Repack from '@callstack/repack'; +import { RsdoctorRspackPlugin } from '@rsdoctor/rspack-plugin'; + +const dirname = Repack.getDirname(import.meta.url); +const { resolve } = createRequire(import.meta.url); + +/** @type {(env: import('@callstack/repack').EnvOptions) => import('@rspack/core').Configuration} */ +export default (env) => { + const { + mode = 'development', + context = dirname, + entry = './index.js', + platform = process.env.PLATFORM, + minimize = mode === 'production', + devServer = undefined, + bundleFilename = undefined, + sourceMapFilename = undefined, + assetsPath = undefined, + reactNativePath = resolve('react-native'), + } = env; + if (!platform) { + throw new Error('Missing platform'); + } + + return { + mode, + devtool: false, + context, + entry, + resolve: { + ...Repack.getResolveOptions(platform), + alias: { + 'react-native': reactNativePath, + }, + }, + output: { + clean: true, + hashFunction: 'xxhash64', + path: path.join(context, 'build/generated', platform), + filename: 'index.bundle', + chunkFilename: '[name].chunk.bundle', + publicPath: Repack.getPublicPath({ platform, devServer }), + }, + optimization: { + minimize, + chunkIds: 'named', + }, + module: { + rules: [ + Repack.REACT_NATIVE_LOADING_RULES, + Repack.NODE_MODULES_LOADING_RULES, + { + test: /\.[jt]sx?$/, + exclude: [/node_modules/], + type: 'javascript/auto', + use: { + loader: 'builtin:swc-loader', + /** @type {import('@rspack/core').SwcLoaderOptions} */ + options: { + env: { + targets: { + 'react-native': '0.74', + }, + }, + jsc: { + externalHelpers: true, + transform: { + react: { + runtime: 'automatic', + }, + }, + }, + }, + }, + }, + // codegen needs to run before other loaders since it needs to access types + Repack.REACT_NATIVE_CODEGEN_RULES, + { + test: Repack.getAssetExtensionsRegExp( + Repack.ASSET_EXTENSIONS.filter((ext) => ext !== 'svg') + ), + exclude: [ + path.join(context, 'src/assetsTest/localAssets'), + path.join(context, 'src/assetsTest/inlineAssets'), + path.join(context, 'src/assetsTest/remoteAssets'), + ], + use: { + loader: '@callstack/repack/assets-loader', + options: { + platform, + devServerEnabled: Boolean(devServer), + }, + }, + }, + { + test: /\.svg$/, + use: [ + { + loader: '@svgr/webpack', + options: { + native: true, + dimensions: false, + }, + }, + ], + }, + { + test: Repack.getAssetExtensionsRegExp( + Repack.ASSET_EXTENSIONS.filter((ext) => ext !== 'svg') + ), + include: [path.join(context, 'src/assetsTest/localAssets')], + use: { + loader: '@callstack/repack/assets-loader', + options: { + platform, + devServerEnabled: Boolean(devServer), + }, + }, + }, + { + test: Repack.getAssetExtensionsRegExp( + Repack.ASSET_EXTENSIONS.filter((ext) => ext !== 'svg') + ), + include: [path.join(context, 'src/assetsTest/inlineAssets')], + use: { + loader: '@callstack/repack/assets-loader', + options: { + platform, + devServerEnabled: Boolean(devServer), + inline: true, + }, + }, + }, + { + test: Repack.getAssetExtensionsRegExp( + Repack.ASSET_EXTENSIONS.filter((ext) => ext !== 'svg') + ), + include: [path.join(context, 'src/assetsTest/remoteAssets')], + use: { + loader: '@callstack/repack/assets-loader', + options: { + platform, + devServerEnabled: Boolean(devServer), + remote: { + enabled: true, + publicPath: 'http://localhost:9999/remote-assets', + }, + }, + }, + }, + ], + }, + + plugins: [ + /** + * Configure other required and additional plugins to make the bundle + * work in React Native and provide good development experience with + * sensible defaults. + * + * `Repack.RepackPlugin` provides some degree of customization, but if you + * need more control, you can replace `Repack.RepackPlugin` with plugins + * from `Repack.plugins`. + */ + new Repack.RepackPlugin({ + context, + mode, + platform, + devServer, + output: { + bundleFilename, + sourceMapFilename, + assetsPath, + auxiliaryAssetsPath: path.join('build/output', platform, 'remote'), + }, + extraChunks: [ + { + include: /.+local.+/, + type: 'local', + }, + { + exclude: /.+local.+/, + type: 'remote', + outputPath: path.join('build/output', platform, 'remote'), + }, + ], + }), + // new Repack.plugins.ChunksToHermesBytecodePlugin({ + // enabled: mode === 'production' && !devServer, + // test: /\.(js)?bundle$/, + // exclude: /index.bundle$/, + // }), + process.env.RSDOCTOR && new RsdoctorRspackPlugin(), + ].filter(Boolean), + }; +}; diff --git a/apps/tester-app/src/App.tsx b/apps/tester-app/src/App.tsx index 6380d7def..a63c68393 100644 --- a/apps/tester-app/src/App.tsx +++ b/apps/tester-app/src/App.tsx @@ -14,11 +14,6 @@ import DeprecatedRemoteDebuggerContainer from './deprecatedRemoteDebugger/Deprec Appearance.setColorScheme('light'); const App = () => { - console.log( - 'Bridgeless: ', - ('RN$Bridgeless' in global && RN$Bridgeless === true) || false - ); - return ( diff --git a/apps/tester-app/src/deprecatedRemoteDebugger/DeprecatedRemoteDebuggerContainer.tsx b/apps/tester-app/src/deprecatedRemoteDebugger/DeprecatedRemoteDebuggerContainer.tsx index fbef0b514..1d55e5285 100644 --- a/apps/tester-app/src/deprecatedRemoteDebugger/DeprecatedRemoteDebuggerContainer.tsx +++ b/apps/tester-app/src/deprecatedRemoteDebugger/DeprecatedRemoteDebuggerContainer.tsx @@ -1,15 +1,19 @@ import React from 'react'; import { Button } from '../ui/Button'; -let enableRemoteDebugger = () => {}; -let disableRemoteDebugger = () => {}; +let enableDebugger: () => void; +let disableDebugger: () => void; if (__DEV__) { const { default: NativeDevSettings, } = require('react-native/Libraries/NativeModules/specs/NativeDevSettings'); - enableRemoteDebugger = () => NativeDevSettings.setIsDebuggingRemotely(true); - disableRemoteDebugger = () => NativeDevSettings.setIsDebuggingRemotely(false); + enableDebugger = () => { + NativeDevSettings.setIsDebuggingRemotely(true); + }; + disableDebugger = () => { + NativeDevSettings.setIsDebuggingRemotely(false); + }; } export default function DeprecatedRemoteDebuggerContainer() { @@ -18,12 +22,12 @@ export default function DeprecatedRemoteDebuggerContainer() {