Skip to content

Commit 8449ebc

Browse files
alejalapenotimneutkensTimer
committed
[Experimental] Add built-in Sass support (vercel#10133)
* Add built-in Sass support * Add copy of CSS tests for SCSS * Fix failing tests * Fix url-loader tests * Remove css file generated by tests * Fix nprogress import for css file * Fix SCSS modules (still 2 tests that need investigating) * Update documentation for Sass support * Fix plain CSS import test * Fix formatting with prettier fix * Update test output to reflect scss usage * Revert "Fix plain CSS import test" This reverts commit 380319d. # Conflicts: # test/integration/scss-modules/test/index.test.js * Update loader structure * Resolve loaders before passing to compile function * Remove dead filter code * Arrange loaders in order and push to array * Fix loader order bug * Fix global Sass loader and make module prepocessor optional * Adjust Sass Modules Implementation * Fix typo * Adjust regexps * Use regexes * Simplify global setup * Adjust comments * fix regex * Simplify identifier file * Update Sass Instructions * Remove unneeded fixtures * Adjust global tests * Remove wrapper * Update source maps * Flag scss behavior * Fix css property value * Update fixtures with Sass vars * Turn on Scss support * fix HMR test * Fix snapshots Co-authored-by: Tim Neutkens <tim@timneutkens.nl> Co-authored-by: Joe Haddad <timer150@gmail.com>
1 parent 7d419f8 commit 8449ebc

File tree

149 files changed

+2524
-30
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

149 files changed

+2524
-30
lines changed

docs/basic-features/built-in-css-support.md

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -157,10 +157,22 @@ export default HelloWorld
157157

158158
Please see the [styled-jsx documentation](https://github.com/zeit/styled-jsx) for more examples.
159159

160-
## Sass, Less, and Stylus Support
160+
## Sass Support
161161

162-
To support importing `.scss``.less` or `.styl` files you can use the following plugins:
162+
Next.js allows you to import Sass using both the `.scss` and `.sass` extensions.
163+
You can use component-level Sass via CSS Modules and the `.module.scss` or `.module.sass` extension.
164+
165+
Before you can use Next.js' built-in Sass support, be sure to install [`sass`](https://github.com/sass/sass):
166+
167+
```bash
168+
npm install sass
169+
```
170+
171+
Sass support has the same benefits and restrictions as the built-in CSS support detailed above.
172+
173+
## Less and Stylus Support
174+
175+
To support importing `.less` or `.styl` files you can use the following plugins:
163176

164-
- [@zeit/next-sass](https://github.com/zeit/next-plugins/tree/master/packages/next-sass)
165177
- [@zeit/next-less](https://github.com/zeit/next-plugins/tree/master/packages/next-less)
166178
- [@zeit/next-stylus](https://github.com/zeit/next-plugins/tree/master/packages/next-stylus)

packages/next/build/webpack-config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -834,6 +834,7 @@ export default async function getBaseWebpackConfig(
834834
isDevelopment: dev,
835835
isServer,
836836
hasSupportCss: !!config.experimental.css,
837+
hasSupportScss: !!config.experimental.scss,
837838
assetPrefix: config.assetPrefix || '',
838839
})
839840

packages/next/build/webpack/config/blocks/css/index.ts

Lines changed: 100 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import curry from 'lodash.curry'
22
import path from 'path'
3-
import { Configuration } from 'webpack'
3+
import webpack, { Configuration } from 'webpack'
44
import MiniCssExtractPlugin from '../../../plugins/mini-css-extract-plugin'
55
import { loader, plugin } from '../../helpers'
66
import { ConfigurationContext, ConfigurationFn, pipe } from '../../utils'
@@ -13,20 +13,53 @@ import {
1313
} from './messages'
1414
import { getPostCssPlugins } from './plugins'
1515

16-
// RegExps for Stylesheets
17-
const regexCssAll = /\.css$/
16+
// RegExps for all Style Sheet variants
17+
const regexLikeCss = /\.(css|scss|sass)$/
18+
19+
// RegExps for Style Sheets
1820
const regexCssGlobal = /(?<!\.module)\.css$/
1921
const regexCssModules = /\.module\.css$/
2022

23+
// RegExps for Syntactically Awesome Style Sheets
24+
const regexSassGlobal = /(?<!\.module)\.(scss|sass)$/
25+
const regexSassModules = /\.module\.(scss|sass)$/
26+
2127
export const css = curry(async function css(
2228
enabled: boolean,
29+
scssEnabled: boolean,
2330
ctx: ConfigurationContext,
2431
config: Configuration
2532
) {
2633
if (!enabled) {
2734
return config
2835
}
2936

37+
const sassPreprocessors: webpack.RuleSetUseItem[] = [
38+
// First, process files with `sass-loader`: this inlines content, and
39+
// compiles away the proprietary syntax.
40+
{
41+
loader: require.resolve('sass-loader'),
42+
options: {
43+
// Source maps are required so that `resolve-url-loader` can locate
44+
// files original to their source directory.
45+
sourceMap: true,
46+
},
47+
},
48+
// Then, `sass-loader` will have passed-through CSS imports as-is instead
49+
// of inlining them. Because they were inlined, the paths are no longer
50+
// correct.
51+
// To fix this, we use `resolve-url-loader` to rewrite the CSS
52+
// imports to real file paths.
53+
{
54+
loader: require.resolve('resolve-url-loader'),
55+
options: {
56+
// Source maps are not required here, but we may as well emit
57+
// them.
58+
sourceMap: true,
59+
},
60+
},
61+
]
62+
3063
const fns: ConfigurationFn[] = [
3164
loader({
3265
oneOf: [
@@ -55,7 +88,7 @@ export const css = curry(async function css(
5588
loader({
5689
oneOf: [
5790
{
58-
test: regexCssAll,
91+
test: regexLikeCss,
5992
// Use a loose regex so we don't have to crawl the file system to
6093
// find the real file name (if present).
6194
issuer: { test: /pages[\\/]_document\./ },
@@ -94,13 +127,41 @@ export const css = curry(async function css(
94127
],
95128
})
96129
)
130+
if (scssEnabled) {
131+
fns.push(
132+
loader({
133+
oneOf: [
134+
// Opt-in support for Sass (using .scss or .sass extensions).
135+
{
136+
// Sass Modules should never have side effects. This setting will
137+
// allow unused Sass to be removed from the production build.
138+
// We ensure this by disallowing `:global()` Sass at the top-level
139+
// via the `pure` mode in `css-loader`.
140+
sideEffects: false,
141+
// Sass Modules are activated via this specific extension.
142+
test: regexSassModules,
143+
// Sass Modules are only supported in the user's application. We're
144+
// not yet allowing Sass imports _within_ `node_modules`.
145+
issuer: {
146+
include: [ctx.rootDirectory],
147+
exclude: /node_modules/,
148+
},
149+
use: getCssModuleLoader(ctx, postCssPlugins, sassPreprocessors),
150+
},
151+
],
152+
})
153+
)
154+
}
97155

98156
// Throw an error for CSS Modules used outside their supported scope
99157
fns.push(
100158
loader({
101159
oneOf: [
102160
{
103-
test: regexCssModules,
161+
test: [
162+
regexCssModules,
163+
(scssEnabled && regexSassModules) as RegExp,
164+
].filter(Boolean),
104165
use: {
105166
loader: 'error-loader',
106167
options: {
@@ -116,7 +177,13 @@ export const css = curry(async function css(
116177
fns.push(
117178
loader({
118179
oneOf: [
119-
{ test: regexCssGlobal, use: require.resolve('ignore-loader') },
180+
{
181+
test: [
182+
regexCssGlobal,
183+
(scssEnabled && regexSassGlobal) as RegExp,
184+
].filter(Boolean),
185+
use: require.resolve('ignore-loader'),
186+
},
120187
],
121188
})
122189
)
@@ -137,14 +204,35 @@ export const css = curry(async function css(
137204
],
138205
})
139206
)
207+
if (scssEnabled) {
208+
fns.push(
209+
loader({
210+
oneOf: [
211+
{
212+
// A global Sass import always has side effects. Webpack will tree
213+
// shake the Sass without this option if the issuer claims to have
214+
// no side-effects.
215+
// See https://github.com/webpack/webpack/issues/6571
216+
sideEffects: true,
217+
test: regexSassGlobal,
218+
issuer: { include: ctx.customAppFile },
219+
use: getGlobalCssLoader(ctx, postCssPlugins, sassPreprocessors),
220+
},
221+
],
222+
})
223+
)
224+
}
140225
}
141226

142227
// Throw an error for Global CSS used inside of `node_modules`
143228
fns.push(
144229
loader({
145230
oneOf: [
146231
{
147-
test: regexCssGlobal,
232+
test: [
233+
regexCssGlobal,
234+
(scssEnabled && regexSassGlobal) as RegExp,
235+
].filter(Boolean),
148236
issuer: { include: [/node_modules/] },
149237
use: {
150238
loader: 'error-loader',
@@ -162,7 +250,10 @@ export const css = curry(async function css(
162250
loader({
163251
oneOf: [
164252
{
165-
test: regexCssGlobal,
253+
test: [
254+
regexCssGlobal,
255+
(scssEnabled && regexSassGlobal) as RegExp,
256+
].filter(Boolean),
166257
use: {
167258
loader: 'error-loader',
168259
options: {
@@ -185,7 +276,7 @@ export const css = curry(async function css(
185276
oneOf: [
186277
{
187278
// This should only be applied to CSS files
188-
issuer: { test: regexCssAll },
279+
issuer: { test: regexLikeCss },
189280
// Exclude extensions that webpack handles by default
190281
exclude: [/\.(js|mjs|jsx|ts|tsx)$/, /\.html$/, /\.json$/],
191282
use: {

packages/next/build/webpack/config/blocks/css/loaders/getCssModuleLocalIdent.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import loaderUtils from 'loader-utils'
22
import path from 'path'
33
import webpack from 'webpack'
44

5+
const regexLikeIndexModule = /(?<!pages[\\/])index\.module\.(scss|sass|css)$/
6+
57
export function getCssModuleLocalIdent(
68
context: webpack.loader.LoaderContext,
79
_: any,
@@ -14,11 +16,9 @@ export function getCssModuleLocalIdent(
1416

1517
// Generate a more meaningful name (parent folder) when the user names the
1618
// file `index.module.css`.
17-
const fileNameOrFolder =
18-
relativePath.endsWith('index.module.css') &&
19-
relativePath !== 'pages/index.module.css'
20-
? '[folder]'
21-
: '[name]'
19+
const fileNameOrFolder = regexLikeIndexModule.test(relativePath)
20+
? '[folder]'
21+
: '[name]'
2222

2323
// Generate a hash to make the class name unique.
2424
const hash = loaderUtils.getHashDigest(

packages/next/build/webpack/config/blocks/css/loaders/global.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ import { getClientStyleLoader } from './client'
55

66
export function getGlobalCssLoader(
77
ctx: ConfigurationContext,
8-
postCssPlugins: postcss.AcceptedPlugin[]
8+
postCssPlugins: postcss.AcceptedPlugin[],
9+
preProcessors: webpack.RuleSetUseItem[] = []
910
): webpack.RuleSetUseItem[] {
1011
const loaders: webpack.RuleSetUseItem[] = []
1112

@@ -23,7 +24,7 @@ export function getGlobalCssLoader(
2324
// Resolve CSS `@import`s and `url()`s
2425
loaders.push({
2526
loader: require.resolve('css-loader'),
26-
options: { importLoaders: 1, sourceMap: true },
27+
options: { importLoaders: 1 + preProcessors.length, sourceMap: true },
2728
})
2829

2930
// Compile CSS
@@ -36,5 +37,11 @@ export function getGlobalCssLoader(
3637
},
3738
})
3839

40+
loaders.push(
41+
// Webpack loaders run like a stack, so we need to reverse the natural
42+
// order of preprocessors.
43+
...preProcessors.reverse()
44+
)
45+
3946
return loaders
4047
}

packages/next/build/webpack/config/blocks/css/loaders/modules.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ import { getCssModuleLocalIdent } from './getCssModuleLocalIdent'
66

77
export function getCssModuleLoader(
88
ctx: ConfigurationContext,
9-
postCssPlugins: postcss.AcceptedPlugin[]
9+
postCssPlugins: postcss.AcceptedPlugin[],
10+
preProcessors: webpack.RuleSetUseItem[] = []
1011
): webpack.RuleSetUseItem[] {
1112
const loaders: webpack.RuleSetUseItem[] = []
1213

@@ -25,7 +26,7 @@ export function getCssModuleLoader(
2526
loaders.push({
2627
loader: require.resolve('css-loader'),
2728
options: {
28-
importLoaders: 1,
29+
importLoaders: 1 + preProcessors.length,
2930
sourceMap: true,
3031
onlyLocals: ctx.isServer,
3132
modules: {
@@ -52,5 +53,11 @@ export function getCssModuleLoader(
5253
},
5354
})
5455

56+
loaders.push(
57+
// Webpack loaders run like a stack, so we need to reverse the natural
58+
// order of preprocessors.
59+
...preProcessors.reverse()
60+
)
61+
5562
return loaders
5663
}

packages/next/build/webpack/config/index.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,15 @@ export async function build(
1111
isDevelopment,
1212
isServer,
1313
hasSupportCss,
14+
hasSupportScss,
1415
assetPrefix,
1516
}: {
1617
rootDirectory: string
1718
customAppFile: string | null
1819
isDevelopment: boolean
1920
isServer: boolean
2021
hasSupportCss: boolean
22+
hasSupportScss: boolean
2123
assetPrefix: string
2224
}
2325
): Promise<webpack.Configuration> {
@@ -35,6 +37,6 @@ export async function build(
3537
: '',
3638
}
3739

38-
const fn = pipe(base(ctx), css(hasSupportCss, ctx))
40+
const fn = pipe(base(ctx), css(hasSupportCss, hasSupportScss, ctx))
3941
return fn(config)
4042
}

packages/next/next-server/server/config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ const defaultConfig: { [key: string]: any } = {
4242
(os.cpus() || { length: 1 }).length) - 1
4343
),
4444
css: true,
45+
scss: false,
4546
documentMiddleware: false,
4647
granularChunks: true,
4748
modern: false,

packages/next/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,8 @@
122122
"raw-body": "2.4.0",
123123
"react-error-overlay": "5.1.6",
124124
"react-is": "16.8.6",
125+
"resolve-url-loader": "3.1.1",
126+
"sass-loader": "8.0.2",
125127
"send": "0.17.1",
126128
"source-map": "0.6.1",
127129
"string-hash": "1.1.3",

test/integration/css-fixtures/nm-module/node_modules/example/index.module.css

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy