Skip to content

Commit dd3fb69

Browse files
fix(dev): improve server build asset handling (#13547)
1 parent b7b1876 commit dd3fb69

File tree

3 files changed

+113
-32
lines changed

3 files changed

+113
-32
lines changed

.changeset/gold-socks-attack.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@react-router/dev": patch
3+
---
4+
5+
Add additional logging to `build` command output when cleaning assets from server build

.changeset/rude-lobsters-design.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@react-router/dev": patch
3+
---
4+
5+
Don't clean assets from server build when `build.ssrEmitAssets` has been enabled in Vite config

packages/react-router-dev/vite/plugin.ts

Lines changed: 103 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1659,49 +1659,120 @@ export const reactRouterVitePlugin: ReactRouterVitePlugin = () => {
16591659
let ssrViteManifest = await loadViteManifest(serverBuildDirectory);
16601660
let ssrAssetPaths = getViteManifestAssetPaths(ssrViteManifest);
16611661

1662-
// We only move assets that aren't in the client build, otherwise we
1663-
// remove them. These assets only exist because we explicitly set
1664-
// `ssrEmitAssets: true` in the SSR Vite config. These assets
1665-
// typically wouldn't exist by default, which is why we assume it's
1666-
// safe to remove them. We're aiming for a clean build output so that
1667-
// unnecessary assets don't get deployed alongside the server code.
1662+
// If the consumer has explicitly opted in to keeping the SSR build
1663+
// assets, we don't remove them. We only copy missing assets from the
1664+
// SSR to the client build.
1665+
let userSsrEmitAssets =
1666+
(ctx.reactRouterConfig.future.unstable_viteEnvironmentApi
1667+
? viteUserConfig.environments?.ssr?.build?.ssrEmitAssets ??
1668+
viteUserConfig.environments?.ssr?.build?.emitAssets
1669+
: null) ??
1670+
viteUserConfig.build?.ssrEmitAssets ??
1671+
false;
1672+
1673+
// We only move/copy assets that aren't in the client build, otherwise
1674+
// we remove them if the consumer hasn't explicitly enabled
1675+
// `ssrEmitAssets` in their Vite config. These assets only exist
1676+
// because we internally enable `ssrEmitAssets` within our plugin.
1677+
// These assets typically wouldn't exist by default, which is why we
1678+
// assume it's safe to remove them.
16681679
let movedAssetPaths: string[] = [];
1680+
let removedAssetPaths: string[] = [];
1681+
let copiedAssetPaths: string[] = [];
16691682
for (let ssrAssetPath of ssrAssetPaths) {
16701683
let src = path.join(serverBuildDirectory, ssrAssetPath);
16711684
let dest = path.join(clientBuildDirectory, ssrAssetPath);
16721685

1673-
if (!fse.existsSync(dest)) {
1674-
await fse.move(src, dest);
1675-
movedAssetPaths.push(dest);
1676-
} else {
1677-
await fse.remove(src);
1686+
if (!userSsrEmitAssets) {
1687+
if (!fse.existsSync(dest)) {
1688+
await fse.move(src, dest);
1689+
movedAssetPaths.push(dest);
1690+
} else {
1691+
await fse.remove(src);
1692+
removedAssetPaths.push(dest);
1693+
}
1694+
} else if (!fse.existsSync(dest)) {
1695+
await fse.copy(src, dest);
1696+
copiedAssetPaths.push(dest);
16781697
}
16791698
}
16801699

1681-
// We assume CSS assets from the SSR build are unnecessary and remove
1682-
// them for the same reasons as above.
1683-
let ssrCssPaths = Object.values(ssrViteManifest).flatMap(
1684-
(chunk) => chunk.css ?? []
1685-
);
1700+
if (!userSsrEmitAssets) {
1701+
// We assume CSS assets from the SSR build are unnecessary and
1702+
// remove them for the same reasons as above.
1703+
let ssrCssPaths = Object.values(ssrViteManifest).flatMap(
1704+
(chunk) => chunk.css ?? []
1705+
);
1706+
await Promise.all(
1707+
ssrCssPaths.map(async (cssPath) => {
1708+
let src = path.join(serverBuildDirectory, cssPath);
1709+
await fse.remove(src);
1710+
removedAssetPaths.push(src);
1711+
})
1712+
);
1713+
}
1714+
1715+
let cleanedAssetPaths = [...removedAssetPaths, ...movedAssetPaths];
1716+
let handledAssetPaths = [...cleanedAssetPaths, ...copiedAssetPaths];
1717+
1718+
// Clean empty asset directories
1719+
let cleanedAssetDirs = new Set(cleanedAssetPaths.map(path.dirname));
16861720
await Promise.all(
1687-
ssrCssPaths.map((cssPath) =>
1688-
fse.remove(path.join(serverBuildDirectory, cssPath))
1689-
)
1721+
Array.from(cleanedAssetDirs).map(async (dir) => {
1722+
try {
1723+
const files = await fse.readdir(dir);
1724+
if (files.length === 0) {
1725+
await fse.remove(dir);
1726+
}
1727+
} catch {}
1728+
})
16901729
);
16911730

1692-
if (movedAssetPaths.length) {
1693-
viteConfig.logger.info(
1694-
[
1695-
"",
1696-
`${colors.green("✓")} ${movedAssetPaths.length} asset${
1697-
movedAssetPaths.length > 1 ? "s" : ""
1698-
} moved from React Router server build to client assets.`,
1699-
...movedAssetPaths.map((movedAssetPath) =>
1700-
colors.dim(path.relative(ctx.rootDirectory, movedAssetPath))
1701-
),
1702-
"",
1703-
].join("\n")
1704-
);
1731+
// If we handled any assets, add some leading whitespace to
1732+
// our logs to make them more prominent
1733+
if (handledAssetPaths.length) {
1734+
viteConfig.logger.info("");
1735+
}
1736+
1737+
function logHandledAssets(paths: string[], message: string) {
1738+
invariant(viteConfig);
1739+
if (paths.length) {
1740+
viteConfig.logger.info(
1741+
[
1742+
`${colors.green("✓")} ${message}`,
1743+
...paths.map((assetPath) =>
1744+
colors.dim(path.relative(ctx.rootDirectory, assetPath))
1745+
),
1746+
].join("\n")
1747+
);
1748+
}
1749+
}
1750+
1751+
logHandledAssets(
1752+
removedAssetPaths,
1753+
`${removedAssetPaths.length} asset${
1754+
removedAssetPaths.length > 1 ? "s" : ""
1755+
} cleaned from React Router server build.`
1756+
);
1757+
1758+
logHandledAssets(
1759+
movedAssetPaths,
1760+
`${movedAssetPaths.length} asset${
1761+
movedAssetPaths.length > 1 ? "s" : ""
1762+
} moved from React Router server build to client assets.`
1763+
);
1764+
1765+
logHandledAssets(
1766+
copiedAssetPaths,
1767+
`${copiedAssetPaths.length} asset${
1768+
copiedAssetPaths.length > 1 ? "s" : ""
1769+
} copied from React Router server build to client assets.`
1770+
);
1771+
1772+
// If we handled any assets, add some leading whitespace to our logs
1773+
// to make them more prominent
1774+
if (handledAssetPaths.length) {
1775+
viteConfig.logger.info("");
17051776
}
17061777

17071778
// Set an environment variable we can look for in the handler to

0 commit comments

Comments
 (0)