Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 41 additions & 1 deletion docs/basic-features/built-in-css-support.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,47 @@ In production, all CSS files will be automatically concatenated into a single mi

### Import styles from `node_modules`

If you’d like to import CSS files from `node_modules`, you must do so inside `pages/_app.js`.
Importing a CSS file from `node_modules` is permitted in anywhere your application.

For global stylesheets, like `bootstrap` or `nprogress`, you should import the file inside `pages/_app.js`.
For example:

```jsx
// pages/_app.js
import 'bootstrap/dist/css/bootstrap.css'

export default function MyApp({ Component, pageProps }) {
return <Component {...pageProps} />
}
```

For importing CSS required by a third party component, you can do so in your component. For example:

```tsx
// components/ExampleDialog.js
import { useState } from 'react'
import { Dialog } from '@reach/dialog'
import '@reach/dialog/styles.css'

function ExampleDialog(props) {
const [showDialog, setShowDialog] = useState(false)
const open = () => setShowDialog(true)
const close = () => setShowDialog(false)

return (
<div>
<button onClick={open}>Open Dialog</button>
<Dialog isOpen={showDialog} onDismiss={close}>
<button className="close-button" onClick={close}>
<VisuallyHidden>Close</VisuallyHidden>
<span aria-hidden>×</span>
</button>
<p>Hello there. I am a dialog</p>
</Dialog>
</div>
)
}
```

## Adding Component-Level CSS

Expand Down
77 changes: 54 additions & 23 deletions packages/next/build/webpack/config/blocks/css/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ export const css = curry(async function css(
loader({
oneOf: [
{
test: [regexCssModules, regexSassModules].filter(Boolean),
test: [regexCssModules, regexSassModules],
use: {
loader: 'error-loader',
options: {
Expand All @@ -171,13 +171,13 @@ export const css = curry(async function css(
loader({
oneOf: [
{
test: [regexCssGlobal, regexSassGlobal].filter(Boolean),
test: [regexCssGlobal, regexSassGlobal],
use: require.resolve('next/dist/compiled/ignore-loader'),
},
],
})
)
} else if (ctx.customAppFile) {
} else {
fns.push(
loader({
oneOf: [
Expand All @@ -188,36 +188,67 @@ export const css = curry(async function css(
// See https://github.com/webpack/webpack/issues/6571
sideEffects: true,
test: regexCssGlobal,
issuer: { and: [ctx.customAppFile] },
// We only allow Global CSS to be imported anywhere in the
// application if it comes from node_modules. This is a best-effort
// heuristic that makes a safety trade-off for better
// interoperability with npm packages that require CSS. Without
// this ability, the component's CSS would have to be included for
// the entire app instead of specific page where it's required.
include: { and: [/node_modules/] },
// Global CSS is only supported in the user's application, not in
// node_modules.
issuer: {
and: [ctx.rootDirectory],
not: [/node_modules/],
},
use: getGlobalCssLoader(ctx, postCssPlugins),
},
],
})
)
fns.push(
loader({
oneOf: [
{
// A global Sass import always has side effects. Webpack will tree
// shake the Sass without this option if the issuer claims to have
// no side-effects.
// See https://github.com/webpack/webpack/issues/6571
sideEffects: true,
test: regexSassGlobal,
issuer: { and: [ctx.customAppFile] },
use: getGlobalCssLoader(ctx, postCssPlugins, sassPreprocessors),
},
],
})
)

if (ctx.customAppFile) {
fns.push(
loader({
oneOf: [
{
// 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,
test: regexCssGlobal,
issuer: { and: [ctx.customAppFile] },
use: getGlobalCssLoader(ctx, postCssPlugins),
},
],
})
)
fns.push(
loader({
oneOf: [
{
// A global Sass import always has side effects. Webpack will tree
// shake the Sass without this option if the issuer claims to have
// no side-effects.
// See https://github.com/webpack/webpack/issues/6571
sideEffects: true,
test: regexSassGlobal,
issuer: { and: [ctx.customAppFile] },
use: getGlobalCssLoader(ctx, postCssPlugins, sassPreprocessors),
},
],
})
)
}
}

// Throw an error for Global CSS used inside of `node_modules`
fns.push(
loader({
oneOf: [
{
test: [regexCssGlobal, regexSassGlobal].filter(Boolean),
test: [regexCssGlobal, regexSassGlobal],
issuer: { and: [/node_modules/] },
use: {
loader: 'error-loader',
Expand All @@ -235,7 +266,7 @@ export const css = curry(async function css(
loader({
oneOf: [
{
test: [regexCssGlobal, regexSassGlobal].filter(Boolean),
test: [regexCssGlobal, regexSassGlobal],
use: {
loader: 'error-loader',
options: {
Expand Down Expand Up @@ -294,7 +325,7 @@ export const css = curry(async function css(
// selector), this assumption is required to code-split CSS.
//
// If this warning were to trigger, it'd be unactionable by the user,
// but also not valid -- so we disable it.
// but likely not valid -- so we disable it.
ignoreOrder: true,
})
)
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import 'example/index.css'

function Home() {
return <div className="red-text">This should be red text.</div>
}

export default Home
29 changes: 29 additions & 0 deletions test/integration/css/test/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,35 @@ describe('CSS Support', () => {
})
})

describe('Valid Global CSS from npm', () => {
const appDir = join(fixturesDir, 'import-global-from-module')

beforeAll(async () => {
await remove(join(appDir, '.next'))
})

it('should compile successfully', async () => {
const { code, stdout } = await nextBuild(appDir, [], {
stdout: true,
})
expect(code).toBe(0)
expect(stdout).toMatch(/Compiled successfully/)
})

it(`should've emitted a single CSS file`, async () => {
const cssFolder = join(appDir, '.next/static/css')

const files = await readdir(cssFolder)
const cssFiles = files.filter((f) => /\.css$/.test(f))

expect(cssFiles.length).toBe(1)
const cssContent = await readFile(join(cssFolder, cssFiles[0]), 'utf8')
expect(
cssContent.replace(/\/\*.*?\*\//g, '').trim()
).toMatchInlineSnapshot(`".red-text{color:\\"red\\"}"`)
})
})

describe('Invalid Global CSS with Custom App', () => {
const appDir = join(fixturesDir, 'invalid-global-with-app')

Expand Down