Fix CSS modules imported from client components in app dir with next build (#38329)
Continue the work in #38310, this PR includes CSS files as chunks in the manifest for each client component, and then make sure the flight client loads the CSS files correctly. ## Bug - [ ] Related issues linked using `fixes #number` - [x] Integration tests added - [ ] Errors have helpful link attached, see `contributing.md` ## Feature - [ ] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR. - [ ] Related issues linked using `fixes #number` - [ ] Integration tests added - [ ] Documentation added - [ ] Telemetry added. In case of a feature if it's used or not. - [ ] Errors have helpful link attached, see `contributing.md` ## Documentation / Examples - [ ] Make sure the linting passes by running `pnpm lint` - [ ] The examples guidelines are followed from [our contributing doc](https://github.com/vercel/next.js/blob/canary/contributing.md#adding-examples)
This commit is contained in:
parent
a5e11612c7
commit
2dc1359149
5 changed files with 80 additions and 25 deletions
|
@ -66,7 +66,7 @@ function generateClientManifest(
|
|||
})
|
||||
}
|
||||
|
||||
function getEntrypointFiles(entrypoint: any): string[] {
|
||||
export function getEntrypointFiles(entrypoint: any): string[] {
|
||||
return (
|
||||
entrypoint
|
||||
?.getFiles()
|
||||
|
|
|
@ -9,6 +9,7 @@ import { webpack, sources } from 'next/dist/compiled/webpack/webpack'
|
|||
import { FLIGHT_MANIFEST } from '../../../shared/lib/constants'
|
||||
import { clientComponentRegex } from '../loaders/utils'
|
||||
import { relative } from 'path'
|
||||
import { getEntrypointFiles } from './build-manifest-plugin'
|
||||
|
||||
// This is the module that will be used to anchor all client references to.
|
||||
// I.e. it will have all the client files as async deps from this point on.
|
||||
|
@ -129,20 +130,35 @@ export class FlightManifestPlugin {
|
|||
)
|
||||
.filter((name) => name !== null)
|
||||
|
||||
// Get all CSS files imported in that chunk.
|
||||
const cssChunks: string[] = []
|
||||
for (const entrypoint of chunk._groups) {
|
||||
if (entrypoint.getFiles) {
|
||||
const files = getEntrypointFiles(entrypoint)
|
||||
for (const file of files) {
|
||||
if (file.endsWith('.css')) {
|
||||
cssChunks.push(file)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
moduleExportedKeys.forEach((name) => {
|
||||
if (!moduleExports[name]) {
|
||||
moduleExports[name] = {
|
||||
id,
|
||||
name,
|
||||
chunks: appDir
|
||||
? chunk.ids.map((chunkId: string) => {
|
||||
return (
|
||||
chunkId +
|
||||
':' +
|
||||
(chunk.name || chunkId) +
|
||||
(dev ? '' : '-' + chunk.hash)
|
||||
)
|
||||
})
|
||||
? chunk.ids
|
||||
.map((chunkId: string) => {
|
||||
return (
|
||||
chunkId +
|
||||
':' +
|
||||
(chunk.name || chunkId) +
|
||||
(dev ? '' : '-' + chunk.hash)
|
||||
)
|
||||
})
|
||||
.concat(cssChunks)
|
||||
: [],
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
import { hydrate, version } from './app-index'
|
||||
|
||||
// Include app-router and layout-router in the main chunk
|
||||
import 'next/dist/client/components/app-router.client.js'
|
||||
import 'next/dist/client/components/layout-router.client.js'
|
||||
|
||||
window.next = {
|
||||
version,
|
||||
root: true,
|
||||
|
@ -23,6 +27,14 @@ self.__next_require__ = __webpack_require__
|
|||
|
||||
// eslint-disable-next-line no-undef
|
||||
self.__next_chunk_load__ = (chunk) => {
|
||||
if (chunk.endsWith('.css')) {
|
||||
const link = document.createElement('link')
|
||||
link.rel = 'stylesheet'
|
||||
link.href = '/_next/' + chunk
|
||||
document.head.appendChild(link)
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
const [chunkId, chunkFileName] = chunk.split(':')
|
||||
chunkFilenameMap[chunkId] = `static/chunks/${chunkFileName}.js`
|
||||
|
||||
|
|
|
@ -120,6 +120,7 @@ function useFlightResponse(
|
|||
rscCache.set(id, entry)
|
||||
|
||||
let bootstrapped = false
|
||||
let remainingFlightResponse = ''
|
||||
const forwardReader = forwardStream.getReader()
|
||||
const writer = writable.getWriter()
|
||||
function process() {
|
||||
|
@ -138,11 +139,41 @@ function useFlightResponse(
|
|||
rscCache.delete(id)
|
||||
writer.close()
|
||||
} else {
|
||||
const responsePartial = decodeText(value)
|
||||
const css = responsePartial
|
||||
.split('\n')
|
||||
.map((partialLine) => {
|
||||
const line = remainingFlightResponse + partialLine
|
||||
remainingFlightResponse = ''
|
||||
|
||||
try {
|
||||
const match = line.match(/^M\d+:(.+)/)
|
||||
if (match) {
|
||||
return JSON.parse(match[1])
|
||||
.chunks.filter((chunkId: string) =>
|
||||
chunkId.endsWith('.css')
|
||||
)
|
||||
.map(
|
||||
(file: string) =>
|
||||
`<link rel="stylesheet" href="/_next/${file}">`
|
||||
)
|
||||
.join('')
|
||||
}
|
||||
return ''
|
||||
} catch (err) {
|
||||
// The JSON is partial
|
||||
remainingFlightResponse = line
|
||||
return ''
|
||||
}
|
||||
})
|
||||
.join('')
|
||||
|
||||
writer.write(
|
||||
encodeText(
|
||||
`<script>(self.__next_s=self.__next_s||[]).push(${htmlEscapeJsonString(
|
||||
JSON.stringify([1, id, decodeText(value)])
|
||||
)})</script>`
|
||||
css +
|
||||
`<script>(self.__next_s=self.__next_s||[]).push(${htmlEscapeJsonString(
|
||||
JSON.stringify([1, id, responsePartial])
|
||||
)})</script>`
|
||||
)
|
||||
)
|
||||
process()
|
||||
|
|
|
@ -5,8 +5,6 @@ import path from 'path'
|
|||
import cheerio from 'cheerio'
|
||||
import webdriver from 'next-webdriver'
|
||||
|
||||
const isDev = (global as any).isNextDev
|
||||
|
||||
describe('views dir', () => {
|
||||
if ((global as any).isNextDeploy) {
|
||||
it('should skip next deploy for now', () => {})
|
||||
|
@ -289,17 +287,15 @@ describe('views dir', () => {
|
|||
})
|
||||
|
||||
describe('css support', () => {
|
||||
if (isDev) {
|
||||
it('should support css modules inside client layouts', async () => {
|
||||
const browser = await webdriver(next.url, '/client-nested')
|
||||
it('should support css modules inside client layouts', async () => {
|
||||
const browser = await webdriver(next.url, '/client-nested')
|
||||
|
||||
// Should render h1 in red
|
||||
expect(
|
||||
await browser.eval(
|
||||
`window.getComputedStyle(document.querySelector('h1')).color`
|
||||
)
|
||||
).toBe('rgb(255, 0, 0)')
|
||||
})
|
||||
}
|
||||
// Should render h1 in red
|
||||
expect(
|
||||
await browser.eval(
|
||||
`window.getComputedStyle(document.querySelector('h1')).color`
|
||||
)
|
||||
).toBe('rgb(255, 0, 0)')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
Loading…
Reference in a new issue