Skip to content

Commit

Permalink
partially fix css duplication in app dir (#61198)
Browse files Browse the repository at this point in the history
### What?

depends on facebook/react#28108

* fixes CSS Ordering issues due to CSS duplication in production mode
* The issues still happen in dev mode
* Highlights broken CSS Ordering for more dev cases, e. g. CSS in client
components


Closes PACK-2300
  • Loading branch information
sokra authored Feb 7, 2024
1 parent 7282682 commit 9d67a9f
Show file tree
Hide file tree
Showing 20 changed files with 475 additions and 60 deletions.
136 changes: 76 additions & 60 deletions packages/next/src/build/webpack-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -966,74 +966,90 @@ export default async function getBaseWebpackConfig(
}
}

const frameworkCacheGroup = {
chunks: 'all' as const,
name: 'framework',
// Ensures the framework chunk is not created for App Router.
layer: isWebpackDefaultLayer,
test(module: any) {
const resource = module.nameForCondition?.()
return resource
? topLevelFrameworkPaths.some((pkgPath) =>
resource.startsWith(pkgPath)
)
: false
},
priority: 40,
// Don't let webpack eliminate this chunk (prevents this chunk from
// becoming a part of the commons chunk)
enforce: true,
}
const libCacheGroup = {
test(module: {
size: Function
nameForCondition: Function
}): boolean {
return (
module.size() > 160000 &&
/node_modules[/\\]/.test(module.nameForCondition() || '')
)
},
name(module: {
layer: string | null | undefined
type: string
libIdent?: Function
updateHash: (hash: crypto.Hash) => void
}): string {
const hash = crypto.createHash('sha1')
if (isModuleCSS(module)) {
module.updateHash(hash)
} else {
if (!module.libIdent) {
throw new Error(
`Encountered unknown module type: ${module.type}. Please open an issue.`
)
}
hash.update(module.libIdent({ context: dir }))
}

// Ensures the name of the chunk is not the same between two modules in different layers
// E.g. if you import 'button-library' in App Router and Pages Router we don't want these to be bundled in the same chunk
// as they're never used on the same page.
if (module.layer) {
hash.update(module.layer)
}

return hash.digest('hex').substring(0, 8)
},
priority: 30,
minChunks: 1,
reuseExistingChunk: true,
}
const cssCacheGroup = {
test: /\.(css|sass|scss)$/i,
chunks: 'all' as const,
enforce: true,
type: /css/,
minChunks: 2,
priority: 100,
}
return {
// Keep main and _app chunks unsplitted in webpack 5
// as we don't need a separate vendor chunk from that
// and all other chunk depend on them so there is no
// duplication that need to be pulled out.
chunks: (chunk: any) =>
!/^(polyfills|main|pages\/_app)$/.test(chunk.name),
cacheGroups: {
framework: {
chunks: 'all',
name: 'framework',
// Ensures the framework chunk is not created for App Router.
layer: isWebpackDefaultLayer,
test(module: any) {
const resource = module.nameForCondition?.()
return resource
? topLevelFrameworkPaths.some((pkgPath) =>
resource.startsWith(pkgPath)
)
: false
},
priority: 40,
// Don't let webpack eliminate this chunk (prevents this chunk from
// becoming a part of the commons chunk)
enforce: true,
},
lib: {
test(module: {
size: Function
nameForCondition: Function
}): boolean {
return (
module.size() > 160000 &&
/node_modules[/\\]/.test(module.nameForCondition() || '')
)
},
name(module: {
layer: string | null | undefined
type: string
libIdent?: Function
updateHash: (hash: crypto.Hash) => void
}): string {
const hash = crypto.createHash('sha1')
if (isModuleCSS(module)) {
module.updateHash(hash)
} else {
if (!module.libIdent) {
throw new Error(
`Encountered unknown module type: ${module.type}. Please open an issue.`
)
}
hash.update(module.libIdent({ context: dir }))
}

// Ensures the name of the chunk is not the same between two modules in different layers
// E.g. if you import 'button-library' in App Router and Pages Router we don't want these to be bundled in the same chunk
// as they're never used on the same page.
if (module.layer) {
hash.update(module.layer)
}

return hash.digest('hex').substring(0, 8)
cacheGroups: appDir
? {
css: cssCacheGroup,
framework: frameworkCacheGroup,
lib: libCacheGroup,
}
: {
framework: frameworkCacheGroup,
lib: libCacheGroup,
},
priority: 30,
minChunks: 1,
reuseExistingChunk: true,
},
},
maxInitialRequests: 25,
minSize: 20000,
}
Expand Down
5 changes: 5 additions & 0 deletions test/e2e/app-dir/css-order/app/base-scss.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
$base1: rgb(255, 1, 0);

.base {
color: $base1;
}
3 changes: 3 additions & 0 deletions test/e2e/app-dir/css-order/app/base.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.base {
color: rgb(255, 0, 0);
}
3 changes: 3 additions & 0 deletions test/e2e/app-dir/css-order/app/base2-scss.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.base {
color: rgb(255, 3, 0);
}
3 changes: 3 additions & 0 deletions test/e2e/app-dir/css-order/app/base2.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.base {
color: rgb(255, 2, 0);
}
34 changes: 34 additions & 0 deletions test/e2e/app-dir/css-order/app/first-client/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
'use client'

import Link from 'next/link'
import baseStyle from '../base2.module.css'
import baseStyle2 from '../base2-scss.module.scss'
import style from './style.module.css'

export default function Page() {
return (
<div>
<p
className={`${style.name} ${baseStyle.base} ${baseStyle2.base}`}
id="hello1c"
>
hello world
</p>
<Link href={'/first'} id="first">
First
</Link>
<Link href={'/first-client'} id="first-client">
First client
</Link>
<Link href={'/second'} id="second">
Second
</Link>
<Link href={'/second-client'} id="second-client">
Second client
</Link>
<Link href={'/third'} id="third">
Third
</Link>
</div>
)
}
4 changes: 4 additions & 0 deletions test/e2e/app-dir/css-order/app/first-client/style.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.name {
composes: base from '../base.module.css';
color: rgb(255, 0, 255);
}
32 changes: 32 additions & 0 deletions test/e2e/app-dir/css-order/app/first/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import Link from 'next/link'
import baseStyle from '../base2.module.css'
import baseStyle2 from '../base2-scss.module.scss'
import style from './style.module.css'

export default function Page() {
return (
<div>
<p
className={`${style.name} ${baseStyle.base} ${baseStyle2.base}`}
id="hello1"
>
hello world
</p>
<Link href={'/first'} id="first">
First
</Link>
<Link href={'/first-client'} id="first-client">
First client
</Link>
<Link href={'/second'} id="second">
Second
</Link>
<Link href={'/second-client'} id="second-client">
Second client
</Link>
<Link href={'/third'} id="third">
Third
</Link>
</div>
)
}
4 changes: 4 additions & 0 deletions test/e2e/app-dir/css-order/app/first/style.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.name {
composes: base from '../base.module.css';
color: blue;
}
7 changes: 7 additions & 0 deletions test/e2e/app-dir/css-order/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export default function Root({ children }: { children: React.ReactNode }) {
return (
<html>
<body>{children}</body>
</html>
)
}
11 changes: 11 additions & 0 deletions test/e2e/app-dir/css-order/app/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import Link from 'next/link'

export default function Page() {
return (
<div>
<p>hello world</p>
<Link href={'/first'}>First</Link>
<Link href={'/second'}>Second</Link>
</div>
)
}
34 changes: 34 additions & 0 deletions test/e2e/app-dir/css-order/app/second-client/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
'use client'

import Link from 'next/link'
import baseStyle from '../base2.module.css'
import baseStyle2 from '../base2-scss.module.scss'
import style from './style.module.css'

export default function Page() {
return (
<div>
<p
className={`${style.name} ${baseStyle.base} ${baseStyle2.base}`}
id="hello2c"
>
hello world
</p>
<Link href={'/first'} id="first">
First
</Link>
<Link href={'/first-client'} id="first-client">
First client
</Link>
<Link href={'/second'} id="second">
Second
</Link>
<Link href={'/second-client'} id="second-client">
Second client
</Link>
<Link href={'/third'} id="third">
Third
</Link>
</div>
)
}
4 changes: 4 additions & 0 deletions test/e2e/app-dir/css-order/app/second-client/style.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.name {
composes: base from '../base.module.css';
color: rgb(255, 128, 0);
}
32 changes: 32 additions & 0 deletions test/e2e/app-dir/css-order/app/second/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import Link from 'next/link'
import baseStyle from '../base2.module.css'
import baseStyle2 from '../base2-scss.module.scss'
import style from './style.module.scss'

export default function Page() {
return (
<div>
<p
className={`${style.name} ${baseStyle.base} ${baseStyle2.base}`}
id="hello2"
>
hello world
</p>
<Link href={'/first'} id="first">
First
</Link>
<Link href={'/first-client'} id="first-client">
First client
</Link>
<Link href={'/second'} id="second">
Second
</Link>
<Link href={'/second-client'} id="second-client">
Second client
</Link>
<Link href={'/third'} id="third">
Third
</Link>
</div>
)
}
4 changes: 4 additions & 0 deletions test/e2e/app-dir/css-order/app/second/style.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.name {
composes: base from '../base-scss.module.scss';
color: green;
}
32 changes: 32 additions & 0 deletions test/e2e/app-dir/css-order/app/third/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import Link from 'next/link'
import baseStyle from '../base2.module.css'
import baseStyle2 from '../base2-scss.module.scss'
import style from './style.module.scss'

export default function Page() {
return (
<div>
<p
className={`${style.name} ${baseStyle.base} ${baseStyle2.base}`}
id="hello3"
>
hello world
</p>
<Link href={'/first'} id="first">
First
</Link>
<Link href={'/first-client'} id="first-client">
First client
</Link>
<Link href={'/second'} id="second">
Second
</Link>
<Link href={'/second-client'} id="second-client">
Second client
</Link>
<Link href={'/third'} id="third">
Third
</Link>
</div>
)
}
4 changes: 4 additions & 0 deletions test/e2e/app-dir/css-order/app/third/style.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.name {
composes: base from '../base-scss.module.scss';
color: rgb(0, 128, 128);
}
Loading

0 comments on commit 9d67a9f

Please sign in to comment.