diff --git a/errors/install-sass.md b/errors/install-sass.md new file mode 100644 index 000000000000000..91b7d1301f39562 --- /dev/null +++ b/errors/install-sass.md @@ -0,0 +1,19 @@ +# Install `sass` to Use Built-In Sass Support + +#### Why This Error Occurred + +Using Next.js' [built-in Sass support](https://nextjs.org/docs/basic-features/built-in-css-support#sass-support) requires that you bring your own version of Sass. + +#### Possible Ways to Fix It + +Please install the `sass` package in your project. + +```bash +npm i sass +# or +yarn add sass +``` + +### Useful Links + +- [Sass Support in Documentation](https://nextjs.org/docs/basic-features/built-in-css-support#sass-support) diff --git a/packages/next/client/dev/error-overlay/format-webpack-messages.js b/packages/next/client/dev/error-overlay/format-webpack-messages.js index 28ddfefac5bee53..c61845352ff6abb 100644 --- a/packages/next/client/dev/error-overlay/format-webpack-messages.js +++ b/packages/next/client/dev/error-overlay/format-webpack-messages.js @@ -27,14 +27,11 @@ SOFTWARE. const friendlySyntaxErrorLabel = 'Syntax error:' function isLikelyASyntaxError(message) { - return false return message.indexOf(friendlySyntaxErrorLabel) !== -1 } // Cleans up webpack error messages. -// eslint-disable-next-line no-unused-vars -function formatMessage(message, isError) { - if (!message.split) return +function formatMessage(message) { let lines = message.split('\n') // Strip Webpack-added headers off errors/warnings @@ -60,9 +57,6 @@ function formatMessage(message, isError) { /SyntaxError\s+\((\d+):(\d+)\)\s*(.+?)\n/g, `${friendlySyntaxErrorLabel} $3 ($1:$2)\n` ) - // Remove columns from ESLint formatter output (we added these for more - // accurate syntax errors) - message = message.replace(/Line (\d+):\d+:/g, 'Line $1:') // Clean up export errors message = message.replace( /^.*export '(.+?)' was not found in '(.+?)'.*$/gm, @@ -76,9 +70,7 @@ function formatMessage(message, isError) { /^.*export '(.+?)' \(imported as '(.+?)'\) was not found in '(.+?)'.*$/gm, `Attempted import error: '$1' is not exported from '$3' (imported as '$2').` ) - if (message.split) { - lines = message.split('\n') - } + lines = message.split('\n') // Remove leading newline if (lines.length > 2 && lines[1].trim() === '') { @@ -97,6 +89,17 @@ function formatMessage(message, isError) { ] } + // Add helpful message for users trying to use Sass for the first time + if (lines[1] && lines[1].match(/Cannot find module.+node-sass/)) { + // ./file.module.scss (<>) => ./file.module.scss + lines[0] = lines[0].replace(/(.+) \(.+?(?=\?\?).+?\)/, '$1') + + lines[1] = + "To use Next.js' built-in Sass support, you first need to install `sass`.\n" + lines[1] += 'Run `npm i sass` or `yarn add sass` inside your workspace.\n' + lines[1] += '\nLearn more: https://err.sh/next.js/install-sass' + } + message = lines.join('\n') // Internal stacks are generally useless so we strip them... with the // exception of stacks containing `webpack:` because they're normally @@ -129,7 +132,6 @@ function formatWebpackMessages(json) { }) const result = { errors: formattedErrors, warnings: formattedWarnings } if (result.errors.some(isLikelyASyntaxError)) { - console.log(result) // If there are any syntax errors, show just them. result.errors = result.errors.filter(isLikelyASyntaxError) } diff --git a/test/integration/scss-fixtures/webpack-error/mock.js b/test/integration/scss-fixtures/webpack-error/mock.js new file mode 100644 index 000000000000000..2b56e6dc476c766 --- /dev/null +++ b/test/integration/scss-fixtures/webpack-error/mock.js @@ -0,0 +1,7 @@ +let originalLoader +const M = require('module') +originalLoader = M._load +M._load = function hookedLoader(request, parent, isMain) { + if (request === 'node-sass') request = 'node-sass-begone' + return originalLoader(request, parent, isMain) +} diff --git a/test/integration/scss-fixtures/webpack-error/pages/_app.js b/test/integration/scss-fixtures/webpack-error/pages/_app.js new file mode 100644 index 000000000000000..b89fe16feb73145 --- /dev/null +++ b/test/integration/scss-fixtures/webpack-error/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/webpack-error/pages/index.js b/test/integration/scss-fixtures/webpack-error/pages/index.js new file mode 100644 index 000000000000000..5cbac8a153d77f0 --- /dev/null +++ b/test/integration/scss-fixtures/webpack-error/pages/index.js @@ -0,0 +1,3 @@ +export default function Home() { + return
This text should be red.
+} diff --git a/test/integration/scss-fixtures/webpack-error/styles/global.scss b/test/integration/scss-fixtures/webpack-error/styles/global.scss new file mode 100644 index 000000000000000..da7b3d1417b1bba --- /dev/null +++ b/test/integration/scss-fixtures/webpack-error/styles/global.scss @@ -0,0 +1,4 @@ +$var: red; +.red-text { + color: $var; +} diff --git a/test/integration/scss/test/index.test.js b/test/integration/scss/test/index.test.js index 531434038b117bf..5269a9698e30caf 100644 --- a/test/integration/scss/test/index.test.js +++ b/test/integration/scss/test/index.test.js @@ -1,26 +1,55 @@ /* eslint-env jest */ /* global jasmine */ +import cheerio from 'cheerio' import 'flat-map-polyfill' -import { join } from 'path' import { readdir, readFile, remove } from 'fs-extra' import { + File, findPort, + killApp, + launchApp, nextBuild, nextStart, - launchApp, - killApp, - File, - waitFor, renderViaHTTP, + waitFor, } from 'next-test-utils' import webdriver from 'next-webdriver' -import cheerio from 'cheerio' +import { join } from 'path' +import { quote as shellQuote } from 'shell-quote' jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000 * 60 * 2 const fixturesDir = join(__dirname, '../..', 'scss-fixtures') describe('SCSS Support', () => { + describe('Friendly Webpack Error', () => { + const appDir = join(fixturesDir, 'webpack-error') + + const mockFile = join(appDir, 'mock.js') + + beforeAll(async () => { + await remove(join(appDir, '.next')) + }) + + it('should be a friendly error successfully', async () => { + const { code, stderr } = await nextBuild(appDir, [], { + env: { NODE_OPTIONS: shellQuote([`--require`, mockFile]) }, + stderr: true, + }) + expect(code).toBe(1) + expect(stderr.split('Require stack:')[0]).toMatchInlineSnapshot(` + "Failed to compile. + + ./styles/global.scss + To use Next.js' built-in Sass support, you first need to install \`sass\`. + Run \`npm i sass\` or \`yarn add sass\` inside your workspace. + + Learn more: https://err.sh/next.js/install-sass + " + `) + }) + }) + describe('Basic Global Support', () => { const appDir = join(fixturesDir, 'single-global')