Skip to content

Commit

Permalink
Fix CSS modules imported from client components in app dir with next …
Browse files Browse the repository at this point in the history
…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)
  • Loading branch information
shuding committed Jul 5, 2022
1 parent a5e1161 commit 2dc1359
Show file tree
Hide file tree
Showing 5 changed files with 80 additions and 25 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ function generateClientManifest(
})
}

function getEntrypointFiles(entrypoint: any): string[] {
export function getEntrypointFiles(entrypoint: any): string[] {
return (
entrypoint
?.getFiles()
Expand Down
32 changes: 24 additions & 8 deletions packages/next/build/webpack/plugins/flight-manifest-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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)
: [],
}
}
Expand Down
12 changes: 12 additions & 0 deletions packages/next/client/app-next.js
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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`

Expand Down
37 changes: 34 additions & 3 deletions packages/next/server/app-render.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand All @@ -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()
Expand Down
22 changes: 9 additions & 13 deletions test/e2e/app-dir/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {})
Expand Down Expand Up @@ -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)')
})
})
})

0 comments on commit 2dc1359

Please sign in to comment.