Replace Webpack with Rsbuild

This commit is contained in:
Samuel Newman
2026-01-10 14:39:27 +02:00
parent c540dae4e7
commit f8bdac60d8
6 changed files with 832 additions and 1693 deletions
+5
View File
@@ -71,6 +71,9 @@ artifacts
# Expo
.expo/
dist/
# Rsbuild
.rsbuild/
*.jks
*.p8
*.p12
@@ -121,7 +124,9 @@ src/locale/locales/**/*.js
bskyogcard/src/assets/fonts/noto-*
# bskyweb build output
bskyweb/static/js/
bskyweb/static/media/*.webp
bskyweb/static/media/*.jpg
bskyweb/static/media/*.png
bskyweb/static/media/*.svg
bskyweb/static/media/*.woff2
+8 -7
View File
@@ -26,10 +26,10 @@
"android:prod": "expo run:android --variant release",
"android:profile": "BSKY_PROFILE=1 expo run:android --variant release",
"ios": "expo run:ios",
"web": "expo start --web",
"web": "rsbuild dev",
"use-build-number": "./scripts/useBuildNumberEnv.sh",
"use-build-number-with-bump": "./scripts/useBuildNumberEnvWithBump.sh",
"build-web": "expo export:web && node ./scripts/post-web-build.js",
"build-web": "rsbuild build && node ./scripts/post-web-build.js",
"build-all": "yarn intl:build && yarn use-build-number-with-bump eas build --platform all",
"build-ios": "yarn use-build-number-with-bump eas build -p ios",
"build-android": "yarn use-build-number-with-bump eas build -p android",
@@ -68,8 +68,8 @@
"export": "npx expo export --dump-sourcemap && yarn upload-native-sourcemaps",
"upload-native-sourcemaps": "npx sentry-expo-upload-sourcemaps dist",
"make-deploy-bundle": "bash scripts/bundleUpdate.sh",
"generate-webpack-stats-file": "EXPO_PUBLIC_GENERATE_STATS=1 yarn build-web",
"open-analyzer": "EXPO_PUBLIC_OPEN_ANALYZER=1 yarn build-web",
"analyze": "BUNDLE_ANALYZE=true yarn build-web",
"analyze:open": "BUNDLE_ANALYZE=server yarn build-web",
"icons:optimize": "svgo -f ./assets/icons"
},
"dependencies": {
@@ -82,7 +82,6 @@
"@discord/bottom-sheet": "bluesky-social/react-native-bottom-sheet",
"@emoji-mart/react": "^1.1.1",
"@expo/html-elements": "^0.12.5",
"@expo/webpack-config": "^19.0.1",
"@floating-ui/dom": "^1.6.3",
"@floating-ui/react-dom": "^2.0.8",
"@formatjs/intl-displaynames": "^6.8.13",
@@ -233,10 +232,12 @@
"@expo/config-plugins": "~54.0.1",
"@lingui/cli": "^4.14.1",
"@lingui/macro": "^4.14.1",
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.15",
"@react-native/babel-preset": "0.81.5",
"@react-native/eslint-config": "^0.81.5",
"@react-native/typescript-config": "^0.81.5",
"@rsbuild/core": "^1.7.2",
"@rsbuild/plugin-babel": "^1.0.6",
"@rsbuild/plugin-react": "^1.4.2",
"@sentry/webpack-plugin": "^3.2.2",
"@testing-library/jest-native": "^5.4.3",
"@testing-library/react-native": "^13.2.0",
@@ -278,7 +279,7 @@
"ts-node": "^10.9.1",
"ts-plugin-sort-import-suggestions": "^1.0.4",
"typescript": "^5.9.2",
"webpack-bundle-analyzer": "^4.10.1"
"webpack": "^5.104.1"
},
"resolutions": {
"@react-native/babel-preset": "0.81.5",
+18 -2
View File
@@ -1,11 +1,11 @@
diff --git a/node_modules/react-native-uitextview/ios/RNUITextViewShadow.swift b/node_modules/react-native-uitextview/ios/RNUITextViewShadow.swift
index c34ba71..13d576a 100644
index c34ba71..b1c4d35 100644
--- a/node_modules/react-native-uitextview/ios/RNUITextViewShadow.swift
+++ b/node_modules/react-native-uitextview/ios/RNUITextViewShadow.swift
@@ -159,13 +159,23 @@ class RNUITextViewShadow: RCTShadowView {
let maxSize = CGSize(width: CGFloat(maxWidth), height: CGFloat(MAXFLOAT))
let textSize = self.attributedText.boundingRect(with: maxSize, options: .usesLineFragmentOrigin, context: nil)
- var totalLines = self.lineHeight == 0.0 ? 0 : Int(ceil(textSize.height / self.lineHeight))
-
- if self.numberOfLines != 0, totalLines > self.numberOfLines {
@@ -31,3 +31,19 @@ index c34ba71..13d576a 100644
return YGSize(width: Float(self.frameSize.width), height: Float(self.frameSize.height))
}
diff --git a/node_modules/react-native-uitextview/src/Text.web.tsx b/node_modules/react-native-uitextview/src/Text.web.tsx
new file mode 100644
index 0000000..0455396
--- /dev/null
+++ b/node_modules/react-native-uitextview/src/Text.web.tsx
@@ -0,0 +1,10 @@
+import {Text, TextProps} from 'react-native'
+
+// Web fallback - just use the standard Text component
+export function UITextView(props: TextProps & {uiTextView?: boolean}) {
+ return <Text {...props} />
+}
+
+// These are not used on web, but export them for compatibility
+export const RNUITextView = Text
+export const RNUITextViewChild = Text
+57 -14
View File
@@ -9,10 +9,38 @@ const templateFile = path.join(
'scripts.html',
)
const {entrypoints} = require(
path.join(projectRoot, 'web-build/asset-manifest.json'),
)
// Support both webpack (asset-manifest.json) and rsbuild (manifest.json) formats
function getEntrypoints() {
const webpackManifestPath = path.join(
projectRoot,
'web-build/asset-manifest.json',
)
const rsbuildManifestPath = path.join(projectRoot, 'web-build/manifest.json')
// Try webpack format first
if (fs.existsSync(webpackManifestPath)) {
const manifest = require(webpackManifestPath)
if (manifest.entrypoints) {
console.log('Using webpack manifest format')
return manifest.entrypoints
}
}
// Try rsbuild format
if (fs.existsSync(rsbuildManifestPath)) {
const manifest = require(rsbuildManifestPath)
if (manifest.entries?.index?.initial) {
console.log('Using rsbuild manifest format')
const initial = manifest.entries.index.initial
// Combine JS and CSS entrypoints, CSS first for proper loading order
return [...(initial.css || []), ...(initial.js || [])]
}
}
throw new Error('No valid manifest found in web-build/')
}
const entrypoints = getEntrypoints()
console.log(`Found ${entrypoints.length} entrypoints`)
console.log(`Writing ${templateFile}`)
@@ -33,16 +61,31 @@ const outputFile = entrypoints
.join('\n')
fs.writeFileSync(templateFile, outputFile)
function copyFiles(sourceDir, targetDir) {
const files = fs.readdirSync(path.join(projectRoot, sourceDir))
files.forEach(file => {
const sourcePath = path.join(projectRoot, sourceDir, file)
const targetPath = path.join(projectRoot, targetDir, file)
fs.copyFileSync(sourcePath, targetPath)
console.log(`Copied ${sourcePath} to ${targetPath}`)
})
function copyFilesRecursive(sourceDir, targetDir) {
const sourcePath = path.join(projectRoot, sourceDir)
const targetPath = path.join(projectRoot, targetDir)
// Ensure target directory exists
fs.mkdirSync(targetPath, {recursive: true})
const entries = fs.readdirSync(sourcePath, {withFileTypes: true})
for (const entry of entries) {
const srcPath = path.join(sourcePath, entry.name)
const destPath = path.join(targetPath, entry.name)
if (entry.isDirectory()) {
// Recursively copy subdirectory
copyFilesRecursive(
path.join(sourceDir, entry.name),
path.join(targetDir, entry.name),
)
} else {
fs.copyFileSync(srcPath, destPath)
console.log(`Copied ${srcPath} to ${destPath}`)
}
}
}
copyFiles('web-build/static/js', 'bskyweb/static/js')
copyFiles('web-build/static/css', 'bskyweb/static/css')
copyFiles('web-build/static/media', 'bskyweb/static/media')
copyFilesRecursive('web-build/static/js', 'bskyweb/static/js')
copyFilesRecursive('web-build/static/css', 'bskyweb/static/css')
copyFilesRecursive('web-build/static/media', 'bskyweb/static/media')
-69
View File
@@ -1,69 +0,0 @@
const createExpoWebpackConfigAsync = require('@expo/webpack-config')
const {withAlias} = require('@expo/webpack-config/addons')
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin')
const {BundleAnalyzerPlugin} = require('webpack-bundle-analyzer')
const {sentryWebpackPlugin} = require('@sentry/webpack-plugin')
const {version} = require('./package.json')
const GENERATE_STATS = process.env.EXPO_PUBLIC_GENERATE_STATS === '1'
const OPEN_ANALYZER = process.env.EXPO_PUBLIC_OPEN_ANALYZER === '1'
const reactNativeWebWebviewConfiguration = {
test: /postMock.html$/,
use: {
loader: 'file-loader',
options: {
name: '[name].[ext]',
},
},
}
module.exports = async function (env, argv) {
let config = await createExpoWebpackConfigAsync(env, argv)
config = withAlias(config, {
'react-native$': 'react-native-web',
'react-native-webview': 'react-native-web-webview',
// Force ESM version
'unicode-segmenter/grapheme': require
.resolve('unicode-segmenter/grapheme')
.replace(/\.cjs$/, '.js'),
'react-native-gesture-handler': false, // RNGH should not be used on web, so let's cause a build error if it sneaks in
})
config.module.rules = [
...(config.module.rules || []),
reactNativeWebWebviewConfiguration,
]
if (env.mode === 'development') {
config.plugins.push(new ReactRefreshWebpackPlugin())
} else {
// Support static CDN for chunks
config.output.publicPath = 'auto'
}
if (GENERATE_STATS || OPEN_ANALYZER) {
config.plugins.push(
new BundleAnalyzerPlugin({
openAnalyzer: OPEN_ANALYZER,
generateStatsFile: true,
statsFilename: '../stats.json',
analyzerMode: OPEN_ANALYZER ? 'server' : 'json',
defaultSizes: 'parsed',
}),
)
}
if (process.env.SENTRY_AUTH_TOKEN) {
config.plugins.push(
sentryWebpackPlugin({
org: 'blueskyweb',
project: 'app',
authToken: process.env.SENTRY_AUTH_TOKEN,
release: {
// fallback needed for Render.com deployments
name: process.env.SENTRY_RELEASE || version,
dist: process.env.SENTRY_DIST,
},
}),
)
}
return config
}
+744 -1601
View File
File diff suppressed because it is too large Load Diff