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

Global CSS Support #8710

Merged
merged 35 commits into from Sep 17, 2019
Merged
Show file tree
Hide file tree
Changes from 34 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
63ab22e
Global CSS Support
Timer Sep 11, 2019
6954e34
Fix webpack configuration
Timer Sep 11, 2019
ae6674a
oneOf rule isn't necessary yet
Timer Sep 11, 2019
a149ab7
Adjust CSS chunk naming
Timer Sep 11, 2019
76f57be
Begin testing CSS behavior
Timer Sep 11, 2019
ce594b9
Add another test TODO
Timer Sep 11, 2019
2fd94e4
Merge branch 'canary' into global-css
Timer Sep 11, 2019
cefc374
Replace null-loader with ignore-loader
Timer Sep 11, 2019
e242d8d
Turn on chunks for new CSS feature
Timer Sep 11, 2019
bce44e8
Merge branch 'canary' into global-css
Timer Sep 12, 2019
d22d5bb
Fix multi test suite
Timer Sep 12, 2019
9c0da3a
Test CSS import order
Timer Sep 12, 2019
95b1d08
Test style HMR
Timer Sep 12, 2019
9a7b79a
Test CSS compilation
Timer Sep 12, 2019
d890f82
Test compilation and prefixing together
Timer Sep 12, 2019
d81e02e
Verify CSS styling works for Development and Production
Timer Sep 12, 2019
0afd8ad
Add missing TODO
Timer Sep 12, 2019
5e6517b
Remove unnecessary test
Timer Sep 12, 2019
3e834a5
Adjust TODO message
Timer Sep 12, 2019
898d4e0
Hide page until React hydrates
Timer Sep 12, 2019
9bef0ef
Revert "Hide page until React hydrates"
Timer Sep 12, 2019
598c81e
Merge branch 'canary' into global-css
Timer Sep 12, 2019
66c2dbb
Hide FOUC during development
Timer Sep 12, 2019
3244161
Test CSS imports
Timer Sep 12, 2019
f71a8cb
Update tests TODO
Timer Sep 12, 2019
c387fac
Add fixture for url() test
Timer Sep 12, 2019
bd64ad4
Test `file-loader` support in CSS files
Timer Sep 12, 2019
a80eb72
Merge branch 'canary' into global-css
Timer Sep 12, 2019
3534892
Merge branch 'canary' into global-css
Timer Sep 12, 2019
0f977e5
Use a simple variant of cssnano
Timer Sep 12, 2019
3e00d28
Self-import
Timer Sep 12, 2019
58098af
Undo bundling
Timer Sep 12, 2019
9684173
Merge branch 'canary' into global-css
ijjk Sep 13, 2019
e28f26f
Merge branch 'canary' into global-css
Timer Sep 17, 2019
a953442
Implement suggestion
Timer Sep 17, 2019
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
106 changes: 105 additions & 1 deletion packages/next/build/webpack-config.ts
@@ -1,5 +1,7 @@
import crypto from 'crypto'
import ForkTsCheckerWebpackPlugin from 'fork-ts-checker-webpack-plugin'
import MiniCssExtractPlugin from 'mini-css-extract-plugin'
import OptimizeCssAssetsPlugin from 'optimize-css-assets-webpack-plugin'
import path from 'path'
// @ts-ignore: Currently missing types
import PnpWebpackPlugin from 'pnp-webpack-plugin'
Expand All @@ -20,6 +22,7 @@ import {
SERVER_DIRECTORY,
SERVERLESS_DIRECTORY,
} from '../next-server/lib/constants'
import { findPageFile } from '../server/lib/find-page-file'
import { WebpackEntrypoints } from './entries'
import BuildManifestPlugin from './webpack/plugins/build-manifest-plugin'
import { ChunkGraphPlugin } from './webpack/plugins/chunk-graph-plugin'
Expand Down Expand Up @@ -291,6 +294,15 @@ export default async function getBaseWebpackConfig(
? 'anonymous'
: config.crossOrigin

let customAppFile = await findPageFile(
Timer marked this conversation as resolved.
Show resolved Hide resolved
path.join(dir, 'pages'),
'/_app',
config.pageExtensions
)
if (customAppFile) {
customAppFile = path.resolve(path.join(dir, 'pages', customAppFile))
}

let webpackConfig: webpack.Configuration = {
devtool,
mode: webpackMode,
Expand Down Expand Up @@ -419,11 +431,26 @@ export default async function getBaseWebpackConfig(
: { name: CLIENT_STATIC_FILES_RUNTIME_WEBPACK },
minimize: !(dev || isServer),
minimizer: [
// Minify JavaScript
new TerserPlugin({
...terserPluginConfig,
terserOptions,
}),
],
// Minify CSS
config.experimental.css &&
new OptimizeCssAssetsPlugin({
cssProcessorOptions: {
map: {
// `inline: false` generates the source map in a separate file.
// Otherwise, the CSS file is needlessly large.
inline: false,
// `annotation: true` appends the `sourceMappingURL` to the end
// of the CSS file for DevTools to find them.
annotation: true,
},
},
}),
].filter(Boolean),
},
recordsPath: path.join(outputPath, 'records.json'),
context: dir,
Expand Down Expand Up @@ -518,6 +545,75 @@ export default async function getBaseWebpackConfig(
},
use: defaultLoaders.babel,
},
config.experimental.css &&
// Support CSS imports
({
test: /\.css$/,
issuer: { include: [customAppFile].filter(Boolean) },
use: isServer
? // Global CSS is ignored on the server because it's only needed
// on the client-side.
require.resolve('ignore-loader')
: [
// During development we load CSS via JavaScript so we can
// hot reload it without refreshing the page.
dev && require.resolve('style-loader'),
// When building for production we extract CSS into
// separate files.
!dev && {
loader: MiniCssExtractPlugin.loader,
options: {},
},

// Resolve CSS `@import`s and `url()`s
{
loader: require.resolve('css-loader'),
options: { importLoaders: 1, sourceMap: !dev },
},

// Compile CSS
{
loader: require.resolve('postcss-loader'),
options: {
ident: 'postcss',
plugins: () => [
// Make Flexbox behave like the spec cross-browser.
require('postcss-flexbugs-fixes'),
// Run Autoprefixer and compile new CSS features.
require('postcss-preset-env')({
autoprefixer: {
// Disable legacy flexbox support
flexbox: 'no-2009',
},
// Enable CSS features that have shipped to the
// web platform, i.e. in 2+ browsers unflagged.
stage: 3,
}),
],
sourceMap: !dev,
},
},
].filter(Boolean),
// A global CSS import always has side effects. Webpack will tree
// shake the CSS without this option if the issuer claims to have
// no side-effects.
// See https://github.com/webpack/webpack/issues/6571
sideEffects: true,
} as webpack.RuleSetRule),
config.experimental.css &&
({
loader: require.resolve('file-loader'),
issuer: {
// file-loader is only used for CSS files, e.g. url() for a SVG
// or font files
test: /\.css$/,
},
// Exclude extensions that webpack handles by default
exclude: [/\.(js|mjs|jsx|ts|tsx)$/, /\.html$/, /\.json$/],
options: {
name: 'static/media/[name].[hash].[ext]',
},
} as webpack.RuleSetRule),
].filter(Boolean),
},
plugins: [
Expand Down Expand Up @@ -652,6 +748,14 @@ export default async function getBaseWebpackConfig(
clientManifest: config.experimental.granularChunks,
modern: config.experimental.modern,
}),
// Extract CSS as CSS file(s) in the client-side production bundle.
config.experimental.css &&
!isServer &&
!dev &&
new MiniCssExtractPlugin({
filename: 'static/css/[contenthash].css',
chunkFilename: 'static/css/[contenthash].chunk.css',
}),
tracer &&
new ProfilingPlugin({
tracer,
Expand Down
22 changes: 20 additions & 2 deletions packages/next/client/index.js
Expand Up @@ -238,10 +238,28 @@ function renderReactElement (reactEl, domEl) {

// The check for `.hydrate` is there to support React alternatives like preact
if (isInitialRender) {
ReactDOM.hydrate(reactEl, domEl, markHydrateComplete)
ReactDOM.hydrate(reactEl, domEl, function () {
if (process.env.NODE_ENV !== 'production') {
document
.querySelectorAll('[data-next-hydrating]')
.forEach(function (el) {
el.remove()
})
}
markHydrateComplete()
})
isInitialRender = false
} else {
ReactDOM.render(reactEl, domEl, markRenderComplete)
ReactDOM.render(reactEl, domEl, function () {
if (process.env.NODE_ENV !== 'production') {
document
.querySelectorAll('[data-next-hydrating]')
.forEach(function (el) {
el.remove()
})
}
markRenderComplete()
})
}
}

Expand Down
2 changes: 2 additions & 0 deletions packages/next/next-server/lib/utils.ts
Expand Up @@ -140,6 +140,8 @@ export type DocumentProps = DocumentInitialProps & {
inAmpMode: boolean
hybridAmp: boolean
staticMarkup: boolean
isDevelopment: boolean
hasCssMode: boolean
devFiles: string[]
files: string[]
dynamicImports: ManifestItem[]
Expand Down
1 change: 1 addition & 0 deletions packages/next/next-server/server/config.ts
Expand Up @@ -40,6 +40,7 @@ const defaultConfig: { [key: string]: any } = {
(Number(process.env.CIRCLE_NODE_TOTAL) ||
(os.cpus() || { length: 1 }).length) - 1
),
css: false,
documentMiddleware: false,
granularChunks: false,
modern: false,
Expand Down
2 changes: 2 additions & 0 deletions packages/next/next-server/server/next-server.ts
Expand Up @@ -76,6 +76,7 @@ export default class Server {
assetPrefix?: string
canonicalBase: string
documentMiddlewareEnabled: boolean
hasCssMode: boolean
dev?: boolean
}
private compression?: Middleware
Expand Down Expand Up @@ -121,6 +122,7 @@ export default class Server {
canonicalBase: this.nextConfig.amp.canonicalBase,
documentMiddlewareEnabled: this.nextConfig.experimental
.documentMiddleware,
hasCssMode: this.nextConfig.experimental.css,
staticMarkup,
buildId: this.buildId,
generateEtags,
Expand Down
4 changes: 4 additions & 0 deletions packages/next/next-server/server/render.tsx
Expand Up @@ -130,6 +130,7 @@ type RenderOpts = {
runtimeConfig?: { [key: string]: any }
dangerousAsPath: string
assetPrefix?: string
hasCssMode: boolean
err?: Error | null
autoExport?: boolean
nextExport?: boolean
Expand Down Expand Up @@ -168,6 +169,7 @@ function renderDocument(
skeleton,
dynamicImportsIds,
dangerousAsPath,
hasCssMode,
err,
dev,
ampPath,
Expand Down Expand Up @@ -219,6 +221,8 @@ function renderDocument(
canonicalBase={canonicalBase}
ampPath={ampPath}
inAmpMode={inAmpMode}
isDevelopment={!!dev}
hasCssMode={hasCssMode}
hybridAmp={hybridAmp}
staticMarkup={staticMarkup}
devFiles={devFiles}
Expand Down
11 changes: 11 additions & 0 deletions packages/next/package.json
Expand Up @@ -84,20 +84,28 @@
"conf": "5.0.0",
"content-type": "1.0.4",
"cookie": "0.4.0",
"css-loader": "3.2.0",
"devalue": "2.0.0",
"etag": "1.8.1",
"file-loader": "4.2.0",
"find-up": "4.0.0",
"fork-ts-checker-webpack-plugin": "1.3.4",
"fresh": "0.5.2",
"ignore-loader": "0.1.2",
"is-docker": "2.0.0",
"jest-worker": "24.9.0",
"launch-editor": "2.2.1",
"loader-utils": "1.2.3",
"mini-css-extract-plugin": "0.8.0",
"mkdirp": "0.5.1",
"node-fetch": "2.6.0",
"optimize-css-assets-webpack-plugin": "5.0.3",
"ora": "3.4.0",
"path-to-regexp": "2.1.0",
"pnp-webpack-plugin": "1.5.0",
"postcss-flexbugs-fixes": "4.1.0",
"postcss-loader": "3.0.0",
"postcss-preset-env": "6.7.0",
"prop-types": "15.7.2",
"prop-types-exact": "1.2.0",
"raw-body": "2.4.0",
Expand All @@ -107,6 +115,7 @@
"source-map": "0.6.1",
"string-hash": "1.1.3",
"strip-ansi": "5.2.0",
"style-loader": "1.0.0",
"styled-jsx": "3.2.2",
"terser": "4.0.0",
"unfetch": "4.1.0",
Expand Down Expand Up @@ -141,9 +150,11 @@
"@types/find-up": "2.1.1",
"@types/fresh": "0.5.0",
"@types/loader-utils": "1.1.3",
"@types/mini-css-extract-plugin": "0.8.0",
"@types/mkdirp": "0.5.2",
"@types/nanoid": "2.0.0",
"@types/node-fetch": "2.3.4",
"@types/optimize-css-assets-webpack-plugin": "5.0.0",
"@types/react": "16.8.18",
"@types/react-dom": "16.8.4",
"@types/react-is": "16.7.1",
Expand Down
19 changes: 19 additions & 0 deletions packages/next/pages/_document.tsx
Expand Up @@ -344,6 +344,25 @@ export class Head extends Component<

return (
<head {...this.props}>
{this.context._documentProps.isDevelopment &&
this.context._documentProps.hasCssMode && (
<>
<style
data-next-hydrating
dangerouslySetInnerHTML={{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the reason for using dangerouslySetInnerHTML here rather than a text node?

__html: `body{-webkit-animation:-next-hydrating 3s steps(1,end) 0s 1 normal both;-moz-animation:-next-hydrating 3s steps(1,end) 0s 1 normal both;-ms-animation:-next-hydrating 3s steps(1,end) 0s 1 normal both;animation:-next-hydrating 3s steps(1,end) 0s 1 normal both}@-webkit-keyframes -next-hydrating{from{visibility:hidden}to{visibility:visible}}@-moz-keyframes -next-hydrating{from{visibility:hidden}to{visibility:visible}}@-ms-keyframes -next-hydrating{from{visibility:hidden}to{visibility:visible}}@-o-keyframes -next-hydrating{from{visibility:hidden}to{visibility:visible}}@keyframes -next-hydrating{from{visibility:hidden}to{visibility:visible}}`,
}}
/>
<noscript>
<style
data-next-hydrating
dangerouslySetInnerHTML={{
__html: `body{-webkit-animation:none;-moz-animation:none;-ms-animation:none;animation:none}`,
}}
/>
</noscript>
</>
)}
{children}
{head}
<meta
Expand Down
@@ -0,0 +1,12 @@
import React from 'react'
import App from 'next/app'
import '../styles/global.css'

class MyApp extends App {
render () {
const { Component, pageProps } = this.props
return <Component {...pageProps} />
}
}

export default MyApp
@@ -0,0 +1,3 @@
export default function Home () {
return <div className='red-text'>This text should be red.</div>
}
@@ -0,0 +1,5 @@
@media (480px <= width < 768px) {
::placeholder {
color: green;
}
}
@@ -0,0 +1,11 @@
import React from 'react'
import App from 'next/app'

class MyApp extends App {
render () {
const { Component, pageProps } = this.props
return <Component {...pageProps} />
}
}

export default MyApp
@@ -0,0 +1,5 @@
import '../styles/global.css'

export default function Home () {
return <div className='red-text'>This text should be red.</div>
}
@@ -0,0 +1,3 @@
.red-text {
color: red;
}
5 changes: 5 additions & 0 deletions test/integration/css/fixtures/invalid-global/pages/index.js
@@ -0,0 +1,5 @@
import '../styles/global.css'

export default function Home () {
return <div className='red-text'>This text should be red.</div>
}
@@ -0,0 +1,3 @@
.red-text {
color: red;
}
@@ -0,0 +1,13 @@
import React from 'react'
import App from 'next/app'
import '../styles/global2.css'
import '../styles/global1.css'

class MyApp extends App {
render () {
const { Component, pageProps } = this.props
return <Component {...pageProps} />
}
}

export default MyApp
@@ -0,0 +1,3 @@
export default function Home () {
return <div className='red-text'>This text should be red.</div>
}
@@ -0,0 +1,3 @@
.red-text {
color: red;
}
@@ -0,0 +1,3 @@
.blue-text {
color: blue;
}