From 90ddd0dc83fefe0eb906f164f0ceea1549088d86 Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Tue, 26 Jul 2022 23:12:54 -0500 Subject: [PATCH] Replace node-sass test dependency with sass (#39053) * Replace node-sass test dependency with sass * breakup css/scss tests for more parallelism * update compiled --- package.json | 2 +- packages/next/compiled/sass-loader/cjs.js | 2 +- packages/next/taskfile.js | 21 +- pnpm-lock.yaml | 147 +-- .../test/group-1.test.js} | 497 +++------- .../test/{index.test.js => group-2.test.js} | 849 ------------------ .../scss-fixtures/webpack-error/mock.js | 2 +- test/integration/scss/test/group-1.test.js | 617 +++++++++++++ test/integration/scss/test/group-2.test.js | 489 ++++++++++ 9 files changed, 1279 insertions(+), 1347 deletions(-) rename test/integration/{scss/test/index.test.js => css/test/group-1.test.js} (69%) rename test/integration/css/test/{index.test.js => group-2.test.js} (55%) create mode 100644 test/integration/scss/test/group-1.test.js create mode 100644 test/integration/scss/test/group-2.test.js diff --git a/package.json b/package.json index b09f5a27783f..ce8eb96e3021 100644 --- a/package.json +++ b/package.json @@ -159,7 +159,6 @@ "next": "workspace:*", "node-fetch": "2.6.7", "node-notifier": "8.0.1", - "node-sass": "6.0.1", "npm-run-all": "4.1.5", "nprogress": "0.2.0", "pixrem": "5.0.0", @@ -186,6 +185,7 @@ "release": "6.3.1", "request-promise-core": "1.1.2", "resolve-from": "5.0.0", + "sass": "1.54.0", "seedrandom": "3.0.5", "selenium-webdriver": "4.0.0-beta.4", "semver": "7.3.7", diff --git a/packages/next/compiled/sass-loader/cjs.js b/packages/next/compiled/sass-loader/cjs.js index 26dbe5b3c2c6..caf0ad3c36c8 100644 --- a/packages/next/compiled/sass-loader/cjs.js +++ b/packages/next/compiled/sass-loader/cjs.js @@ -1 +1 @@ -(function(){var __webpack_modules__={814:function(e,t){function set(e,t,s){if(typeof s.value==="object")s.value=klona(s.value);if(!s.enumerable||s.get||s.set||!s.configurable||!s.writable||t==="__proto__"){Object.defineProperty(e,t,s)}else e[t]=s.value}function klona(e){if(typeof e!=="object")return e;var t=0,s,r,n,o=Object.prototype.toString.call(e);if(o==="[object Object]"){n=Object.create(e.__proto__||null)}else if(o==="[object Array]"){n=Array(e.length)}else if(o==="[object Set]"){n=new Set;e.forEach((function(e){n.add(klona(e))}))}else if(o==="[object Map]"){n=new Map;e.forEach((function(e,t){n.set(klona(t),klona(e))}))}else if(o==="[object Date]"){n=new Date(+e)}else if(o==="[object RegExp]"){n=new RegExp(e.source,e.flags)}else if(o==="[object DataView]"){n=new e.constructor(klona(e.buffer))}else if(o==="[object ArrayBuffer]"){n=e.slice(0)}else if(o.slice(-6)==="Array]"){n=new e.constructor(e)}if(n){for(r=Object.getOwnPropertySymbols(e);t{if(e){if(e.file){this.addDependency(r.default.normalize(e.file))}s(new a.default(e));return}let n=t.map?JSON.parse(t.map):null;if(n&&c){n=(0,o.normalizeSourceMap)(n,this.rootContext)}t.stats.includedFiles.forEach((e=>{const t=r.default.normalize(e);if(r.default.isAbsolute(t)){this.addDependency(t)}}));s(null,t.css.toString(),n)}))}var i=loader;t["default"]=i},179:function(__unused_webpack_module,exports,__nccwpck_require__){"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.getRenderFunctionFromSassImplementation=getRenderFunctionFromSassImplementation;exports.getSassImplementation=getSassImplementation;exports.getSassOptions=getSassOptions;exports.getWebpackImporter=getWebpackImporter;exports.getWebpackResolver=getWebpackResolver;exports.isSupportedFibers=isSupportedFibers;exports.normalizeSourceMap=normalizeSourceMap;var _url=_interopRequireDefault(__nccwpck_require__(310));var _path=_interopRequireDefault(__nccwpck_require__(17));var _full=__nccwpck_require__(814);var _neoAsync=_interopRequireDefault(__nccwpck_require__(175));var _SassWarning=_interopRequireDefault(__nccwpck_require__(66));function _interopRequireDefault(e){return e&&e.__esModule?e:{default:e}}function getDefaultSassImplementation(){let sassImplPkg="sass";try{require.resolve("sass")}catch(error){try{eval("require.resolve('node-sass')");sassImplPkg="node-sass"}catch(e){sassImplPkg="sass"}}return require(sassImplPkg)}function getSassImplementation(e,t){let s=t;if(!s){try{s=getDefaultSassImplementation()}catch(t){e.emitError(t);return}}if(typeof s==="string"){try{s=require(s)}catch(t){e.emitError(t);return}}const{info:r}=s;if(!r){e.emitError(new Error("Unknown Sass implementation."));return}const n=r.split("\t");if(n.length<2){e.emitError(new Error(`Unknown Sass implementation "${r}".`));return}const[o]=n;if(o==="dart-sass"){return s}else if(o==="node-sass"){return s}e.emitError(new Error(`Unknown Sass implementation "${o}".`))}function isProductionLikeMode(e){return e.mode==="production"||!e.mode}function proxyCustomImporters(e,t){return[].concat(e).map((e=>function proxyImporter(...s){const r={...this,webpackLoaderContext:t};return e.apply(r,s)}))}function isSupportedFibers(){const[e]=process.versions.node.split(".");return Number(e)<16}async function getSassOptions(e,t,s,r,n){const o=(0,_full.klona)(t.sassOptions?typeof t.sassOptions==="function"?t.sassOptions(e)||{}:t.sassOptions:{});const a=r.info.includes("dart-sass");if(a&&isSupportedFibers()){const e=!o.fiber&&o.fiber!==false;if(e){let e;try{e=require.resolve("fibers")}catch(e){}if(e){o.fiber=require(e)}}else if(o.fiber===false){delete o.fiber}}else{delete o.fiber}o.file=e.resourcePath;o.data=t.additionalData?typeof t.additionalData==="function"?await t.additionalData(s,e):`${t.additionalData}\n${s}`:s;if(!o.outputStyle&&isProductionLikeMode(e)){o.outputStyle="compressed"}if(n){o.sourceMap=true;o.outFile=_path.default.join(e.rootContext,"style.css.map");o.sourceMapContents=true;o.omitSourceMapUrl=true;o.sourceMapEmbed=false}const{resourcePath:i}=e;const c=_path.default.extname(i);if(c&&c.toLowerCase()===".sass"&&typeof o.indentedSyntax==="undefined"){o.indentedSyntax=true}else{o.indentedSyntax=Boolean(o.indentedSyntax)}o.importer=o.importer?proxyCustomImporters(Array.isArray(o.importer)?o.importer:[o.importer],e):[];o.includePaths=[].concat(process.cwd()).concat((o.includePaths||[]).map((e=>_path.default.isAbsolute(e)?e:_path.default.join(process.cwd(),e)))).concat(process.env.SASS_PATH?process.env.SASS_PATH.split(process.platform==="win32"?";":":"):[]);if(typeof o.charset==="undefined"){o.charset=true}if(!o.logger){const s=t.warnRuleAsWarning===true;const r=e.getLogger("sass-loader");const formatSpan=e=>`${e.url||"-"}:${e.start.line}:${e.start.column}: `;o.logger={debug(e,t){let s="";if(t.span){s=formatSpan(t.span)}s+=e;r.debug(s)},warn(t,n){let o="";if(n.deprecation){o+="Deprecation "}if(n.span&&!n.stack){o=formatSpan(n.span)}o+=t;if(n.stack){o+=`\n\n${n.stack}`}if(s){e.emitWarning(new _SassWarning.default(o,n))}else{r.warn(o)}}}}return o}const MODULE_REQUEST_REGEX=/^[^?]*~/;const IS_MODULE_IMPORT=/^~([^/]+|[^/]+\/|@[^/]+[/][^/]+|@[^/]+\/?|@[^/]+[/][^/]+\/)$/;function getPossibleRequests(e,t=false,s=false){let r=e;if(t){if(MODULE_REQUEST_REGEX.test(e)){r=r.replace(MODULE_REQUEST_REGEX,"")}if(IS_MODULE_IMPORT.test(e)){r=r[r.length-1]==="/"?r:`${r}/`;return[...new Set([r,e])]}}const n=_path.default.extname(r).toLowerCase();if(n===".css"){return[]}const o=_path.default.dirname(r);const a=o==="."?"":`${o}/`;const i=_path.default.basename(r);const c=_path.default.basename(r,n);return[...new Set([].concat(s?[`${a}_${c}.import${n}`,`${a}${c}.import${n}`]:[]).concat([`${a}_${i}`,`${a}${i}`]).concat(t?[e]:[]))]}function promiseResolve(e){return(t,s)=>new Promise(((r,n)=>{e(t,s,((e,t)=>{if(e){n(e)}else{r(t)}}))}))}const IS_SPECIAL_MODULE_IMPORT=/^~[^/]+$/;const IS_NATIVE_WIN32_PATH=/^[a-z]:[/\\]|^\\\\/i;function getWebpackResolver(e,t,s=[]){async function startResolving(e){if(e.length===0){return Promise.reject()}const[{possibleRequests:t}]=e;if(t.length===0){return Promise.reject()}const[{resolve:s,context:r}]=e;try{return await s(r,t[0])}catch(s){const[,...r]=t;if(r.length===0){const[,...t]=e;return startResolving(t)}e[0].possibleRequests=r;return startResolving(e)}}const r=t.info.includes("dart-sass");const n=promiseResolve(e({alias:[],aliasFields:[],conditionNames:[],descriptionFiles:[],extensions:[".sass",".scss",".css"],exportsFields:[],mainFields:[],mainFiles:["_index","index"],modules:[],restrictions:[/\.((sa|sc|c)ss)$/i],preferRelative:true}));const o=promiseResolve(e({alias:[],aliasFields:[],conditionNames:[],descriptionFiles:[],extensions:[".sass",".scss",".css"],exportsFields:[],mainFields:[],mainFiles:["_index.import","_index","index.import","index"],modules:[],restrictions:[/\.((sa|sc|c)ss)$/i],preferRelative:true}));const a=promiseResolve(e({dependencyType:"sass",conditionNames:["sass","style"],mainFields:["sass","style","main","..."],mainFiles:["_index","index","..."],extensions:[".sass",".scss",".css"],restrictions:[/\.((sa|sc|c)ss)$/i],preferRelative:true}));const i=promiseResolve(e({dependencyType:"sass",conditionNames:["sass","style"],mainFields:["sass","style","main","..."],mainFiles:["_index.import","_index","index.import","index","..."],extensions:[".sass",".scss",".css"],restrictions:[/\.((sa|sc|c)ss)$/i],preferRelative:true}));return(e,t,c)=>{if(!r&&!_path.default.isAbsolute(e)){return Promise.reject()}const l=t;const u=l.slice(0,5).toLowerCase()==="file:";if(u){try{t=_url.default.fileURLToPath(l)}catch(e){t=t.slice(7)}}let p=[];const f=!IS_SPECIAL_MODULE_IMPORT.test(t)&&!u&&!l.startsWith("/")&&!IS_NATIVE_WIN32_PATH.test(l);if(s.length>0&&f){const a=getPossibleRequests(t,false,c);if(!r){p=p.concat({resolve:c?o:n,context:_path.default.dirname(e),possibleRequests:a})}p=p.concat(s.map((e=>({resolve:c?o:n,context:e,possibleRequests:a}))))}const d=getPossibleRequests(t,true,c);p=p.concat({resolve:c?i:a,context:_path.default.dirname(e),possibleRequests:d});return startResolving(p)}}const MATCH_CSS=/\.css$/i;function getWebpackImporter(e,t,s){const r=getWebpackResolver(e.getResolve,t,s);return function importer(t,s,n){const{fromImport:o}=this;r(s,t,o).then((t=>{e.addDependency(_path.default.normalize(t));n({file:t.replace(MATCH_CSS,"")})})).catch((()=>{n({file:t})}))}}let nodeSassJobQueue=null;function getRenderFunctionFromSassImplementation(e){const t=e.info.includes("dart-sass");if(t){return e.render.bind(e)}if(nodeSassJobQueue===null){const t=Number(process.env.UV_THREADPOOL_SIZE||4);nodeSassJobQueue=_neoAsync.default.queue(e.render.bind(e),t-1)}return nodeSassJobQueue.push.bind(nodeSassJobQueue)}const ABSOLUTE_SCHEME=/^[A-Za-z0-9+\-.]+:/;function getURLType(e){if(e[0]==="/"){if(e[1]==="/"){return"scheme-relative"}return"path-absolute"}if(IS_NATIVE_WIN32_PATH.test(e)){return"path-absolute"}return ABSOLUTE_SCHEME.test(e)?"absolute":"path-relative"}function normalizeSourceMap(e,t){const s=e;delete s.file;s.sourceRoot="";s.sources=s.sources.map((e=>{const s=getURLType(e);if(s==="path-relative"){return _path.default.resolve(t,_path.default.normalize(e))}return e}));return s}},175:function(e){"use strict";e.exports=require("next/dist/compiled/neo-async")},17:function(e){"use strict";e.exports=require("path")},310:function(e){"use strict";e.exports=require("url")},615:function(e){"use strict";e.exports=JSON.parse('{"title":"Sass Loader options","type":"object","properties":{"implementation":{"description":"The implementation of the sass to be used.","link":"https://github.com/webpack-contrib/sass-loader#implementation","anyOf":[{"type":"string"},{"type":"object"}]},"sassOptions":{"description":"Options for `node-sass` or `sass` (`Dart Sass`) implementation.","link":"https://github.com/webpack-contrib/sass-loader#sassoptions","anyOf":[{"type":"object","additionalProperties":true},{"instanceof":"Function"}]},"additionalData":{"description":"Prepends/Appends `Sass`/`SCSS` code before the actual entry file.","link":"https://github.com/webpack-contrib/sass-loader#additionaldata","anyOf":[{"type":"string"},{"instanceof":"Function"}]},"sourceMap":{"description":"Enables/Disables generation of source maps.","link":"https://github.com/webpack-contrib/sass-loader#sourcemap","type":"boolean"},"webpackImporter":{"description":"Enables/Disables default `webpack` importer.","link":"https://github.com/webpack-contrib/sass-loader#webpackimporter","type":"boolean"},"warnRuleAsWarning":{"description":"Treats the \'@warn\' rule as a webpack warning.","link":"https://github.com/webpack-contrib/sass-loader#warnruleaswarning","type":"boolean"}},"additionalProperties":false}')}};var __webpack_module_cache__={};function __nccwpck_require__(e){var t=__webpack_module_cache__[e];if(t!==undefined){return t.exports}var s=__webpack_module_cache__[e]={exports:{}};var r=true;try{__webpack_modules__[e](s,s.exports,__nccwpck_require__);r=false}finally{if(r)delete __webpack_module_cache__[e]}return s.exports}if(typeof __nccwpck_require__!=="undefined")__nccwpck_require__.ab=__dirname+"/";var __webpack_exports__=__nccwpck_require__(172);module.exports=__webpack_exports__})(); \ No newline at end of file +(function(){var __webpack_modules__={814:function(e,t){function set(e,t,s){if(typeof s.value==="object")s.value=klona(s.value);if(!s.enumerable||s.get||s.set||!s.configurable||!s.writable||t==="__proto__"){Object.defineProperty(e,t,s)}else e[t]=s.value}function klona(e){if(typeof e!=="object")return e;var t=0,s,r,n,o=Object.prototype.toString.call(e);if(o==="[object Object]"){n=Object.create(e.__proto__||null)}else if(o==="[object Array]"){n=Array(e.length)}else if(o==="[object Set]"){n=new Set;e.forEach((function(e){n.add(klona(e))}))}else if(o==="[object Map]"){n=new Map;e.forEach((function(e,t){n.set(klona(t),klona(e))}))}else if(o==="[object Date]"){n=new Date(+e)}else if(o==="[object RegExp]"){n=new RegExp(e.source,e.flags)}else if(o==="[object DataView]"){n=new e.constructor(klona(e.buffer))}else if(o==="[object ArrayBuffer]"){n=e.slice(0)}else if(o.slice(-6)==="Array]"){n=new e.constructor(e)}if(n){for(r=Object.getOwnPropertySymbols(e);t{if(e){if(e.file){this.addDependency(r.default.normalize(e.file))}s(new a.default(e));return}let n=t.map?JSON.parse(t.map):null;if(n&&c){n=(0,o.normalizeSourceMap)(n,this.rootContext)}t.stats.includedFiles.forEach((e=>{const t=r.default.normalize(e);if(r.default.isAbsolute(t)){this.addDependency(t)}}));s(null,t.css.toString(),n)}))}var i=loader;t["default"]=i},179:function(__unused_webpack_module,exports,__nccwpck_require__){"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.getRenderFunctionFromSassImplementation=getRenderFunctionFromSassImplementation;exports.getSassImplementation=getSassImplementation;exports.getSassOptions=getSassOptions;exports.getWebpackImporter=getWebpackImporter;exports.getWebpackResolver=getWebpackResolver;exports.isSupportedFibers=isSupportedFibers;exports.normalizeSourceMap=normalizeSourceMap;var _url=_interopRequireDefault(__nccwpck_require__(310));var _path=_interopRequireDefault(__nccwpck_require__(17));var _full=__nccwpck_require__(814);var _neoAsync=_interopRequireDefault(__nccwpck_require__(175));var _SassWarning=_interopRequireDefault(__nccwpck_require__(66));function _interopRequireDefault(e){return e&&e.__esModule?e:{default:e}}function getDefaultSassImplementation(){let sassImplPkg="sass";try{eval("require").resolve("sass")}catch(error){try{eval("require").resolve("node-sass");sassImplPkg="node-sass"}catch(e){sassImplPkg="sass"}}return __nccwpck_require__(438)}function getSassImplementation(e,t){let s=t;if(!s){try{s=getDefaultSassImplementation()}catch(t){e.emitError(t);return}}if(typeof s==="string"){try{s=require(s)}catch(t){e.emitError(t);return}}const{info:r}=s;if(!r){e.emitError(new Error("Unknown Sass implementation."));return}const n=r.split("\t");if(n.length<2){e.emitError(new Error(`Unknown Sass implementation "${r}".`));return}const[o]=n;if(o==="dart-sass"){return s}else if(o==="node-sass"){return s}e.emitError(new Error(`Unknown Sass implementation "${o}".`))}function isProductionLikeMode(e){return e.mode==="production"||!e.mode}function proxyCustomImporters(e,t){return[].concat(e).map((e=>function proxyImporter(...s){const r={...this,webpackLoaderContext:t};return e.apply(r,s)}))}function isSupportedFibers(){const[e]=process.versions.node.split(".");return Number(e)<16}async function getSassOptions(e,t,s,r,n){const o=(0,_full.klona)(t.sassOptions?typeof t.sassOptions==="function"?t.sassOptions(e)||{}:t.sassOptions:{});const a=r.info.includes("dart-sass");if(a&&isSupportedFibers()){const e=!o.fiber&&o.fiber!==false;if(e){let e;try{e=require.resolve("fibers")}catch(e){}if(e){o.fiber=require(e)}}else if(o.fiber===false){delete o.fiber}}else{delete o.fiber}o.file=e.resourcePath;o.data=t.additionalData?typeof t.additionalData==="function"?await t.additionalData(s,e):`${t.additionalData}\n${s}`:s;if(!o.outputStyle&&isProductionLikeMode(e)){o.outputStyle="compressed"}if(n){o.sourceMap=true;o.outFile=_path.default.join(e.rootContext,"style.css.map");o.sourceMapContents=true;o.omitSourceMapUrl=true;o.sourceMapEmbed=false}const{resourcePath:i}=e;const c=_path.default.extname(i);if(c&&c.toLowerCase()===".sass"&&typeof o.indentedSyntax==="undefined"){o.indentedSyntax=true}else{o.indentedSyntax=Boolean(o.indentedSyntax)}o.importer=o.importer?proxyCustomImporters(Array.isArray(o.importer)?o.importer:[o.importer],e):[];o.includePaths=[].concat(process.cwd()).concat((o.includePaths||[]).map((e=>_path.default.isAbsolute(e)?e:_path.default.join(process.cwd(),e)))).concat(process.env.SASS_PATH?process.env.SASS_PATH.split(process.platform==="win32"?";":":"):[]);if(typeof o.charset==="undefined"){o.charset=true}if(!o.logger){const s=t.warnRuleAsWarning===true;const r=e.getLogger("sass-loader");const formatSpan=e=>`${e.url||"-"}:${e.start.line}:${e.start.column}: `;o.logger={debug(e,t){let s="";if(t.span){s=formatSpan(t.span)}s+=e;r.debug(s)},warn(t,n){let o="";if(n.deprecation){o+="Deprecation "}if(n.span&&!n.stack){o=formatSpan(n.span)}o+=t;if(n.stack){o+=`\n\n${n.stack}`}if(s){e.emitWarning(new _SassWarning.default(o,n))}else{r.warn(o)}}}}return o}const MODULE_REQUEST_REGEX=/^[^?]*~/;const IS_MODULE_IMPORT=/^~([^/]+|[^/]+\/|@[^/]+[/][^/]+|@[^/]+\/?|@[^/]+[/][^/]+\/)$/;function getPossibleRequests(e,t=false,s=false){let r=e;if(t){if(MODULE_REQUEST_REGEX.test(e)){r=r.replace(MODULE_REQUEST_REGEX,"")}if(IS_MODULE_IMPORT.test(e)){r=r[r.length-1]==="/"?r:`${r}/`;return[...new Set([r,e])]}}const n=_path.default.extname(r).toLowerCase();if(n===".css"){return[]}const o=_path.default.dirname(r);const a=o==="."?"":`${o}/`;const i=_path.default.basename(r);const c=_path.default.basename(r,n);return[...new Set([].concat(s?[`${a}_${c}.import${n}`,`${a}${c}.import${n}`]:[]).concat([`${a}_${i}`,`${a}${i}`]).concat(t?[e]:[]))]}function promiseResolve(e){return(t,s)=>new Promise(((r,n)=>{e(t,s,((e,t)=>{if(e){n(e)}else{r(t)}}))}))}const IS_SPECIAL_MODULE_IMPORT=/^~[^/]+$/;const IS_NATIVE_WIN32_PATH=/^[a-z]:[/\\]|^\\\\/i;function getWebpackResolver(e,t,s=[]){async function startResolving(e){if(e.length===0){return Promise.reject()}const[{possibleRequests:t}]=e;if(t.length===0){return Promise.reject()}const[{resolve:s,context:r}]=e;try{return await s(r,t[0])}catch(s){const[,...r]=t;if(r.length===0){const[,...t]=e;return startResolving(t)}e[0].possibleRequests=r;return startResolving(e)}}const r=t.info.includes("dart-sass");const n=promiseResolve(e({alias:[],aliasFields:[],conditionNames:[],descriptionFiles:[],extensions:[".sass",".scss",".css"],exportsFields:[],mainFields:[],mainFiles:["_index","index"],modules:[],restrictions:[/\.((sa|sc|c)ss)$/i],preferRelative:true}));const o=promiseResolve(e({alias:[],aliasFields:[],conditionNames:[],descriptionFiles:[],extensions:[".sass",".scss",".css"],exportsFields:[],mainFields:[],mainFiles:["_index.import","_index","index.import","index"],modules:[],restrictions:[/\.((sa|sc|c)ss)$/i],preferRelative:true}));const a=promiseResolve(e({dependencyType:"sass",conditionNames:["sass","style"],mainFields:["sass","style","main","..."],mainFiles:["_index","index","..."],extensions:[".sass",".scss",".css"],restrictions:[/\.((sa|sc|c)ss)$/i],preferRelative:true}));const i=promiseResolve(e({dependencyType:"sass",conditionNames:["sass","style"],mainFields:["sass","style","main","..."],mainFiles:["_index.import","_index","index.import","index","..."],extensions:[".sass",".scss",".css"],restrictions:[/\.((sa|sc|c)ss)$/i],preferRelative:true}));return(e,t,c)=>{if(!r&&!_path.default.isAbsolute(e)){return Promise.reject()}const l=t;const u=l.slice(0,5).toLowerCase()==="file:";if(u){try{t=_url.default.fileURLToPath(l)}catch(e){t=t.slice(7)}}let p=[];const f=!IS_SPECIAL_MODULE_IMPORT.test(t)&&!u&&!l.startsWith("/")&&!IS_NATIVE_WIN32_PATH.test(l);if(s.length>0&&f){const a=getPossibleRequests(t,false,c);if(!r){p=p.concat({resolve:c?o:n,context:_path.default.dirname(e),possibleRequests:a})}p=p.concat(s.map((e=>({resolve:c?o:n,context:e,possibleRequests:a}))))}const d=getPossibleRequests(t,true,c);p=p.concat({resolve:c?i:a,context:_path.default.dirname(e),possibleRequests:d});return startResolving(p)}}const MATCH_CSS=/\.css$/i;function getWebpackImporter(e,t,s){const r=getWebpackResolver(e.getResolve,t,s);return function importer(t,s,n){const{fromImport:o}=this;r(s,t,o).then((t=>{e.addDependency(_path.default.normalize(t));n({file:t.replace(MATCH_CSS,"")})})).catch((()=>{n({file:t})}))}}let nodeSassJobQueue=null;function getRenderFunctionFromSassImplementation(e){const t=e.info.includes("dart-sass");if(t){return e.render.bind(e)}if(nodeSassJobQueue===null){const t=Number(process.env.UV_THREADPOOL_SIZE||4);nodeSassJobQueue=_neoAsync.default.queue(e.render.bind(e),t-1)}return nodeSassJobQueue.push.bind(nodeSassJobQueue)}const ABSOLUTE_SCHEME=/^[A-Za-z0-9+\-.]+:/;function getURLType(e){if(e[0]==="/"){if(e[1]==="/"){return"scheme-relative"}return"path-absolute"}if(IS_NATIVE_WIN32_PATH.test(e)){return"path-absolute"}return ABSOLUTE_SCHEME.test(e)?"absolute":"path-relative"}function normalizeSourceMap(e,t){const s=e;delete s.file;s.sourceRoot="";s.sources=s.sources.map((e=>{const s=getURLType(e);if(s==="path-relative"){return _path.default.resolve(t,_path.default.normalize(e))}return e}));return s}},175:function(e){"use strict";e.exports=require("next/dist/compiled/neo-async")},17:function(e){"use strict";e.exports=require("path")},438:function(e){"use strict";e.exports=require("sass")},310:function(e){"use strict";e.exports=require("url")},615:function(e){"use strict";e.exports=JSON.parse('{"title":"Sass Loader options","type":"object","properties":{"implementation":{"description":"The implementation of the sass to be used.","link":"https://github.com/webpack-contrib/sass-loader#implementation","anyOf":[{"type":"string"},{"type":"object"}]},"sassOptions":{"description":"Options for `node-sass` or `sass` (`Dart Sass`) implementation.","link":"https://github.com/webpack-contrib/sass-loader#sassoptions","anyOf":[{"type":"object","additionalProperties":true},{"instanceof":"Function"}]},"additionalData":{"description":"Prepends/Appends `Sass`/`SCSS` code before the actual entry file.","link":"https://github.com/webpack-contrib/sass-loader#additionaldata","anyOf":[{"type":"string"},{"instanceof":"Function"}]},"sourceMap":{"description":"Enables/Disables generation of source maps.","link":"https://github.com/webpack-contrib/sass-loader#sourcemap","type":"boolean"},"webpackImporter":{"description":"Enables/Disables default `webpack` importer.","link":"https://github.com/webpack-contrib/sass-loader#webpackimporter","type":"boolean"},"warnRuleAsWarning":{"description":"Treats the \'@warn\' rule as a webpack warning.","link":"https://github.com/webpack-contrib/sass-loader#warnruleaswarning","type":"boolean"}},"additionalProperties":false}')}};var __webpack_module_cache__={};function __nccwpck_require__(e){var t=__webpack_module_cache__[e];if(t!==undefined){return t.exports}var s=__webpack_module_cache__[e]={exports:{}};var r=true;try{__webpack_modules__[e](s,s.exports,__nccwpck_require__);r=false}finally{if(r)delete __webpack_module_cache__[e]}return s.exports}if(typeof __nccwpck_require__!=="undefined")__nccwpck_require__.ab=__dirname+"/";var __webpack_exports__=__nccwpck_require__(172);module.exports=__webpack_exports__})(); \ No newline at end of file diff --git a/packages/next/taskfile.js b/packages/next/taskfile.js index 1dd80baefe66..a0e73d1b5d2b 100644 --- a/packages/next/taskfile.js +++ b/packages/next/taskfile.js @@ -1473,15 +1473,22 @@ export async function copy_react_server_dom_webpack(task, opts) { // eslint-disable-next-line camelcase externals['sass-loader'] = 'next/dist/compiled/sass-loader' export async function ncc_sass_loader(task, opts) { + const sassLoaderPath = require.resolve('sass-loader') + const utilsPath = join(dirname(sassLoaderPath), 'utils.js') + const originalContent = await fs.readFile(utilsPath, 'utf8') + + await fs.writeFile( + utilsPath, + originalContent.replace( + /require\.resolve\(["'](sass|node-sass)["']\)/g, + 'eval("require").resolve("$1")' + ) + ) + await task - .source(opts.src || relative(__dirname, require.resolve('sass-loader'))) + .source(opts.src || relative(__dirname, sassLoaderPath)) .ncc({ packageName: 'sass-loader', - customEmit(path, isRequire) { - if (isRequire && path === 'sass') return false - if (path.indexOf('node-sass') !== -1) - return `eval("require.resolve('node-sass')")` - }, externals: { ...externals, 'schema-utils': externals['schema-utils3'], @@ -1854,7 +1861,6 @@ export async function ncc(task, opts) { 'ncc_postcss_modules_values', 'ncc_postcss_value_parser', 'ncc_icss_utils', - 'ncc_sass_loader', 'ncc_schema_utils2', 'ncc_schema_utils3', 'ncc_semver', @@ -1888,6 +1894,7 @@ export async function ncc(task, opts) { 'copy_constants_browserify', 'copy_react_server_dom_webpack', 'copy_react_is', + 'ncc_sass_loader', 'ncc_jest_worker', 'ncc_edge_runtime_primitives', 'ncc_edge_runtime', diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 40a8fe610689..c4a041e08987 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -122,7 +122,6 @@ importers: next: workspace:* node-fetch: 2.6.7 node-notifier: 8.0.1 - node-sass: 6.0.1 npm-run-all: 4.1.5 nprogress: 0.2.0 pixrem: 5.0.0 @@ -149,6 +148,7 @@ importers: release: 6.3.1 request-promise-core: 1.1.2 resolve-from: 5.0.0 + sass: 1.54.0 seedrandom: 3.0.5 selenium-webdriver: 4.0.0-beta.4 semver: 7.3.7 @@ -275,7 +275,6 @@ importers: next: link:packages/next node-fetch: 2.6.7 node-notifier: 8.0.1 - node-sass: 6.0.1 npm-run-all: 4.1.5 nprogress: 0.2.0 pixrem: 5.0.0 @@ -302,6 +301,7 @@ importers: release: 6.3.1 request-promise-core: 1.1.2_request@2.88.2 resolve-from: 5.0.0 + sass: 1.54.0 seedrandom: 3.0.5 selenium-webdriver: 4.0.0-beta.4 semver: 7.3.7 @@ -6711,11 +6711,6 @@ packages: resolution: {integrity: sha512-0FcBfdcmaumGPQ0qPn7Q5qTgz/ooXgIyp1rf8ik5bGX8mpE2YHjC0P/eyQvxu1GURYQgq9ozf2mteQ5ZD9YiyQ==} dev: true - /amdefine/1.0.1: - resolution: {integrity: sha512-S2Hw0TtNkMJhIabBwIojKL9YHO5T0n5eNqWJ7Lrlel/zDbftQpxpapi8tZs3X1HWa+u+QeydGmzzNU0m09+Rcg==} - engines: {node: '>=0.4.2'} - dev: true - /amphtml-validator/1.0.35: resolution: {integrity: sha512-C67JzC5EI6pE2C0sAo/zuCp8ARDl1Vtt6/s0nr+3NuXDNOdkjclZUkaNAd/ZnsEvvYodkXZ6T/uww890IQh9dQ==} hasBin: true @@ -7073,10 +7068,6 @@ packages: resolution: {integrity: sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==} dev: true - /async-foreach/0.1.3: - resolution: {integrity: sha512-VUeSMD8nEGBWaZK4lizI1sf3yEC7pnAQ/mrI7pC2fBz2s/tq5jWWEngTwaf0Gruu/OoXRGLGg1XFqpYBiGTYJA==} - dev: true - /async-retry/1.2.1: resolution: {integrity: sha512-FadV8UDcyZDjzb6eV7MCJj0bfrNjwKw7/X0QHPFCbYP6T20FXgZCYXpJKlQC8RxEQP1E6Xs8pNHdh3bcrZAuAw==} dependencies: @@ -7460,7 +7451,6 @@ packages: resolution: {integrity: sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==} engines: {node: '>=8'} dev: true - optional: true /bindings/1.5.0: resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==} @@ -8231,7 +8221,6 @@ packages: optionalDependencies: fsevents: 2.1.3 dev: true - optional: true /chownr/1.1.3: resolution: {integrity: sha512-i70fVHhmV3DtTl6nqvZOnIjbY0Pe4kAUjwHj8z0zAdgBtYrJyYwLKCCuRBQ5ppkyL0AkN7HKRnETdmdp1zqNXw==} @@ -10448,6 +10437,7 @@ packages: eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 dependencies: eslint: 7.32.0 + dev: false /eslint-plugin-react/7.23.2_eslint@7.24.0: resolution: {integrity: sha512-AfjgFQB+nYszudkxRkTFu0UR1zEQig0ArVMPloKhxwlwkzaw/fBiH0QWcBBhZONlXqQC51+nfqFrkn4EzHcGBw==} @@ -11528,13 +11518,6 @@ packages: wide-align: 1.1.3 dev: true - /gaze/1.1.3: - resolution: {integrity: sha512-BRdNm8hbWzFzWHERTrejLqwHDfS4GibPoq5wjTPIoJHoBtKGPg3xAFfxmM+9ztbXelxcf2hwQcaz1PtmFeue8g==} - engines: {node: '>= 4.0.0'} - dependencies: - globule: 1.3.0 - dev: true - /generic-names/2.0.1: resolution: {integrity: sha512-kPCHWa1m9wGG/OwQpeweTwM/PYiQLrUIxXbt/P4Nic3LbGjCP0YwrALHW1uNLKZ0LIMg+RF+XRlj2ekT9ZlZAQ==} dependencies: @@ -11938,15 +11921,6 @@ packages: resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==} dev: true - /globule/1.3.0: - resolution: {integrity: sha512-YlD4kdMqRCQHrhVdonet4TdRtv1/sZKepvoxNT4Nrhrp5HI8XFfc8kFlGlBn2myBo80aGp8Eft259mbcUJhgSg==} - engines: {node: '>= 0.10'} - dependencies: - glob: 7.1.7 - lodash: 4.17.21 - minimatch: 3.0.4 - dev: true - /got/10.7.0: resolution: {integrity: sha512-aWTDeNw9g+XqEZNcTjMMZSy7B7yE9toWOFYip7ofFTLleJhvZwUxxTxkTpKvF+p1SAA4VHmuEy7PiHTHyq8tJg==} engines: {node: '>=10'} @@ -12609,6 +12583,10 @@ packages: resolution: {integrity: sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=} dev: true + /immutable/4.1.0: + resolution: {integrity: sha512-oNkuqVTA8jqG1Q6c+UglTOD1xhC1BtjKI7XkCXRkZHrN5m18/XsnUp8Q89GkQO/z+0WjonSvl0FLhDYftp46nQ==} + dev: true + /import-cwd/3.0.0: resolution: {integrity: sha512-4pnzH16plW+hgvRECbDWpQl3cqtvSofHWh44met7ESfZ8UZOWWddm8hEyDTqREJ9RbYHY8gi8DqmaelApoOGMg==} engines: {node: '>=8'} @@ -12885,7 +12863,6 @@ packages: dependencies: binary-extensions: 2.1.0 dev: true - optional: true /is-boolean-object/1.1.0: resolution: {integrity: sha512-a7Uprx8UtD+HWdyYwnD1+ExtTgqQtD2k/1yJgtXP6wnMm8byhkoTZRl+95LLThpzNZJ5aEvi46cdH+ayMFRwmA==} @@ -15327,24 +15304,6 @@ packages: yargs-parser: 20.2.4 dev: true - /meow/9.0.0: - resolution: {integrity: sha512-+obSblOQmRhcyBt62furQqRAQpNyWXo8BuQ5bN7dG8wmwQ+vwHKp/rCFD4CrTP8CsDQD1sjoZ94K417XEUk8IQ==} - engines: {node: '>=10'} - dependencies: - '@types/minimist': 1.2.0 - camelcase-keys: 6.2.2 - decamelize: 1.2.0 - decamelize-keys: 1.1.0 - hard-rejection: 2.1.0 - minimist-options: 4.1.0 - normalize-package-data: 3.0.0 - read-pkg-up: 7.0.1 - redent: 3.0.0 - trim-newlines: 3.0.0 - type-fest: 0.18.1 - yargs-parser: 20.2.4 - dev: true - /merge-descriptors/1.0.1: resolution: {integrity: sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=} dev: true @@ -16105,29 +16064,6 @@ packages: /node-releases/2.0.3: resolution: {integrity: sha512-maHFz6OLqYxz+VQyCAtA3PTX4UP/53pa05fyDNc9CwjvJ0yEh6+xBwKsgCxMNhS8taUKBFYxfuiaD9U/55iFaw==} - /node-sass/6.0.1: - resolution: {integrity: sha512-f+Rbqt92Ful9gX0cGtdYwjTrWAaGURgaK5rZCWOgCNyGWusFYHhbqCCBoFBeat+HKETOU02AyTxNhJV0YZf2jQ==} - engines: {node: '>=12'} - hasBin: true - requiresBuild: true - dependencies: - async-foreach: 0.1.3 - chalk: 1.1.3 - cross-spawn: 7.0.3 - gaze: 1.1.3 - get-stdin: 4.0.1 - glob: 7.2.0 - lodash: 4.17.21 - meow: 9.0.0 - nan: 2.15.0 - node-gyp: 7.1.2 - npmlog: 4.1.2 - request: 2.88.2 - sass-graph: 2.2.5 - stdout-stream: 1.4.1 - true-case-path: 1.0.3 - dev: true - /node-version/1.1.3: resolution: {integrity: sha512-rEwE51JWn0yN3Wl5BXeGn5d52OGbSXzWiiXRjAQeuyvcGKyvuSILW2rb3G7Xh+nexzLwhTpek6Ehxd6IjvHePg==} engines: {node: '>=4.0.0'} @@ -18895,7 +18831,6 @@ packages: dependencies: picomatch: 2.2.3 dev: true - optional: true /recast/0.20.5: resolution: {integrity: sha512-E5qICoPoNL4yU0H0NoBDntNB0Q5oMSNh9usFctYniLBluTthi3RsQVBXIJNbApOlvSwW/RGxIuokPcAc59J5fQ==} @@ -19642,16 +19577,6 @@ packages: /safer-buffer/2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} - /sass-graph/2.2.5: - resolution: {integrity: sha512-VFWDAHOe6mRuT4mZRd4eKE+d8Uedrk6Xnh7Sh9b4NGufQLQjOrvf/MQoOdx+0s92L89FeyUUNfU597j/3uNpag==} - hasBin: true - dependencies: - glob: 7.2.0 - lodash: 4.17.21 - scss-tokenizer: 0.2.3 - yargs: 13.3.2 - dev: true - /sass-loader/12.4.0: resolution: {integrity: sha512-7xN+8khDIzym1oL9XyS6zP6Ges+Bo2B2xbPrjdMHEYyV3AQYhd/wXeru++3ODHF0zMjYmVadblSKrPrjEkL8mg==} engines: {node: '>= 12.13.0'} @@ -19672,6 +19597,16 @@ packages: neo-async: 2.6.2 dev: true + /sass/1.54.0: + resolution: {integrity: sha512-C4zp79GCXZfK0yoHZg+GxF818/aclhp9F48XBu/+bm9vXEVAYov9iU3FBVRMq3Hx3OA4jfKL+p2K9180mEh0xQ==} + engines: {node: '>=12.0.0'} + hasBin: true + dependencies: + chokidar: 3.4.3 + immutable: 4.1.0 + source-map-js: 1.0.2 + dev: true + /sax/1.2.4: resolution: {integrity: sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==} dev: true @@ -19738,13 +19673,6 @@ packages: ajv-keywords: 3.5.2_ajv@6.12.6 dev: true - /scss-tokenizer/0.2.3: - resolution: {integrity: sha1-jrBtualyMzOCTT9VMGQRSYR85dE=} - dependencies: - js-base64: 2.5.1 - source-map: 0.4.4 - dev: true - /seedrandom/3.0.5: resolution: {integrity: sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==} dev: true @@ -20136,13 +20064,6 @@ packages: deprecated: See https://github.com/lydell/source-map-url#deprecated requiresBuild: true - /source-map/0.4.4: - resolution: {integrity: sha1-66T12pwNyZneaAMti092FzZSA2s=} - engines: {node: '>=0.8.0'} - dependencies: - amdefine: 1.0.1 - dev: true - /source-map/0.5.7: resolution: {integrity: sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==} engines: {node: '>=0.10.0'} @@ -20303,12 +20224,6 @@ packages: engines: {node: '>= 0.6'} dev: true - /stdout-stream/1.4.1: - resolution: {integrity: sha512-j4emi03KXqJWcIeF8eIXkjMFN1Cmb8gUlDYGeBALLPo5qdyTfA9bOtl8m33lRoC+vFMkP3gl0WsDr6+gzxbbTA==} - dependencies: - readable-stream: 2.3.7 - dev: true - /stream-browserify/2.0.2: resolution: {integrity: sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg==} dependencies: @@ -21249,12 +21164,6 @@ packages: resolution: {integrity: sha512-tdzBRDGWcI1OpPVmChbdSKhvSVurznZ8X36AYURAcl+0o2ldlCY2XPzyXNNxwJwwyIU+rIglTCG4kxtNKBQH7Q==} dev: true - /true-case-path/1.0.3: - resolution: {integrity: sha512-m6s2OdQe5wgpFMC+pAJ+q9djG82O2jcHPOI6RNg1yy9rCYR+WD6Nbpl32fDpfC56nirdRy+opFa/Vk7HYhqaew==} - dependencies: - glob: 7.2.0 - dev: true - /tsconfig-paths/3.14.1: resolution: {integrity: sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==} dependencies: @@ -22679,13 +22588,6 @@ packages: engines: {node: '>= 6'} dev: true - /yargs-parser/13.1.2: - resolution: {integrity: sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==} - dependencies: - camelcase: 5.3.1 - decamelize: 1.2.0 - dev: true - /yargs-parser/15.0.0: resolution: {integrity: sha512-xLTUnCMc4JhxrPEPUYD5IBR1mWCK/aT6+RJ/K29JY2y1vD+FhtgKK0AXRWvI262q3QSffAQuTouFIKUuHX89wQ==} dependencies: @@ -22710,21 +22612,6 @@ packages: engines: {node: '>=12'} dev: true - /yargs/13.3.2: - resolution: {integrity: sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==} - dependencies: - cliui: 5.0.0 - find-up: 3.0.0 - get-caller-file: 2.0.5 - require-directory: 2.1.1 - require-main-filename: 2.0.0 - set-blocking: 2.0.0 - string-width: 3.1.0 - which-module: 2.0.0 - y18n: 4.0.0 - yargs-parser: 13.1.2 - dev: true - /yargs/14.2.2: resolution: {integrity: sha512-/4ld+4VV5RnrynMhPZJ/ZpOCGSCeghMykZ3BhdFBDa9Wy/RH6uEGNWDJog+aUlq+9OM1CFTgtYRW5Is1Po9NOA==} dependencies: diff --git a/test/integration/scss/test/index.test.js b/test/integration/css/test/group-1.test.js similarity index 69% rename from test/integration/scss/test/index.test.js rename to test/integration/css/test/group-1.test.js index 5e2553abc519..30b6ac6f1a0b 100644 --- a/test/integration/scss/test/index.test.js +++ b/test/integration/css/test/group-1.test.js @@ -1,9 +1,9 @@ /* eslint-env jest */ - import cheerio from 'cheerio' import 'flat-map-polyfill' import { readdir, readFile, remove } from 'fs-extra' import { + check, File, findPort, killApp, @@ -15,41 +15,10 @@ import { } from 'next-test-utils' import webdriver from 'next-webdriver' import { join } from 'path' -import { quote as shellQuote } from 'shell-quote' - -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, - }) - let cleanScssErrMsg = - '\n\n' + - './styles/global.scss\n' + - "To use Next.js' built-in Sass support, you first need to install `sass`.\n" + - 'Run `npm i sass` or `yarn add sass` inside your workspace.\n' + - '\n' + - 'Learn more: https://nextjs.org/docs/messages/install-sass\n' - - expect(code).toBe(1) - expect(stderr).toContain('Failed to compile.') - expect(stderr).toContain(cleanScssErrMsg) - expect(stderr).not.toContain('css-loader') - expect(stderr).not.toContain('sass-loader') - }) - }) +const fixturesDir = join(__dirname, '../..', 'css-fixtures') +describe('CSS Support', () => { describe('Basic Global Support', () => { const appDir = join(fixturesDir, 'single-global') @@ -78,36 +47,8 @@ describe('SCSS Support', () => { }) }) - describe('Basic Module Include Paths Support', () => { - const appDir = join(fixturesDir, 'basic-module-include-paths') - - beforeAll(async () => { - await remove(join(appDir, '.next')) - }) - - it('should compile successfully', async () => { - const { code, stdout } = await nextBuild(appDir, [], { - stdout: true, - }) - expect(code).toBe(0) - expect(stdout).toMatch(/Compiled successfully/) - }) - - it(`should've emitted a single CSS file`, async () => { - const cssFolder = join(appDir, '.next/static/css') - - const files = await readdir(cssFolder) - const cssFiles = files.filter((f) => /\.css$/.test(f)) - - expect(cssFiles.length).toBe(1) - expect(await readFile(join(cssFolder, cssFiles[0]), 'utf8')).toContain( - 'color:red' - ) - }) - }) - - describe('Basic Module Prepend Data Support', () => { - const appDir = join(fixturesDir, 'basic-module-prepend-data') + describe('Basic Global Support with special characters in path', () => { + const appDir = join(fixturesDir, 'single-global-special-characters', 'a+b') beforeAll(async () => { await remove(join(appDir, '.next')) @@ -248,7 +189,7 @@ describe('SCSS Support', () => { expect( cssContent.replace(/\/\*.*?\*\//g, '').trim() ).toMatchInlineSnapshot( - `".redText ::-moz-placeholder{color:red}.redText :-ms-input-placeholder{color:red}.redText ::placeholder{color:red}.flex-parsing{flex:0 0 calc(50% - var(--vertical-gutter))}"` + `"@media (min-width:480px) and (max-width:767px){::-moz-placeholder{color:green}:-ms-input-placeholder{color:green}::placeholder{color:green}}.flex-parsing{flex:0 0 calc(50% - var(--vertical-gutter))}.transform-parsing{transform:translate3d(0,0)}.css-grid-shorthand{grid-column:span 2}.g-docs-sidenav .filter::-webkit-input-placeholder{opacity:80%}"` ) // Contains a source map @@ -269,18 +210,29 @@ describe('SCSS Support', () => { const { version, mappings, sourcesContent } = JSON.parse(cssMapContent) expect({ version, mappings, sourcesContent }).toMatchInlineSnapshot(` Object { - "mappings": "AACA,4BAEI,SAHK,CACT,gCAEI,SAHK,CACT,uBAEI,SAHK,CAIN,cAID,2CAA4C", + "mappings": "AAAA,+CACE,mBACE,WACF,CAFA,uBACE,WACF,CAFA,cACE,WACF,CACF,CAEA,cACE,2CACF,CAEA,mBACE,0BACF,CAEA,oBACE,kBACF,CAEA,mDACE,WACF", "sourcesContent": Array [ - "$var: red; - .redText { + "@media (480px <= width < 768px) { ::placeholder { - color: $var; + color: green; } } .flex-parsing { flex: 0 0 calc(50% - var(--vertical-gutter)); } + + .transform-parsing { + transform: translate3d(0px, 0px); + } + + .css-grid-shorthand { + grid-column: span 2; + } + + .g-docs-sidenav .filter::-webkit-input-placeholder { + opacity: 80%; + } ", ], "version": 3, @@ -319,6 +271,82 @@ describe('SCSS Support', () => { }) }) + describe('React Lifecyce Order (dev)', () => { + const appDir = join(fixturesDir, 'transition-react') + 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 the correct color on mount after navigation', async () => { + let browser + try { + browser = await webdriver(appPort, '/') + + // Navigate to other: + await browser.waitForElementByCss('#link-other').click() + const text = await browser.waitForElementByCss('#red-title').text() + expect(text).toMatchInlineSnapshot(`"rgb(255, 0, 0)"`) + } finally { + if (browser) { + await browser.close() + } + } + }) + }) + + describe('React Lifecyce Order (production)', () => { + const appDir = join(fixturesDir, 'transition-react') + beforeAll(async () => { + await remove(join(appDir, '.next')) + }) + + let appPort + let app + let code + let stdout + beforeAll(async () => { + ;({ code, stdout } = await nextBuild(appDir, [], { + stdout: true, + })) + appPort = await findPort() + app = await nextStart(appDir, appPort) + }) + afterAll(async () => { + await killApp(app) + }) + + it('should have compiled successfully', () => { + expect(code).toBe(0) + expect(stdout).toMatch(/Compiled successfully/) + }) + + it('should have the correct color on mount after navigation', async () => { + let browser + try { + browser = await webdriver(appPort, '/') + + // Navigate to other: + await browser.waitForElementByCss('#link-other').click() + const text = await browser.waitForElementByCss('#red-title').text() + expect(text).toMatchInlineSnapshot(`"rgb(255, 0, 0)"`) + } finally { + if (browser) { + await browser.close() + } + } + }) + }) + describe('Invalid CSS in _document', () => { const appDir = join(fixturesDir, 'invalid-module-document') @@ -332,7 +360,7 @@ describe('SCSS Support', () => { }) expect(code).not.toBe(0) expect(stderr).toContain('Failed to compile') - expect(stderr).toContain('styles.module.scss') + expect(stderr).toContain('styles.module.css') expect(stderr).toMatch( /CSS.*cannot.*be imported within.*pages[\\/]_document\.js/ ) @@ -353,7 +381,7 @@ describe('SCSS Support', () => { }) expect(code).not.toBe(0) expect(stderr).toContain('Failed to compile') - expect(stderr).toContain('styles/global.scss') + expect(stderr).toContain('styles/global.css') expect(stderr).toMatch( /Please move all first-party global CSS imports.*?pages(\/|\\)_app/ ) @@ -361,6 +389,35 @@ describe('SCSS Support', () => { }) }) + describe('Valid Global CSS from npm', () => { + const appDir = join(fixturesDir, 'import-global-from-module') + + beforeAll(async () => { + await remove(join(appDir, '.next')) + }) + + it('should compile successfully', async () => { + const { code, stdout } = await nextBuild(appDir, [], { + stdout: true, + }) + expect(code).toBe(0) + expect(stdout).toMatch(/Compiled successfully/) + }) + + it(`should've emitted a single CSS file`, async () => { + const cssFolder = join(appDir, '.next/static/css') + + const files = await readdir(cssFolder) + const cssFiles = files.filter((f) => /\.css$/.test(f)) + + expect(cssFiles.length).toBe(1) + const cssContent = await readFile(join(cssFolder, cssFiles[0]), 'utf8') + expect( + cssContent.replace(/\/\*.*?\*\//g, '').trim() + ).toMatchInlineSnapshot(`".red-text{color:\\"red\\"}"`) + }) + }) + describe('Invalid Global CSS with Custom App', () => { const appDir = join(fixturesDir, 'invalid-global-with-app') @@ -374,7 +431,7 @@ describe('SCSS Support', () => { }) expect(code).not.toBe(0) expect(stderr).toContain('Failed to compile') - expect(stderr).toContain('styles/global.scss') + expect(stderr).toContain('styles/global.css') expect(stderr).toMatch( /Please move all first-party global CSS imports.*?pages(\/|\\)_app/ ) @@ -395,7 +452,7 @@ describe('SCSS Support', () => { }) expect(code).not.toBe(0) expect(stderr).toContain('Failed to compile') - expect(stderr).toContain('styles/global.scss') + expect(stderr).toContain('styles/global.css') expect(stderr).toContain('Please move all first-party global CSS imports') expect(stderr).toMatch(/Location:.*pages[\\/]index\.js/) }) @@ -434,15 +491,17 @@ describe('SCSS Support', () => { ) expect(currentColor).toMatchInlineSnapshot(`"rgb(255, 0, 0)"`) - const cssFile = new File(join(appDir, 'styles/global1.scss')) + const cssFile = new File(join(appDir, 'styles/global1.css')) 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` + cssFile.replace('color: red', 'color: purple') + + await check( + () => + browser.eval( + `window.getComputedStyle(document.querySelector('.red-text')).color` + ), + 'rgb(128, 0, 128)' ) - expect(refreshedColor).toMatchInlineSnapshot(`"rgb(128, 0, 128)"`) // ensure text remained expect(await browser.elementById('text-input').getValue()).toBe( @@ -661,54 +720,6 @@ describe('SCSS Support', () => { }) }) - describe('CSS URL via file-loader sass partial', () => { - const appDir = join(fixturesDir, 'url-global-partial') - - beforeAll(async () => { - await remove(join(appDir, '.next')) - }) - - it('should compile successfully', async () => { - const { code, stdout } = await nextBuild(appDir, [], { - stdout: true, - }) - expect(code).toBe(0) - expect(stdout).toMatch(/Compiled successfully/) - }) - - it(`should've emitted 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() - ).toMatchInlineSnapshot( - '".red-text{color:red;background-image:url(/_next/static/media/darka.6b01655b.svg),url(/_next/static/media/darkb.6b01655b.svg)}.blue-text{color:orange;font-weight:bolder;background-image:url(/_next/static/media/light.2da1d3d6.svg);color:blue}"' - ) - - const mediaFiles = await readdir(mediaFolder) - expect(mediaFiles.length).toBe(3) - expect( - mediaFiles - .map((fileName) => - /^(.+?)\..{8}\.(.+?)$/.exec(fileName).slice(1).join('.') - ) - .sort() - ).toMatchInlineSnapshot(` - Array [ - "darka.svg", - "darkb.svg", - "light.svg", - ] - `) - }) - }) - describe('CSS URL via `file-loader` and asset prefix (1)', () => { const appDir = join(fixturesDir, 'url-global-asset-prefix-1') @@ -801,64 +812,6 @@ describe('SCSS Support', () => { }) }) - describe('Data Urls', () => { - const appDir = join(fixturesDir, 'data-url') - - beforeAll(async () => { - await remove(join(appDir, '.next')) - }) - - it('should compile successfully', async () => { - const { code, stdout } = await nextBuild(appDir, [], { - stdout: true, - }) - expect(code).toBe(0) - expect(stdout).toMatch(/Compiled successfully/) - }) - - it(`should've emitted expected files`, 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( - /^\.red-text\{color:red;background-image:url\("data:[^"]+"\)\}$/ - ) - }) - }) - - describe('External imports', () => { - const appDir = join(fixturesDir, 'external-url') - - beforeAll(async () => { - await remove(join(appDir, '.next')) - }) - - it('should compile successfully', async () => { - const { code, stdout } = await nextBuild(appDir, [], { - stdout: true, - }) - expect(code).toBe(0) - expect(stdout).toMatch(/Compiled successfully/) - }) - - it(`should've emitted expected files`, 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( - /^@import url\("https:\/\/fonts\.googleapis\.com\/css2\?family=Poppins:wght@400&display=swap"\);\.red-text\{color:red;font-family:Poppins;font-style:normal;font-weight:400\}$/ - ) - }) - }) - describe('Good CSS Import from node_modules', () => { const appDir = join(fixturesDir, 'npm-import') @@ -914,176 +867,4 @@ describe('SCSS Support', () => { ).toMatchInlineSnapshot(`".other{color:blue}.test{color:red}"`) }) }) - - describe('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 { code, stderr } = await nextBuild(appDir, [], { stderr: true }) - - expect(code).toBe(0) - expect(stderr).not.toMatch(/Can't resolve '[^']*?nprogress[^']*?'/) - expect(stderr).not.toMatch(/Build error occurred/) - }) - }) - - describe('Preprocessor loader order', () => { - const appDir = join(fixturesDir, 'loader-order') - - beforeAll(async () => { - await remove(join(appDir, '.next')) - }) - - it('should compile successfully', async () => { - const { stdout } = await nextBuild(appDir, [], { - stdout: true, - }) - expect(stdout).toMatch(/Compiled successfully/) - }) - }) - - 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 - let stdout - let code - beforeAll(async () => { - await remove(join(appDir, '.next')) - ;({ code, stdout } = await nextBuild(appDir, [], { - stdout: true, - })) - appPort = await findPort() - app = await nextStart(appDir, appPort) - }) - afterAll(async () => { - await killApp(app) - }) - - it('should have compiled successfully', () => { - expect(code).toBe(0) - expect(stdout).toMatch(/Compiled successfully/) - }) - - 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 compile successfully', async () => { - const { code, stdout } = await nextBuild(appDir, [], { - stdout: true, - }) - expect(code).toBe(0) - expect(stdout).toMatch(/Compiled successfully/) - }) - - 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 compile successfully', async () => { - const { code, stdout } = await nextBuild(appDir, [], { - stdout: true, - }) - expect(code).toBe(0) - expect(stdout).toMatch(/Compiled successfully/) - }) - - 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/test/integration/css/test/index.test.js b/test/integration/css/test/group-2.test.js similarity index 55% rename from test/integration/css/test/index.test.js rename to test/integration/css/test/group-2.test.js index 09cef727dd03..867c68b8c4bd 100644 --- a/test/integration/css/test/index.test.js +++ b/test/integration/css/test/group-2.test.js @@ -19,855 +19,6 @@ import { join } from 'path' const fixturesDir = join(__dirname, '../..', 'css-fixtures') describe('CSS Support', () => { - describe('Basic Global Support', () => { - const appDir = join(fixturesDir, 'single-global') - - beforeAll(async () => { - await remove(join(appDir, '.next')) - }) - - it('should compile successfully', async () => { - const { code, stdout } = await nextBuild(appDir, [], { - stdout: true, - }) - expect(code).toBe(0) - expect(stdout).toMatch(/Compiled successfully/) - }) - - it(`should've emitted a single CSS file`, async () => { - const cssFolder = join(appDir, '.next/static/css') - - const files = await readdir(cssFolder) - const cssFiles = files.filter((f) => /\.css$/.test(f)) - - expect(cssFiles.length).toBe(1) - expect(await readFile(join(cssFolder, cssFiles[0]), 'utf8')).toContain( - 'color:red' - ) - }) - }) - - describe('Basic Global Support with special characters in path', () => { - const appDir = join(fixturesDir, 'single-global-special-characters', 'a+b') - - beforeAll(async () => { - await remove(join(appDir, '.next')) - }) - - it('should compile successfully', async () => { - const { code, stdout } = await nextBuild(appDir, [], { - stdout: true, - }) - expect(code).toBe(0) - expect(stdout).toMatch(/Compiled successfully/) - }) - - it(`should've emitted a single CSS file`, async () => { - const cssFolder = join(appDir, '.next/static/css') - - const files = await readdir(cssFolder) - const cssFiles = files.filter((f) => /\.css$/.test(f)) - - expect(cssFiles.length).toBe(1) - 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 compile successfully', async () => { - const { code, stdout } = await nextBuild(appDir, [], { - stdout: true, - }) - expect(code).toBe(0) - expect(stdout).toMatch(/Compiled successfully/) - }) - - it(`should've emitted a single CSS file`, async () => { - const cssFolder = join(appDir, '.next/static/css') - - const files = await readdir(cssFolder) - const cssFiles = files.filter((f) => /\.css$/.test(f)) - - expect(cssFiles.length).toBe(1) - 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 compile successfully', async () => { - const { code, stdout } = await nextBuild(appDir, [], { - stdout: true, - }) - expect(code).toBe(0) - expect(stdout).toMatch(/Compiled successfully/) - }) - - it(`should've emitted a single CSS file`, async () => { - const cssFolder = join(appDir, '.next/static/css') - - const files = await readdir(cssFolder) - const cssFiles = files.filter((f) => /\.css$/.test(f)) - - expect(cssFiles.length).toBe(1) - const cssContent = await readFile(join(cssFolder, cssFiles[0]), 'utf8') - expect( - cssContent.replace(/\/\*.*?\*\//g, '').trim() - ).toMatchInlineSnapshot(`".red-text{color:red}.blue-text{color:blue}"`) - }) - }) - - describe('Nested @import() Global Support', () => { - const appDir = join(fixturesDir, 'nested-global') - - beforeAll(async () => { - await remove(join(appDir, '.next')) - }) - - it('should compile successfully', async () => { - const { code, stdout } = await nextBuild(appDir, [], { - stdout: true, - }) - expect(code).toBe(0) - expect(stdout).toMatch(/Compiled successfully/) - }) - - it(`should've emitted a single CSS file`, async () => { - const cssFolder = join(appDir, '.next/static/css') - - const files = await readdir(cssFolder) - const cssFiles = files.filter((f) => /\.css$/.test(f)) - - expect(cssFiles.length).toBe(1) - const cssContent = await readFile(join(cssFolder, cssFiles[0]), 'utf8') - expect( - cssContent.replace(/\/\*.*?\*\//g, '').trim() - ).toMatchInlineSnapshot( - `".red-text{color:purple;font-weight:bolder;color:red}.blue-text{color:orange;font-weight:bolder;color:blue}"` - ) - }) - }) - - describe('CSS Compilation and Prefixing', () => { - const appDir = join(fixturesDir, 'compilation-and-prefixing') - - beforeAll(async () => { - await remove(join(appDir, '.next')) - }) - - it('should compile successfully', async () => { - const { code, stdout } = await nextBuild(appDir, [], { - stdout: true, - }) - expect(code).toBe(0) - expect(stdout).toMatch(/Compiled successfully/) - }) - - it(`should've 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( - `"@media (min-width:480px) and (max-width:767px){::-moz-placeholder{color:green}:-ms-input-placeholder{color:green}::placeholder{color:green}}.flex-parsing{flex:0 0 calc(50% - var(--vertical-gutter))}.transform-parsing{transform:translate3d(0,0)}.css-grid-shorthand{grid-column:span 2}.g-docs-sidenav .filter::-webkit-input-placeholder{opacity:80%}"` - ) - - // 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": "AAAA,+CACE,mBACE,WACF,CAFA,uBACE,WACF,CAFA,cACE,WACF,CACF,CAEA,cACE,2CACF,CAEA,mBACE,0BACF,CAEA,oBACE,kBACF,CAEA,mDACE,WACF", - "sourcesContent": Array [ - "@media (480px <= width < 768px) { - ::placeholder { - color: green; - } - } - - .flex-parsing { - flex: 0 0 calc(50% - var(--vertical-gutter)); - } - - .transform-parsing { - transform: translate3d(0px, 0px); - } - - .css-grid-shorthand { - grid-column: span 2; - } - - .g-docs-sidenav .filter::-webkit-input-placeholder { - opacity: 80%; - } - ", - ], - "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 compile successfully', async () => { - const { code, stdout } = await nextBuild(appDir, [], { - stdout: true, - }) - expect(code).toBe(0) - expect(stdout).toMatch(/Compiled successfully/) - }) - - it(`should've emitted a single CSS file`, async () => { - const cssFolder = join(appDir, '.next/static/css') - - const files = await readdir(cssFolder) - const cssFiles = files.filter((f) => /\.css$/.test(f)) - - expect(cssFiles.length).toBe(1) - const cssContent = await readFile(join(cssFolder, cssFiles[0]), 'utf8') - expect( - cssContent.replace(/\/\*.*?\*\//g, '').trim() - ).toMatchInlineSnapshot(`".blue-text{color:blue}.red-text{color:red}"`) - }) - }) - - describe('React Lifecyce Order (dev)', () => { - const appDir = join(fixturesDir, 'transition-react') - 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 the correct color on mount after navigation', async () => { - let browser - try { - browser = await webdriver(appPort, '/') - - // Navigate to other: - await browser.waitForElementByCss('#link-other').click() - const text = await browser.waitForElementByCss('#red-title').text() - expect(text).toMatchInlineSnapshot(`"rgb(255, 0, 0)"`) - } finally { - if (browser) { - await browser.close() - } - } - }) - }) - - describe('React Lifecyce Order (production)', () => { - const appDir = join(fixturesDir, 'transition-react') - beforeAll(async () => { - await remove(join(appDir, '.next')) - }) - - let appPort - let app - let code - let stdout - beforeAll(async () => { - ;({ code, stdout } = await nextBuild(appDir, [], { - stdout: true, - })) - appPort = await findPort() - app = await nextStart(appDir, appPort) - }) - afterAll(async () => { - await killApp(app) - }) - - it('should have compiled successfully', () => { - expect(code).toBe(0) - expect(stdout).toMatch(/Compiled successfully/) - }) - - it('should have the correct color on mount after navigation', async () => { - let browser - try { - browser = await webdriver(appPort, '/') - - // Navigate to other: - await browser.waitForElementByCss('#link-other').click() - const text = await browser.waitForElementByCss('#red-title').text() - expect(text).toMatchInlineSnapshot(`"rgb(255, 0, 0)"`) - } finally { - if (browser) { - await browser.close() - } - } - }) - }) - - 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 { code, stderr } = await nextBuild(appDir, [], { - stderr: true, - }) - expect(code).not.toBe(0) - expect(stderr).toContain('Failed to compile') - expect(stderr).toContain('styles.module.css') - 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 { code, stderr } = await nextBuild(appDir, [], { - stderr: true, - }) - expect(code).not.toBe(0) - expect(stderr).toContain('Failed to compile') - expect(stderr).toContain('styles/global.css') - expect(stderr).toMatch( - /Please move all first-party global CSS imports.*?pages(\/|\\)_app/ - ) - expect(stderr).toMatch(/Location:.*pages[\\/]index\.js/) - }) - }) - - describe('Valid Global CSS from npm', () => { - const appDir = join(fixturesDir, 'import-global-from-module') - - beforeAll(async () => { - await remove(join(appDir, '.next')) - }) - - it('should compile successfully', async () => { - const { code, stdout } = await nextBuild(appDir, [], { - stdout: true, - }) - expect(code).toBe(0) - expect(stdout).toMatch(/Compiled successfully/) - }) - - it(`should've emitted a single CSS file`, async () => { - const cssFolder = join(appDir, '.next/static/css') - - const files = await readdir(cssFolder) - const cssFiles = files.filter((f) => /\.css$/.test(f)) - - expect(cssFiles.length).toBe(1) - const cssContent = await readFile(join(cssFolder, cssFiles[0]), 'utf8') - expect( - cssContent.replace(/\/\*.*?\*\//g, '').trim() - ).toMatchInlineSnapshot(`".red-text{color:\\"red\\"}"`) - }) - }) - - describe('Invalid Global CSS with Custom App', () => { - const appDir = join(fixturesDir, 'invalid-global-with-app') - - beforeAll(async () => { - await remove(join(appDir, '.next')) - }) - - it('should fail to build', async () => { - const { code, stderr } = await nextBuild(appDir, [], { - stderr: true, - }) - expect(code).not.toBe(0) - expect(stderr).toContain('Failed to compile') - expect(stderr).toContain('styles/global.css') - expect(stderr).toMatch( - /Please move all first-party 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 { code, stderr } = await nextBuild(appDir, [], { - stderr: true, - }) - expect(code).not.toBe(0) - expect(stderr).toContain('Failed to compile') - expect(stderr).toContain('styles/global.css') - expect(stderr).toContain('Please move all first-party 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.css')) - try { - cssFile.replace('color: red', 'color: purple') - - await check( - () => - browser.eval( - `window.getComputedStyle(document.querySelector('.red-text')).color` - ), - '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 - let stdout - let code - beforeAll(async () => { - await remove(join(appDir, '.next')) - ;({ code, stdout } = await nextBuild(appDir, [], { - stdout: true, - })) - appPort = await findPort() - app = await nextStart(appDir, appPort) - }) - afterAll(async () => { - await killApp(app) - }) - - it('should have compiled successfully', () => { - expect(code).toBe(0) - expect(stdout).toMatch(/Compiled successfully/) - }) - - 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 compile successfully', async () => { - const { code, stdout } = await nextBuild(appDir, [], { - stdout: true, - }) - expect(code).toBe(0) - expect(stdout).toMatch(/Compiled successfully/) - }) - - 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-f0-9]{8}\.svg\) url\(\/_next\/static\/media\/dark2\.[a-f0-9]{8}\.svg\)\}\.blue-text\{color:orange;font-weight:bolder;background-image:url\(\/_next\/static\/media\/light\.[a-f0-9]{8}\.svg\);color:blue\}$/ - ) - - const mediaFiles = await readdir(mediaFolder) - expect(mediaFiles.length).toBe(3) - expect( - mediaFiles - .map((fileName) => - /^(.+?)\..{8}\.(.+?)$/.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 compile successfully', async () => { - const { code, stdout } = await nextBuild(appDir, [], { - stdout: true, - }) - expect(code).toBe(0) - expect(stdout).toMatch(/Compiled successfully/) - }) - - 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-f0-9]{8}\.svg\) url\(\/foo\/_next\/static\/media\/dark2\.[a-f0-9]{8}\.svg\)\}\.blue-text\{color:orange;font-weight:bolder;background-image:url\(\/foo\/_next\/static\/media\/light\.[a-f0-9]{8}\.svg\);color:blue\}$/ - ) - - const mediaFiles = await readdir(mediaFolder) - expect(mediaFiles.length).toBe(3) - expect( - mediaFiles - .map((fileName) => - /^(.+?)\..{8}\.(.+?)$/.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 compile successfully', async () => { - const { code, stdout } = await nextBuild(appDir, [], { - stdout: true, - }) - expect(code).toBe(0) - expect(stdout).toMatch(/Compiled successfully/) - }) - - 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-f0-9]{8}\.svg\) url\(\/foo\/_next\/static\/media\/dark2\.[a-f0-9]{8}\.svg\)\}\.blue-text\{color:orange;font-weight:bolder;background-image:url\(\/foo\/_next\/static\/media\/light\.[a-f0-9]{8}\.svg\);color:blue\}$/ - ) - - const mediaFiles = await readdir(mediaFolder) - expect(mediaFiles.length).toBe(3) - expect( - mediaFiles - .map((fileName) => - /^(.+?)\..{8}\.(.+?)$/.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 compile successfully', async () => { - const { code, stdout } = await nextBuild(appDir, [], { - stdout: true, - }) - expect(code).toBe(0) - expect(stdout).toMatch(/Compiled successfully/) - }) - - it(`should've emitted a single CSS file`, async () => { - const cssFolder = join(appDir, '.next/static/css') - - const files = await readdir(cssFolder) - const cssFiles = files.filter((f) => /\.css$/.test(f)) - - expect(cssFiles.length).toBe(1) - const cssContent = await readFile(join(cssFolder, cssFiles[0]), 'utf8') - expect(cssContent.replace(/\/\*.*?\*\//g, '').trim()).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 compile successfully', async () => { - const { code, stdout } = await nextBuild(appDir, [], { - stdout: true, - }) - expect(code).toBe(0) - expect(stdout).toMatch(/Compiled successfully/) - }) - - it(`should've emitted a single CSS file`, async () => { - const cssFolder = join(appDir, '.next/static/css') - - const files = await readdir(cssFolder) - const cssFiles = files.filter((f) => /\.css$/.test(f)) - - expect(cssFiles.length).toBe(1) - const cssContent = await readFile(join(cssFolder, cssFiles[0]), 'utf8') - expect( - cssContent.replace(/\/\*.*?\*\//g, '').trim() - ).toMatchInlineSnapshot(`".other{color:blue}.test{color:red}"`) - }) - }) - describe('CSS Import from node_modules', () => { const appDir = join(fixturesDir, 'npm-import-bad') diff --git a/test/integration/scss-fixtures/webpack-error/mock.js b/test/integration/scss-fixtures/webpack-error/mock.js index 2b56e6dc476c..2a4d406552f3 100644 --- a/test/integration/scss-fixtures/webpack-error/mock.js +++ b/test/integration/scss-fixtures/webpack-error/mock.js @@ -2,6 +2,6 @@ let originalLoader const M = require('module') originalLoader = M._load M._load = function hookedLoader(request, parent, isMain) { - if (request === 'node-sass') request = 'node-sass-begone' + if (request === 'sass') request = 'sass-begone' return originalLoader(request, parent, isMain) } diff --git a/test/integration/scss/test/group-1.test.js b/test/integration/scss/test/group-1.test.js new file mode 100644 index 000000000000..778ce80c882c --- /dev/null +++ b/test/integration/scss/test/group-1.test.js @@ -0,0 +1,617 @@ +/* eslint-env jest */ + +import cheerio from 'cheerio' +import 'flat-map-polyfill' +import { readdir, readFile, remove } from 'fs-extra' +import { + File, + findPort, + killApp, + launchApp, + nextBuild, + nextStart, + renderViaHTTP, + waitFor, +} from 'next-test-utils' +import webdriver from 'next-webdriver' +import { join } from 'path' +import { quote as shellQuote } from 'shell-quote' + +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, + }) + let cleanScssErrMsg = + '\n\n' + + './styles/global.scss\n' + + "To use Next.js' built-in Sass support, you first need to install `sass`.\n" + + 'Run `npm i sass` or `yarn add sass` inside your workspace.\n' + + '\n' + + 'Learn more: https://nextjs.org/docs/messages/install-sass\n' + + expect(code).toBe(1) + expect(stderr).toContain('Failed to compile.') + expect(stderr).toContain(cleanScssErrMsg) + expect(stderr).not.toContain('css-loader') + expect(stderr).not.toContain('sass-loader') + }) + }) + + describe('Basic Global Support', () => { + const appDir = join(fixturesDir, 'single-global') + + beforeAll(async () => { + await remove(join(appDir, '.next')) + }) + + it('should compile successfully', async () => { + const { code, stdout } = await nextBuild(appDir, [], { + stdout: true, + }) + expect(code).toBe(0) + expect(stdout).toMatch(/Compiled successfully/) + }) + + it(`should've emitted a single CSS file`, async () => { + const cssFolder = join(appDir, '.next/static/css') + + const files = await readdir(cssFolder) + const cssFiles = files.filter((f) => /\.css$/.test(f)) + + expect(cssFiles.length).toBe(1) + expect(await readFile(join(cssFolder, cssFiles[0]), 'utf8')).toContain( + 'color:red' + ) + }) + }) + + describe('Basic Module Include Paths Support', () => { + const appDir = join(fixturesDir, 'basic-module-include-paths') + + beforeAll(async () => { + await remove(join(appDir, '.next')) + }) + + it('should compile successfully', async () => { + const { code, stdout } = await nextBuild(appDir, [], { + stdout: true, + }) + expect(code).toBe(0) + expect(stdout).toMatch(/Compiled successfully/) + }) + + it(`should've emitted a single CSS file`, async () => { + const cssFolder = join(appDir, '.next/static/css') + + const files = await readdir(cssFolder) + const cssFiles = files.filter((f) => /\.css$/.test(f)) + + expect(cssFiles.length).toBe(1) + expect(await readFile(join(cssFolder, cssFiles[0]), 'utf8')).toContain( + 'color:red' + ) + }) + }) + + describe('Basic Module Prepend Data Support', () => { + const appDir = join(fixturesDir, 'basic-module-prepend-data') + + beforeAll(async () => { + await remove(join(appDir, '.next')) + }) + + it('should compile successfully', async () => { + const { code, stdout } = await nextBuild(appDir, [], { + stdout: true, + }) + expect(code).toBe(0) + expect(stdout).toMatch(/Compiled successfully/) + }) + + it(`should've emitted a single CSS file`, async () => { + const cssFolder = join(appDir, '.next/static/css') + + const files = await readdir(cssFolder) + const cssFiles = files.filter((f) => /\.css$/.test(f)) + + expect(cssFiles.length).toBe(1) + 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 compile successfully', async () => { + const { code, stdout } = await nextBuild(appDir, [], { + stdout: true, + }) + expect(code).toBe(0) + expect(stdout).toMatch(/Compiled successfully/) + }) + + it(`should've emitted a single CSS file`, async () => { + const cssFolder = join(appDir, '.next/static/css') + + const files = await readdir(cssFolder) + const cssFiles = files.filter((f) => /\.css$/.test(f)) + + expect(cssFiles.length).toBe(1) + 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 compile successfully', async () => { + const { code, stdout } = await nextBuild(appDir, [], { + stdout: true, + }) + expect(code).toBe(0) + expect(stdout).toMatch(/Compiled successfully/) + }) + + it(`should've emitted a single CSS file`, async () => { + const cssFolder = join(appDir, '.next/static/css') + + const files = await readdir(cssFolder) + const cssFiles = files.filter((f) => /\.css$/.test(f)) + + expect(cssFiles.length).toBe(1) + const cssContent = await readFile(join(cssFolder, cssFiles[0]), 'utf8') + expect( + cssContent.replace(/\/\*.*?\*\//g, '').trim() + ).toMatchInlineSnapshot(`".red-text{color:red}.blue-text{color:blue}"`) + }) + }) + + describe('Nested @import() Global Support', () => { + const appDir = join(fixturesDir, 'nested-global') + + beforeAll(async () => { + await remove(join(appDir, '.next')) + }) + + it('should compile successfully', async () => { + const { code, stdout } = await nextBuild(appDir, [], { + stdout: true, + }) + expect(code).toBe(0) + expect(stdout).toMatch(/Compiled successfully/) + }) + + it(`should've emitted a single CSS file`, async () => { + const cssFolder = join(appDir, '.next/static/css') + + const files = await readdir(cssFolder) + const cssFiles = files.filter((f) => /\.css$/.test(f)) + + expect(cssFiles.length).toBe(1) + const cssContent = await readFile(join(cssFolder, cssFiles[0]), 'utf8') + expect( + cssContent.replace(/\/\*.*?\*\//g, '').trim() + ).toMatchInlineSnapshot( + `".red-text{color:purple;font-weight:bolder;color:red}.blue-text{color:orange;font-weight:bolder;color:blue}"` + ) + }) + }) + + describe('CSS Compilation and Prefixing', () => { + const appDir = join(fixturesDir, 'compilation-and-prefixing') + + beforeAll(async () => { + await remove(join(appDir, '.next')) + }) + + it('should compile successfully', async () => { + const { code, stdout } = await nextBuild(appDir, [], { + stdout: true, + }) + expect(code).toBe(0) + expect(stdout).toMatch(/Compiled successfully/) + }) + + it(`should've 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 ::-moz-placeholder{color:red}.redText :-ms-input-placeholder{color:red}.redText ::placeholder{color:red}.flex-parsing{flex:0 0 calc(50% - var(--vertical-gutter))}"` + ) + + // 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": "AAEE,4BACE,SAHE,CAEJ,gCACE,SAHE,CAEJ,uBACE,SAHE,CAON,cACE,2CAAA", + "sourcesContent": Array [ + "$var: red; + .redText { + ::placeholder { + color: $var; + } + } + + .flex-parsing { + flex: 0 0 calc(50% - var(--vertical-gutter)); + } + ", + ], + "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 compile successfully', async () => { + const { code, stdout } = await nextBuild(appDir, [], { + stdout: true, + }) + expect(code).toBe(0) + expect(stdout).toMatch(/Compiled successfully/) + }) + + it(`should've emitted a single CSS file`, async () => { + const cssFolder = join(appDir, '.next/static/css') + + const files = await readdir(cssFolder) + const cssFiles = files.filter((f) => /\.css$/.test(f)) + + expect(cssFiles.length).toBe(1) + const cssContent = await readFile(join(cssFolder, cssFiles[0]), 'utf8') + expect( + cssContent.replace(/\/\*.*?\*\//g, '').trim() + ).toMatchInlineSnapshot(`".blue-text{color:blue}.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 { code, stderr } = await nextBuild(appDir, [], { + stderr: true, + }) + expect(code).not.toBe(0) + 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 { code, stderr } = await nextBuild(appDir, [], { + stderr: true, + }) + expect(code).not.toBe(0) + expect(stderr).toContain('Failed to compile') + expect(stderr).toContain('styles/global.scss') + expect(stderr).toMatch( + /Please move all first-party 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 { code, stderr } = await nextBuild(appDir, [], { + stderr: true, + }) + expect(code).not.toBe(0) + expect(stderr).toContain('Failed to compile') + expect(stderr).toContain('styles/global.scss') + expect(stderr).toMatch( + /Please move all first-party 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 { code, stderr } = await nextBuild(appDir, [], { + stderr: true, + }) + expect(code).not.toBe(0) + expect(stderr).toContain('Failed to compile') + expect(stderr).toContain('styles/global.scss') + expect(stderr).toContain('Please move all first-party 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 + let stdout + let code + beforeAll(async () => { + await remove(join(appDir, '.next')) + ;({ code, stdout } = await nextBuild(appDir, [], { + stdout: true, + })) + appPort = await findPort() + app = await nextStart(appDir, appPort) + }) + afterAll(async () => { + await killApp(app) + }) + + it('should have compiled successfully', () => { + expect(code).toBe(0) + expect(stdout).toMatch(/Compiled successfully/) + }) + + 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]) + }) + }) +}) diff --git a/test/integration/scss/test/group-2.test.js b/test/integration/scss/test/group-2.test.js new file mode 100644 index 000000000000..7aa845efe698 --- /dev/null +++ b/test/integration/scss/test/group-2.test.js @@ -0,0 +1,489 @@ +/* eslint-env jest */ + +import 'flat-map-polyfill' +import { readdir, readFile, remove } from 'fs-extra' +import { + findPort, + killApp, + launchApp, + nextBuild, + nextStart, +} from 'next-test-utils' +import webdriver from 'next-webdriver' +import { join } from 'path' + +const fixturesDir = join(__dirname, '../..', 'scss-fixtures') + +describe('SCSS Support', () => { + describe('CSS URL via `file-loader`', () => { + const appDir = join(fixturesDir, 'url-global') + + beforeAll(async () => { + await remove(join(appDir, '.next')) + }) + + it('should compile successfully', async () => { + const { code, stdout } = await nextBuild(appDir, [], { + stdout: true, + }) + expect(code).toBe(0) + expect(stdout).toMatch(/Compiled successfully/) + }) + + it(`should've emitted 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-f0-9]{8}\.svg\) url\(\/_next\/static\/media\/dark2\.[a-f0-9]{8}\.svg\)\}\.blue-text\{color:orange;font-weight:bolder;background-image:url\(\/_next\/static\/media\/light\.[a-f0-9]{8}\.svg\);color:blue\}$/ + ) + + const mediaFiles = await readdir(mediaFolder) + expect(mediaFiles.length).toBe(3) + expect( + mediaFiles + .map((fileName) => + /^(.+?)\..{8}\.(.+?)$/.exec(fileName).slice(1).join('.') + ) + .sort() + ).toMatchInlineSnapshot(` + Array [ + "dark.svg", + "dark2.svg", + "light.svg", + ] + `) + }) + }) + + describe('CSS URL via file-loader sass partial', () => { + const appDir = join(fixturesDir, 'url-global-partial') + + beforeAll(async () => { + await remove(join(appDir, '.next')) + }) + + it('should compile successfully', async () => { + const { code, stdout } = await nextBuild(appDir, [], { + stdout: true, + }) + expect(code).toBe(0) + expect(stdout).toMatch(/Compiled successfully/) + }) + + it(`should've emitted 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() + ).toMatchInlineSnapshot( + '".red-text{color:red;background-image:url(/_next/static/media/darka.6b01655b.svg),url(/_next/static/media/darkb.6b01655b.svg)}.blue-text{color:orange;font-weight:bolder;background-image:url(/_next/static/media/light.2da1d3d6.svg);color:blue}"' + ) + + const mediaFiles = await readdir(mediaFolder) + expect(mediaFiles.length).toBe(3) + expect( + mediaFiles + .map((fileName) => + /^(.+?)\..{8}\.(.+?)$/.exec(fileName).slice(1).join('.') + ) + .sort() + ).toMatchInlineSnapshot(` + Array [ + "darka.svg", + "darkb.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 compile successfully', async () => { + const { code, stdout } = await nextBuild(appDir, [], { + stdout: true, + }) + expect(code).toBe(0) + expect(stdout).toMatch(/Compiled successfully/) + }) + + 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-f0-9]{8}\.svg\) url\(\/foo\/_next\/static\/media\/dark2\.[a-f0-9]{8}\.svg\)\}\.blue-text\{color:orange;font-weight:bolder;background-image:url\(\/foo\/_next\/static\/media\/light\.[a-f0-9]{8}\.svg\);color:blue\}$/ + ) + + const mediaFiles = await readdir(mediaFolder) + expect(mediaFiles.length).toBe(3) + expect( + mediaFiles + .map((fileName) => + /^(.+?)\..{8}\.(.+?)$/.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 compile successfully', async () => { + const { code, stdout } = await nextBuild(appDir, [], { + stdout: true, + }) + expect(code).toBe(0) + expect(stdout).toMatch(/Compiled successfully/) + }) + + 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-f0-9]{8}\.svg\) url\(\/foo\/_next\/static\/media\/dark2\.[a-f0-9]{8}\.svg\)\}\.blue-text\{color:orange;font-weight:bolder;background-image:url\(\/foo\/_next\/static\/media\/light\.[a-f0-9]{8}\.svg\);color:blue\}$/ + ) + + const mediaFiles = await readdir(mediaFolder) + expect(mediaFiles.length).toBe(3) + expect( + mediaFiles + .map((fileName) => + /^(.+?)\..{8}\.(.+?)$/.exec(fileName).slice(1).join('.') + ) + .sort() + ).toMatchInlineSnapshot(` + Array [ + "dark.svg", + "dark2.svg", + "light.svg", + ] + `) + }) + }) + + describe('Data Urls', () => { + const appDir = join(fixturesDir, 'data-url') + + beforeAll(async () => { + await remove(join(appDir, '.next')) + }) + + it('should compile successfully', async () => { + const { code, stdout } = await nextBuild(appDir, [], { + stdout: true, + }) + expect(code).toBe(0) + expect(stdout).toMatch(/Compiled successfully/) + }) + + it(`should've emitted expected files`, 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( + /^\.red-text\{color:red;background-image:url\("data:[^"]+"\)\}$/ + ) + }) + }) + + describe('External imports', () => { + const appDir = join(fixturesDir, 'external-url') + + beforeAll(async () => { + await remove(join(appDir, '.next')) + }) + + it('should compile successfully', async () => { + const { code, stdout } = await nextBuild(appDir, [], { + stdout: true, + }) + expect(code).toBe(0) + expect(stdout).toMatch(/Compiled successfully/) + }) + + it(`should've emitted expected files`, 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( + /^@import"https:\/\/fonts\.googleapis\.com\/css2\?family=Poppins:wght@400&display=swap";\.red-text\{color:red;font-family:Poppins;font-style:normal;font-weight:400\}$/ + ) + }) + }) + + describe('Good CSS Import from node_modules', () => { + const appDir = join(fixturesDir, 'npm-import') + + beforeAll(async () => { + await remove(join(appDir, '.next')) + }) + + it('should compile successfully', async () => { + const { code, stdout } = await nextBuild(appDir, [], { + stdout: true, + }) + expect(code).toBe(0) + expect(stdout).toMatch(/Compiled successfully/) + }) + + it(`should've emitted a single CSS file`, async () => { + const cssFolder = join(appDir, '.next/static/css') + + const files = await readdir(cssFolder) + const cssFiles = files.filter((f) => /\.css$/.test(f)) + + expect(cssFiles.length).toBe(1) + const cssContent = await readFile(join(cssFolder, cssFiles[0]), 'utf8') + expect(cssContent.replace(/\/\*.*?\*\//g, '').trim()).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 compile successfully', async () => { + const { code, stdout } = await nextBuild(appDir, [], { + stdout: true, + }) + expect(code).toBe(0) + expect(stdout).toMatch(/Compiled successfully/) + }) + + it(`should've emitted a single CSS file`, async () => { + const cssFolder = join(appDir, '.next/static/css') + + const files = await readdir(cssFolder) + const cssFiles = files.filter((f) => /\.css$/.test(f)) + + expect(cssFiles.length).toBe(1) + const cssContent = await readFile(join(cssFolder, cssFiles[0]), 'utf8') + expect( + cssContent.replace(/\/\*.*?\*\//g, '').trim() + ).toMatchInlineSnapshot(`".other{color:blue}.test{color:red}"`) + }) + }) + + describe('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 { code, stderr } = await nextBuild(appDir, [], { stderr: true }) + + expect(code).toBe(0) + expect(stderr).not.toMatch(/Can't resolve '[^']*?nprogress[^']*?'/) + expect(stderr).not.toMatch(/Build error occurred/) + }) + }) + + describe('Preprocessor loader order', () => { + const appDir = join(fixturesDir, 'loader-order') + + beforeAll(async () => { + await remove(join(appDir, '.next')) + }) + + it('should compile successfully', async () => { + const { stdout } = await nextBuild(appDir, [], { + stdout: true, + }) + expect(stdout).toMatch(/Compiled successfully/) + }) + }) + + 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 + let stdout + let code + beforeAll(async () => { + await remove(join(appDir, '.next')) + ;({ code, stdout } = await nextBuild(appDir, [], { + stdout: true, + })) + appPort = await findPort() + app = await nextStart(appDir, appPort) + }) + afterAll(async () => { + await killApp(app) + }) + + it('should have compiled successfully', () => { + expect(code).toBe(0) + expect(stdout).toMatch(/Compiled successfully/) + }) + + 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 compile successfully', async () => { + const { code, stdout } = await nextBuild(appDir, [], { + stdout: true, + }) + expect(code).toBe(0) + expect(stdout).toMatch(/Compiled successfully/) + }) + + 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 compile successfully', async () => { + const { code, stdout } = await nextBuild(appDir, [], { + stdout: true, + }) + expect(code).toBe(0) + expect(stdout).toMatch(/Compiled successfully/) + }) + + 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) + }) + }) +})