Skip to content

Commit

Permalink
[BREAKING] feat: support Rspack & Webpack simultaneously (#720)
Browse files Browse the repository at this point in the history
* 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 57bb2bb.

* 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
  • Loading branch information
jbroma authored Sep 12, 2024
1 parent 146baf2 commit a7b557e
Show file tree
Hide file tree
Showing 197 changed files with 10,394 additions and 6,940 deletions.
5 changes: 5 additions & 0 deletions .changeset/spicy-pumas-whisper.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@callstack/repack': major
---

Support for Rspack & Webpack simultaneously
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -372,3 +372,6 @@ packages/**/docs

# turborepo
.turbo/

# clinic
**/.clinic
9 changes: 4 additions & 5 deletions apps/tester-app/.eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -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}'],
Expand All @@ -13,9 +17,4 @@ module.exports = {
},
},
],
settings: {
jest: {
version: 'latest',
},
},
};
233 changes: 127 additions & 106 deletions apps/tester-app/__tests__/bundle.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<typeof bundle.func>[1];
type Args = Parameters<typeof bundle.func>[2];

const TMP_DIR = path.join(__dirname, 'out/bundle-test-output');
const REACT_NATIVE_PATH = require.resolve('react-native', {
paths: [path.dirname(__dirname)],
});
Expand All @@ -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/[email protected]',
'assets/src/assetsTest/localAssets/[email protected]',
'remote-assets/assets/src/assetsTest/remoteAssets/webpack.png',
'remote-assets/assets/src/assetsTest/remoteAssets/[email protected]',
'remote-assets/assets/src/assetsTest/remoteAssets/[email protected]',
'react-native-bundle-output/assets/src/assetsTest/localAssets/webpack.png',
'react-native-bundle-output/assets/src/assetsTest/localAssets/[email protected]',
'react-native-bundle-output/assets/src/assetsTest/localAssets/[email protected]',
`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/[email protected]',
'remote-assets/assets/src/assetsTest/remoteAssets/[email protected]',
],
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/[email protected]',
'assets/src/assetsTest/localAssets/[email protected]',
'remote-assets/assets/src/assetsTest/remoteAssets/webpack.png',
'remote-assets/assets/src/assetsTest/remoteAssets/[email protected]',
'remote-assets/assets/src/assetsTest/remoteAssets/[email protected]',
'react-native-bundle-output/assets/src/assetsTest/localAssets/webpack.png',
'react-native-bundle-output/assets/src/assetsTest/localAssets/[email protected]',
'react-native-bundle-output/assets/src/assetsTest/localAssets/[email protected]',
`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/[email protected]',
'remote-assets/assets/src/assetsTest/remoteAssets/[email protected]',
],
},
])('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
);
});
});
});
12 changes: 12 additions & 0 deletions apps/tester-app/__tests__/rspack.config.mjs
Original file line number Diff line number Diff line change
@@ -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,
},
};
};
Loading

0 comments on commit a7b557e

Please sign in to comment.