Skip to content

Commit

Permalink
experimental: css inlining
Browse files Browse the repository at this point in the history
  • Loading branch information
gaojude committed Nov 1, 2024
1 parent a44a0d9 commit e6cfb50
Show file tree
Hide file tree
Showing 12 changed files with 93 additions and 8 deletions.
1 change: 1 addition & 0 deletions packages/next/src/export/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,7 @@ async function exportAppImpl(
expireTime: nextConfig.expireTime,
after: nextConfig.experimental.after ?? false,
dynamicIO: nextConfig.experimental.dynamicIO ?? false,
inlineCss: nextConfig.experimental.inlineCss ?? false,
},
reactMaxHeadersLength: nextConfig.reactMaxHeadersLength,
}
Expand Down
35 changes: 27 additions & 8 deletions packages/next/src/server/app-render/get-layer-assets.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import type { AppRenderContext } from './app-render'
import { getAssetQueryString } from './get-asset-query-string'
import { encodeURIPath } from '../../shared/lib/encode-uri-path'
import type { PreloadCallbacks } from './types'
import path from 'path'
import { readFileSync } from 'fs'

export function getLayerAssets({
ctx,
Expand Down Expand Up @@ -74,6 +76,15 @@ export function getLayerAssets({

const styles = styleTags
? styleTags.map((href, index) => {
// `Precedence` is an opt-in signal for React to handle resource
// loading and deduplication, etc. It's also used as the key to sort
// resources so they will be injected in the correct order.
// During HMR, it's critical to use different `precedence` values
// for different stylesheets, so their order will be kept.
// https://github.com/facebook/react/pull/25060
const precedence =
process.env.NODE_ENV === 'development' ? 'next_' + href : 'next'

// In dev, Safari and Firefox will cache the resource during HMR:
// - https://github.com/vercel/next.js/issues/5860
// - https://bugs.webkit.org/show_bug.cgi?id=187726
Expand All @@ -84,14 +95,22 @@ export function getLayerAssets({
href
)}${getAssetQueryString(ctx, true)}`

// `Precedence` is an opt-in signal for React to handle resource
// loading and deduplication, etc. It's also used as the key to sort
// resources so they will be injected in the correct order.
// During HMR, it's critical to use different `precedence` values
// for different stylesheets, so their order will be kept.
// https://github.com/facebook/react/pull/25060
const precedence =
process.env.NODE_ENV === 'development' ? 'next_' + href : 'next'
if (ctx.renderOpts.experimental.inlineCss) {
return (
<style
key={index}
dangerouslySetInnerHTML={{
__html: String(
readFileSync(path.join(ctx.renderOpts.distDir ?? '', href))
),
}}
nonce={ctx.nonce}
// @ts-ignore
precedence={precedence}
data-href={href}
/>
)
}

preloadCallbacks.push(() => {
ctx.componentMod.preloadStyle(
Expand Down
2 changes: 2 additions & 0 deletions packages/next/src/server/app-render/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ export type ServerOnInstrumentationRequestError = (
) => void | Promise<void>

export interface RenderOptsPartial {
distDir?: string
previewProps: __ApiPreviewProps | undefined
err?: Error | null
dev?: boolean
Expand Down Expand Up @@ -182,6 +183,7 @@ export interface RenderOptsPartial {
clientTraceMetadata: string[] | undefined
after: boolean
dynamicIO: boolean
inlineCss: boolean
}
postponed?: string
/**
Expand Down
2 changes: 2 additions & 0 deletions packages/next/src/server/base-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -603,6 +603,7 @@ export default abstract class Server<
clientTraceMetadata: this.nextConfig.experimental.clientTraceMetadata,
after: this.nextConfig.experimental.after ?? false,
dynamicIO: this.nextConfig.experimental.dynamicIO ?? false,
inlineCss: this.nextConfig.experimental.inlineCss ?? false,
},
onInstrumentationRequestError:
this.instrumentationOnRequestError.bind(this),
Expand Down Expand Up @@ -2493,6 +2494,7 @@ export default abstract class Server<
onAfterTaskError: undefined,
// only available in dev
setAppIsrStatus: (this as any).setAppIsrStatus,
distDir: this.distDir,
}

if (isDebugStaticShell || isDebugDynamicAccesses) {
Expand Down
1 change: 1 addition & 0 deletions packages/next/src/server/config-schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,7 @@ export const configSchema: zod.ZodType<NextConfig> = z.lazy(() =>
disableOptimizedLoading: z.boolean().optional(),
disablePostcssPresetEnv: z.boolean().optional(),
dynamicIO: z.boolean().optional(),
inlineCss: z.boolean().optional(),
esmExternals: z.union([z.boolean(), z.literal('loose')]).optional(),
serverActions: z
.object({
Expand Down
5 changes: 5 additions & 0 deletions packages/next/src/server/config-shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -556,6 +556,11 @@ export interface ExperimentalConfig {
* unless explicitly cached.
*/
dynamicIO?: boolean

/**
* Render <style> tags inline in the HTML for imported CSS assets.
*/
inlineCss?: boolean
}

export type ExportPathMap = {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
p {
color: yellow;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export default function RootLayout({ children }) {
return (
<html>
<body>{children}</body>
</html>
)
}
5 changes: 5 additions & 0 deletions test/e2e/app-dir/app-css-experimental-inline-css/app/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import './global.css'

export default function Home() {
return <p>Home</p>
}
22 changes: 22 additions & 0 deletions test/e2e/app-dir/app-css-experimental-inline-css/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { nextTestSetup } from 'e2e-utils'

describe('app dir - css - experimental inline css', () => {
const { next, skipped } = nextTestSetup({
files: __dirname,
skipDeployment: true,
})

if (skipped) {
return
}

it('should render page with correct styles', async () => {
const browser = await next.browser('/')

const inlineStyleTag = await browser.elementByCss('style')
expect(await inlineStyleTag.text()).toContain('color: yellow;')

const p = await browser.elementByCss('p')
expect(await p.getComputedCss('color')).toBe('rgb(255, 255, 0)') // yellow
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module.exports = {
experimental: {
inlineCss: true,
},
}
13 changes: 13 additions & 0 deletions test/e2e/app-dir/app-css/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -870,6 +870,19 @@ describe('app dir - css', () => {
}
})

describe('inline CSS', () => {
it('should support css modules inside server layouts', async () => {
const browser = await next.browser('/css/css-nested')
await check(
async () =>
await browser.eval(
`window.getComputedStyle(document.querySelector('#server-cssm')).color`
),
'rgb(0, 128, 0)'
)
})
})

if (isNextDev) {
describe('Suspensey CSS', () => {
it('should suspend on CSS imports if its slow on client navigation', async () => {
Expand Down

0 comments on commit e6cfb50

Please sign in to comment.