diff --git a/.gitignore b/.gitignore
index 26816642a7..1939ff555a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -134,3 +134,6 @@ bskyweb/static/media/*.svg
# superpowers plugin plans/specs — local-only workspace
docs/superpowers/
+
+# worktrees
+.claude/worktrees/
diff --git a/bskyweb/.gitignore b/bskyweb/.gitignore
index a63a381f94..81a5fc6980 100644
--- a/bskyweb/.gitignore
+++ b/bskyweb/.gitignore
@@ -6,17 +6,12 @@ test-coverage.out
/embedr
# Don't accidentally commit JS-generated code
-static/js/*.js
-static/js/*.map
-static/js/*.js.LICENSE.txt
-static/js/empty.txt
-static/css/*.css
-static/css/*.map
-static/css/*.css.LICENSE.txt
-static/css/empty.txt
+static/_expo/
+static/assets/
static/media/*.png
static/media/empty.txt
templates/scripts.html
+templates/fonts.html
templates/*-embed.html
static/embed/*.html
static/embed/assets/*.js
diff --git a/bskyweb/cmd/bskyweb/server.go b/bskyweb/cmd/bskyweb/server.go
index fcaef6d740..1236e49e88 100644
--- a/bskyweb/cmd/bskyweb/server.go
+++ b/bskyweb/cmd/bskyweb/server.go
@@ -260,8 +260,8 @@ func serve(cctx *cli.Context) error {
path := c.Request().URL.Path
maxAge := 1 * (60 * 60) // default is 1 hour
- // all assets in /static/js, /static/css, /static/media are content-hashed and can be cached for a long time
- if strings.HasPrefix(path, "/static/js/") || strings.HasPrefix(path, "/static/css/") || strings.HasPrefix(path, "/static/media/") {
+ // all assets in /static/_expo, /static/assets, /static/media are content-hashed and can be cached for a long time
+ if strings.HasPrefix(path, "/static/_expo/") || strings.HasPrefix(path, "/static/assets/") || strings.HasPrefix(path, "/static/media/") {
maxAge = 365 * (60 * 60 * 24) // 1 year
}
diff --git a/bskyweb/static.go b/bskyweb/static.go
index 38adb83335..4b59f27d76 100644
--- a/bskyweb/static.go
+++ b/bskyweb/static.go
@@ -2,7 +2,10 @@ package bskyweb
import "embed"
-//go:embed static/*
+// `all:` disables the default exclusion of files/dirs beginning with `_` or `.`,
+// which is needed because Metro emits chunks like `__common-...js` and
+// `__expo-metro-runtime-...js`.
+//go:embed all:static
var StaticFS embed.FS
//go:embed embedr-static/*
diff --git a/bskyweb/static/css/.gitkeep b/bskyweb/static/css/.gitkeep
deleted file mode 100644
index e69de29bb2..0000000000
diff --git a/bskyweb/templates/base.html b/bskyweb/templates/base.html
index e33479e697..fab7664730 100644
--- a/bskyweb/templates/base.html
+++ b/bskyweb/templates/base.html
@@ -13,7 +13,7 @@
-
+ {% include "fonts.html" %}
+`
+console.log(`Writing ${fontsTemplateFile}`)
+fs.writeFileSync(fontsTemplateFile, fontsHtml)
+
// Clean previous build output to avoid stale files
function cleanDir(dir) {
if (fs.existsSync(dir)) {
@@ -71,11 +117,10 @@ function cleanDir(dir) {
}
}
-cleanDir(path.join(bskywebStatic, 'js', 'web'))
-cleanDir(path.join(bskywebStatic, 'css'))
+cleanDir(path.join(bskywebStatic, '_expo'))
cleanDir(path.join(bskywebStatic, 'assets'))
-// Recursively copy a directory, skipping source map files
+// Recursively copy a directory.
function copyDir(sourceDir, targetDir) {
if (!fs.existsSync(sourceDir)) {
console.log(`Skipping ${sourceDir} (does not exist)`)
@@ -88,26 +133,18 @@ function copyDir(sourceDir, targetDir) {
const targetPath = path.join(targetDir, entry.name)
if (entry.isDirectory()) {
copyDir(sourcePath, targetPath)
- } else if (!entry.name.endsWith('.map')) {
+ } else {
fs.copyFileSync(sourcePath, targetPath)
console.log(`Copied ${sourcePath} to ${targetPath}`)
}
}
}
-// Copy JS chunks
-copyDir(
- path.join(distDir, '_expo', 'static', 'js', 'web'),
- path.join(bskywebStatic, 'js', 'web'),
-)
+// Copy Metro's _expo/ tree wholesale so JS chunks (including lazy chunks
+// referenced by hash) and source maps resolve at the same paths the runtime expects.
+copyDir(path.join(distDir, '_expo'), path.join(bskywebStatic, '_expo'))
-// Copy CSS
-copyDir(
- path.join(distDir, '_expo', 'static', 'css'),
- path.join(bskywebStatic, 'css'),
-)
-
-// Copy assets (fonts, images, icons, etc.)
+// Copy assets (fonts, images, icons, etc.) referenced as /static/assets/... by the bundle.
copyDir(path.join(distDir, 'assets'), path.join(bskywebStatic, 'assets'))
// Upload source maps to Sentry