Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor client CSS imports #39758

Merged
merged 4 commits into from Aug 22, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -71,11 +71,6 @@ export class FlightClientEntryPlugin {
// For each SC server compilation entry, we need to create its corresponding
// client component entry.
for (const [name, entry] of compilation.entries.entries()) {
// If the request is not for `app` directory entry skip it.
// if (!entry.request || !entry.request.startsWith('next-app-loader')) {
// continue
// }

// Check if the page entry is a server component or not.
const entryDependency = entry.dependencies?.[0]
// Ensure only next-app-loader entries are handled.
Expand Down Expand Up @@ -193,7 +188,10 @@ export class FlightClientEntryPlugin {
const clientComponentImports: ClientComponentImports = []
const serverCSSImports: CssImports = {}

const filterClientComponents = (dependencyToFilter: any): void => {
const filterClientComponents = (
dependencyToFilter: any,
inClientComponentBoundary: boolean
): void => {
const mod: webpack.NormalModule =
compilation.moduleGraph.getResolvedModule(dependencyToFilter)
if (!mod) return
Expand Down Expand Up @@ -236,20 +234,23 @@ export class FlightClientEntryPlugin {
}

// Check if request is for css file.
if (isClientComponent || isCSS) {
if ((!inClientComponentBoundary && isClientComponent) || isCSS) {
clientComponentImports.push(modRequest)
return
}

compilation.moduleGraph
.getOutgoingConnections(mod)
.forEach((connection: any) => {
filterClientComponents(connection.dependency)
filterClientComponents(
connection.dependency,
inClientComponentBoundary || isClientComponent
)
})
}

// Traverse the module graph to find all client components.
filterClientComponents(dependency)
filterClientComponents(dependency, false)

return [clientComponentImports, serverCSSImports]
}
Expand Down
51 changes: 19 additions & 32 deletions packages/next/build/webpack/plugins/flight-manifest-plugin.ts
Expand Up @@ -114,6 +114,9 @@ export class FlightManifestPlugin {
const dev = this.dev

compilation.chunkGroups.forEach((chunkGroup) => {
const cssResourcesInChunkGroup: string[] = []
let entryFilepath: string = ''

function recordModule(
chunk: webpack.Chunk,
id: ModuleId,
Expand Down Expand Up @@ -176,6 +179,11 @@ export class FlightManifestPlugin {
}
manifest.__ssr_module_mapping__ = moduleIdMapping
}

if (chunkGroup.name) {
cssResourcesInChunkGroup.push(resource)
}

return
}

Expand All @@ -186,6 +194,10 @@ export class FlightManifestPlugin {
return
}

if (/\/(page|layout)\.client\.(ts|js)x?$/.test(resource)) {
entryFilepath = resource
}

const exportsInfo = compilation.moduleGraph.getExportsInfo(mod)
const cjsExports = [
...new Set([
Expand Down Expand Up @@ -218,37 +230,6 @@ export class FlightManifestPlugin {
)
.filter((name) => name !== null)

// Get all CSS files imported from the module's dependencies.
const visitedModule = new Set()
const cssChunks: Set<string> = new Set()

function collectClientImportedCss(module: any) {
if (!module) return

const modRequest = module.userRequest
if (visitedModule.has(modRequest)) return
visitedModule.add(modRequest)

if (/\.css$/.test(modRequest)) {
// collect relative imported css chunks
compilation.chunkGraph.getModuleChunks(module).forEach((c) => {
;[...c.files]
.filter((file) => file.endsWith('.css'))
.forEach((file) => cssChunks.add(file))
})
}

const connections = Array.from(
compilation.moduleGraph.getOutgoingConnections(module)
)
connections.forEach((connection) => {
collectClientImportedCss(
compilation.moduleGraph.getResolvedModule(connection.dependency!)
)
})
}
collectClientImportedCss(mod)

moduleExportedKeys.forEach((name) => {
let requiredChunks: ManifestChunks = []
if (!moduleExports[name]) {
Expand All @@ -273,7 +254,7 @@ export class FlightManifestPlugin {
moduleExports[name] = {
id,
name,
chunks: requiredChunks.concat([...cssChunks]),
chunks: requiredChunks,
}
}

Expand Down Expand Up @@ -310,6 +291,12 @@ export class FlightManifestPlugin {
}
}
})

const clientCSSManifest: any = manifest.__client_css_manifest__ || {}
if (entryFilepath) {
clientCSSManifest[entryFilepath] = cssResourcesInChunkGroup
}
manifest.__client_css_manifest__ = clientCSSManifest
})

const file = 'server/' + FLIGHT_MANIFEST
Expand Down
12 changes: 0 additions & 12 deletions packages/next/client/app-index.tsx
Expand Up @@ -32,18 +32,6 @@ self.__next_require__ = __webpack_require__
// eslint-disable-next-line no-undef
;(self as any).__next_chunk_load__ = (chunk: string) => {
if (!chunk) return Promise.resolve()
if (chunk.endsWith('.css')) {
// @ts-expect-error __webpack_public_path__ is inlined by webpack
const chunkPath = `${__webpack_public_path__ || ''}${chunk}`
const existingTag = document.querySelector(`link[href="${chunkPath}"]`)
if (!existingTag) {
const link = document.createElement('link')
link.rel = 'stylesheet'
link.href = chunkPath
document.head.appendChild(link)
}
return Promise.resolve()
}
const [chunkId, chunkFileName] = chunk.split(':')
chunkFilenameMap[chunkId] = `static/chunks/${chunkFileName}.js`

Expand Down
4 changes: 3 additions & 1 deletion packages/next/server/app-render.tsx
Expand Up @@ -383,7 +383,9 @@ function getCssInlinedLinkTags(
serverCSSManifest: FlightCSSManifest,
filePath: string
): string[] {
const layoutOrPageCss = serverCSSManifest[filePath]
const layoutOrPageCss =
serverCSSManifest[filePath] ||
serverComponentManifest.__client_css_manifest__?.[filePath]

if (!layoutOrPageCss) {
return []
Expand Down
3 changes: 3 additions & 0 deletions test/e2e/app-dir/app/app/css/css-client/client-foo.css
@@ -0,0 +1,3 @@
.foo {
color: blue;
}
3 changes: 3 additions & 0 deletions test/e2e/app-dir/app/app/css/css-client/client-layout.css
@@ -0,0 +1,3 @@
body {
background: cyan;
}
6 changes: 6 additions & 0 deletions test/e2e/app-dir/app/app/css/css-client/client-page.css
@@ -0,0 +1,6 @@
h1 {
color: red !important;
}
h1::after {
content: ' (from css-client!!)';
}
5 changes: 5 additions & 0 deletions test/e2e/app-dir/app/app/css/css-client/foo.js
@@ -0,0 +1,5 @@
import './client-foo.css'

export default function Foo() {
return <b className="foo">foo</b>
}
12 changes: 12 additions & 0 deletions test/e2e/app-dir/app/app/css/css-client/layout.client.js
@@ -0,0 +1,12 @@
import './client-layout.css'

import Foo from './foo'

export default function ServerLayout({ children }) {
return (
<>
{children}
<Foo />
</>
)
}
5 changes: 5 additions & 0 deletions test/e2e/app-dir/app/app/css/css-client/page.client.js
@@ -0,0 +1,5 @@
import './client-page.css'

export default function Page() {
return <h1>Page!!!</h1>
}