Skip to content

Commit

Permalink
App dir sass (#41690)
Browse files Browse the repository at this point in the history
sass & scss support for app

## Bug

- [ ] Related issues linked using `fixes #number`
- [ ] Integration tests added
- [ ] Errors have a helpful link attached, see `contributing.md`

## Feature

- [ ] Implements an existing feature request or RFC. Make sure the
feature request has been accepted for implementation before opening a
PR.
- [ ] Related issues linked using `fixes #number`
- [ ] Integration tests added
- [ ] Documentation added
- [ ] Telemetry added. In case of a feature if it's used or not.
- [ ] Errors have a helpful link attached, see `contributing.md`

## Documentation / Examples

- [ ] Make sure the linting passes by running `pnpm lint`
- [ ] The "examples guidelines" are followed from [our contributing
doc](https://github.com/vercel/next.js/blob/canary/contributing/examples/adding-examples.md)
  • Loading branch information
Hannes Bornö committed Oct 24, 2022
1 parent d5a682e commit af39504
Show file tree
Hide file tree
Showing 26 changed files with 302 additions and 5 deletions.
40 changes: 40 additions & 0 deletions packages/next/build/webpack/config/blocks/css/index.ts
Expand Up @@ -456,6 +456,28 @@ export const css = curry(async function css(
],
})
)
fns.push(
loader({
oneOf: [
markRemovable({
// A global SASS import always has side effects. Webpack will tree
// shake the CSS without this option if the issuer claims to have
// no side-effects.
// See https://github.com/webpack/webpack/issues/6571
sideEffects: true,
test: regexSassGlobal,
use: [
require.resolve('../../../loaders/next-flight-css-dev-loader'),
...getGlobalCssLoader(
ctx,
lazyPostCSSInitializer,
sassPreprocessors
),
],
}),
],
})
)
fns.push(
loader({
oneOf: [
Expand All @@ -470,6 +492,24 @@ export const css = curry(async function css(
],
})
)
fns.push(
loader({
oneOf: [
markRemovable({
sideEffects: false,
test: regexSassModules,
use: [
require.resolve('../../../loaders/next-flight-css-dev-loader'),
...getCssModuleLoader(
ctx,
lazyPostCSSInitializer,
sassPreprocessors
),
],
}),
],
})
)
} else {
fns.push(
loader({
Expand Down
Expand Up @@ -25,7 +25,7 @@ export default async function transformSource(this: any): Promise<string> {
// Filter out css files on the server
.filter((request) => (isServer ? !regexCSS.test(request) : true))
.map((request) =>
request.endsWith('.css')
regexCSS.test(request)
? `(() => import(/* webpackMode: "lazy" */ ${JSON.stringify(request)}))`
: `import(/* webpackMode: "eager" */ ${JSON.stringify(request)})`
)
Expand Down
Expand Up @@ -5,7 +5,7 @@
*/

export function pitch(this: any) {
const content = this.fs.readFileSync(this.resource)
const content = this.fs.readFileSync(this.resourcePath)
this.data.__checksum = (
typeof content === 'string' ? Buffer.from(content) : content
).toString('hex')
Expand All @@ -14,7 +14,7 @@ export function pitch(this: any) {
const NextServerCSSLoader = function (this: any, content: string) {
this.cacheable && this.cacheable()

const isCSSModule = this.resource.match(/\.module\.css$/)
const isCSSModule = this.resourcePath.match(/\.module\.(css|sass|scss)$/)
if (isCSSModule) {
return (
content +
Expand Down
3 changes: 1 addition & 2 deletions packages/next/build/webpack/loaders/utils.ts
Expand Up @@ -11,5 +11,4 @@ export function isClientComponentModule(mod: {
return hasClientDirective || imageRegex.test(mod.resource)
}

// TODO-APP: ensure .scss / .sass also works.
export const regexCSS = /\.css(\?.*)?$/
export const regexCSS = /\.(css|scss|sass)(\?.*)?$/
3 changes: 3 additions & 0 deletions test/e2e/app-dir/app/app/css/sass-client/global.sass
@@ -0,0 +1,3 @@
#sass-client-layout
color: brown

3 changes: 3 additions & 0 deletions test/e2e/app-dir/app/app/css/sass-client/global.scss
@@ -0,0 +1,3 @@
#scss-client-layout {
color: burlywood;
}
3 changes: 3 additions & 0 deletions test/e2e/app-dir/app/app/css/sass-client/inner/global.sass
@@ -0,0 +1,3 @@
#sass-client-page
color: wheat

3 changes: 3 additions & 0 deletions test/e2e/app-dir/app/app/css/sass-client/inner/global.scss
@@ -0,0 +1,3 @@
#scss-client-page {
color: tomato;
}
19 changes: 19 additions & 0 deletions test/e2e/app-dir/app/app/css/sass-client/inner/page.js
@@ -0,0 +1,19 @@
'use client'

import './global.scss'
import './global.sass'
import sass from './styles.module.sass'
import scss from './styles.module.scss'

export default function Page() {
return (
<>
<div id="sass-client-page" className={sass.mod}>
sass client page
</div>
<div id="scss-client-page" className={scss.mod}>
scss client page
</div>
</>
)
}
@@ -0,0 +1,2 @@
.mod
background-color: indigo
@@ -0,0 +1,3 @@
.mod {
background-color: aqua;
}
20 changes: 20 additions & 0 deletions test/e2e/app-dir/app/app/css/sass-client/layout.js
@@ -0,0 +1,20 @@
'use client'

import './global.scss'
import './global.sass'
import sass from './styles.module.sass'
import scss from './styles.module.scss'

export default function Layout({ children }) {
return (
<>
<div id="sass-client-layout" className={sass.mod}>
sass client layout
</div>
<div id="scss-client-layout" className={scss.mod}>
scss client layout
</div>
{children}
</>
)
}
2 changes: 2 additions & 0 deletions test/e2e/app-dir/app/app/css/sass-client/styles.module.sass
@@ -0,0 +1,2 @@
.mod
background-color: darksalmon
3 changes: 3 additions & 0 deletions test/e2e/app-dir/app/app/css/sass-client/styles.module.scss
@@ -0,0 +1,3 @@
.mod {
background-color: darkred;
}
3 changes: 3 additions & 0 deletions test/e2e/app-dir/app/app/css/sass/global.sass
@@ -0,0 +1,3 @@
#sass-server-layout
color: brown

3 changes: 3 additions & 0 deletions test/e2e/app-dir/app/app/css/sass/global.scss
@@ -0,0 +1,3 @@
#scss-server-layout {
color: burlywood;
}
3 changes: 3 additions & 0 deletions test/e2e/app-dir/app/app/css/sass/inner/global.sass
@@ -0,0 +1,3 @@
#sass-server-page
color: wheat

3 changes: 3 additions & 0 deletions test/e2e/app-dir/app/app/css/sass/inner/global.scss
@@ -0,0 +1,3 @@
#scss-server-page {
color: tomato;
}
17 changes: 17 additions & 0 deletions test/e2e/app-dir/app/app/css/sass/inner/page.js
@@ -0,0 +1,17 @@
import './global.scss'
import './global.sass'
import sass from './styles.module.sass'
import scss from './styles.module.scss'

export default function Page() {
return (
<>
<div id="sass-server-page" className={sass.mod}>
sass server page
</div>
<div id="scss-server-page" className={scss.mod}>
scss server page
</div>
</>
)
}
2 changes: 2 additions & 0 deletions test/e2e/app-dir/app/app/css/sass/inner/styles.module.sass
@@ -0,0 +1,2 @@
.mod
background-color: indigo
3 changes: 3 additions & 0 deletions test/e2e/app-dir/app/app/css/sass/inner/styles.module.scss
@@ -0,0 +1,3 @@
.mod {
background-color: aqua;
}
18 changes: 18 additions & 0 deletions test/e2e/app-dir/app/app/css/sass/layout.js
@@ -0,0 +1,18 @@
import './global.scss'
import './global.sass'
import sass from './styles.module.sass'
import scss from './styles.module.scss'

export default function Layout({ children }) {
return (
<>
<div id="sass-server-layout" className={sass.mod}>
sass server layout
</div>
<div id="scss-server-layout" className={scss.mod}>
scss server layout
</div>
{children}
</>
)
}
2 changes: 2 additions & 0 deletions test/e2e/app-dir/app/app/css/sass/styles.module.sass
@@ -0,0 +1,2 @@
.mod
background-color: darksalmon
3 changes: 3 additions & 0 deletions test/e2e/app-dir/app/app/css/sass/styles.module.scss
@@ -0,0 +1,3 @@
.mod {
background-color: darkred;
}
139 changes: 139 additions & 0 deletions test/e2e/app-dir/index.test.ts
Expand Up @@ -30,6 +30,7 @@ describe('app dir', () => {
dependencies: {
react: 'experimental',
'react-dom': 'experimental',
sass: 'latest',
},
skipStart: true,
})
Expand Down Expand Up @@ -1374,6 +1375,144 @@ describe('app dir', () => {
})
})
})
describe('sass support', () => {
describe('server layouts', () => {
it('should support global sass/scss inside server layouts', async () => {
const browser = await webdriver(next.url, '/css/sass/inner')
// .sass
expect(
await browser.eval(
`window.getComputedStyle(document.querySelector('#sass-server-layout')).color`
)
).toBe('rgb(165, 42, 42)')
// .scss
expect(
await browser.eval(
`window.getComputedStyle(document.querySelector('#scss-server-layout')).color`
)
).toBe('rgb(222, 184, 135)')
})

it('should support sass/scss modules inside server layouts', async () => {
const browser = await webdriver(next.url, '/css/sass/inner')
// .sass
expect(
await browser.eval(
`window.getComputedStyle(document.querySelector('#sass-server-layout')).backgroundColor`
)
).toBe('rgb(233, 150, 122)')
// .scss
expect(
await browser.eval(
`window.getComputedStyle(document.querySelector('#scss-server-layout')).backgroundColor`
)
).toBe('rgb(139, 0, 0)')
})
})

describe('server pages', () => {
it('should support global sass/scss inside server pages', async () => {
const browser = await webdriver(next.url, '/css/sass/inner')
// .sass
expect(
await browser.eval(
`window.getComputedStyle(document.querySelector('#sass-server-page')).color`
)
).toBe('rgb(245, 222, 179)')
// .scss
expect(
await browser.eval(
`window.getComputedStyle(document.querySelector('#scss-server-page')).color`
)
).toBe('rgb(255, 99, 71)')
})

it('should support sass/scss modules inside server pages', async () => {
const browser = await webdriver(next.url, '/css/sass/inner')
// .sass
expect(
await browser.eval(
`window.getComputedStyle(document.querySelector('#sass-server-page')).backgroundColor`
)
).toBe('rgb(75, 0, 130)')
// .scss
expect(
await browser.eval(
`window.getComputedStyle(document.querySelector('#scss-server-page')).backgroundColor`
)
).toBe('rgb(0, 255, 255)')
})
})

describe('client layouts', () => {
it('should support global sass/scss inside client layouts', async () => {
const browser = await webdriver(next.url, '/css/sass-client/inner')
// .sass
expect(
await browser.eval(
`window.getComputedStyle(document.querySelector('#sass-client-layout')).color`
)
).toBe('rgb(165, 42, 42)')
// .scss
expect(
await browser.eval(
`window.getComputedStyle(document.querySelector('#scss-client-layout')).color`
)
).toBe('rgb(222, 184, 135)')
})

it('should support sass/scss modules inside client layouts', async () => {
const browser = await webdriver(next.url, '/css/sass-client/inner')
// .sass
expect(
await browser.eval(
`window.getComputedStyle(document.querySelector('#sass-client-layout')).backgroundColor`
)
).toBe('rgb(233, 150, 122)')
// .scss
expect(
await browser.eval(
`window.getComputedStyle(document.querySelector('#scss-client-layout')).backgroundColor`
)
).toBe('rgb(139, 0, 0)')
})
})
})

describe('client pages', () => {
it('should support global sass/scss inside client pages', async () => {
const browser = await webdriver(next.url, '/css/sass-client/inner')
await waitFor(5000)
// .sass
expect(
await browser.eval(
`window.getComputedStyle(document.querySelector('#sass-client-page')).color`
)
).toBe('rgb(245, 222, 179)')
// .scss
expect(
await browser.eval(
`window.getComputedStyle(document.querySelector('#scss-client-page')).color`
)
).toBe('rgb(255, 99, 71)')
})

it('should support sass/scss modules inside client pages', async () => {
const browser = await webdriver(next.url, '/css/sass-client/inner')
// .sass
expect(
await browser.eval(
`window.getComputedStyle(document.querySelector('#sass-client-page')).backgroundColor`
)
).toBe('rgb(75, 0, 130)')
// .scss
expect(
await browser.eval(
`window.getComputedStyle(document.querySelector('#scss-client-page')).backgroundColor`
)
).toBe('rgb(0, 255, 255)')
})
})
;(isDev ? describe.skip : describe)('Subresource Integrity', () => {
function fetchWithPolicy(policy: string | null) {
return fetchViaHTTP(next.url, '/dashboard', undefined, {
Expand Down
1 change: 1 addition & 0 deletions test/e2e/app-dir/vercel-analytics.test.ts
Expand Up @@ -21,6 +21,7 @@ describe('vercel analytics', () => {
dependencies: {
react: 'experimental',
'react-dom': 'experimental',
sass: 'latest',
},
skipStart: true,
env: {
Expand Down

0 comments on commit af39504

Please sign in to comment.