Skip to content

Commit

Permalink
fix: include dynamic css to build manifest
Browse files Browse the repository at this point in the history
  • Loading branch information
devjiwonchoi committed Aug 27, 2024
1 parent 5da02ef commit a11a69f
Show file tree
Hide file tree
Showing 2 changed files with 83 additions and 10 deletions.
81 changes: 77 additions & 4 deletions packages/next/src/build/webpack/plugins/build-manifest-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -273,15 +273,88 @@ export default class BuildManifestPlugin {
if (SYSTEM_ENTRYPOINTS.has(entrypoint.name)) continue
const pagePath = getRouteFromEntrypoint(entrypoint.name)

if (!pagePath) {
continue
}
if (!pagePath) continue

const filesForPage = getEntrypointFiles(entrypoint)

assetMap.pages[pagePath] = [...new Set([...mainFiles, ...filesForPage])]
}

// When a CSS file is server-rendered, it is cleaned up during
// the client render. During navigation, if the target route
// dynamically imports the same CSS file, the client will skip
// loading it as it was already loaded statically, but still
// cleaned up during the client render. This results in missing
// the expected CSS styles.
// x-ref: `next/src/client/index.tsx`

// 1. page transition
// 2. import (CSS already exists)
// 3. client render starts
// 4. clean up server-rendered CSS
// 5. CSS is missing

// To prevent this, we add the dynamically imported CSS files
// to the build manifest to signal the client not to clean up
// the CSS file if it's loaded dynamically, preserving the styles.

/* Inspired by `next/src/build/webpack/react-loadable-plugin.ts` */
const _handleBlock = (block: any) => {
block.blocks.forEach(_handleBlock)

const dynamicCSSFiles = new Set<string>()

const chunkGroup = compilation.chunkGraph.getBlockChunkGroup(block)
if (chunkGroup) {
for (const chunk of chunkGroup.chunks) {
chunk.files.forEach((file: string) => {
if (file.endsWith('.css') && file.match(/^static\/css\//)) {
dynamicCSSFiles.add(file)
}
})
}
}

for (const dependency of block.dependencies) {
// We target the two types of dynamic import() dependencies:
// `ImportDependency` and `ContextElementDependency`.

// ImportDependency:
// - syntax: import("./module")
// - dependency.type: "import()"

// ContextElementDependency:
// - syntax: import(`./module/${param}`)
// - dependency.type: "import() context element"
if (dependency.type.startsWith('import()')) {
const parentModule =
compilation.moduleGraph.getParentModule(dependency)

const parentChunks =
compilation.chunkGraph.getModuleChunks(parentModule)

for (const chunk of parentChunks) {
const chunkName = chunk.name
if (!chunkName || !chunkName.startsWith('pages')) continue

const pagePath = getRouteFromEntrypoint(chunkName)
if (!pagePath) continue

const assetMapPages = assetMap.pages[pagePath]
// We should already have `assetMapPages` from the `entrypoint`.
if (!assetMapPages) continue

assetMap.pages[pagePath] = [
...new Set([...assetMapPages, ...dynamicCSSFiles]),
]
}
}
}
}

for (const module of compilation.modules) {
module.blocks.forEach(_handleBlock)
}

if (!this.isDevFallback) {
// Add the runtime build manifest file (generated later in this file)
// as a dependency for the app. If the flag is false, the file won't be
Expand Down
12 changes: 6 additions & 6 deletions test/e2e/pages-dir/next-dynamic/with-static-import/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,18 @@ describe('css-module-with-next-dynamic-and-static-import', () => {
'My background should be red!'
)

// TODO: remove this - button's background should be red, but is gray.
// button's background should be red, not gray.
expect(
await browser.eval(
`window.getComputedStyle(document.querySelector('button')).backgroundColor`
)
).toBe('rgb(239, 239, 239)')
).not.toBe('rgb(239, 239, 239)')

expect(
await browser.eval(
`window.getComputedStyle(document.querySelector('button')).backgroundColor`
)
).not.toBe('rgb(255, 0, 0)')
).toBe('rgb(255, 0, 0)')
})

it('should be able to load the same css module with both next/dynamic (variable-inserted path) and static import', async () => {
Expand All @@ -35,17 +35,17 @@ describe('css-module-with-next-dynamic-and-static-import', () => {
'My background should be red!'
)

// TODO: remove this - button's background should be red, but is gray.
// button's background should be red, not gray.
expect(
await browser.eval(
`window.getComputedStyle(document.querySelector('button')).backgroundColor`
)
).toBe('rgb(239, 239, 239)')
).not.toBe('rgb(239, 239, 239)')

expect(
await browser.eval(
`window.getComputedStyle(document.querySelector('button')).backgroundColor`
)
).not.toBe('rgb(255, 0, 0)')
).toBe('rgb(255, 0, 0)')
})
})

0 comments on commit a11a69f

Please sign in to comment.