diff --git a/docs/basic-features/built-in-css-support.md b/docs/basic-features/built-in-css-support.md
index 1bc6a1274659c..62425b01e4919 100644
--- a/docs/basic-features/built-in-css-support.md
+++ b/docs/basic-features/built-in-css-support.md
@@ -157,10 +157,22 @@ export default HelloWorld
Please see the [styled-jsx documentation](https://github.com/zeit/styled-jsx) for more examples.
-## Sass, Less, and Stylus Support
+## Sass Support
-To support importing `.scss`, `.less` or `.styl` files you can use the following plugins:
+Next.js allows you to import Sass using both the `.scss` and `.sass` extensions.
+You can use component-level Sass via CSS Modules and the `.module.scss` or `.module.sass` extension.
+
+Before you can use Next.js' built-in Sass support, be sure to install [`sass`](https://github.com/sass/sass):
+
+```bash
+npm install sass
+```
+
+Sass support has the same benefits and restrictions as the built-in CSS support detailed above.
+
+## Less and Stylus Support
+
+To support importing `.less` or `.styl` files you can use the following plugins:
-- [@zeit/next-sass](https://github.com/zeit/next-plugins/tree/master/packages/next-sass)
- [@zeit/next-less](https://github.com/zeit/next-plugins/tree/master/packages/next-less)
- [@zeit/next-stylus](https://github.com/zeit/next-plugins/tree/master/packages/next-stylus)
diff --git a/packages/next/build/webpack-config.ts b/packages/next/build/webpack-config.ts
index 777213d13dc68..ab38642e45bf4 100644
--- a/packages/next/build/webpack-config.ts
+++ b/packages/next/build/webpack-config.ts
@@ -834,6 +834,7 @@ export default async function getBaseWebpackConfig(
isDevelopment: dev,
isServer,
hasSupportCss: !!config.experimental.css,
+ hasSupportScss: !!config.experimental.scss,
assetPrefix: config.assetPrefix || '',
})
diff --git a/packages/next/build/webpack/config/blocks/css/index.ts b/packages/next/build/webpack/config/blocks/css/index.ts
index 3a7625eb2a4b4..0ba6ec7051db3 100644
--- a/packages/next/build/webpack/config/blocks/css/index.ts
+++ b/packages/next/build/webpack/config/blocks/css/index.ts
@@ -1,6 +1,6 @@
import curry from 'lodash.curry'
import path from 'path'
-import { Configuration } from 'webpack'
+import webpack, { Configuration } from 'webpack'
import MiniCssExtractPlugin from '../../../plugins/mini-css-extract-plugin'
import { loader, plugin } from '../../helpers'
import { ConfigurationContext, ConfigurationFn, pipe } from '../../utils'
@@ -13,13 +13,20 @@ import {
} from './messages'
import { getPostCssPlugins } from './plugins'
-// RegExps for Stylesheets
-const regexCssAll = /\.css$/
+// RegExps for all Style Sheet variants
+const regexLikeCss = /\.(css|scss|sass)$/
+
+// RegExps for Style Sheets
const regexCssGlobal = /(? {
@@ -35,6 +37,6 @@ export async function build(
: '',
}
- const fn = pipe(base(ctx), css(hasSupportCss, ctx))
+ const fn = pipe(base(ctx), css(hasSupportCss, hasSupportScss, ctx))
return fn(config)
}
diff --git a/packages/next/next-server/server/config.ts b/packages/next/next-server/server/config.ts
index 57a50d0df8b0b..564742f40bf6c 100644
--- a/packages/next/next-server/server/config.ts
+++ b/packages/next/next-server/server/config.ts
@@ -42,6 +42,7 @@ const defaultConfig: { [key: string]: any } = {
(os.cpus() || { length: 1 }).length) - 1
),
css: true,
+ scss: false,
documentMiddleware: false,
granularChunks: true,
modern: false,
diff --git a/packages/next/package.json b/packages/next/package.json
index 6203148e6577b..0a8d9f2f432b6 100644
--- a/packages/next/package.json
+++ b/packages/next/package.json
@@ -122,6 +122,8 @@
"raw-body": "2.4.0",
"react-error-overlay": "5.1.6",
"react-is": "16.8.6",
+ "resolve-url-loader": "3.1.1",
+ "sass-loader": "8.0.2",
"send": "0.17.1",
"source-map": "0.6.1",
"string-hash": "1.1.3",
diff --git a/test/integration/css-fixtures/nm-module/node_modules/example/index.module.css b/test/integration/css-fixtures/nm-module/node_modules/example/index.module.css
index f77fe0ef0bdbe..08a38e09ef8ea 100644
--- a/test/integration/css-fixtures/nm-module/node_modules/example/index.module.css
+++ b/test/integration/css-fixtures/nm-module/node_modules/example/index.module.css
@@ -1,3 +1,3 @@
.redText {
- color: 'red';
+ color: red;
}
diff --git a/test/integration/css-modules/test/index.test.js b/test/integration/css-modules/test/index.test.js
index 3c0d865fd44dc..e990a2e04624c 100644
--- a/test/integration/css-modules/test/index.test.js
+++ b/test/integration/css-modules/test/index.test.js
@@ -286,7 +286,7 @@ describe('Valid CSS Module Usage from within node_modules', () => {
const cssContent = await readFile(join(cssFolder, cssFiles[0]), 'utf8')
expect(cssContent.replace(/\/\*.*?\*\//g, '').trim()).toMatchInlineSnapshot(
- `".example_redText__1rb5g{color:\\"red\\"}"`
+ `".example_redText__1rb5g{color:red}"`
)
})
})
diff --git a/test/integration/scss-fixtures/3rd-party-module/pages/index.js b/test/integration/scss-fixtures/3rd-party-module/pages/index.js
new file mode 100644
index 0000000000000..57f5707a4c221
--- /dev/null
+++ b/test/integration/scss-fixtures/3rd-party-module/pages/index.js
@@ -0,0 +1,5 @@
+import { foo } from './index.module.scss'
+
+export default function Home() {
+ return
+}
diff --git a/test/integration/scss-fixtures/3rd-party-module/pages/index.module.scss b/test/integration/scss-fixtures/3rd-party-module/pages/index.module.scss
new file mode 100644
index 0000000000000..99d63206009a1
--- /dev/null
+++ b/test/integration/scss-fixtures/3rd-party-module/pages/index.module.scss
@@ -0,0 +1,17 @@
+.foo {
+ position: relative;
+}
+
+.foo :global(.bar),
+.foo :global(.baz) {
+ height: 100%;
+ overflow: hidden;
+}
+
+.foo :global(.lol) {
+ width: 80%;
+}
+
+.foo > :global(.lel) {
+ width: 80%;
+}
diff --git a/test/integration/scss-fixtures/basic-module/pages/index.js b/test/integration/scss-fixtures/basic-module/pages/index.js
new file mode 100644
index 0000000000000..7b42859a67e32
--- /dev/null
+++ b/test/integration/scss-fixtures/basic-module/pages/index.js
@@ -0,0 +1,9 @@
+import { redText } from './index.module.scss'
+
+export default function Home() {
+ return (
+
+ This text should be red.
+
+ )
+}
diff --git a/test/integration/scss-fixtures/basic-module/pages/index.module.scss b/test/integration/scss-fixtures/basic-module/pages/index.module.scss
new file mode 100644
index 0000000000000..eb8bb05c73aca
--- /dev/null
+++ b/test/integration/scss-fixtures/basic-module/pages/index.module.scss
@@ -0,0 +1,4 @@
+$var: red;
+.redText {
+ color: $var;
+}
diff --git a/test/integration/scss-fixtures/compilation-and-prefixing/pages/_app.js b/test/integration/scss-fixtures/compilation-and-prefixing/pages/_app.js
new file mode 100644
index 0000000000000..b89fe16feb731
--- /dev/null
+++ b/test/integration/scss-fixtures/compilation-and-prefixing/pages/_app.js
@@ -0,0 +1,12 @@
+import React from 'react'
+import App from 'next/app'
+import '../styles/global.scss'
+
+class MyApp extends App {
+ render() {
+ const { Component, pageProps } = this.props
+ return
+ }
+}
+
+export default MyApp
diff --git a/test/integration/scss-fixtures/compilation-and-prefixing/pages/index.js b/test/integration/scss-fixtures/compilation-and-prefixing/pages/index.js
new file mode 100644
index 0000000000000..5cbac8a153d77
--- /dev/null
+++ b/test/integration/scss-fixtures/compilation-and-prefixing/pages/index.js
@@ -0,0 +1,3 @@
+export default function Home() {
+ return This text should be red.
+}
diff --git a/test/integration/scss-fixtures/compilation-and-prefixing/styles/global.scss b/test/integration/scss-fixtures/compilation-and-prefixing/styles/global.scss
new file mode 100644
index 0000000000000..2cc5c559f728a
--- /dev/null
+++ b/test/integration/scss-fixtures/compilation-and-prefixing/styles/global.scss
@@ -0,0 +1,6 @@
+$var: red;
+.redText {
+ ::placeholder {
+ color: $var;
+ }
+}
diff --git a/test/integration/scss-fixtures/composes-basic/pages/index.js b/test/integration/scss-fixtures/composes-basic/pages/index.js
new file mode 100644
index 0000000000000..0af8621c4649a
--- /dev/null
+++ b/test/integration/scss-fixtures/composes-basic/pages/index.js
@@ -0,0 +1,9 @@
+import { subClass } from './index.module.scss'
+
+export default function Home() {
+ return (
+
+ This text should be yellow on blue.
+
+ )
+}
diff --git a/test/integration/scss-fixtures/composes-basic/pages/index.module.scss b/test/integration/scss-fixtures/composes-basic/pages/index.module.scss
new file mode 100644
index 0000000000000..e3c6430bd6f70
--- /dev/null
+++ b/test/integration/scss-fixtures/composes-basic/pages/index.module.scss
@@ -0,0 +1,10 @@
+$var: red;
+.className {
+ background: $var;
+ color: yellow;
+}
+
+.subClass {
+ composes: className;
+ background: blue;
+}
diff --git a/test/integration/scss-fixtures/composes-external/pages/index.js b/test/integration/scss-fixtures/composes-external/pages/index.js
new file mode 100644
index 0000000000000..0af8621c4649a
--- /dev/null
+++ b/test/integration/scss-fixtures/composes-external/pages/index.js
@@ -0,0 +1,9 @@
+import { subClass } from './index.module.scss'
+
+export default function Home() {
+ return (
+
+ This text should be yellow on blue.
+
+ )
+}
diff --git a/test/integration/scss-fixtures/composes-external/pages/index.module.scss b/test/integration/scss-fixtures/composes-external/pages/index.module.scss
new file mode 100644
index 0000000000000..196db5d888917
--- /dev/null
+++ b/test/integration/scss-fixtures/composes-external/pages/index.module.scss
@@ -0,0 +1,5 @@
+$var: blue;
+.subClass {
+ composes: className from './other.scss';
+ background: $var;
+}
diff --git a/test/integration/scss-fixtures/composes-external/pages/other.scss b/test/integration/scss-fixtures/composes-external/pages/other.scss
new file mode 100644
index 0000000000000..382dd3097baa0
--- /dev/null
+++ b/test/integration/scss-fixtures/composes-external/pages/other.scss
@@ -0,0 +1,5 @@
+$var: red;
+.className {
+ background: $var;
+ color: yellow;
+}
diff --git a/test/integration/scss-fixtures/dev-module/pages/index.js b/test/integration/scss-fixtures/dev-module/pages/index.js
new file mode 100644
index 0000000000000..7b42859a67e32
--- /dev/null
+++ b/test/integration/scss-fixtures/dev-module/pages/index.js
@@ -0,0 +1,9 @@
+import { redText } from './index.module.scss'
+
+export default function Home() {
+ return (
+
+ This text should be red.
+
+ )
+}
diff --git a/test/integration/scss-fixtures/dev-module/pages/index.module.scss b/test/integration/scss-fixtures/dev-module/pages/index.module.scss
new file mode 100644
index 0000000000000..eb8bb05c73aca
--- /dev/null
+++ b/test/integration/scss-fixtures/dev-module/pages/index.module.scss
@@ -0,0 +1,4 @@
+$var: red;
+.redText {
+ color: $var;
+}
diff --git a/test/integration/scss-fixtures/hmr-module/pages/index.js b/test/integration/scss-fixtures/hmr-module/pages/index.js
new file mode 100644
index 0000000000000..18a31282f756c
--- /dev/null
+++ b/test/integration/scss-fixtures/hmr-module/pages/index.js
@@ -0,0 +1,15 @@
+import { redText } from './index.module.scss'
+
+function Home() {
+ return (
+ <>
+
+ This text should be red.
+
+
+
+ >
+ )
+}
+
+export default Home
diff --git a/test/integration/scss-fixtures/hmr-module/pages/index.module.scss b/test/integration/scss-fixtures/hmr-module/pages/index.module.scss
new file mode 100644
index 0000000000000..eb8bb05c73aca
--- /dev/null
+++ b/test/integration/scss-fixtures/hmr-module/pages/index.module.scss
@@ -0,0 +1,4 @@
+$var: red;
+.redText {
+ color: $var;
+}
diff --git a/test/integration/scss-fixtures/invalid-global-module/node_modules/example/index.js b/test/integration/scss-fixtures/invalid-global-module/node_modules/example/index.js
new file mode 100644
index 0000000000000..4a5b22813be5a
--- /dev/null
+++ b/test/integration/scss-fixtures/invalid-global-module/node_modules/example/index.js
@@ -0,0 +1 @@
+require('./index.scss')
diff --git a/test/integration/scss-fixtures/invalid-global-module/node_modules/example/index.mjs b/test/integration/scss-fixtures/invalid-global-module/node_modules/example/index.mjs
new file mode 100644
index 0000000000000..63810a6819f33
--- /dev/null
+++ b/test/integration/scss-fixtures/invalid-global-module/node_modules/example/index.mjs
@@ -0,0 +1 @@
+import './index.scss'
diff --git a/test/integration/scss-fixtures/invalid-global-module/node_modules/example/index.scss b/test/integration/scss-fixtures/invalid-global-module/node_modules/example/index.scss
new file mode 100644
index 0000000000000..f77fe0ef0bdbe
--- /dev/null
+++ b/test/integration/scss-fixtures/invalid-global-module/node_modules/example/index.scss
@@ -0,0 +1,3 @@
+.redText {
+ color: 'red';
+}
diff --git a/test/integration/scss-fixtures/invalid-global-module/node_modules/example/package.json b/test/integration/scss-fixtures/invalid-global-module/node_modules/example/package.json
new file mode 100644
index 0000000000000..ab5f2cd4a1cf7
--- /dev/null
+++ b/test/integration/scss-fixtures/invalid-global-module/node_modules/example/package.json
@@ -0,0 +1,4 @@
+{
+ "name": "example",
+ "main": "index"
+}
diff --git a/test/integration/scss-fixtures/invalid-global-module/pages/index.js b/test/integration/scss-fixtures/invalid-global-module/pages/index.js
new file mode 100644
index 0000000000000..9a79166027dd7
--- /dev/null
+++ b/test/integration/scss-fixtures/invalid-global-module/pages/index.js
@@ -0,0 +1,7 @@
+import 'example'
+
+function Home() {
+ return This should fail at build time.
+}
+
+export default Home
diff --git a/test/integration/scss-fixtures/invalid-global-with-app/pages/_app.js b/test/integration/scss-fixtures/invalid-global-with-app/pages/_app.js
new file mode 100644
index 0000000000000..68b8ee24d7fd5
--- /dev/null
+++ b/test/integration/scss-fixtures/invalid-global-with-app/pages/_app.js
@@ -0,0 +1,11 @@
+import React from 'react'
+import App from 'next/app'
+
+class MyApp extends App {
+ render() {
+ const { Component, pageProps } = this.props
+ return
+ }
+}
+
+export default MyApp
diff --git a/test/integration/scss-fixtures/invalid-global-with-app/pages/index.js b/test/integration/scss-fixtures/invalid-global-with-app/pages/index.js
new file mode 100644
index 0000000000000..38d46eb2a45eb
--- /dev/null
+++ b/test/integration/scss-fixtures/invalid-global-with-app/pages/index.js
@@ -0,0 +1,5 @@
+import '../styles/global.scss'
+
+export default function Home() {
+ return This text should be red.
+}
diff --git a/test/integration/scss-fixtures/invalid-global-with-app/styles/global.scss b/test/integration/scss-fixtures/invalid-global-with-app/styles/global.scss
new file mode 100644
index 0000000000000..8e1524022baf8
--- /dev/null
+++ b/test/integration/scss-fixtures/invalid-global-with-app/styles/global.scss
@@ -0,0 +1,3 @@
+.red-text {
+ color: red;
+}
diff --git a/test/integration/scss-fixtures/invalid-global/pages/index.js b/test/integration/scss-fixtures/invalid-global/pages/index.js
new file mode 100644
index 0000000000000..38d46eb2a45eb
--- /dev/null
+++ b/test/integration/scss-fixtures/invalid-global/pages/index.js
@@ -0,0 +1,5 @@
+import '../styles/global.scss'
+
+export default function Home() {
+ return This text should be red.
+}
diff --git a/test/integration/scss-fixtures/invalid-global/styles/global.scss b/test/integration/scss-fixtures/invalid-global/styles/global.scss
new file mode 100644
index 0000000000000..8e1524022baf8
--- /dev/null
+++ b/test/integration/scss-fixtures/invalid-global/styles/global.scss
@@ -0,0 +1,3 @@
+.red-text {
+ color: red;
+}
diff --git a/test/integration/scss-fixtures/invalid-module-document/pages/_document.js b/test/integration/scss-fixtures/invalid-module-document/pages/_document.js
new file mode 100644
index 0000000000000..a14aca67a844d
--- /dev/null
+++ b/test/integration/scss-fixtures/invalid-module-document/pages/_document.js
@@ -0,0 +1,23 @@
+import Document, { Head, Html, Main, NextScript } from 'next/document'
+import styles from '../styles.module.scss'
+
+class MyDocument extends Document {
+ static async getInitialProps(ctx) {
+ const initialProps = await Document.getInitialProps(ctx)
+ return { ...initialProps }
+ }
+
+ render() {
+ return (
+
+
+
+
+
+
+ )
+ }
+}
+
+export default MyDocument
diff --git a/test/integration/scss-fixtures/invalid-module-document/pages/index.js b/test/integration/scss-fixtures/invalid-module-document/pages/index.js
new file mode 100644
index 0000000000000..08fe48e9b56c8
--- /dev/null
+++ b/test/integration/scss-fixtures/invalid-module-document/pages/index.js
@@ -0,0 +1,3 @@
+export default function Home() {
+ return Hello
+}
diff --git a/test/integration/scss-fixtures/invalid-module-document/styles.module.scss b/test/integration/scss-fixtures/invalid-module-document/styles.module.scss
new file mode 100644
index 0000000000000..8e1524022baf8
--- /dev/null
+++ b/test/integration/scss-fixtures/invalid-module-document/styles.module.scss
@@ -0,0 +1,3 @@
+.red-text {
+ color: red;
+}
diff --git a/test/integration/scss-fixtures/invalid-module/node_modules/example/index.js b/test/integration/scss-fixtures/invalid-module/node_modules/example/index.js
new file mode 100644
index 0000000000000..a8023d4c7c1bf
--- /dev/null
+++ b/test/integration/scss-fixtures/invalid-module/node_modules/example/index.js
@@ -0,0 +1 @@
+module.exports = require('./index.module.scss')
diff --git a/test/integration/scss-fixtures/invalid-module/node_modules/example/index.mjs b/test/integration/scss-fixtures/invalid-module/node_modules/example/index.mjs
new file mode 100644
index 0000000000000..f72c6d4acfbe2
--- /dev/null
+++ b/test/integration/scss-fixtures/invalid-module/node_modules/example/index.mjs
@@ -0,0 +1 @@
+export * from './index.module.scss'
diff --git a/test/integration/scss-fixtures/invalid-module/node_modules/example/index.module.scss b/test/integration/scss-fixtures/invalid-module/node_modules/example/index.module.scss
new file mode 100644
index 0000000000000..f77fe0ef0bdbe
--- /dev/null
+++ b/test/integration/scss-fixtures/invalid-module/node_modules/example/index.module.scss
@@ -0,0 +1,3 @@
+.redText {
+ color: 'red';
+}
diff --git a/test/integration/scss-fixtures/invalid-module/node_modules/example/package.json b/test/integration/scss-fixtures/invalid-module/node_modules/example/package.json
new file mode 100644
index 0000000000000..ab5f2cd4a1cf7
--- /dev/null
+++ b/test/integration/scss-fixtures/invalid-module/node_modules/example/package.json
@@ -0,0 +1,4 @@
+{
+ "name": "example",
+ "main": "index"
+}
diff --git a/test/integration/scss-fixtures/invalid-module/pages/index.js b/test/integration/scss-fixtures/invalid-module/pages/index.js
new file mode 100644
index 0000000000000..83fa4e1381b64
--- /dev/null
+++ b/test/integration/scss-fixtures/invalid-module/pages/index.js
@@ -0,0 +1,7 @@
+import * as classes from 'example'
+
+function Home() {
+ return This should fail at build time {JSON.stringify(classes)}.
+}
+
+export default Home
diff --git a/test/integration/scss-fixtures/multi-global-reversed/pages/_app.js b/test/integration/scss-fixtures/multi-global-reversed/pages/_app.js
new file mode 100644
index 0000000000000..dc344a9bd9d27
--- /dev/null
+++ b/test/integration/scss-fixtures/multi-global-reversed/pages/_app.js
@@ -0,0 +1,13 @@
+import React from 'react'
+import App from 'next/app'
+import '../styles/global2.scss'
+import '../styles/global1.scss'
+
+class MyApp extends App {
+ render() {
+ const { Component, pageProps } = this.props
+ return
+ }
+}
+
+export default MyApp
diff --git a/test/integration/scss-fixtures/multi-global-reversed/pages/index.js b/test/integration/scss-fixtures/multi-global-reversed/pages/index.js
new file mode 100644
index 0000000000000..5cbac8a153d77
--- /dev/null
+++ b/test/integration/scss-fixtures/multi-global-reversed/pages/index.js
@@ -0,0 +1,3 @@
+export default function Home() {
+ return This text should be red.
+}
diff --git a/test/integration/scss-fixtures/multi-global-reversed/styles/global1.scss b/test/integration/scss-fixtures/multi-global-reversed/styles/global1.scss
new file mode 100644
index 0000000000000..da7b3d1417b1b
--- /dev/null
+++ b/test/integration/scss-fixtures/multi-global-reversed/styles/global1.scss
@@ -0,0 +1,4 @@
+$var: red;
+.red-text {
+ color: $var;
+}
diff --git a/test/integration/scss-fixtures/multi-global-reversed/styles/global2.scss b/test/integration/scss-fixtures/multi-global-reversed/styles/global2.scss
new file mode 100644
index 0000000000000..3dd5eeaf6db92
--- /dev/null
+++ b/test/integration/scss-fixtures/multi-global-reversed/styles/global2.scss
@@ -0,0 +1,4 @@
+$var: blue;
+.blue-text {
+ color: $var;
+}
diff --git a/test/integration/scss-fixtures/multi-global/pages/_app.js b/test/integration/scss-fixtures/multi-global/pages/_app.js
new file mode 100644
index 0000000000000..29beb9fdb3cc5
--- /dev/null
+++ b/test/integration/scss-fixtures/multi-global/pages/_app.js
@@ -0,0 +1,13 @@
+import React from 'react'
+import App from 'next/app'
+import '../styles/global1.scss'
+import '../styles/global2.scss'
+
+class MyApp extends App {
+ render() {
+ const { Component, pageProps } = this.props
+ return
+ }
+}
+
+export default MyApp
diff --git a/test/integration/scss-fixtures/multi-global/pages/index.js b/test/integration/scss-fixtures/multi-global/pages/index.js
new file mode 100644
index 0000000000000..5cbac8a153d77
--- /dev/null
+++ b/test/integration/scss-fixtures/multi-global/pages/index.js
@@ -0,0 +1,3 @@
+export default function Home() {
+ return This text should be red.
+}
diff --git a/test/integration/scss-fixtures/multi-global/styles/global1.scss b/test/integration/scss-fixtures/multi-global/styles/global1.scss
new file mode 100644
index 0000000000000..da7b3d1417b1b
--- /dev/null
+++ b/test/integration/scss-fixtures/multi-global/styles/global1.scss
@@ -0,0 +1,4 @@
+$var: red;
+.red-text {
+ color: $var;
+}
diff --git a/test/integration/scss-fixtures/multi-global/styles/global2.scss b/test/integration/scss-fixtures/multi-global/styles/global2.scss
new file mode 100644
index 0000000000000..3dd5eeaf6db92
--- /dev/null
+++ b/test/integration/scss-fixtures/multi-global/styles/global2.scss
@@ -0,0 +1,4 @@
+$var: blue;
+.blue-text {
+ color: $var;
+}
diff --git a/test/integration/scss-fixtures/multi-module/pages/blue.js b/test/integration/scss-fixtures/multi-module/pages/blue.js
new file mode 100644
index 0000000000000..1c45c34e41a84
--- /dev/null
+++ b/test/integration/scss-fixtures/multi-module/pages/blue.js
@@ -0,0 +1,20 @@
+import Link from 'next/link'
+import { blueText } from './blue.module.scss'
+
+export default function Blue() {
+ return (
+ <>
+
+ This text should be blue.
+
+
+
+ Red
+
+
+
+ None
+
+ >
+ )
+}
diff --git a/test/integration/scss-fixtures/multi-module/pages/blue.module.scss b/test/integration/scss-fixtures/multi-module/pages/blue.module.scss
new file mode 100644
index 0000000000000..a227734ba1db1
--- /dev/null
+++ b/test/integration/scss-fixtures/multi-module/pages/blue.module.scss
@@ -0,0 +1,4 @@
+$var: blue;
+.blueText {
+ color: $var;
+}
diff --git a/test/integration/scss-fixtures/multi-module/pages/none.js b/test/integration/scss-fixtures/multi-module/pages/none.js
new file mode 100644
index 0000000000000..f26c2d80b3ae0
--- /dev/null
+++ b/test/integration/scss-fixtures/multi-module/pages/none.js
@@ -0,0 +1,19 @@
+import Link from 'next/link'
+
+export default function None() {
+ return (
+ <>
+
+ This text should be black.
+
+
+
+ Red
+
+
+
+ Blue
+
+ >
+ )
+}
diff --git a/test/integration/scss-fixtures/multi-module/pages/red.js b/test/integration/scss-fixtures/multi-module/pages/red.js
new file mode 100644
index 0000000000000..af8b699159986
--- /dev/null
+++ b/test/integration/scss-fixtures/multi-module/pages/red.js
@@ -0,0 +1,20 @@
+import Link from 'next/link'
+import { redText } from './red.module.scss'
+
+export default function Red() {
+ return (
+ <>
+
+ This text should be red.
+
+
+
+ Blue
+
+
+
+ None
+
+ >
+ )
+}
diff --git a/test/integration/scss-fixtures/multi-module/pages/red.module.scss b/test/integration/scss-fixtures/multi-module/pages/red.module.scss
new file mode 100644
index 0000000000000..eb8bb05c73aca
--- /dev/null
+++ b/test/integration/scss-fixtures/multi-module/pages/red.module.scss
@@ -0,0 +1,4 @@
+$var: red;
+.redText {
+ color: $var;
+}
diff --git a/test/integration/scss-fixtures/multi-page/pages/_app.js b/test/integration/scss-fixtures/multi-page/pages/_app.js
new file mode 100644
index 0000000000000..29beb9fdb3cc5
--- /dev/null
+++ b/test/integration/scss-fixtures/multi-page/pages/_app.js
@@ -0,0 +1,13 @@
+import React from 'react'
+import App from 'next/app'
+import '../styles/global1.scss'
+import '../styles/global2.scss'
+
+class MyApp extends App {
+ render() {
+ const { Component, pageProps } = this.props
+ return
+ }
+}
+
+export default MyApp
diff --git a/test/integration/scss-fixtures/multi-page/pages/page1.js b/test/integration/scss-fixtures/multi-page/pages/page1.js
new file mode 100644
index 0000000000000..94b9d0f24ca37
--- /dev/null
+++ b/test/integration/scss-fixtures/multi-page/pages/page1.js
@@ -0,0 +1,14 @@
+import Link from 'next/link'
+export default function Page1() {
+ return (
+ <>
+ This text should be red.
+
+
+
+
+ Switch page
+
+ >
+ )
+}
diff --git a/test/integration/scss-fixtures/multi-page/pages/page2.js b/test/integration/scss-fixtures/multi-page/pages/page2.js
new file mode 100644
index 0000000000000..e8e49a87514dc
--- /dev/null
+++ b/test/integration/scss-fixtures/multi-page/pages/page2.js
@@ -0,0 +1,14 @@
+import Link from 'next/link'
+export default function Page2() {
+ return (
+ <>
+ This text should be blue.
+
+
+
+
+ Switch page
+
+ >
+ )
+}
diff --git a/test/integration/scss-fixtures/multi-page/styles/global1.scss b/test/integration/scss-fixtures/multi-page/styles/global1.scss
new file mode 100644
index 0000000000000..da7b3d1417b1b
--- /dev/null
+++ b/test/integration/scss-fixtures/multi-page/styles/global1.scss
@@ -0,0 +1,4 @@
+$var: red;
+.red-text {
+ color: $var;
+}
diff --git a/test/integration/scss-fixtures/multi-page/styles/global2.scss b/test/integration/scss-fixtures/multi-page/styles/global2.scss
new file mode 100644
index 0000000000000..3dd5eeaf6db92
--- /dev/null
+++ b/test/integration/scss-fixtures/multi-page/styles/global2.scss
@@ -0,0 +1,4 @@
+$var: blue;
+.blue-text {
+ color: $var;
+}
diff --git a/test/integration/scss-fixtures/nested-global/pages/_app.js b/test/integration/scss-fixtures/nested-global/pages/_app.js
new file mode 100644
index 0000000000000..29beb9fdb3cc5
--- /dev/null
+++ b/test/integration/scss-fixtures/nested-global/pages/_app.js
@@ -0,0 +1,13 @@
+import React from 'react'
+import App from 'next/app'
+import '../styles/global1.scss'
+import '../styles/global2.scss'
+
+class MyApp extends App {
+ render() {
+ const { Component, pageProps } = this.props
+ return
+ }
+}
+
+export default MyApp
diff --git a/test/integration/scss-fixtures/nested-global/pages/index.js b/test/integration/scss-fixtures/nested-global/pages/index.js
new file mode 100644
index 0000000000000..5cbac8a153d77
--- /dev/null
+++ b/test/integration/scss-fixtures/nested-global/pages/index.js
@@ -0,0 +1,3 @@
+export default function Home() {
+ return This text should be red.
+}
diff --git a/test/integration/scss-fixtures/nested-global/styles/global1.scss b/test/integration/scss-fixtures/nested-global/styles/global1.scss
new file mode 100644
index 0000000000000..096036b10f2dd
--- /dev/null
+++ b/test/integration/scss-fixtures/nested-global/styles/global1.scss
@@ -0,0 +1,6 @@
+@import './global1b.scss';
+
+$var: red;
+.red-text {
+ color: $var;
+}
diff --git a/test/integration/scss-fixtures/nested-global/styles/global1b.scss b/test/integration/scss-fixtures/nested-global/styles/global1b.scss
new file mode 100644
index 0000000000000..e288c3ca0d12e
--- /dev/null
+++ b/test/integration/scss-fixtures/nested-global/styles/global1b.scss
@@ -0,0 +1,5 @@
+$var: purple;
+.red-text {
+ color: $var;
+ font-weight: bolder;
+}
diff --git a/test/integration/scss-fixtures/nested-global/styles/global2.scss b/test/integration/scss-fixtures/nested-global/styles/global2.scss
new file mode 100644
index 0000000000000..4b659d369c561
--- /dev/null
+++ b/test/integration/scss-fixtures/nested-global/styles/global2.scss
@@ -0,0 +1,6 @@
+@import './global2b.scss';
+
+$var: blue;
+.blue-text {
+ color: $var;
+}
diff --git a/test/integration/scss-fixtures/nested-global/styles/global2b.scss b/test/integration/scss-fixtures/nested-global/styles/global2b.scss
new file mode 100644
index 0000000000000..cc1554c3c8334
--- /dev/null
+++ b/test/integration/scss-fixtures/nested-global/styles/global2b.scss
@@ -0,0 +1,5 @@
+$var: orange;
+.blue-text {
+ color: $var;
+ font-weight: bolder;
+}
diff --git a/test/integration/scss-fixtures/next.config.js b/test/integration/scss-fixtures/next.config.js
new file mode 100644
index 0000000000000..b97156a83b09e
--- /dev/null
+++ b/test/integration/scss-fixtures/next.config.js
@@ -0,0 +1,11 @@
+module.exports = {
+ onDemandEntries: {
+ // Make sure entries are not getting disposed.
+ maxInactiveAge: 1000 * 60 * 60,
+ },
+ experimental: { scss: true },
+ webpack(cfg) {
+ cfg.devtool = 'source-map'
+ return cfg
+ },
+}
diff --git a/test/integration/scss-fixtures/nm-module-nested/node_modules/example/index.js b/test/integration/scss-fixtures/nm-module-nested/node_modules/example/index.js
new file mode 100644
index 0000000000000..7d1fdcb8879d9
--- /dev/null
+++ b/test/integration/scss-fixtures/nm-module-nested/node_modules/example/index.js
@@ -0,0 +1,3 @@
+const message = 'Why hello there'
+
+module.exports = { message }
diff --git a/test/integration/scss-fixtures/nm-module-nested/node_modules/example/index.mjs b/test/integration/scss-fixtures/nm-module-nested/node_modules/example/index.mjs
new file mode 100644
index 0000000000000..a81498ed3ad53
--- /dev/null
+++ b/test/integration/scss-fixtures/nm-module-nested/node_modules/example/index.mjs
@@ -0,0 +1 @@
+export const message = 'Why hello there'
diff --git a/test/integration/scss-fixtures/nm-module-nested/node_modules/example/index.module.scss b/test/integration/scss-fixtures/nm-module-nested/node_modules/example/index.module.scss
new file mode 100644
index 0000000000000..d2d4babe01664
--- /dev/null
+++ b/test/integration/scss-fixtures/nm-module-nested/node_modules/example/index.module.scss
@@ -0,0 +1,7 @@
+@import 'other2.scss';
+
+$var: blue;
+.subClass {
+ composes: className from './other.scss';
+ background: $var;
+}
diff --git a/test/integration/scss-fixtures/nm-module-nested/node_modules/example/other.scss b/test/integration/scss-fixtures/nm-module-nested/node_modules/example/other.scss
new file mode 100644
index 0000000000000..eb0f82f7aacb4
--- /dev/null
+++ b/test/integration/scss-fixtures/nm-module-nested/node_modules/example/other.scss
@@ -0,0 +1,7 @@
+@import 'other3.scss';
+
+$var: red;
+.className {
+ background: $var;
+ color: yellow;
+}
diff --git a/test/integration/scss-fixtures/nm-module-nested/node_modules/example/other2.scss b/test/integration/scss-fixtures/nm-module-nested/node_modules/example/other2.scss
new file mode 100644
index 0000000000000..7525334516ec4
--- /dev/null
+++ b/test/integration/scss-fixtures/nm-module-nested/node_modules/example/other2.scss
@@ -0,0 +1,4 @@
+$var: red;
+.other2 {
+ color: $var;
+}
diff --git a/test/integration/scss-fixtures/nm-module-nested/node_modules/example/other3.scss b/test/integration/scss-fixtures/nm-module-nested/node_modules/example/other3.scss
new file mode 100644
index 0000000000000..ecbdacff019d8
--- /dev/null
+++ b/test/integration/scss-fixtures/nm-module-nested/node_modules/example/other3.scss
@@ -0,0 +1,4 @@
+$var: violet;
+.other3 {
+ color: $var;
+}
diff --git a/test/integration/scss-fixtures/nm-module-nested/node_modules/example/package.json b/test/integration/scss-fixtures/nm-module-nested/node_modules/example/package.json
new file mode 100644
index 0000000000000..ab5f2cd4a1cf7
--- /dev/null
+++ b/test/integration/scss-fixtures/nm-module-nested/node_modules/example/package.json
@@ -0,0 +1,4 @@
+{
+ "name": "example",
+ "main": "index"
+}
diff --git a/test/integration/scss-fixtures/nm-module-nested/pages/index.js b/test/integration/scss-fixtures/nm-module-nested/pages/index.js
new file mode 100644
index 0000000000000..897696bd01572
--- /dev/null
+++ b/test/integration/scss-fixtures/nm-module-nested/pages/index.js
@@ -0,0 +1,12 @@
+import * as data from 'example'
+import * as classes from 'example/index.module.scss'
+
+function Home() {
+ return (
+
+ {JSON.stringify(data)} {JSON.stringify(classes)}
+
+ )
+}
+
+export default Home
diff --git a/test/integration/scss-fixtures/nm-module/node_modules/example/index.js b/test/integration/scss-fixtures/nm-module/node_modules/example/index.js
new file mode 100644
index 0000000000000..7d1fdcb8879d9
--- /dev/null
+++ b/test/integration/scss-fixtures/nm-module/node_modules/example/index.js
@@ -0,0 +1,3 @@
+const message = 'Why hello there'
+
+module.exports = { message }
diff --git a/test/integration/scss-fixtures/nm-module/node_modules/example/index.mjs b/test/integration/scss-fixtures/nm-module/node_modules/example/index.mjs
new file mode 100644
index 0000000000000..a81498ed3ad53
--- /dev/null
+++ b/test/integration/scss-fixtures/nm-module/node_modules/example/index.mjs
@@ -0,0 +1 @@
+export const message = 'Why hello there'
diff --git a/test/integration/scss-fixtures/nm-module/node_modules/example/index.module.scss b/test/integration/scss-fixtures/nm-module/node_modules/example/index.module.scss
new file mode 100644
index 0000000000000..eb8bb05c73aca
--- /dev/null
+++ b/test/integration/scss-fixtures/nm-module/node_modules/example/index.module.scss
@@ -0,0 +1,4 @@
+$var: red;
+.redText {
+ color: $var;
+}
diff --git a/test/integration/scss-fixtures/nm-module/node_modules/example/package.json b/test/integration/scss-fixtures/nm-module/node_modules/example/package.json
new file mode 100644
index 0000000000000..ab5f2cd4a1cf7
--- /dev/null
+++ b/test/integration/scss-fixtures/nm-module/node_modules/example/package.json
@@ -0,0 +1,4 @@
+{
+ "name": "example",
+ "main": "index"
+}
diff --git a/test/integration/scss-fixtures/nm-module/pages/index.js b/test/integration/scss-fixtures/nm-module/pages/index.js
new file mode 100644
index 0000000000000..897696bd01572
--- /dev/null
+++ b/test/integration/scss-fixtures/nm-module/pages/index.js
@@ -0,0 +1,12 @@
+import * as data from 'example'
+import * as classes from 'example/index.module.scss'
+
+function Home() {
+ return (
+
+ {JSON.stringify(data)} {JSON.stringify(classes)}
+
+ )
+}
+
+export default Home
diff --git a/test/integration/scss-fixtures/npm-import-bad/pages/_app.js b/test/integration/scss-fixtures/npm-import-bad/pages/_app.js
new file mode 100644
index 0000000000000..b89fe16feb731
--- /dev/null
+++ b/test/integration/scss-fixtures/npm-import-bad/pages/_app.js
@@ -0,0 +1,12 @@
+import React from 'react'
+import App from 'next/app'
+import '../styles/global.scss'
+
+class MyApp extends App {
+ render() {
+ const { Component, pageProps } = this.props
+ return
+ }
+}
+
+export default MyApp
diff --git a/test/integration/scss-fixtures/npm-import-bad/pages/index.js b/test/integration/scss-fixtures/npm-import-bad/pages/index.js
new file mode 100644
index 0000000000000..5cbac8a153d77
--- /dev/null
+++ b/test/integration/scss-fixtures/npm-import-bad/pages/index.js
@@ -0,0 +1,3 @@
+export default function Home() {
+ return This text should be red.
+}
diff --git a/test/integration/scss-fixtures/npm-import-bad/styles/global.scss b/test/integration/scss-fixtures/npm-import-bad/styles/global.scss
new file mode 100644
index 0000000000000..bc9b10bfd0877
--- /dev/null
+++ b/test/integration/scss-fixtures/npm-import-bad/styles/global.scss
@@ -0,0 +1 @@
+@import 'nprogress/nprogress.css';
diff --git a/test/integration/scss-fixtures/npm-import-nested/node_modules/example/index.js b/test/integration/scss-fixtures/npm-import-nested/node_modules/example/index.js
new file mode 100644
index 0000000000000..7d1fdcb8879d9
--- /dev/null
+++ b/test/integration/scss-fixtures/npm-import-nested/node_modules/example/index.js
@@ -0,0 +1,3 @@
+const message = 'Why hello there'
+
+module.exports = { message }
diff --git a/test/integration/scss-fixtures/npm-import-nested/node_modules/example/index.mjs b/test/integration/scss-fixtures/npm-import-nested/node_modules/example/index.mjs
new file mode 100644
index 0000000000000..a81498ed3ad53
--- /dev/null
+++ b/test/integration/scss-fixtures/npm-import-nested/node_modules/example/index.mjs
@@ -0,0 +1 @@
+export const message = 'Why hello there'
diff --git a/test/integration/scss-fixtures/npm-import-nested/node_modules/example/other.scss b/test/integration/scss-fixtures/npm-import-nested/node_modules/example/other.scss
new file mode 100644
index 0000000000000..b2a7622386198
--- /dev/null
+++ b/test/integration/scss-fixtures/npm-import-nested/node_modules/example/other.scss
@@ -0,0 +1,4 @@
+$var: blue;
+.other {
+ color: $var;
+}
diff --git a/test/integration/scss-fixtures/npm-import-nested/node_modules/example/package.json b/test/integration/scss-fixtures/npm-import-nested/node_modules/example/package.json
new file mode 100644
index 0000000000000..ab5f2cd4a1cf7
--- /dev/null
+++ b/test/integration/scss-fixtures/npm-import-nested/node_modules/example/package.json
@@ -0,0 +1,4 @@
+{
+ "name": "example",
+ "main": "index"
+}
diff --git a/test/integration/scss-fixtures/npm-import-nested/node_modules/example/test.scss b/test/integration/scss-fixtures/npm-import-nested/node_modules/example/test.scss
new file mode 100644
index 0000000000000..2db9b0627d1ef
--- /dev/null
+++ b/test/integration/scss-fixtures/npm-import-nested/node_modules/example/test.scss
@@ -0,0 +1,6 @@
+@import 'other.scss';
+
+$var: red;
+.test {
+ color: $var;
+}
diff --git a/test/integration/scss-fixtures/npm-import-nested/pages/_app.js b/test/integration/scss-fixtures/npm-import-nested/pages/_app.js
new file mode 100644
index 0000000000000..b89fe16feb731
--- /dev/null
+++ b/test/integration/scss-fixtures/npm-import-nested/pages/_app.js
@@ -0,0 +1,12 @@
+import React from 'react'
+import App from 'next/app'
+import '../styles/global.scss'
+
+class MyApp extends App {
+ render() {
+ const { Component, pageProps } = this.props
+ return
+ }
+}
+
+export default MyApp
diff --git a/test/integration/scss-fixtures/npm-import-nested/pages/index.js b/test/integration/scss-fixtures/npm-import-nested/pages/index.js
new file mode 100644
index 0000000000000..5cbac8a153d77
--- /dev/null
+++ b/test/integration/scss-fixtures/npm-import-nested/pages/index.js
@@ -0,0 +1,3 @@
+export default function Home() {
+ return This text should be red.
+}
diff --git a/test/integration/scss-fixtures/npm-import-nested/styles/global.scss b/test/integration/scss-fixtures/npm-import-nested/styles/global.scss
new file mode 100644
index 0000000000000..fb61b329db5f7
--- /dev/null
+++ b/test/integration/scss-fixtures/npm-import-nested/styles/global.scss
@@ -0,0 +1 @@
+@import '~example/test.scss';
diff --git a/test/integration/scss-fixtures/npm-import/pages/_app.js b/test/integration/scss-fixtures/npm-import/pages/_app.js
new file mode 100644
index 0000000000000..b89fe16feb731
--- /dev/null
+++ b/test/integration/scss-fixtures/npm-import/pages/_app.js
@@ -0,0 +1,12 @@
+import React from 'react'
+import App from 'next/app'
+import '../styles/global.scss'
+
+class MyApp extends App {
+ render() {
+ const { Component, pageProps } = this.props
+ return
+ }
+}
+
+export default MyApp
diff --git a/test/integration/scss-fixtures/npm-import/pages/index.js b/test/integration/scss-fixtures/npm-import/pages/index.js
new file mode 100644
index 0000000000000..5cbac8a153d77
--- /dev/null
+++ b/test/integration/scss-fixtures/npm-import/pages/index.js
@@ -0,0 +1,3 @@
+export default function Home() {
+ return This text should be red.
+}
diff --git a/test/integration/scss-fixtures/npm-import/styles/global.scss b/test/integration/scss-fixtures/npm-import/styles/global.scss
new file mode 100644
index 0000000000000..a369dafd92cc4
--- /dev/null
+++ b/test/integration/scss-fixtures/npm-import/styles/global.scss
@@ -0,0 +1 @@
+@import '~nprogress/nprogress.css';
diff --git a/test/integration/scss-fixtures/prod-module/pages/index.js b/test/integration/scss-fixtures/prod-module/pages/index.js
new file mode 100644
index 0000000000000..7b42859a67e32
--- /dev/null
+++ b/test/integration/scss-fixtures/prod-module/pages/index.js
@@ -0,0 +1,9 @@
+import { redText } from './index.module.scss'
+
+export default function Home() {
+ return (
+
+ This text should be red.
+
+ )
+}
diff --git a/test/integration/scss-fixtures/prod-module/pages/index.module.scss b/test/integration/scss-fixtures/prod-module/pages/index.module.scss
new file mode 100644
index 0000000000000..eb8bb05c73aca
--- /dev/null
+++ b/test/integration/scss-fixtures/prod-module/pages/index.module.scss
@@ -0,0 +1,4 @@
+$var: red;
+.redText {
+ color: $var;
+}
diff --git a/test/integration/scss-fixtures/single-global-src/src/pages/_app.js b/test/integration/scss-fixtures/single-global-src/src/pages/_app.js
new file mode 100644
index 0000000000000..13558a96f8e8a
--- /dev/null
+++ b/test/integration/scss-fixtures/single-global-src/src/pages/_app.js
@@ -0,0 +1,12 @@
+import React from 'react'
+import App from 'next/app'
+import '../../styles/global.scss'
+
+class MyApp extends App {
+ render() {
+ const { Component, pageProps } = this.props
+ return
+ }
+}
+
+export default MyApp
diff --git a/test/integration/scss-fixtures/single-global-src/src/pages/index.js b/test/integration/scss-fixtures/single-global-src/src/pages/index.js
new file mode 100644
index 0000000000000..5cbac8a153d77
--- /dev/null
+++ b/test/integration/scss-fixtures/single-global-src/src/pages/index.js
@@ -0,0 +1,3 @@
+export default function Home() {
+ return This text should be red.
+}
diff --git a/test/integration/scss-fixtures/single-global-src/styles/global.scss b/test/integration/scss-fixtures/single-global-src/styles/global.scss
new file mode 100644
index 0000000000000..da7b3d1417b1b
--- /dev/null
+++ b/test/integration/scss-fixtures/single-global-src/styles/global.scss
@@ -0,0 +1,4 @@
+$var: red;
+.red-text {
+ color: $var;
+}
diff --git a/test/integration/scss-fixtures/single-global/pages/_app.js b/test/integration/scss-fixtures/single-global/pages/_app.js
new file mode 100644
index 0000000000000..b89fe16feb731
--- /dev/null
+++ b/test/integration/scss-fixtures/single-global/pages/_app.js
@@ -0,0 +1,12 @@
+import React from 'react'
+import App from 'next/app'
+import '../styles/global.scss'
+
+class MyApp extends App {
+ render() {
+ const { Component, pageProps } = this.props
+ return
+ }
+}
+
+export default MyApp
diff --git a/test/integration/scss-fixtures/single-global/pages/index.js b/test/integration/scss-fixtures/single-global/pages/index.js
new file mode 100644
index 0000000000000..5cbac8a153d77
--- /dev/null
+++ b/test/integration/scss-fixtures/single-global/pages/index.js
@@ -0,0 +1,3 @@
+export default function Home() {
+ return This text should be red.
+}
diff --git a/test/integration/scss-fixtures/single-global/styles/global.scss b/test/integration/scss-fixtures/single-global/styles/global.scss
new file mode 100644
index 0000000000000..da7b3d1417b1b
--- /dev/null
+++ b/test/integration/scss-fixtures/single-global/styles/global.scss
@@ -0,0 +1,4 @@
+$var: red;
+.red-text {
+ color: $var;
+}
diff --git a/test/integration/scss-fixtures/unused/pages/index.js b/test/integration/scss-fixtures/unused/pages/index.js
new file mode 100644
index 0000000000000..b3ba78da2d5e1
--- /dev/null
+++ b/test/integration/scss-fixtures/unused/pages/index.js
@@ -0,0 +1,3 @@
+export default function Home() {
+ return
+}
diff --git a/test/integration/scss-fixtures/url-global-asset-prefix-1/assets/light.svg b/test/integration/scss-fixtures/url-global-asset-prefix-1/assets/light.svg
new file mode 100644
index 0000000000000..0194cbce06561
--- /dev/null
+++ b/test/integration/scss-fixtures/url-global-asset-prefix-1/assets/light.svg
@@ -0,0 +1,17 @@
+
+
diff --git a/test/integration/scss-fixtures/url-global-asset-prefix-1/next.config.js b/test/integration/scss-fixtures/url-global-asset-prefix-1/next.config.js
new file mode 100644
index 0000000000000..177a5d7903f16
--- /dev/null
+++ b/test/integration/scss-fixtures/url-global-asset-prefix-1/next.config.js
@@ -0,0 +1,5 @@
+const config = require('../next.config.js')
+module.exports = {
+ ...config,
+ assetPrefix: '/foo/',
+}
diff --git a/test/integration/scss-fixtures/url-global-asset-prefix-1/pages/_app.js b/test/integration/scss-fixtures/url-global-asset-prefix-1/pages/_app.js
new file mode 100644
index 0000000000000..29beb9fdb3cc5
--- /dev/null
+++ b/test/integration/scss-fixtures/url-global-asset-prefix-1/pages/_app.js
@@ -0,0 +1,13 @@
+import React from 'react'
+import App from 'next/app'
+import '../styles/global1.scss'
+import '../styles/global2.scss'
+
+class MyApp extends App {
+ render() {
+ const { Component, pageProps } = this.props
+ return
+ }
+}
+
+export default MyApp
diff --git a/test/integration/scss-fixtures/url-global-asset-prefix-1/pages/index.js b/test/integration/scss-fixtures/url-global-asset-prefix-1/pages/index.js
new file mode 100644
index 0000000000000..5cbac8a153d77
--- /dev/null
+++ b/test/integration/scss-fixtures/url-global-asset-prefix-1/pages/index.js
@@ -0,0 +1,3 @@
+export default function Home() {
+ return This text should be red.
+}
diff --git a/test/integration/scss-fixtures/url-global-asset-prefix-1/styles/dark.svg b/test/integration/scss-fixtures/url-global-asset-prefix-1/styles/dark.svg
new file mode 100644
index 0000000000000..bdae3d6587872
--- /dev/null
+++ b/test/integration/scss-fixtures/url-global-asset-prefix-1/styles/dark.svg
@@ -0,0 +1,17 @@
+
+
diff --git a/test/integration/scss-fixtures/url-global-asset-prefix-1/styles/dark2.svg b/test/integration/scss-fixtures/url-global-asset-prefix-1/styles/dark2.svg
new file mode 100644
index 0000000000000..bdae3d6587872
--- /dev/null
+++ b/test/integration/scss-fixtures/url-global-asset-prefix-1/styles/dark2.svg
@@ -0,0 +1,17 @@
+
+
diff --git a/test/integration/scss-fixtures/url-global-asset-prefix-1/styles/global1.scss b/test/integration/scss-fixtures/url-global-asset-prefix-1/styles/global1.scss
new file mode 100644
index 0000000000000..24f6145a7a6fa
--- /dev/null
+++ b/test/integration/scss-fixtures/url-global-asset-prefix-1/styles/global1.scss
@@ -0,0 +1,5 @@
+$var: red;
+.red-text {
+ color: $var;
+ background-image: url('./dark.svg') url(dark2.svg);
+}
diff --git a/test/integration/scss-fixtures/url-global-asset-prefix-1/styles/global2.scss b/test/integration/scss-fixtures/url-global-asset-prefix-1/styles/global2.scss
new file mode 100644
index 0000000000000..4b659d369c561
--- /dev/null
+++ b/test/integration/scss-fixtures/url-global-asset-prefix-1/styles/global2.scss
@@ -0,0 +1,6 @@
+@import './global2b.scss';
+
+$var: blue;
+.blue-text {
+ color: $var;
+}
diff --git a/test/integration/scss-fixtures/url-global-asset-prefix-1/styles/global2b.scss b/test/integration/scss-fixtures/url-global-asset-prefix-1/styles/global2b.scss
new file mode 100644
index 0000000000000..0fe57600718f5
--- /dev/null
+++ b/test/integration/scss-fixtures/url-global-asset-prefix-1/styles/global2b.scss
@@ -0,0 +1,6 @@
+$var: orange;
+.blue-text {
+ color: $var;
+ font-weight: bolder;
+ background-image: url(../assets/light.svg);
+}
diff --git a/test/integration/scss-fixtures/url-global-asset-prefix-2/assets/light.svg b/test/integration/scss-fixtures/url-global-asset-prefix-2/assets/light.svg
new file mode 100644
index 0000000000000..0194cbce06561
--- /dev/null
+++ b/test/integration/scss-fixtures/url-global-asset-prefix-2/assets/light.svg
@@ -0,0 +1,17 @@
+
+
diff --git a/test/integration/scss-fixtures/url-global-asset-prefix-2/next.config.js b/test/integration/scss-fixtures/url-global-asset-prefix-2/next.config.js
new file mode 100644
index 0000000000000..72ff6da7d9adf
--- /dev/null
+++ b/test/integration/scss-fixtures/url-global-asset-prefix-2/next.config.js
@@ -0,0 +1,5 @@
+const config = require('../next.config.js')
+module.exports = {
+ ...config,
+ assetPrefix: '/foo',
+}
diff --git a/test/integration/scss-fixtures/url-global-asset-prefix-2/pages/_app.js b/test/integration/scss-fixtures/url-global-asset-prefix-2/pages/_app.js
new file mode 100644
index 0000000000000..29beb9fdb3cc5
--- /dev/null
+++ b/test/integration/scss-fixtures/url-global-asset-prefix-2/pages/_app.js
@@ -0,0 +1,13 @@
+import React from 'react'
+import App from 'next/app'
+import '../styles/global1.scss'
+import '../styles/global2.scss'
+
+class MyApp extends App {
+ render() {
+ const { Component, pageProps } = this.props
+ return
+ }
+}
+
+export default MyApp
diff --git a/test/integration/scss-fixtures/url-global-asset-prefix-2/pages/index.js b/test/integration/scss-fixtures/url-global-asset-prefix-2/pages/index.js
new file mode 100644
index 0000000000000..5cbac8a153d77
--- /dev/null
+++ b/test/integration/scss-fixtures/url-global-asset-prefix-2/pages/index.js
@@ -0,0 +1,3 @@
+export default function Home() {
+ return This text should be red.
+}
diff --git a/test/integration/scss-fixtures/url-global-asset-prefix-2/styles/dark.svg b/test/integration/scss-fixtures/url-global-asset-prefix-2/styles/dark.svg
new file mode 100644
index 0000000000000..bdae3d6587872
--- /dev/null
+++ b/test/integration/scss-fixtures/url-global-asset-prefix-2/styles/dark.svg
@@ -0,0 +1,17 @@
+
+
diff --git a/test/integration/scss-fixtures/url-global-asset-prefix-2/styles/dark2.svg b/test/integration/scss-fixtures/url-global-asset-prefix-2/styles/dark2.svg
new file mode 100644
index 0000000000000..bdae3d6587872
--- /dev/null
+++ b/test/integration/scss-fixtures/url-global-asset-prefix-2/styles/dark2.svg
@@ -0,0 +1,17 @@
+
+
diff --git a/test/integration/scss-fixtures/url-global-asset-prefix-2/styles/global1.scss b/test/integration/scss-fixtures/url-global-asset-prefix-2/styles/global1.scss
new file mode 100644
index 0000000000000..24f6145a7a6fa
--- /dev/null
+++ b/test/integration/scss-fixtures/url-global-asset-prefix-2/styles/global1.scss
@@ -0,0 +1,5 @@
+$var: red;
+.red-text {
+ color: $var;
+ background-image: url('./dark.svg') url(dark2.svg);
+}
diff --git a/test/integration/scss-fixtures/url-global-asset-prefix-2/styles/global2.scss b/test/integration/scss-fixtures/url-global-asset-prefix-2/styles/global2.scss
new file mode 100644
index 0000000000000..4b659d369c561
--- /dev/null
+++ b/test/integration/scss-fixtures/url-global-asset-prefix-2/styles/global2.scss
@@ -0,0 +1,6 @@
+@import './global2b.scss';
+
+$var: blue;
+.blue-text {
+ color: $var;
+}
diff --git a/test/integration/scss-fixtures/url-global-asset-prefix-2/styles/global2b.scss b/test/integration/scss-fixtures/url-global-asset-prefix-2/styles/global2b.scss
new file mode 100644
index 0000000000000..0fe57600718f5
--- /dev/null
+++ b/test/integration/scss-fixtures/url-global-asset-prefix-2/styles/global2b.scss
@@ -0,0 +1,6 @@
+$var: orange;
+.blue-text {
+ color: $var;
+ font-weight: bolder;
+ background-image: url(../assets/light.svg);
+}
diff --git a/test/integration/scss-fixtures/url-global/assets/light.svg b/test/integration/scss-fixtures/url-global/assets/light.svg
new file mode 100644
index 0000000000000..0194cbce06561
--- /dev/null
+++ b/test/integration/scss-fixtures/url-global/assets/light.svg
@@ -0,0 +1,17 @@
+
+
diff --git a/test/integration/scss-fixtures/url-global/pages/_app.js b/test/integration/scss-fixtures/url-global/pages/_app.js
new file mode 100644
index 0000000000000..29beb9fdb3cc5
--- /dev/null
+++ b/test/integration/scss-fixtures/url-global/pages/_app.js
@@ -0,0 +1,13 @@
+import React from 'react'
+import App from 'next/app'
+import '../styles/global1.scss'
+import '../styles/global2.scss'
+
+class MyApp extends App {
+ render() {
+ const { Component, pageProps } = this.props
+ return
+ }
+}
+
+export default MyApp
diff --git a/test/integration/scss-fixtures/url-global/pages/index.js b/test/integration/scss-fixtures/url-global/pages/index.js
new file mode 100644
index 0000000000000..5cbac8a153d77
--- /dev/null
+++ b/test/integration/scss-fixtures/url-global/pages/index.js
@@ -0,0 +1,3 @@
+export default function Home() {
+ return This text should be red.
+}
diff --git a/test/integration/scss-fixtures/url-global/styles/dark.svg b/test/integration/scss-fixtures/url-global/styles/dark.svg
new file mode 100644
index 0000000000000..bdae3d6587872
--- /dev/null
+++ b/test/integration/scss-fixtures/url-global/styles/dark.svg
@@ -0,0 +1,17 @@
+
+
diff --git a/test/integration/scss-fixtures/url-global/styles/dark2.svg b/test/integration/scss-fixtures/url-global/styles/dark2.svg
new file mode 100644
index 0000000000000..bdae3d6587872
--- /dev/null
+++ b/test/integration/scss-fixtures/url-global/styles/dark2.svg
@@ -0,0 +1,17 @@
+
+
diff --git a/test/integration/scss-fixtures/url-global/styles/global1.scss b/test/integration/scss-fixtures/url-global/styles/global1.scss
new file mode 100644
index 0000000000000..24f6145a7a6fa
--- /dev/null
+++ b/test/integration/scss-fixtures/url-global/styles/global1.scss
@@ -0,0 +1,5 @@
+$var: red;
+.red-text {
+ color: $var;
+ background-image: url('./dark.svg') url(dark2.svg);
+}
diff --git a/test/integration/scss-fixtures/url-global/styles/global2.scss b/test/integration/scss-fixtures/url-global/styles/global2.scss
new file mode 100644
index 0000000000000..4b659d369c561
--- /dev/null
+++ b/test/integration/scss-fixtures/url-global/styles/global2.scss
@@ -0,0 +1,6 @@
+@import './global2b.scss';
+
+$var: blue;
+.blue-text {
+ color: $var;
+}
diff --git a/test/integration/scss-fixtures/url-global/styles/global2b.scss b/test/integration/scss-fixtures/url-global/styles/global2b.scss
new file mode 100644
index 0000000000000..0fe57600718f5
--- /dev/null
+++ b/test/integration/scss-fixtures/url-global/styles/global2b.scss
@@ -0,0 +1,6 @@
+$var: orange;
+.blue-text {
+ color: $var;
+ font-weight: bolder;
+ background-image: url(../assets/light.svg);
+}
diff --git a/test/integration/scss-fixtures/valid-and-invalid-global/pages/_app.js b/test/integration/scss-fixtures/valid-and-invalid-global/pages/_app.js
new file mode 100644
index 0000000000000..b89fe16feb731
--- /dev/null
+++ b/test/integration/scss-fixtures/valid-and-invalid-global/pages/_app.js
@@ -0,0 +1,12 @@
+import React from 'react'
+import App from 'next/app'
+import '../styles/global.scss'
+
+class MyApp extends App {
+ render() {
+ const { Component, pageProps } = this.props
+ return
+ }
+}
+
+export default MyApp
diff --git a/test/integration/scss-fixtures/valid-and-invalid-global/pages/index.js b/test/integration/scss-fixtures/valid-and-invalid-global/pages/index.js
new file mode 100644
index 0000000000000..38d46eb2a45eb
--- /dev/null
+++ b/test/integration/scss-fixtures/valid-and-invalid-global/pages/index.js
@@ -0,0 +1,5 @@
+import '../styles/global.scss'
+
+export default function Home() {
+ return This text should be red.
+}
diff --git a/test/integration/scss-fixtures/valid-and-invalid-global/styles/global.scss b/test/integration/scss-fixtures/valid-and-invalid-global/styles/global.scss
new file mode 100644
index 0000000000000..da7b3d1417b1b
--- /dev/null
+++ b/test/integration/scss-fixtures/valid-and-invalid-global/styles/global.scss
@@ -0,0 +1,4 @@
+$var: red;
+.red-text {
+ color: $var;
+}
diff --git a/test/integration/scss-fixtures/with-styled-jsx/pages/_app.js b/test/integration/scss-fixtures/with-styled-jsx/pages/_app.js
new file mode 100644
index 0000000000000..b89fe16feb731
--- /dev/null
+++ b/test/integration/scss-fixtures/with-styled-jsx/pages/_app.js
@@ -0,0 +1,12 @@
+import React from 'react'
+import App from 'next/app'
+import '../styles/global.scss'
+
+class MyApp extends App {
+ render() {
+ const { Component, pageProps } = this.props
+ return
+ }
+}
+
+export default MyApp
diff --git a/test/integration/scss-fixtures/with-styled-jsx/pages/index.js b/test/integration/scss-fixtures/with-styled-jsx/pages/index.js
new file mode 100644
index 0000000000000..ed9aa0621925e
--- /dev/null
+++ b/test/integration/scss-fixtures/with-styled-jsx/pages/index.js
@@ -0,0 +1,12 @@
+export default function Home() {
+ return (
+ <>
+ This text should be green.
+
+ >
+ )
+}
diff --git a/test/integration/scss-fixtures/with-styled-jsx/styles/global.scss b/test/integration/scss-fixtures/with-styled-jsx/styles/global.scss
new file mode 100644
index 0000000000000..30a5d888c5011
--- /dev/null
+++ b/test/integration/scss-fixtures/with-styled-jsx/styles/global.scss
@@ -0,0 +1,4 @@
+$var: red;
+.my-text {
+ color: $var;
+}
diff --git a/test/integration/scss-fixtures/with-tailwindcss-and-purgecss/pages/_app.js b/test/integration/scss-fixtures/with-tailwindcss-and-purgecss/pages/_app.js
new file mode 100644
index 0000000000000..0e40312aad8ac
--- /dev/null
+++ b/test/integration/scss-fixtures/with-tailwindcss-and-purgecss/pages/_app.js
@@ -0,0 +1,5 @@
+import '../styles/global.scss'
+
+export default function MyApp({ Component, pageProps }) {
+ return
+}
diff --git a/test/integration/scss-fixtures/with-tailwindcss-and-purgecss/pages/index.js b/test/integration/scss-fixtures/with-tailwindcss-and-purgecss/pages/index.js
new file mode 100644
index 0000000000000..ee6a89480a706
--- /dev/null
+++ b/test/integration/scss-fixtures/with-tailwindcss-and-purgecss/pages/index.js
@@ -0,0 +1,38 @@
+import Link from 'next/link'
+
+const links = [
+ { href: 'https://github.com/zeit/next.js', label: 'GitHub' },
+ { href: 'https://nextjs.org/docs', label: 'Docs' },
+]
+
+function Nav() {
+ return (
+
+ )
+}
+
+export default () => (
+
+
+
+
Next.js + Tailwind CSS + PurgeCSS
+
+
+)
diff --git a/test/integration/scss-fixtures/with-tailwindcss-and-purgecss/postcss.config.js b/test/integration/scss-fixtures/with-tailwindcss-and-purgecss/postcss.config.js
new file mode 100644
index 0000000000000..b39d3d7ff9d14
--- /dev/null
+++ b/test/integration/scss-fixtures/with-tailwindcss-and-purgecss/postcss.config.js
@@ -0,0 +1,13 @@
+const path = require('path')
+module.exports = {
+ plugins: [
+ 'tailwindcss',
+ [
+ '@fullhuman/postcss-purgecss',
+ {
+ content: [path.join(__dirname, './pages/**/*.{js,jsx,ts,tsx}')],
+ defaultExtractor: content => content.match(/[A-Za-z0-9-_:/]+/g) || [],
+ },
+ ],
+ ],
+}
diff --git a/test/integration/scss-fixtures/with-tailwindcss-and-purgecss/styles/global.scss b/test/integration/scss-fixtures/with-tailwindcss-and-purgecss/styles/global.scss
new file mode 100644
index 0000000000000..b5c61c956711f
--- /dev/null
+++ b/test/integration/scss-fixtures/with-tailwindcss-and-purgecss/styles/global.scss
@@ -0,0 +1,3 @@
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
diff --git a/test/integration/scss-fixtures/with-tailwindcss/package.json b/test/integration/scss-fixtures/with-tailwindcss/package.json
new file mode 100644
index 0000000000000..50375a78eb20b
--- /dev/null
+++ b/test/integration/scss-fixtures/with-tailwindcss/package.json
@@ -0,0 +1,7 @@
+{
+ "postcss": {
+ "plugins": {
+ "tailwindcss": {}
+ }
+ }
+}
diff --git a/test/integration/scss-fixtures/with-tailwindcss/pages/_app.js b/test/integration/scss-fixtures/with-tailwindcss/pages/_app.js
new file mode 100644
index 0000000000000..0e40312aad8ac
--- /dev/null
+++ b/test/integration/scss-fixtures/with-tailwindcss/pages/_app.js
@@ -0,0 +1,5 @@
+import '../styles/global.scss'
+
+export default function MyApp({ Component, pageProps }) {
+ return
+}
diff --git a/test/integration/scss-fixtures/with-tailwindcss/pages/index.js b/test/integration/scss-fixtures/with-tailwindcss/pages/index.js
new file mode 100644
index 0000000000000..b3ba78da2d5e1
--- /dev/null
+++ b/test/integration/scss-fixtures/with-tailwindcss/pages/index.js
@@ -0,0 +1,3 @@
+export default function Home() {
+ return
+}
diff --git a/test/integration/scss-fixtures/with-tailwindcss/styles/global.scss b/test/integration/scss-fixtures/with-tailwindcss/styles/global.scss
new file mode 100644
index 0000000000000..b5c61c956711f
--- /dev/null
+++ b/test/integration/scss-fixtures/with-tailwindcss/styles/global.scss
@@ -0,0 +1,3 @@
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
diff --git a/test/integration/scss-modules/test/index.test.js b/test/integration/scss-modules/test/index.test.js
new file mode 100644
index 0000000000000..751ef2e07889c
--- /dev/null
+++ b/test/integration/scss-modules/test/index.test.js
@@ -0,0 +1,383 @@
+/* eslint-env jest */
+/* global jasmine */
+import { join } from 'path'
+import { remove, readFile, readdir } from 'fs-extra'
+import {
+ nextBuild,
+ nextStart,
+ findPort,
+ killApp,
+ launchApp,
+ waitFor,
+ renderViaHTTP,
+} from 'next-test-utils'
+import cheerio from 'cheerio'
+import webdriver from 'next-webdriver'
+
+jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000 * 60 * 1
+
+const fixturesDir = join(__dirname, '../../scss-fixtures')
+
+describe('Basic SCSS Module Support', () => {
+ const appDir = join(fixturesDir, 'basic-module')
+
+ let appPort
+ let app
+ beforeAll(async () => {
+ await remove(join(appDir, '.next'))
+ await nextBuild(appDir)
+ appPort = await findPort()
+ app = await nextStart(appDir, appPort)
+ })
+ afterAll(async () => {
+ await killApp(app)
+ })
+
+ 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(
+ `".index_redText__2VIiM{color:red}"`
+ )
+ })
+
+ it(`should've injected the CSS on server render`, async () => {
+ const content = await renderViaHTTP(appPort, '/')
+ const $ = cheerio.load(content)
+
+ const cssPreload = $('link[rel="preload"][as="style"]')
+ expect(cssPreload.length).toBe(1)
+ expect(cssPreload.attr('href')).toMatch(/^\/_next\/static\/css\/.*\.css$/)
+
+ const cssSheet = $('link[rel="stylesheet"]')
+ expect(cssSheet.length).toBe(1)
+ expect(cssSheet.attr('href')).toMatch(/^\/_next\/static\/css\/.*\.css$/)
+
+ expect($('#verify-red').attr('class')).toMatchInlineSnapshot(
+ `"index_redText__2VIiM"`
+ )
+ })
+})
+
+describe('3rd Party CSS Module Support', () => {
+ const appDir = join(fixturesDir, '3rd-party-module')
+
+ let appPort
+ let app
+ beforeAll(async () => {
+ await remove(join(appDir, '.next'))
+ await nextBuild(appDir)
+ appPort = await findPort()
+ app = await nextStart(appDir, appPort)
+ })
+ afterAll(async () => {
+ await killApp(app)
+ })
+
+ 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(
+ `".index_foo__9_fxH{position:relative}.index_foo__9_fxH .bar,.index_foo__9_fxH .baz{height:100%;overflow:hidden}.index_foo__9_fxH .lol,.index_foo__9_fxH>.lel{width:80%}"`
+ )
+ })
+
+ it(`should've injected the CSS on server render`, async () => {
+ const content = await renderViaHTTP(appPort, '/')
+ const $ = cheerio.load(content)
+
+ const cssPreload = $('link[rel="preload"][as="style"]')
+ expect(cssPreload.length).toBe(1)
+ expect(cssPreload.attr('href')).toMatch(/^\/_next\/static\/css\/.*\.css$/)
+
+ const cssSheet = $('link[rel="stylesheet"]')
+ expect(cssSheet.length).toBe(1)
+ expect(cssSheet.attr('href')).toMatch(/^\/_next\/static\/css\/.*\.css$/)
+
+ expect($('#verify-div').attr('class')).toMatchInlineSnapshot(
+ `"index_foo__9_fxH"`
+ )
+ })
+})
+
+describe('Has CSS Module in computed styles in Development', () => {
+ const appDir = join(fixturesDir, 'dev-module')
+
+ let appPort
+ let app
+ beforeAll(async () => {
+ await remove(join(appDir, '.next'))
+ appPort = await findPort()
+ app = await launchApp(appDir, appPort)
+ })
+ afterAll(async () => {
+ await killApp(app)
+ })
+
+ it('should have CSS for page', async () => {
+ const browser = await webdriver(appPort, '/')
+
+ const currentColor = await browser.eval(
+ `window.getComputedStyle(document.querySelector('#verify-red')).color`
+ )
+ expect(currentColor).toMatchInlineSnapshot(`"rgb(255, 0, 0)"`)
+ })
+})
+
+describe('Has CSS Module in computed styles in Production', () => {
+ const appDir = join(fixturesDir, 'prod-module')
+
+ let appPort
+ let app
+ beforeAll(async () => {
+ await remove(join(appDir, '.next'))
+ await nextBuild(appDir)
+ appPort = await findPort()
+ app = await nextStart(appDir, appPort)
+ })
+ afterAll(async () => {
+ await killApp(app)
+ })
+
+ it('should have CSS for page', async () => {
+ const browser = await webdriver(appPort, '/')
+
+ const currentColor = await browser.eval(
+ `window.getComputedStyle(document.querySelector('#verify-red')).color`
+ )
+ expect(currentColor).toMatchInlineSnapshot(`"rgb(255, 0, 0)"`)
+ })
+})
+
+xdescribe('Can hot reload CSS Module without losing state', () => {
+ const appDir = join(fixturesDir, 'hmr-module')
+
+ let appPort
+ let app
+ beforeAll(async () => {
+ await remove(join(appDir, '.next'))
+ appPort = await findPort()
+ app = await launchApp(appDir, appPort)
+ })
+ afterAll(async () => {
+ await killApp(app)
+ })
+
+ // FIXME: this is broken
+ it('should update CSS color without remounting ', async () => {
+ const browser = await webdriver(appPort, '/')
+
+ const desiredText = 'hello world'
+ await browser.elementById('text-input').type(desiredText)
+ expect(await browser.elementById('text-input').getValue()).toBe(desiredText)
+
+ const currentColor = await browser.eval(
+ `window.getComputedStyle(document.querySelector('#verify-red')).color`
+ )
+ expect(currentColor).toMatchInlineSnapshot(`"rgb(255, 0, 0)"`)
+
+ const cssFile = new File(join(appDir, 'pages/index.module.scss'))
+ try {
+ cssFile.replace('color: red', 'color: purple')
+ await waitFor(2000) // wait for HMR
+
+ const refreshedColor = await browser.eval(
+ `window.getComputedStyle(document.querySelector('#verify-red')).color`
+ )
+ expect(refreshedColor).toMatchInlineSnapshot(`"rgb(128, 0, 128)"`)
+
+ // ensure text remained
+ expect(await browser.elementById('text-input').getValue()).toBe(
+ desiredText
+ )
+ } finally {
+ cssFile.restore()
+ }
+ })
+})
+
+describe('Invalid CSS Module Usage in node_modules', () => {
+ const appDir = join(fixturesDir, 'invalid-module')
+
+ beforeAll(async () => {
+ await remove(join(appDir, '.next'))
+ })
+
+ it('should fail to build', async () => {
+ const { stderr } = await nextBuild(appDir, [], {
+ stderr: true,
+ })
+ expect(stderr).toContain('Failed to compile')
+ expect(stderr).toContain('node_modules/example/index.module.scss')
+ expect(stderr).toMatch(
+ /CSS Modules.*cannot.*be imported from within.*node_modules/
+ )
+ expect(stderr).toMatch(/Location:.*node_modules[\\/]example[\\/]index\.mjs/)
+ })
+})
+
+describe('Invalid CSS Module Usage in node_modules', () => {
+ const appDir = join(fixturesDir, 'invalid-global-module')
+
+ beforeAll(async () => {
+ await remove(join(appDir, '.next'))
+ })
+
+ it('should fail to build', async () => {
+ const { stderr } = await nextBuild(appDir, [], {
+ stderr: true,
+ })
+ expect(stderr).toContain('Failed to compile')
+ expect(stderr).toContain('node_modules/example/index.scss')
+ expect(stderr).toMatch(
+ /Global CSS.*cannot.*be imported from within.*node_modules/
+ )
+ expect(stderr).toMatch(/Location:.*node_modules[\\/]example[\\/]index\.mjs/)
+ })
+})
+
+describe('Valid CSS Module Usage from within node_modules', () => {
+ const appDir = join(fixturesDir, 'nm-module')
+
+ beforeAll(async () => {
+ await remove(join(appDir, '.next'))
+ })
+
+ let appPort
+ let app
+ beforeAll(async () => {
+ await nextBuild(appDir)
+ appPort = await findPort()
+ app = await nextStart(appDir, appPort)
+ })
+ afterAll(async () => {
+ await killApp(app)
+ })
+
+ it(`should've prerendered with relevant data`, async () => {
+ const content = await renderViaHTTP(appPort, '/')
+ const $ = cheerio.load(content)
+
+ const cssPreload = $('#nm-div')
+ expect(cssPreload.text()).toMatchInlineSnapshot(
+ `"{\\"message\\":\\"Why hello there\\"} {\\"redText\\":\\"example_redText__1hNNA\\"}"`
+ )
+ })
+
+ 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(
+ `".example_redText__1hNNA{color:red}"`
+ )
+ })
+})
+
+describe('Valid Nested CSS Module Usage from within node_modules', () => {
+ const appDir = join(fixturesDir, 'nm-module-nested')
+
+ beforeAll(async () => {
+ await remove(join(appDir, '.next'))
+ })
+
+ let appPort
+ let app
+ beforeAll(async () => {
+ await nextBuild(appDir)
+ appPort = await findPort()
+ app = await nextStart(appDir, appPort)
+ })
+ afterAll(async () => {
+ await killApp(app)
+ })
+
+ it(`should've prerendered with relevant data`, async () => {
+ const content = await renderViaHTTP(appPort, '/')
+ const $ = cheerio.load(content)
+
+ const cssPreload = $('#nm-div')
+ expect(cssPreload.text()).toMatchInlineSnapshot(
+ `"{\\"message\\":\\"Why hello there\\"} {\\"other2\\":\\"example_other2__1pnJV\\",\\"subClass\\":\\"example_subClass__2EbKX other_className__E6nd8\\"}"`
+ )
+ })
+
+ 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(
+ `".other_other3__ZPN-Y{color:violet}.other_className__E6nd8{background:red;color:#ff0}.example_other2__1pnJV{color:red}.example_subClass__2EbKX{background:#00f}"`
+ )
+ })
+})
+
+describe('CSS Module Composes Usage (Basic)', () => {
+ // This is a very bad feature. Do not use it.
+ const appDir = join(fixturesDir, 'composes-basic')
+
+ beforeAll(async () => {
+ await remove(join(appDir, '.next'))
+ await nextBuild(appDir)
+ })
+
+ 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(
+ `".index_className__2O8Wt{background:red;color:#ff0}.index_subClass__3e6Re{background:#00f}"`
+ )
+ })
+})
+
+describe('CSS Module Composes Usage (External)', () => {
+ // This is a very bad feature. Do not use it.
+ const appDir = join(fixturesDir, 'composes-external')
+
+ beforeAll(async () => {
+ await remove(join(appDir, '.next'))
+ await nextBuild(appDir)
+ })
+
+ 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(
+ `".other_className__2VTl4{background:red;color:#ff0}.index_subClass__3e6Re{background:#00f}"`
+ )
+ })
+})
diff --git a/test/integration/scss/test/index.test.js b/test/integration/scss/test/index.test.js
new file mode 100644
index 0000000000000..aca8a733f9951
--- /dev/null
+++ b/test/integration/scss/test/index.test.js
@@ -0,0 +1,813 @@
+/* eslint-env jest */
+/* global jasmine */
+import 'flat-map-polyfill'
+import { join } from 'path'
+import { readdir, readFile, remove } from 'fs-extra'
+import {
+ findPort,
+ nextBuild,
+ nextStart,
+ launchApp,
+ killApp,
+ File,
+ waitFor,
+ renderViaHTTP,
+} from 'next-test-utils'
+import webdriver from 'next-webdriver'
+import cheerio from 'cheerio'
+
+jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000 * 60 * 2
+
+const fixturesDir = join(__dirname, '../..', 'scss-fixtures')
+
+describe('SCSS Support', () => {
+ describe('Basic Global Support', () => {
+ const appDir = join(fixturesDir, 'single-global')
+
+ beforeAll(async () => {
+ await remove(join(appDir, '.next'))
+ })
+
+ it('should build successfully', async () => {
+ await nextBuild(appDir)
+ })
+
+ 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)
+ expect(await readFile(join(cssFolder, cssFiles[0]), 'utf8')).toContain(
+ 'color:red'
+ )
+ })
+ })
+
+ describe('Basic Global Support with src/ dir', () => {
+ const appDir = join(fixturesDir, 'single-global-src')
+
+ beforeAll(async () => {
+ await remove(join(appDir, '.next'))
+ })
+
+ it('should build successfully', async () => {
+ await nextBuild(appDir)
+ })
+
+ 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)
+ expect(await readFile(join(cssFolder, cssFiles[0]), 'utf8')).toContain(
+ 'color:red'
+ )
+ })
+ })
+
+ describe('Multi Global Support', () => {
+ const appDir = join(fixturesDir, 'multi-global')
+
+ beforeAll(async () => {
+ await remove(join(appDir, '.next'))
+ })
+
+ it('should build successfully', async () => {
+ await nextBuild(appDir)
+ })
+
+ 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}.blue-text{color:#00f}"`)
+ })
+ })
+
+ describe('Nested @import() Global Support', () => {
+ const appDir = join(fixturesDir, 'nested-global')
+
+ beforeAll(async () => {
+ await remove(join(appDir, '.next'))
+ })
+
+ it('should build successfully', async () => {
+ await nextBuild(appDir)
+ })
+
+ 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:purple;font-weight:bolder;color:red}.blue-text{color:orange;font-weight:bolder;color:#00f}"`
+ )
+ })
+ })
+
+ describe('CSS Compilation and Prefixing', () => {
+ const appDir = join(fixturesDir, 'compilation-and-prefixing')
+
+ beforeAll(async () => {
+ await remove(join(appDir, '.next'))
+ })
+
+ it('should build successfully', async () => {
+ await nextBuild(appDir)
+ })
+
+ it(`should've compiled and prefixed`, 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(
+ `".redText ::-webkit-input-placeholder{color:red}.redText ::-moz-placeholder{color:red}.redText :-ms-input-placeholder{color:red}.redText ::-ms-input-placeholder{color:red}.redText ::placeholder{color:red}"`
+ )
+
+ // Contains a source map
+ expect(cssContent).toMatch(/\/\*#\s*sourceMappingURL=(.+\.map)\s*\*\//)
+ })
+
+ it(`should've emitted a source map`, async () => {
+ const cssFolder = join(appDir, '.next/static/css')
+
+ const files = await readdir(cssFolder)
+ const cssMapFiles = files.filter(f => /\.css\.map$/.test(f))
+
+ expect(cssMapFiles.length).toBe(1)
+ const cssMapContent = (
+ await readFile(join(cssFolder, cssMapFiles[0]), 'utf8')
+ ).trim()
+
+ const { version, mappings, sourcesContent } = JSON.parse(cssMapContent)
+ expect({ version, mappings, sourcesContent }).toMatchInlineSnapshot(`
+ Object {
+ "mappings": "AACA,qCAEI,SAHK,CACT,4BAEI,SAHK,CACT,gCAEI,SAHK,CACT,iCAEI,SAHK,CACT,uBAEI,SAHK",
+ "sourcesContent": Array [
+ "$var: red;
+ .redText {
+ ::placeholder {
+ color: $var;
+ }
+ }
+ ",
+ ],
+ "version": 3,
+ }
+ `)
+ })
+ })
+
+ // Tests css ordering
+ describe('Multi Global Support (reversed)', () => {
+ const appDir = join(fixturesDir, 'multi-global-reversed')
+
+ beforeAll(async () => {
+ await remove(join(appDir, '.next'))
+ })
+
+ it('should build successfully', async () => {
+ await nextBuild(appDir)
+ })
+
+ 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(`".blue-text{color:#00f}.red-text{color:red}"`)
+ })
+ })
+
+ describe('Invalid CSS in _document', () => {
+ const appDir = join(fixturesDir, 'invalid-module-document')
+
+ beforeAll(async () => {
+ await remove(join(appDir, '.next'))
+ })
+
+ it('should fail to build', async () => {
+ const { stderr } = await nextBuild(appDir, [], {
+ stderr: true,
+ })
+ expect(stderr).toContain('Failed to compile')
+ expect(stderr).toContain('styles.module.scss')
+ expect(stderr).toMatch(
+ /CSS.*cannot.*be imported within.*pages[\\/]_document\.js/
+ )
+ expect(stderr).toMatch(/Location:.*pages[\\/]_document\.js/)
+ })
+ })
+
+ describe('Invalid Global CSS', () => {
+ const appDir = join(fixturesDir, 'invalid-global')
+
+ beforeAll(async () => {
+ await remove(join(appDir, '.next'))
+ })
+
+ it('should fail to build', async () => {
+ const { stderr } = await nextBuild(appDir, [], {
+ stderr: true,
+ })
+ expect(stderr).toContain('Failed to compile')
+ expect(stderr).toContain('styles/global.scss')
+ expect(stderr).toMatch(
+ /Please move all global CSS imports.*?pages(\/|\\)_app/
+ )
+ expect(stderr).toMatch(/Location:.*pages[\\/]index\.js/)
+ })
+ })
+
+ describe('Invalid Global CSS with Custom App', () => {
+ const appDir = join(fixturesDir, 'invalid-global-with-app')
+
+ beforeAll(async () => {
+ await remove(join(appDir, '.next'))
+ })
+
+ it('should fail to build', async () => {
+ const { stderr } = await nextBuild(appDir, [], {
+ stderr: true,
+ })
+ expect(stderr).toContain('Failed to compile')
+ expect(stderr).toContain('styles/global.scss')
+ expect(stderr).toMatch(
+ /Please move all global CSS imports.*?pages(\/|\\)_app/
+ )
+ expect(stderr).toMatch(/Location:.*pages[\\/]index\.js/)
+ })
+ })
+
+ describe('Valid and Invalid Global CSS with Custom App', () => {
+ const appDir = join(fixturesDir, 'valid-and-invalid-global')
+
+ beforeAll(async () => {
+ await remove(join(appDir, '.next'))
+ })
+
+ it('should fail to build', async () => {
+ const { stderr } = await nextBuild(appDir, [], {
+ stderr: true,
+ })
+ expect(stderr).toContain('Failed to compile')
+ expect(stderr).toContain('styles/global.scss')
+ expect(stderr).toContain('Please move all global CSS imports')
+ expect(stderr).toMatch(/Location:.*pages[\\/]index\.js/)
+ })
+ })
+
+ describe('Can hot reload CSS without losing state', () => {
+ const appDir = join(fixturesDir, 'multi-page')
+
+ beforeAll(async () => {
+ await remove(join(appDir, '.next'))
+ })
+
+ let appPort
+ let app
+ beforeAll(async () => {
+ appPort = await findPort()
+ app = await launchApp(appDir, appPort)
+ })
+ afterAll(async () => {
+ await killApp(app)
+ })
+
+ it('should update CSS color without remounting ', async () => {
+ let browser
+ try {
+ browser = await webdriver(appPort, '/page1')
+
+ const desiredText = 'hello world'
+ await browser.elementById('text-input').type(desiredText)
+ expect(await browser.elementById('text-input').getValue()).toBe(
+ desiredText
+ )
+
+ const currentColor = await browser.eval(
+ `window.getComputedStyle(document.querySelector('.red-text')).color`
+ )
+ expect(currentColor).toMatchInlineSnapshot(`"rgb(255, 0, 0)"`)
+
+ const cssFile = new File(join(appDir, 'styles/global1.scss'))
+ try {
+ cssFile.replace('$var: red', '$var: purple')
+ await waitFor(2000) // wait for HMR
+
+ const refreshedColor = await browser.eval(
+ `window.getComputedStyle(document.querySelector('.red-text')).color`
+ )
+ expect(refreshedColor).toMatchInlineSnapshot(`"rgb(128, 0, 128)"`)
+
+ // ensure text remained
+ expect(await browser.elementById('text-input').getValue()).toBe(
+ desiredText
+ )
+ } finally {
+ cssFile.restore()
+ }
+ } finally {
+ if (browser) {
+ await browser.close()
+ }
+ }
+ })
+ })
+
+ describe('Has CSS in computed styles in Development', () => {
+ const appDir = join(fixturesDir, 'multi-page')
+
+ beforeAll(async () => {
+ await remove(join(appDir, '.next'))
+ })
+
+ let appPort
+ let app
+ beforeAll(async () => {
+ appPort = await findPort()
+ app = await launchApp(appDir, appPort)
+ })
+ afterAll(async () => {
+ await killApp(app)
+ })
+
+ it('should have CSS for page', async () => {
+ let browser
+ try {
+ browser = await webdriver(appPort, '/page2')
+
+ const currentColor = await browser.eval(
+ `window.getComputedStyle(document.querySelector('.blue-text')).color`
+ )
+ expect(currentColor).toMatchInlineSnapshot(`"rgb(0, 0, 255)"`)
+ } finally {
+ if (browser) {
+ await browser.close()
+ }
+ }
+ })
+ })
+
+ describe('Body is not hidden when unused in Development', () => {
+ const appDir = join(fixturesDir, 'unused')
+
+ beforeAll(async () => {
+ await remove(join(appDir, '.next'))
+ })
+
+ let appPort
+ let app
+ beforeAll(async () => {
+ appPort = await findPort()
+ app = await launchApp(appDir, appPort)
+ })
+ afterAll(async () => {
+ await killApp(app)
+ })
+
+ it('should have body visible', async () => {
+ let browser
+ try {
+ browser = await webdriver(appPort, '/')
+ const currentDisplay = await browser.eval(
+ `window.getComputedStyle(document.querySelector('body')).display`
+ )
+ expect(currentDisplay).toBe('block')
+ } finally {
+ if (browser) {
+ await browser.close()
+ }
+ }
+ })
+ })
+
+ describe('Body is not hidden when broken in Development', () => {
+ const appDir = join(fixturesDir, 'unused')
+
+ let appPort
+ let app
+ beforeAll(async () => {
+ await remove(join(appDir, '.next'))
+ appPort = await findPort()
+ app = await launchApp(appDir, appPort)
+ })
+ afterAll(async () => {
+ await killApp(app)
+ })
+
+ it('should have body visible', async () => {
+ const pageFile = new File(join(appDir, 'pages/index.js'))
+ let browser
+ try {
+ pageFile.replace('', '')
+ await waitFor(2000) // wait for recompile
+
+ browser = await webdriver(appPort, '/')
+ const currentDisplay = await browser.eval(
+ `window.getComputedStyle(document.querySelector('body')).display`
+ )
+ expect(currentDisplay).toBe('block')
+ } finally {
+ pageFile.restore()
+ if (browser) {
+ await browser.close()
+ }
+ }
+ })
+ })
+
+ describe('Has CSS in computed styles in Production', () => {
+ const appDir = join(fixturesDir, 'multi-page')
+
+ let appPort
+ let app
+ beforeAll(async () => {
+ await remove(join(appDir, '.next'))
+ await nextBuild(appDir)
+ appPort = await findPort()
+ app = await nextStart(appDir, appPort)
+ })
+ afterAll(async () => {
+ await killApp(app)
+ })
+
+ it('should have CSS for page', async () => {
+ const browser = await webdriver(appPort, '/page2')
+
+ const currentColor = await browser.eval(
+ `window.getComputedStyle(document.querySelector('.blue-text')).color`
+ )
+ expect(currentColor).toMatchInlineSnapshot(`"rgb(0, 0, 255)"`)
+ })
+
+ it(`should've preloaded the CSS file and injected it in `, async () => {
+ const content = await renderViaHTTP(appPort, '/page2')
+ const $ = cheerio.load(content)
+
+ const cssPreload = $('link[rel="preload"][as="style"]')
+ expect(cssPreload.length).toBe(1)
+ expect(cssPreload.attr('href')).toMatch(/^\/_next\/static\/css\/.*\.css$/)
+
+ const cssSheet = $('link[rel="stylesheet"]')
+ expect(cssSheet.length).toBe(1)
+ expect(cssSheet.attr('href')).toMatch(/^\/_next\/static\/css\/.*\.css$/)
+
+ /* ensure CSS preloaded first */
+ const allPreloads = [].slice.call($('link[rel="preload"]'))
+ const styleIndexes = allPreloads.flatMap((p, i) =>
+ p.attribs.as === 'style' ? i : []
+ )
+ expect(styleIndexes).toEqual([0])
+ })
+ })
+
+ describe('CSS URL via `file-loader`', () => {
+ const appDir = join(fixturesDir, 'url-global')
+
+ beforeAll(async () => {
+ await remove(join(appDir, '.next'))
+ })
+
+ it('should build successfully', async () => {
+ await nextBuild(appDir)
+ })
+
+ it(`should've emitted expected files`, async () => {
+ const cssFolder = join(appDir, '.next/static/css')
+ const mediaFolder = join(appDir, '.next/static/media')
+
+ 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()).toMatch(
+ /^\.red-text\{color:red;background-image:url\(\/_next\/static\/media\/dark\.[a-z0-9]{32}\.svg\) url\(\/_next\/static\/media\/dark2\.[a-z0-9]{32}\.svg\)\}\.blue-text\{color:orange;font-weight:bolder;background-image:url\(\/_next\/static\/media\/light\.[a-z0-9]{32}\.svg\);color:#00f\}$/
+ )
+
+ const mediaFiles = await readdir(mediaFolder)
+ expect(mediaFiles.length).toBe(3)
+ expect(
+ mediaFiles
+ .map(fileName =>
+ /^(.+?)\..{32}\.(.+?)$/
+ .exec(fileName)
+ .slice(1)
+ .join('.')
+ )
+ .sort()
+ ).toMatchInlineSnapshot(`
+ Array [
+ "dark.svg",
+ "dark2.svg",
+ "light.svg",
+ ]
+ `)
+ })
+ })
+
+ describe('CSS URL via `file-loader` and asset prefix (1)', () => {
+ const appDir = join(fixturesDir, 'url-global-asset-prefix-1')
+
+ beforeAll(async () => {
+ await remove(join(appDir, '.next'))
+ })
+
+ it('should build successfully', async () => {
+ await nextBuild(appDir)
+ })
+
+ it(`should've emitted expected files`, async () => {
+ const cssFolder = join(appDir, '.next/static/css')
+ const mediaFolder = join(appDir, '.next/static/media')
+
+ 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()).toMatch(
+ /^\.red-text\{color:red;background-image:url\(\/foo\/_next\/static\/media\/dark\.[a-z0-9]{32}\.svg\) url\(\/foo\/_next\/static\/media\/dark2\.[a-z0-9]{32}\.svg\)\}\.blue-text\{color:orange;font-weight:bolder;background-image:url\(\/foo\/_next\/static\/media\/light\.[a-z0-9]{32}\.svg\);color:#00f\}$/
+ )
+
+ const mediaFiles = await readdir(mediaFolder)
+ expect(mediaFiles.length).toBe(3)
+ expect(
+ mediaFiles
+ .map(fileName =>
+ /^(.+?)\..{32}\.(.+?)$/
+ .exec(fileName)
+ .slice(1)
+ .join('.')
+ )
+ .sort()
+ ).toMatchInlineSnapshot(`
+ Array [
+ "dark.svg",
+ "dark2.svg",
+ "light.svg",
+ ]
+ `)
+ })
+ })
+
+ describe('CSS URL via `file-loader` and asset prefix (2)', () => {
+ const appDir = join(fixturesDir, 'url-global-asset-prefix-2')
+
+ beforeAll(async () => {
+ await remove(join(appDir, '.next'))
+ })
+
+ it('should build successfully', async () => {
+ await nextBuild(appDir)
+ })
+
+ it(`should've emitted expected files`, async () => {
+ const cssFolder = join(appDir, '.next/static/css')
+ const mediaFolder = join(appDir, '.next/static/media')
+
+ 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()).toMatch(
+ /^\.red-text\{color:red;background-image:url\(\/foo\/_next\/static\/media\/dark\.[a-z0-9]{32}\.svg\) url\(\/foo\/_next\/static\/media\/dark2\.[a-z0-9]{32}\.svg\)\}\.blue-text\{color:orange;font-weight:bolder;background-image:url\(\/foo\/_next\/static\/media\/light\.[a-z0-9]{32}\.svg\);color:#00f\}$/
+ )
+
+ const mediaFiles = await readdir(mediaFolder)
+ expect(mediaFiles.length).toBe(3)
+ expect(
+ mediaFiles
+ .map(fileName =>
+ /^(.+?)\..{32}\.(.+?)$/
+ .exec(fileName)
+ .slice(1)
+ .join('.')
+ )
+ .sort()
+ ).toMatchInlineSnapshot(`
+ Array [
+ "dark.svg",
+ "dark2.svg",
+ "light.svg",
+ ]
+ `)
+ })
+ })
+
+ describe('Good CSS Import from node_modules', () => {
+ const appDir = join(fixturesDir, 'npm-import')
+
+ beforeAll(async () => {
+ await remove(join(appDir, '.next'))
+ })
+
+ it('should build successfully', async () => {
+ await nextBuild(appDir)
+ })
+
+ 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()).toMatch(/nprogress/)
+ })
+ })
+
+ describe('Good Nested CSS Import from node_modules', () => {
+ const appDir = join(fixturesDir, 'npm-import-nested')
+
+ beforeAll(async () => {
+ await remove(join(appDir, '.next'))
+ })
+
+ it('should build successfully', async () => {
+ await nextBuild(appDir)
+ })
+
+ 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(`".other{color:#00f}.test{color:red}"`)
+ })
+ })
+
+ describe('Bad CSS Import from node_modules', () => {
+ const appDir = join(fixturesDir, 'npm-import-bad')
+
+ beforeAll(async () => {
+ await remove(join(appDir, '.next'))
+ })
+
+ it('should fail the build', async () => {
+ const { stderr } = await nextBuild(appDir, [], { stderr: true })
+
+ expect(stderr).toMatch(/Can't resolve '[^']*?nprogress[^']*?'/)
+ expect(stderr).toMatch(/Build error occurred/)
+ })
+ })
+
+ describe('Ordering with styled-jsx (dev)', () => {
+ const appDir = join(fixturesDir, 'with-styled-jsx')
+
+ let appPort
+ let app
+ beforeAll(async () => {
+ await remove(join(appDir, '.next'))
+ appPort = await findPort()
+ app = await launchApp(appDir, appPort)
+ })
+ afterAll(async () => {
+ await killApp(app)
+ })
+
+ it('should have the correct color (css ordering)', async () => {
+ const browser = await webdriver(appPort, '/')
+
+ const currentColor = await browser.eval(
+ `window.getComputedStyle(document.querySelector('.my-text')).color`
+ )
+ expect(currentColor).toMatchInlineSnapshot(`"rgb(0, 128, 0)"`)
+ })
+ })
+
+ describe('Ordering with styled-jsx (prod)', () => {
+ const appDir = join(fixturesDir, 'with-styled-jsx')
+
+ let appPort
+ let app
+ beforeAll(async () => {
+ await remove(join(appDir, '.next'))
+ await nextBuild(appDir)
+ appPort = await findPort()
+ app = await nextStart(appDir, appPort)
+ })
+ afterAll(async () => {
+ await killApp(app)
+ })
+
+ it('should have the correct color (css ordering)', async () => {
+ const browser = await webdriver(appPort, '/')
+
+ const currentColor = await browser.eval(
+ `window.getComputedStyle(document.querySelector('.my-text')).color`
+ )
+ expect(currentColor).toMatchInlineSnapshot(`"rgb(0, 128, 0)"`)
+ })
+ })
+
+ describe('Basic Tailwind CSS', () => {
+ const appDir = join(fixturesDir, 'with-tailwindcss')
+
+ beforeAll(async () => {
+ await remove(join(appDir, '.next'))
+ })
+
+ it('should build successfully', async () => {
+ await nextBuild(appDir)
+ })
+
+ it(`should've compiled and prefixed`, 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).toMatch(/object-right-bottom/) // look for tailwind's CSS
+ expect(cssContent).not.toMatch(/tailwind/) // ensure @tailwind was removed
+
+ // Contains a source map
+ expect(cssContent).toMatch(/\/\*#\s*sourceMappingURL=(.+\.map)\s*\*\//)
+ })
+
+ it(`should've emitted a source map`, async () => {
+ const cssFolder = join(appDir, '.next/static/css')
+
+ const files = await readdir(cssFolder)
+ const cssMapFiles = files.filter(f => /\.css\.map$/.test(f))
+
+ expect(cssMapFiles.length).toBe(1)
+ })
+ })
+
+ describe('Tailwind and Purge CSS', () => {
+ const appDir = join(fixturesDir, 'with-tailwindcss-and-purgecss')
+
+ beforeAll(async () => {
+ await remove(join(appDir, '.next'))
+ })
+
+ it('should build successfully', async () => {
+ await nextBuild(appDir)
+ })
+
+ it(`should've compiled and prefixed`, 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).not.toMatch(/object-right-bottom/) // this was unused and should be gone
+ expect(cssContent).toMatch(/text-blue-500/) // this was used
+ expect(cssContent).not.toMatch(/tailwind/) // ensure @tailwind was removed
+
+ // Contains a source map
+ expect(cssContent).toMatch(/\/\*#\s*sourceMappingURL=(.+\.map)\s*\*\//)
+ })
+
+ it(`should've emitted a source map`, async () => {
+ const cssFolder = join(appDir, '.next/static/css')
+
+ const files = await readdir(cssFolder)
+ const cssMapFiles = files.filter(f => /\.css\.map$/.test(f))
+
+ expect(cssMapFiles.length).toBe(1)
+ })
+ })
+})
diff --git a/yarn.lock b/yarn.lock
index 46d1480b54c51..907b2f185d5e2 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3031,6 +3031,17 @@ acorn@^7.1.0:
resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.1.0.tgz#949d36f2c292535da602283586c2477c57eb2d6c"
integrity sha512-kL5CuoXA/dgxlBbVrflsflzQ3PAas7RYZB52NOm/6839iVYJgKMJ3cQJD+t2i5+qFa8h3MDpEOJiS64E8JLnSQ==
+adjust-sourcemap-loader@2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/adjust-sourcemap-loader/-/adjust-sourcemap-loader-2.0.0.tgz#6471143af75ec02334b219f54bc7970c52fb29a4"
+ integrity sha512-4hFsTsn58+YjrU9qKzML2JSSDqKvN8mUGQ0nNIrfPi8hmIONT4L3uUaT6MKdMsZ9AjsU6D2xDkZxCkbQPxChrA==
+ dependencies:
+ assert "1.4.1"
+ camelcase "5.0.0"
+ loader-utils "1.2.3"
+ object-path "0.11.4"
+ regex-parser "2.2.10"
+
after-all-results@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/after-all-results/-/after-all-results-2.0.0.tgz#6ac2fc202b500f88da8f4f5530cfa100f4c6a2d0"
@@ -3237,6 +3248,11 @@ args@4.0.0:
leven "2.1.0"
mri "1.1.0"
+arity-n@^1.0.4:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/arity-n/-/arity-n-1.0.4.tgz#d9e76b11733e08569c0847ae7b39b2860b30b745"
+ integrity sha1-2edrEXM+CFacCEeuezmyhgswt0U=
+
arr-diff@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-2.0.0.tgz#8f3b827f955a8bd669697e4a4256ac3ceae356cf"
@@ -3359,6 +3375,13 @@ assert-plus@1.0.0, assert-plus@^1.0.0:
resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525"
integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=
+assert@1.4.1:
+ version "1.4.1"
+ resolved "https://registry.yarnpkg.com/assert/-/assert-1.4.1.tgz#99912d591836b5a6f5b345c0f07eefc08fc65d91"
+ integrity sha1-mZEtWRg2tab1s0XA8H7vwI/GXZE=
+ dependencies:
+ util "0.10.3"
+
assert@^1.1.1:
version "1.5.0"
resolved "https://registry.yarnpkg.com/assert/-/assert-1.5.0.tgz#55c109aaf6e0aefdb3dc4b71240c70bf574b18eb"
@@ -4090,6 +4113,11 @@ camelcase@5.0.0:
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.0.0.tgz#03295527d58bd3cd4aa75363f35b2e8d97be2f42"
integrity sha512-faqwZqnWxbxn+F1d399ygeamQNy3lPp/H9H6rNrqYh4FSVCtcY+3cub1MxA8o9mDd55mM8Aghuu/kuyYA6VTsA==
+camelcase@5.3.1, camelcase@^5.0.0, camelcase@^5.3.1:
+ version "5.3.1"
+ resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320"
+ integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==
+
camelcase@^2.0.0, camelcase@^2.0.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-2.1.1.tgz#7c1d16d679a1bbe59ca02cacecfb011e201f5a1f"
@@ -4105,11 +4133,6 @@ camelcase@^4.1.0:
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd"
integrity sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=
-camelcase@^5.0.0, camelcase@^5.3.1:
- version "5.3.1"
- resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320"
- integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==
-
caniuse-api@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/caniuse-api/-/caniuse-api-3.0.0.tgz#5e4d90e2274961d46291997df599e3ed008ee4c0"
@@ -4434,6 +4457,15 @@ clone-deep@^0.3.0:
kind-of "^3.2.2"
shallow-clone "^0.1.2"
+clone-deep@^4.0.1:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387"
+ integrity sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==
+ dependencies:
+ is-plain-object "^2.0.4"
+ kind-of "^6.0.2"
+ shallow-clone "^3.0.0"
+
clone-response@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.2.tgz#d1dc973920314df67fbeb94223b4ee350239e96b"
@@ -4589,6 +4621,13 @@ component-emitter@^1.2.0, component-emitter@^1.2.1:
resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0"
integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==
+compose-function@3.0.3:
+ version "3.0.3"
+ resolved "https://registry.yarnpkg.com/compose-function/-/compose-function-3.0.3.tgz#9ed675f13cc54501d30950a486ff6a7ba3ab185f"
+ integrity sha1-ntZ18TzFRQHTCVCkhv9qe6OrGF8=
+ dependencies:
+ arity-n "^1.0.4"
+
compressible@~2.0.16:
version "2.0.18"
resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.18.tgz#af53cca6b070d4c3c0750fbd77286a6d7cc46fba"
@@ -4792,6 +4831,11 @@ convert-source-map@1.7.0, convert-source-map@^1.4.0, convert-source-map@^1.7.0:
dependencies:
safe-buffer "~5.1.1"
+convert-source-map@^0.3.3:
+ version "0.3.5"
+ resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-0.3.5.tgz#f1d802950af7dd2631a1febe0596550c86ab3190"
+ integrity sha1-8dgClQr33SYxof6+BZZVDIarMZA=
+
cookie-signature@1.0.6:
version "1.0.6"
resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c"
@@ -5135,7 +5179,7 @@ css-what@^3.2.1:
resolved "https://registry.yarnpkg.com/css-what/-/css-what-3.2.1.tgz#f4a8f12421064621b456755e34a03a2c22df5da1"
integrity sha512-WwOrosiQTvyms+Ti5ZC5vGEK0Vod3FTt1ca+payZqvKuGJF+dq7bG63DstxtN0dpm6FxY27a/zS3Wten+gEtGw==
-css@2.2.4:
+css@2.2.4, css@^2.0.0:
version "2.2.4"
resolved "https://registry.yarnpkg.com/css/-/css-2.2.4.tgz#c646755c73971f2bba6a601e2cf2fd71b1298929"
integrity sha512-oUnjmWpy0niI3x/mPL8dVEI1l7MnG3+HHyRPHf+YFSbK+svOhXpmSOcDURUh2aOCgl2grzrOPt1nHLuCVFULLw==
@@ -5291,6 +5335,14 @@ cyclist@^1.0.1:
resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9"
integrity sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=
+d@1, d@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/d/-/d-1.0.1.tgz#8698095372d58dbee346ffd0c7093f99f8f9eb5a"
+ integrity sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==
+ dependencies:
+ es5-ext "^0.10.50"
+ type "^1.0.1"
+
dargs@^4.0.1:
version "4.1.0"
resolved "https://registry.yarnpkg.com/dargs/-/dargs-4.1.0.tgz#03a9dbb4b5c2f139bf14ae53f0b8a2a6a86f4e17"
@@ -5845,6 +5897,24 @@ es-to-primitive@^1.2.1:
is-date-object "^1.0.1"
is-symbol "^1.0.2"
+es5-ext@^0.10.35, es5-ext@^0.10.50:
+ version "0.10.53"
+ resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.53.tgz#93c5a3acfdbef275220ad72644ad02ee18368de1"
+ integrity sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q==
+ dependencies:
+ es6-iterator "~2.0.3"
+ es6-symbol "~3.1.3"
+ next-tick "~1.0.0"
+
+es6-iterator@2.0.3, es6-iterator@~2.0.3:
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.3.tgz#a7de889141a05a94b0854403b2d0a0fbfa98f3b7"
+ integrity sha1-p96IkUGgWpSwhUQDstCg+/qY87c=
+ dependencies:
+ d "1"
+ es5-ext "^0.10.35"
+ es6-symbol "^3.1.1"
+
es6-promise@^4.0.3, es6-promise@^4.1.1:
version "4.2.8"
resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a"
@@ -5857,6 +5927,14 @@ es6-promisify@^5.0.0:
dependencies:
es6-promise "^4.0.3"
+es6-symbol@^3.1.1, es6-symbol@~3.1.3:
+ version "3.1.3"
+ resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.3.tgz#bad5d3c1bcdac28269f4cb331e431c78ac705d18"
+ integrity sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==
+ dependencies:
+ d "^1.0.1"
+ ext "^1.1.2"
+
escape-goat@1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/escape-goat/-/escape-goat-1.3.0.tgz#bf3ee8ad1e488fbba404b084b2e4a55e09231c64"
@@ -6235,6 +6313,13 @@ express@^4.16.3:
utils-merge "1.0.1"
vary "~1.1.2"
+ext@^1.1.2:
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/ext/-/ext-1.4.0.tgz#89ae7a07158f79d35517882904324077e4379244"
+ integrity sha512-Key5NIsUxdqKg3vIsdw9dSuXpPCQ297y6wBjL30edxwPgt2E44WcWBZey/ZvUc6sERLTxKdyCu4gZFmUbk1Q7A==
+ dependencies:
+ type "^2.0.0"
+
extend-shallow@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f"
@@ -10111,6 +10196,11 @@ nested-error-stacks@^2.0.0, nested-error-stacks@^2.1.0:
resolved "https://registry.yarnpkg.com/nested-error-stacks/-/nested-error-stacks-2.1.0.tgz#0fbdcf3e13fe4994781280524f8b96b0cdff9c61"
integrity sha512-AO81vsIO1k1sM4Zrd6Hu7regmJN1NSiAja10gc4bX3F0wd+9rQmcuHQaHVQCYIEC8iFXnE+mavh23GOt7wBgug==
+next-tick@~1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.0.0.tgz#ca86d1fe8828169b0120208e3dc8424b9db8342c"
+ integrity sha1-yobR/ogoFpsBICCOPchCS524NCw=
+
nice-try@^1.0.4:
version "1.0.5"
resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366"
@@ -10559,6 +10649,11 @@ object-keys@~0.4.0:
resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-0.4.0.tgz#28a6aae7428dd2c3a92f3d95f21335dd204e0336"
integrity sha1-KKaq50KN0sOpLz2V8hM13SBOAzY=
+object-path@0.11.4:
+ version "0.11.4"
+ resolved "https://registry.yarnpkg.com/object-path/-/object-path-0.11.4.tgz#370ae752fbf37de3ea70a861c23bba8915691949"
+ integrity sha1-NwrnUvvzfePqcKhhwju6iRVpGUk=
+
object-visit@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb"
@@ -12049,6 +12144,15 @@ postcss-values-parser@^2.0.0, postcss-values-parser@^2.0.1:
indexes-of "^1.0.1"
uniq "^1.0.1"
+postcss@7.0.21:
+ version "7.0.21"
+ resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.21.tgz#06bb07824c19c2021c5d056d5b10c35b989f7e17"
+ integrity sha512-uIFtJElxJo29QC753JzhidoAhvp/e/Exezkdhfmt8AymWT6/5B7W1WmponYWkHk2eg6sONyTch0A3nkMPun3SQ==
+ dependencies:
+ chalk "^2.4.2"
+ source-map "^0.6.1"
+ supports-color "^6.1.0"
+
postcss@^5.0.10:
version "5.2.18"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-5.2.18.tgz#badfa1497d46244f6390f58b319830d9107853c5"
@@ -12722,6 +12826,11 @@ regex-not@^1.0.0, regex-not@^1.0.2:
extend-shallow "^3.0.2"
safe-regex "^1.1.0"
+regex-parser@2.2.10:
+ version "2.2.10"
+ resolved "https://registry.yarnpkg.com/regex-parser/-/regex-parser-2.2.10.tgz#9e66a8f73d89a107616e63b39d4deddfee912b37"
+ integrity sha512-8t6074A68gHfU8Neftl0Le6KTDwfGAj7IyjPIMSfikI2wJUTHDMaIq42bUsfVnj8mhx0R+45rdUXHGpN164avA==
+
regexpp@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-2.0.1.tgz#8d19d31cf632482b589049f8281f93dbcba4d07f"
@@ -13029,6 +13138,22 @@ resolve-from@^4.0.0:
resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6"
integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==
+resolve-url-loader@3.1.1:
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/resolve-url-loader/-/resolve-url-loader-3.1.1.tgz#28931895fa1eab9be0647d3b2958c100ae3c0bf0"
+ integrity sha512-K1N5xUjj7v0l2j/3Sgs5b8CjrrgtC70SmdCuZiJ8tSyb5J+uk3FoeZ4b7yTnH6j7ngI+Bc5bldHJIa8hYdu2gQ==
+ dependencies:
+ adjust-sourcemap-loader "2.0.0"
+ camelcase "5.3.1"
+ compose-function "3.0.3"
+ convert-source-map "1.7.0"
+ es6-iterator "2.0.3"
+ loader-utils "1.2.3"
+ postcss "7.0.21"
+ rework "1.0.1"
+ rework-visit "1.0.0"
+ source-map "0.6.1"
+
resolve-url@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a"
@@ -13103,6 +13228,19 @@ reusify@^1.0.0:
resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76"
integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==
+rework-visit@1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/rework-visit/-/rework-visit-1.0.0.tgz#9945b2803f219e2f7aca00adb8bc9f640f842c9a"
+ integrity sha1-mUWygD8hni96ygCtuLyfZA+ELJo=
+
+rework@1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/rework/-/rework-1.0.1.tgz#30806a841342b54510aa4110850cd48534144aa7"
+ integrity sha1-MIBqhBNCtUUQqkEQhQzUhTQUSqc=
+ dependencies:
+ convert-source-map "^0.3.3"
+ css "^2.0.0"
+
rewrite-imports@^1.0.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/rewrite-imports/-/rewrite-imports-1.4.0.tgz#198ebb73f59cfee8d214516c774b6aeef2c54a6e"
@@ -13260,6 +13398,17 @@ sass-loader@6.0.6:
lodash.tail "^4.1.1"
pify "^3.0.0"
+sass-loader@8.0.2:
+ version "8.0.2"
+ resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-8.0.2.tgz#debecd8c3ce243c76454f2e8290482150380090d"
+ integrity sha512-7o4dbSK8/Ol2KflEmSco4jTjQoV988bM82P9CZdmo9hR3RLnvNc0ufMNdMrB0caq38JQ/FgF4/7RcbcfKzxoFQ==
+ dependencies:
+ clone-deep "^4.0.1"
+ loader-utils "^1.2.3"
+ neo-async "^2.6.1"
+ schema-utils "^2.6.1"
+ semver "^6.3.0"
+
sax@>=0.6.0, sax@^1.2.4, sax@~1.2.4:
version "1.2.4"
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
@@ -13290,6 +13439,14 @@ schema-utils@^2.0.0, schema-utils@^2.0.1, schema-utils@^2.6.0:
ajv "^6.10.2"
ajv-keywords "^3.4.1"
+schema-utils@^2.6.1:
+ version "2.6.4"
+ resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.6.4.tgz#a27efbf6e4e78689d91872ee3ccfa57d7bdd0f53"
+ integrity sha512-VNjcaUxVnEeun6B2fiiUDjXXBtD4ZSH7pdbfIu1pOFwgptDPLMo/z9jr4sUfsjFVPqDCEin/F7IYlq7/E6yDbQ==
+ dependencies:
+ ajv "^6.10.2"
+ ajv-keywords "^3.4.1"
+
scss-tokenizer@^0.2.3:
version "0.2.3"
resolved "https://registry.yarnpkg.com/scss-tokenizer/-/scss-tokenizer-0.2.3.tgz#8eb06db9a9723333824d3f5530641149847ce5d1"
@@ -13447,6 +13604,13 @@ shallow-clone@^0.1.2:
lazy-cache "^0.2.3"
mixin-object "^2.0.1"
+shallow-clone@^3.0.0:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-3.0.1.tgz#8f2981ad92531f55035b01fb230769a40e02efa3"
+ integrity sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==
+ dependencies:
+ kind-of "^6.0.2"
+
shebang-command@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea"
@@ -14662,6 +14826,16 @@ type-is@~1.6.17, type-is@~1.6.18:
media-typer "0.3.0"
mime-types "~2.1.24"
+type@^1.0.1:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/type/-/type-1.2.0.tgz#848dd7698dafa3e54a6c479e759c4bc3f18847a0"
+ integrity sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==
+
+type@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/type/-/type-2.0.0.tgz#5f16ff6ef2eb44f260494dae271033b29c09a9c3"
+ integrity sha512-KBt58xCHry4Cejnc2ISQAF7QY+ORngsWfxezO68+12hKV6lQY8P/psIkcbjeHWn7MqcgciWJyCCevFMJdIXpow==
+
typedarray-to-buffer@^3.1.5:
version "3.1.5"
resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080"