diff --git a/packages/next/src/build/webpack-config.ts b/packages/next/src/build/webpack-config.ts
index 0471429b0ac88..6564c30a60e2e 100644
--- a/packages/next/src/build/webpack-config.ts
+++ b/packages/next/src/build/webpack-config.ts
@@ -966,6 +966,73 @@ 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
@@ -973,67 +1040,16 @@ export default async function getBaseWebpackConfig(
// 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,
}
diff --git a/test/e2e/app-dir/css-order/app/base-scss.module.scss b/test/e2e/app-dir/css-order/app/base-scss.module.scss
new file mode 100644
index 0000000000000..6067b55d88d63
--- /dev/null
+++ b/test/e2e/app-dir/css-order/app/base-scss.module.scss
@@ -0,0 +1,5 @@
+$base1: rgb(255, 1, 0);
+
+.base {
+ color: $base1;
+}
diff --git a/test/e2e/app-dir/css-order/app/base.module.css b/test/e2e/app-dir/css-order/app/base.module.css
new file mode 100644
index 0000000000000..546b7a181f5b7
--- /dev/null
+++ b/test/e2e/app-dir/css-order/app/base.module.css
@@ -0,0 +1,3 @@
+.base {
+ color: rgb(255, 0, 0);
+}
diff --git a/test/e2e/app-dir/css-order/app/base2-scss.module.scss b/test/e2e/app-dir/css-order/app/base2-scss.module.scss
new file mode 100644
index 0000000000000..9484078ecb60c
--- /dev/null
+++ b/test/e2e/app-dir/css-order/app/base2-scss.module.scss
@@ -0,0 +1,3 @@
+.base {
+ color: rgb(255, 3, 0);
+}
diff --git a/test/e2e/app-dir/css-order/app/base2.module.css b/test/e2e/app-dir/css-order/app/base2.module.css
new file mode 100644
index 0000000000000..755c87eccb2aa
--- /dev/null
+++ b/test/e2e/app-dir/css-order/app/base2.module.css
@@ -0,0 +1,3 @@
+.base {
+ color: rgb(255, 2, 0);
+}
diff --git a/test/e2e/app-dir/css-order/app/first-client/page.tsx b/test/e2e/app-dir/css-order/app/first-client/page.tsx
new file mode 100644
index 0000000000000..591cf63428b0b
--- /dev/null
+++ b/test/e2e/app-dir/css-order/app/first-client/page.tsx
@@ -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 (
+
+
+ hello world
+
+
+ First
+
+
+ First client
+
+
+ Second
+
+
+ Second client
+
+
+ Third
+
+
+ )
+}
diff --git a/test/e2e/app-dir/css-order/app/first-client/style.module.css b/test/e2e/app-dir/css-order/app/first-client/style.module.css
new file mode 100644
index 0000000000000..ff118a9778c2e
--- /dev/null
+++ b/test/e2e/app-dir/css-order/app/first-client/style.module.css
@@ -0,0 +1,4 @@
+.name {
+ composes: base from '../base.module.css';
+ color: rgb(255, 0, 255);
+}
diff --git a/test/e2e/app-dir/css-order/app/first/page.tsx b/test/e2e/app-dir/css-order/app/first/page.tsx
new file mode 100644
index 0000000000000..d8ad145fa2622
--- /dev/null
+++ b/test/e2e/app-dir/css-order/app/first/page.tsx
@@ -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 (
+
+
+ hello world
+
+
+ First
+
+
+ First client
+
+
+ Second
+
+
+ Second client
+
+
+ Third
+
+
+ )
+}
diff --git a/test/e2e/app-dir/css-order/app/first/style.module.css b/test/e2e/app-dir/css-order/app/first/style.module.css
new file mode 100644
index 0000000000000..90026c621f188
--- /dev/null
+++ b/test/e2e/app-dir/css-order/app/first/style.module.css
@@ -0,0 +1,4 @@
+.name {
+ composes: base from '../base.module.css';
+ color: blue;
+}
diff --git a/test/e2e/app-dir/css-order/app/layout.tsx b/test/e2e/app-dir/css-order/app/layout.tsx
new file mode 100644
index 0000000000000..e7077399c03ce
--- /dev/null
+++ b/test/e2e/app-dir/css-order/app/layout.tsx
@@ -0,0 +1,7 @@
+export default function Root({ children }: { children: React.ReactNode }) {
+ return (
+
+ {children}
+
+ )
+}
diff --git a/test/e2e/app-dir/css-order/app/page.tsx b/test/e2e/app-dir/css-order/app/page.tsx
new file mode 100644
index 0000000000000..136769782c178
--- /dev/null
+++ b/test/e2e/app-dir/css-order/app/page.tsx
@@ -0,0 +1,11 @@
+import Link from 'next/link'
+
+export default function Page() {
+ return (
+
+
hello world
+
First
+
Second
+
+ )
+}
diff --git a/test/e2e/app-dir/css-order/app/second-client/page.tsx b/test/e2e/app-dir/css-order/app/second-client/page.tsx
new file mode 100644
index 0000000000000..c2442d3d7829c
--- /dev/null
+++ b/test/e2e/app-dir/css-order/app/second-client/page.tsx
@@ -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 (
+
+
+ hello world
+
+
+ First
+
+
+ First client
+
+
+ Second
+
+
+ Second client
+
+
+ Third
+
+
+ )
+}
diff --git a/test/e2e/app-dir/css-order/app/second-client/style.module.css b/test/e2e/app-dir/css-order/app/second-client/style.module.css
new file mode 100644
index 0000000000000..08841ce8710ce
--- /dev/null
+++ b/test/e2e/app-dir/css-order/app/second-client/style.module.css
@@ -0,0 +1,4 @@
+.name {
+ composes: base from '../base.module.css';
+ color: rgb(255, 128, 0);
+}
diff --git a/test/e2e/app-dir/css-order/app/second/page.tsx b/test/e2e/app-dir/css-order/app/second/page.tsx
new file mode 100644
index 0000000000000..ac8870b490e38
--- /dev/null
+++ b/test/e2e/app-dir/css-order/app/second/page.tsx
@@ -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 (
+
+
+ hello world
+
+
+ First
+
+
+ First client
+
+
+ Second
+
+
+ Second client
+
+
+ Third
+
+
+ )
+}
diff --git a/test/e2e/app-dir/css-order/app/second/style.module.scss b/test/e2e/app-dir/css-order/app/second/style.module.scss
new file mode 100644
index 0000000000000..92a409e7791cf
--- /dev/null
+++ b/test/e2e/app-dir/css-order/app/second/style.module.scss
@@ -0,0 +1,4 @@
+.name {
+ composes: base from '../base-scss.module.scss';
+ color: green;
+}
diff --git a/test/e2e/app-dir/css-order/app/third/page.tsx b/test/e2e/app-dir/css-order/app/third/page.tsx
new file mode 100644
index 0000000000000..cd5c620743825
--- /dev/null
+++ b/test/e2e/app-dir/css-order/app/third/page.tsx
@@ -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 (
+
+
+ hello world
+
+
+ First
+
+
+ First client
+
+
+ Second
+
+
+ Second client
+
+
+ Third
+
+
+ )
+}
diff --git a/test/e2e/app-dir/css-order/app/third/style.module.scss b/test/e2e/app-dir/css-order/app/third/style.module.scss
new file mode 100644
index 0000000000000..35e2d2d900d67
--- /dev/null
+++ b/test/e2e/app-dir/css-order/app/third/style.module.scss
@@ -0,0 +1,4 @@
+.name {
+ composes: base from '../base-scss.module.scss';
+ color: rgb(0, 128, 128);
+}
diff --git a/test/e2e/app-dir/css-order/css-order.test.ts b/test/e2e/app-dir/css-order/css-order.test.ts
new file mode 100644
index 0000000000000..8b5afb3d2a54d
--- /dev/null
+++ b/test/e2e/app-dir/css-order/css-order.test.ts
@@ -0,0 +1,144 @@
+import { createNextDescribe } from 'e2e-utils'
+
+function getPairs(all) {
+ const result = []
+
+ for (const first of all) {
+ for (const second of all) {
+ if (first === second) {
+ continue
+ }
+ result.push([first, second])
+ }
+ }
+
+ return result
+}
+
+const PAGES = {
+ first: {
+ url: '/first',
+ selector: '#hello1',
+ color: 'rgb(0, 0, 255)',
+ },
+ second: {
+ url: '/second',
+ selector: '#hello2',
+ color: 'rgb(0, 128, 0)',
+ },
+ third: {
+ url: '/third',
+ selector: '#hello3',
+ color: 'rgb(0, 128, 128)',
+ },
+ 'first-client': {
+ url: '/first-client',
+ selector: '#hello1c',
+ color: 'rgb(255, 0, 255)',
+ },
+ 'second-client': {
+ url: '/second-client',
+ selector: '#hello2c',
+ color: 'rgb(255, 128, 0)',
+ },
+}
+
+const allPairs = getPairs(Object.keys(PAGES))
+
+createNextDescribe(
+ 'css-order',
+ {
+ files: __dirname,
+ dependencies: {
+ sass: 'latest',
+ },
+ },
+ ({ next, isNextDev, isTurbopack }) => {
+ for (const ordering of allPairs) {
+ const name = `should load correct styles navigating back again ${ordering.join(
+ ' -> '
+ )} -> ${ordering.join(' -> ')}`
+ // TODO fix this case
+ const broken = isNextDev
+ if (broken) {
+ it.todo(name)
+ continue
+ }
+ it(name, async () => {
+ const start = PAGES[ordering[0]]
+ const browser = await next.browser(start.url)
+ const check = async (pageInfo) => {
+ expect(
+ await browser
+ .waitForElementByCss(pageInfo.selector)
+ .getComputedCss('color')
+ ).toBe(pageInfo.color)
+ }
+ const navigate = async (page) => {
+ await browser.waitForElementByCss('#' + page).click()
+ }
+ await check(start)
+ for (const page of ordering.slice(1)) {
+ await navigate(page)
+ await check(PAGES[page])
+ }
+ for (const page of ordering) {
+ await navigate(page)
+ await check(PAGES[page])
+ }
+ })
+ }
+ for (const ordering of allPairs) {
+ const name = `should load correct styles navigating ${ordering.join(
+ ' -> '
+ )}`
+ // TODO fix this case
+ const broken =
+ isNextDev &&
+ !isTurbopack &&
+ ordering.some(
+ (page) => page.includes('client') || page.includes('first')
+ )
+ if (broken) {
+ it.todo(name)
+ continue
+ }
+ it(name, async () => {
+ const start = PAGES[ordering[0]]
+ const browser = await next.browser(start.url)
+ const check = async (pageInfo) => {
+ expect(
+ await browser
+ .waitForElementByCss(pageInfo.selector)
+ .getComputedCss('color')
+ ).toBe(pageInfo.color)
+ }
+ const navigate = async (page) => {
+ await browser.waitForElementByCss('#' + page).click()
+ }
+ await check(start)
+ for (const page of ordering.slice(1)) {
+ await navigate(page)
+ await check(PAGES[page])
+ }
+ })
+ }
+ for (const [page, pageInfo] of Object.entries(PAGES)) {
+ const name = `should load correct styles on ${page}`
+ // TODO fix this case
+ const broken = isNextDev && !isTurbopack && page.includes('client')
+ if (broken) {
+ it.todo(name)
+ continue
+ }
+ it(name, async () => {
+ const browser = await next.browser(pageInfo.url)
+ expect(
+ await browser
+ .waitForElementByCss(pageInfo.selector)
+ .getComputedCss('color')
+ ).toBe(pageInfo.color)
+ })
+ }
+ }
+)
diff --git a/test/e2e/app-dir/css-order/next.config.js b/test/e2e/app-dir/css-order/next.config.js
new file mode 100644
index 0000000000000..807126e4cf0bf
--- /dev/null
+++ b/test/e2e/app-dir/css-order/next.config.js
@@ -0,0 +1,6 @@
+/**
+ * @type {import('next').NextConfig}
+ */
+const nextConfig = {}
+
+module.exports = nextConfig
diff --git a/test/turbopack-tests-manifest.json b/test/turbopack-tests-manifest.json
index 6620b1141bd96..3a948a3b1c049 100644
--- a/test/turbopack-tests-manifest.json
+++ b/test/turbopack-tests-manifest.json
@@ -3233,6 +3233,39 @@
"flakey": [],
"runtimeError": false
},
+ "test/e2e/app-dir/css-order/css-order.test.ts": {
+ "passed": [
+ "css-order should load correct styles navigating first -> first-client",
+ "css-order should load correct styles navigating first -> second-client",
+ "css-order should load correct styles navigating first -> second",
+ "css-order should load correct styles navigating first -> third",
+ "css-order should load correct styles navigating first-client -> first",
+ "css-order should load correct styles navigating first-client -> second-client",
+ "css-order should load correct styles navigating first-client -> second",
+ "css-order should load correct styles navigating first-client -> third",
+ "css-order should load correct styles navigating second -> first-client",
+ "css-order should load correct styles navigating second -> first",
+ "css-order should load correct styles navigating second -> second-client",
+ "css-order should load correct styles navigating second -> third",
+ "css-order should load correct styles navigating second-client -> first-client",
+ "css-order should load correct styles navigating second-client -> first",
+ "css-order should load correct styles navigating second-client -> second",
+ "css-order should load correct styles navigating second-client -> third",
+ "css-order should load correct styles navigating third -> first-client",
+ "css-order should load correct styles navigating third -> first",
+ "css-order should load correct styles navigating third -> second-client",
+ "css-order should load correct styles navigating third -> second",
+ "css-order should load correct styles on first-client",
+ "css-order should load correct styles on first",
+ "css-order should load correct styles on second-client",
+ "css-order should load correct styles on second",
+ "css-order should load correct styles on third"
+ ],
+ "failed": [],
+ "pending": [],
+ "flakey": [],
+ "runtimeError": false
+ },
"test/e2e/app-dir/dynamic-data/dynamic-data.test.ts": {
"passed": [
"dynamic-data inside cache scope displays redbox when accessing dynamic data inside a cache scope",