Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/modernjs-v3-content-length-bytes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@module-federation/modern-js-v3': patch
---

Fix truncated remote chunks served from `/bundles` by computing `Content-Length` in bytes instead of characters. Chunks containing non-ASCII characters were cut short, causing `SyntaxError` when an SSR consumer evaluated them.
43 changes: 43 additions & 0 deletions packages/modernjs-v3/src/server/staticMiddleware.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,49 @@ describe('staticMiddleware', () => {
expect(result).toBe('empty-response');
expect(nextSpy).not.toHaveBeenCalled();
});

it('should set Content-Length in bytes for non-ASCII content', async () => {
// Multi-byte characters make byte length differ from string length.
// Using the string length here truncates the response body.
const mockFileContent = 'console.log("中文注释");';
const mockFileResult = {
content: mockFileContent,
lastModified: Date.now(),
};

mockContext.req.path = '/bundles/non-ascii.js';
(access as any).mockResolvedValue(undefined);
(fileCache.getFile as any).mockResolvedValue(mockFileResult);

await middleware(mockContext, nextSpy);

expect(Buffer.byteLength(mockFileContent)).toBeGreaterThan(
mockFileContent.length,
);
expect(mockContext.header).toHaveBeenCalledWith(
'Content-Length',
String(Buffer.byteLength(mockFileContent)),
);
});

it('should set Content-Length in bytes for ASCII content', async () => {
const mockFileContent = 'console.log("ascii only");';
const mockFileResult = {
content: mockFileContent,
lastModified: Date.now(),
};

mockContext.req.path = '/bundles/ascii.js';
(access as any).mockResolvedValue(undefined);
(fileCache.getFile as any).mockResolvedValue(mockFileResult);

await middleware(mockContext, nextSpy);

expect(mockContext.header).toHaveBeenCalledWith(
'Content-Length',
String(mockFileContent.length),
);
});
});

describe('asset prefix handling', () => {
Expand Down
5 changes: 4 additions & 1 deletion packages/modernjs-v3/src/server/staticMiddleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,10 @@ const createStaticMiddleware = (options: {
}

c.header('Content-Type', 'application/javascript');
c.header('Content-Length', String(fileResult.content.length));
// The file is read as a UTF-8 string, so `content.length` is the number of
// characters. Content-Length must be the number of bytes, otherwise any
// chunk containing non-ASCII characters is truncated by the difference.
c.header('Content-Length', String(Buffer.byteLength(fileResult.content)));
return c.body(fileResult.content, 200);
};
};
Expand Down
Loading