未验证 提交 1a7f3e51 编写于 作者: K khades 提交者: GitHub

Store css file dependencies info for dynamic imports and apply it at SSR (#12843)

To prevent FOUC, discussed in #10557 i need to store information about css file dependencies for chunk. Right now current implementation just throws away everything but js.

Can there be more than one css file in chunk? If no - code will be simplified.

closes #10557 
上级 735aab6f
......@@ -22,9 +22,9 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWAR
// Modified to strip out unneeded results for Next's specific use case
import webpack, {
Compiler,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
compilation as CompilationType,
Compiler,
} from 'webpack'
import sources from 'webpack-sources'
......@@ -65,7 +65,12 @@ function buildManifest(
chunkGroup.chunks.forEach((chunk: any) => {
chunk.files.forEach((file: string) => {
if (!file.match(/\.js$/) || !file.match(/^static\/chunks\//)) {
if (
!(
(file.endsWith('.js') || file.endsWith('.css')) &&
file.match(/^static\/(chunks|css)\//)
)
) {
return
}
......@@ -85,10 +90,7 @@ function buildManifest(
continue
}
manifest[request].push({
id,
file,
})
manifest[request].push({ id, file })
}
})
})
......
......@@ -25,9 +25,9 @@ export type OriginProps = {
crossOrigin?: string
}
function dedupe(bundles: any[]): any[] {
const files = new Set()
const kept = []
function dedupe<T extends { file: string }>(bundles: T[]): T[] {
const files = new Set<string>()
const kept: T[] = []
for (const bundle of bundles) {
if (files.has(bundle.file)) continue
......@@ -157,10 +157,25 @@ export class Head extends Component<
context!: React.ContextType<typeof DocumentComponentContext>
getCssLinks(files: DocumentFiles): JSX.Element[] | null {
const { assetPrefix, devOnlyCacheBusterQueryString } = this.context
const {
assetPrefix,
devOnlyCacheBusterQueryString,
dynamicImports,
} = this.context
const cssFiles = files.allFiles.filter((f) => f.endsWith('.css'))
const sharedFiles = new Set(files.sharedFiles)
let dynamicCssFiles = dedupe(
dynamicImports.filter((f) => f.file.endsWith('.css'))
).map((f) => f.file)
if (dynamicCssFiles.length) {
const existing = new Set(cssFiles)
dynamicCssFiles = dynamicCssFiles.filter(
(f) => !(existing.has(f) || sharedFiles.has(f))
)
cssFiles.push(...dynamicCssFiles)
}
const cssLinkElements: JSX.Element[] = []
cssFiles.forEach((file) => {
const isSharedFile = sharedFiles.has(file)
......@@ -193,7 +208,6 @@ export class Head extends Component<
/>
)
})
return cssLinkElements.length === 0 ? null : cssLinkElements
}
......@@ -206,7 +220,7 @@ export class Head extends Component<
return (
dedupe(dynamicImports)
.map((bundle: any) => {
.map((bundle) => {
// `dynamicImports` will contain both `.js` and `.module.js` when the
// feature is enabled. This clause will filter down to the modern
// variants only.
......@@ -540,7 +554,7 @@ export class NextScript extends Component<OriginProps> {
devOnlyCacheBusterQueryString,
} = this.context
return dedupe(dynamicImports).map((bundle: any) => {
return dedupe(dynamicImports).map((bundle) => {
let modernProps = {}
if (process.env.__NEXT_MODERN_BUILD) {
modernProps = bundle.file.endsWith('.module.js')
......
import styles from './with-css-1.module.css'
export default () => <p className={styles.content}>With CSS 1</p>
import styles from './with-css-2.module.css'
export default () => <p className={styles.content}>With CSS 2</p>
import styles from './with-css-3.module.css'
export default () => <p className={styles.content}>With CSS 3</p>
import styles from './with-css.module.css'
import styles2 from './with-css-2.module.css'
export default () => (
<div className={styles.content}>
<p className={styles2.text}>With CSS</p>
</div>
)
import styles2 from './with-css-2.module.css'
export default () => <p className={styles2.text}>With CSS</p>
import styles from './with-css.module.css'
import Nested from './Nested'
export default () => (
<div className={styles.content}>
<Nested />
</div>
)
import styles from './with-css-2.module.css'
import stylesShared from './with-css-shared.module.css'
export default () => (
<div className={styles.content}>
<p className={stylesShared.test}>With CSS</p>
</div>
)
import styles from './with-css.module.css'
import stylesShared from './with-css-shared.module.css'
export default () => (
<div className={styles.content}>
<p className={stylesShared.test}>With CSS</p>
</div>
)
import styles from './with-css.module.css'
export default () => <p className={styles.content}>With CSS</p>
import dynamic from 'next/dynamic'
const Hello = dynamic(import('../../components/dynamic-css/with-css'))
export default Hello
import dynamic from 'next/dynamic'
const Component = dynamic(
import('../../components/dynamic-css/many-modules/with-css')
)
export default Component
import dynamic from 'next/dynamic'
const First = dynamic(
import('../../components/dynamic-css/many-imports/with-css-1')
)
const Second = dynamic(
import('../../components/dynamic-css/many-imports/with-css-2')
)
const Third = dynamic(
import('../../components/dynamic-css/many-imports/with-css-3')
)
export default function Page() {
return (
<div>
<First />
<Second />
<Third />
</div>
)
}
import dynamic from 'next/dynamic'
const Component = dynamic(
import('../../components/dynamic-css/nested/with-css')
)
export default Component
import dynamic from 'next/dynamic'
const Hello = dynamic(import('../../components/dynamic-css/no-css'))
export default Hello
import dynamic from 'next/dynamic'
const First = dynamic(
import('../../components/dynamic-css/shared-css-module/with-css')
)
const Second = dynamic(
import('../../components/dynamic-css/shared-css-module/with-css-2')
)
export default function Page() {
return (
<div>
<First />
<Second />
</div>
)
}
......@@ -16,6 +16,43 @@ export default (context, render) => {
expect($('body').text()).toMatch(/Hello World 1/)
})
it('should render one dynamically imported component and load its css files', async () => {
const $ = await get$('/dynamic/css')
const cssFiles = $('link[rel=stylesheet]')
expect(cssFiles.length).toBe(1)
})
it('should render three dynamically imported components and load their css files', async () => {
const $ = await get$('/dynamic/many-dynamic-css')
const cssFiles = $('link[rel=stylesheet]')
expect(cssFiles.length).toBe(3)
})
it('should bundle two css modules for one dynamically imported component into one css file', async () => {
const $ = await get$('/dynamic/many-css-modules')
const cssFiles = $('link[rel=stylesheet]')
expect(cssFiles.length).toBe(1)
})
it('should bundle two css modules for nested components into one css file', async () => {
const $ = await get$('/dynamic/nested-css')
const cssFiles = $('link[rel=stylesheet]')
expect(cssFiles.length).toBe(1)
})
// It seem to be abnormal, dynamic CSS modules are completely self-sufficient, so shared styles are copied across files
it('should output two css files even in case of three css module files while one is shared across files', async () => {
const $ = await get$('/dynamic/shared-css-module')
const cssFiles = $('link[rel=stylesheet]')
expect(cssFiles.length).toBe(2)
})
it('should render one dynamically imported component without any css files', async () => {
const $ = await get$('/dynamic/no-css')
const cssFiles = $('link[rel=stylesheet]')
expect(cssFiles.length).toBe(0)
})
it('should render even there are no physical chunk exists', async () => {
let browser
try {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册