diff --git a/.vscode/settings.json b/.vscode/settings.json index 06425d362dd17..845c6e3d7dd9d 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -58,16 +58,16 @@ "RUST_BACKTRACE": "0" }, "cSpell.words": [ - "codemod", - "codemods", - "Destructuring", "buildtime", "callsites", "codemod", + "codemods", "datastream", "deduped", + "Destructuring", "draftmode", "Entrypoints", + "jiwon", "jscodeshift", "napi", "navigations", diff --git a/Cargo.lock b/Cargo.lock index 287f07ea90ded..1afdabb1b880f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4025,6 +4025,7 @@ dependencies = [ "regex", "remove_console", "rustc-hash 1.1.0", + "semver 1.0.23", "serde", "serde_json", "swc_core", @@ -4048,6 +4049,7 @@ dependencies = [ "turbopack-image", "turbopack-node", "turbopack-nodejs", + "turbopack-resolve", "turbopack-static", "turbopack-trace-server", "turbopack-trace-utils", diff --git a/crates/next-core/Cargo.toml b/crates/next-core/Cargo.toml index f5bce8214cf26..137cf71196aca 100644 --- a/crates/next-core/Cargo.toml +++ b/crates/next-core/Cargo.toml @@ -33,6 +33,7 @@ tracing = { workspace = true } rustc-hash = { workspace = true } react_remove_properties = "0.24.25" remove_console = "0.25.25" +semver = { workspace = true } auto-hash-map = { workspace = true } @@ -76,6 +77,7 @@ turbopack-nodejs = { workspace = true } turbopack-static = { workspace = true } turbopack-trace-server = { workspace = true } turbopack-trace-utils = { workspace = true } +turbopack-resolve = { workspace = true } [build-dependencies] turbo-tasks-build = { workspace = true } diff --git a/crates/next-core/src/next_shared/webpack_rules/mod.rs b/crates/next-core/src/next_shared/webpack_rules/mod.rs index eda92a6e58a52..72825edc1dc37 100644 --- a/crates/next-core/src/next_shared/webpack_rules/mod.rs +++ b/crates/next-core/src/next_shared/webpack_rules/mod.rs @@ -17,7 +17,8 @@ pub async fn webpack_loader_options( conditions: Vec, ) -> Result>> { let rules = *next_config.webpack_rules(conditions).await?; - let rules = *maybe_add_sass_loader(next_config.sass_config(), rules.map(|v| *v)).await?; + let rules = + *maybe_add_sass_loader(next_config.sass_config(), rules.map(|v| *v), *project_path).await?; let rules = if foreign { rules } else { diff --git a/crates/next-core/src/next_shared/webpack_rules/sass.rs b/crates/next-core/src/next_shared/webpack_rules/sass.rs index 296142d0f49ba..b21791cc21a86 100644 --- a/crates/next-core/src/next_shared/webpack_rules/sass.rs +++ b/crates/next-core/src/next_shared/webpack_rules/sass.rs @@ -1,24 +1,76 @@ -use std::mem::take; +use std::{io::Read, mem::take}; use anyhow::{bail, Result}; +use semver::Version; use serde_json::Value as JsonValue; use turbo_tasks::{ResolvedVc, Vc}; +use turbo_tasks_fs::{FileContent, FileSystemPath}; use turbopack::module_options::{LoaderRuleItem, OptionWebpackRules, WebpackRules}; +use turbopack_core::{ + asset::{Asset, AssetContent}, + resolve::{pattern::Pattern, resolve_raw}, +}; use turbopack_node::transforms::webpack::WebpackLoaderItem; #[turbo_tasks::function] pub async fn maybe_add_sass_loader( sass_options: Vc, webpack_rules: Option>, + package_dir: ResolvedVc, ) -> Result> { let sass_options = sass_options.await?; let Some(mut sass_options) = sass_options.as_object().cloned() else { bail!("sass_options must be an object"); }; - // TODO: Remove this once we upgrade to sass-loader 16 + + let sass_package_json_path = resolve_raw( + *package_dir, + Pattern::Constant("sass/package.json".into()).cell(), + false, + ) + .first_source() + .await?; + + let sass_version = match &*sass_package_json_path { + Some(sass_package_json) => { + let sass_package_json_content = sass_package_json.content().await?; + match &*sass_package_json_content { + AssetContent::File(file_content) => match &*file_content.await? { + FileContent::Content(file) => { + let mut reader = file.read(); + let mut buf = Vec::new(); + reader.read_to_end(&mut buf)?; + let json_value = serde_json::from_slice::(&buf)?; + json_value + .get("version") + .unwrap() + .as_str() + .map(Version::parse) + } + _ => None, + }, + _ => None, + } + } + None => None, + } + .transpose()? + .unwrap_or_else(|| Version::new(1, 45, 0)); + + // The modern Sass API with breaking changes was added in sass@1.45.0. + // https://sass-lang.com/documentation/breaking-changes/legacy-js-api + // Since sass-loader and our peer dependency sass version is ^1.3.0, + // we need to use the legacy Sass API for versions less than 1.45.0. + let should_use_legacy_sass_api = sass_version < Version::parse("1.45.0")?; + + // TODO(jiwon): Once the peer dependency for sass is upgraded to >= 1.45.0, we can remove this. sass_options.insert( - "silenceDeprecations".into(), - serde_json::json!(["legacy-js-api"]), + "api".into(), + serde_json::json!(if should_use_legacy_sass_api { + "legacy" + } else { + "modern" + }), ); let mut rules = if let Some(webpack_rules) = webpack_rules { webpack_rules.await?.clone_value() diff --git a/packages/next/package.json b/packages/next/package.json index c665357155ee5..22d41fc39639a 100644 --- a/packages/next/package.json +++ b/packages/next/package.json @@ -293,7 +293,7 @@ "raw-body": "2.4.1", "react-refresh": "0.12.0", "regenerator-runtime": "0.13.4", - "sass-loader": "15.0.0", + "sass-loader": "16.0.0", "schema-utils2": "npm:schema-utils@2.7.1", "schema-utils3": "npm:schema-utils@3.0.0", "semver": "7.3.2", diff --git a/packages/next/src/build/webpack/config/blocks/css/index.ts b/packages/next/src/build/webpack/config/blocks/css/index.ts index 99d3f0117e8d2..c25a3a68f29f8 100644 --- a/packages/next/src/build/webpack/config/blocks/css/index.ts +++ b/packages/next/src/build/webpack/config/blocks/css/index.ts @@ -1,5 +1,6 @@ import curry from 'next/dist/compiled/lodash.curry' import type { webpack } from 'next/dist/compiled/webpack/webpack' +import { lt as semverLessThan } from 'next/dist/compiled/semver' import { loader, plugin } from '../../helpers' import { pipe } from '../../utils' import type { ConfigurationContext, ConfigurationFn } from '../../utils' @@ -162,6 +163,19 @@ export const css = curry(async function css( ctx.experimental.useLightningcss ) + // Since sass is an optional peer dependency, it may be missing. + // enable modern API by default + let sassVersion = '1.45.0' + try { + sassVersion = require(require.resolve('sass/package.json')).version + } catch {} + + // The modern Sass API with breaking changes was added in sass@1.45.0. + // https://sass-lang.com/documentation/breaking-changes/legacy-js-api + // Since sass-loader and our peer dependency sass version is ^1.3.0, + // we need to use the legacy Sass API for versions less than 1.45.0. + const shouldUseLegacySassAPI = semverLessThan(sassVersion, '1.45.0') + const sassPreprocessors: webpack.RuleSetUseItem[] = [ // First, process files with `sass-loader`: this inlines content, and // compiles away the proprietary syntax. @@ -181,8 +195,8 @@ export const css = curry(async function css( // Since it's optional and not required, we'll disable it by default // to avoid the confusion. fibers: false, - // TODO: Remove this once we upgrade to sass-loader 16 - silenceDeprecations: ['legacy-js-api'], + // TODO(jiwon): Once the peer dependency for sass is upgraded to >= 1.45.0, we can remove this. + api: shouldUseLegacySassAPI ? 'legacy' : 'modern', ...sassOptions, }, additionalData: sassPrependData || sassAdditionalData, diff --git a/packages/next/src/compiled/sass-loader/cjs.js b/packages/next/src/compiled/sass-loader/cjs.js index bb8534eecba35..bbbc5c01d76f7 100644 --- a/packages/next/src/compiled/sass-loader/cjs.js +++ b/packages/next/src/compiled/sass-loader/cjs.js @@ -1 +1 @@ -(function(){"use strict";var __webpack_modules__={966:function(e,t,s){const n=s(248);e.exports=n.default},248:function(e,t,s){Object.defineProperty(t,"__esModule",{value:true});t["default"]=void 0;var n=_interopRequireDefault(s(310));var o=_interopRequireDefault(s(17));var r=_interopRequireDefault(s(713));var a=s(816);function _interopRequireDefault(e){return e&&e.__esModule?e:{default:e}}async function loader(e){const t=this.getOptions(r.default);const s=this.async();let i;try{i=(0,a.getSassImplementation)(this,t.implementation)}catch(e){s(e);return}const c=typeof t.sourceMap==="boolean"?t.sourceMap:this.sourceMap;const l=await(0,a.getSassOptions)(this,t,e,i,c);const p=typeof t.webpackImporter==="boolean"?t.webpackImporter:true;if(p){const e=t.api==="modern"||t.api==="modern-compiler";if(!e){const{includePaths:e}=l;l.importer.push((0,a.getWebpackImporter)(this,i,e))}else{l.importers.push((0,a.getModernWebpackImporter)(this,i,[]))}}let u;try{u=(0,a.getCompileFn)(this,i,t)}catch(e){s(e);return}let d;try{d=await u(l)}catch(e){if(e.span&&typeof e.span.url!=="undefined"){this.addDependency(n.default.fileURLToPath(e.span.url))}else if(typeof e.file!=="undefined"){this.addDependency(o.default.normalize(e.file))}s((0,a.errorFactory)(e));return}let f=d.sourceMap?d.sourceMap:d.map?JSON.parse(d.map):null;if(f&&c){f=(0,a.normalizeSourceMap)(f,this.rootContext)}if(typeof d.loadedUrls!=="undefined"){d.loadedUrls.filter((e=>e.protocol==="file:")).forEach((e=>{const t=n.default.fileURLToPath(e);if(o.default.isAbsolute(t)){this.addDependency(t)}}))}else if(typeof d.stats!=="undefined"&&typeof d.stats.includedFiles!=="undefined"){d.stats.includedFiles.forEach((e=>{const t=o.default.normalize(e);if(o.default.isAbsolute(t)){this.addDependency(t)}}))}s(null,d.css.toString(),f)}var i=t["default"]=loader},816:function(__unused_webpack_module,exports,__nccwpck_require__){Object.defineProperty(exports,"__esModule",{value:true});exports.errorFactory=errorFactory;exports.getCompileFn=getCompileFn;exports.getModernWebpackImporter=getModernWebpackImporter;exports.getSassImplementation=getSassImplementation;exports.getSassOptions=getSassOptions;exports.getWebpackImporter=getWebpackImporter;exports.getWebpackResolver=getWebpackResolver;exports.normalizeSourceMap=normalizeSourceMap;var _url=_interopRequireDefault(__nccwpck_require__(310));var _path=_interopRequireDefault(__nccwpck_require__(17));function _interopRequireDefault(e){return e&&e.__esModule?e:{default:e}}function getDefaultSassImplementation(){let sassImplPkg="sass";try{require.resolve("sass-embedded");sassImplPkg="sass-embedded"}catch(ignoreError){try{eval("require").resolve("sass")}catch(_ignoreError){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){s=getDefaultSassImplementation()}if(typeof s==="string"){s=require(s)}const{info:n}=s;if(!n){throw new Error("Unknown Sass implementation.")}const o=n.split("\t");if(o.length<2){throw new Error(`Unknown Sass implementation "${n}".`)}const[r]=o;if(r==="dart-sass"){return s}else if(r==="node-sass"){return s}else if(r==="sass-embedded"){return s}throw new Error(`Unknown Sass implementation "${r}".`)}function isProductionLikeMode(e){return e.mode==="production"||!e.mode}function proxyCustomImporters(e,t){return[].concat(e).map((e=>function proxyImporter(...s){const n={...this,webpackLoaderContext:t};return e.apply(n,s)}))}async function getSassOptions(e,t,s,n,o){const r=t.sassOptions?typeof t.sassOptions==="function"?t.sassOptions(e)||{}:t.sassOptions:{};const a={...r,data:t.additionalData?typeof t.additionalData==="function"?await t.additionalData(s,e):`${t.additionalData}\n${s}`:s};if(!a.logger){const s=t.warnRuleAsWarning!==false;const n=e.getLogger("sass-loader");const formatSpan=e=>`Warning on line ${e.start.line}, column ${e.start.column} of ${e.url||"-"}:${e.start.line}:${e.start.column}:\n`;const formatDebugSpan=e=>`[debug:${e.start.line}:${e.start.column}] `;a.logger={debug(e,t){let s="";if(t.span){s=formatDebugSpan(t.span)}s+=e;n.debug(s)},warn(t,o){let r="";if(o.deprecation){r+="Deprecation "}if(o.span){r+=formatSpan(o.span)}r+=t;if(o.span&&o.span.context){r+=`\n\n${o.span.start.line} | ${o.span.context}`}if(o.stack&&o.stack!=="null"){r+=`\n\n${o.stack}`}if(s){const t=new Error(r);t.name="SassWarning";t.stack=null;e.emitWarning(t)}else{n.warn(r)}}}}const i=t.api==="modern"||t.api==="modern-compiler";const{resourcePath:c}=e;if(i){a.url=_url.default.pathToFileURL(c);if(!a.style&&isProductionLikeMode(e)){a.style="compressed"}if(o){a.sourceMap=true}if(typeof a.syntax==="undefined"){const e=_path.default.extname(c);if(e&&e.toLowerCase()===".scss"){a.syntax="scss"}else if(e&&e.toLowerCase()===".sass"){a.syntax="indented"}else if(e&&e.toLowerCase()===".css"){a.syntax="css"}}a.loadPaths=[].concat((a.loadPaths?a.loadPaths.slice():[]).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"?";":":"):[]);a.importers=a.importers?Array.isArray(a.importers)?a.importers.slice():[a.importers]:[]}else{a.file=c;if(!a.outputStyle&&isProductionLikeMode(e)){a.outputStyle="compressed"}if(o){a.sourceMap=true;a.outFile=_path.default.join(e.rootContext,"style.css.map");a.sourceMapContents=true;a.omitSourceMapUrl=true;a.sourceMapEmbed=false}const s=_path.default.extname(c);if(s&&s.toLowerCase()===".sass"&&typeof a.indentedSyntax==="undefined"){a.indentedSyntax=true}else{a.indentedSyntax=Boolean(a.indentedSyntax)}a.importer=a.importer?proxyCustomImporters(Array.isArray(a.importer)?a.importer.slice():[a.importer],e):[];if(t.webpackImporter===false&&a.importer.length===0){a.importer=undefined}a.includePaths=[].concat(process.cwd()).concat((a.includePaths?a.includePaths.slice():[]).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 a.charset==="undefined"){a.charset=true}}return a}const MODULE_REQUEST_REGEX=/^[^?]*~/;const IS_MODULE_IMPORT=/^~([^/]+|[^/]+\/|@[^/]+[/][^/]+|@[^/]+\/?|@[^/]+[/][^/]+\/)$/;const IS_PKG_SCHEME=/^pkg:/i;function getPossibleRequests(e,t=false,s=false){let n=e;if(t){if(MODULE_REQUEST_REGEX.test(e)){n=n.replace(MODULE_REQUEST_REGEX,"")}if(IS_PKG_SCHEME.test(e)){n=`${n.slice(4)}`;return[...new Set([n,e])]}if(IS_MODULE_IMPORT.test(e)||IS_PKG_SCHEME.test(e)){n=n[n.length-1]==="/"?n:`${n}/`;return[...new Set([n,e])]}}const o=_path.default.extname(n).toLowerCase();if(o===".css"){return[]}const r=_path.default.dirname(n);const a=r==="."?"":`${r}/`;const i=_path.default.basename(n);const c=_path.default.basename(n,o);return[...new Set([].concat(s?[`${a}_${c}.import${o}`,`${a}${c}.import${o}`]:[]).concat([`${a}_${i}`,`${a}${i}`]).concat(t?[e]:[]))]}function promiseResolve(e){return(t,s)=>new Promise(((n,o)=>{e(t,s,((e,t)=>{if(e){o(e)}else{n(t)}}))}))}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:n}]=e;try{return await s(n,t[0])}catch(s){const[,...n]=t;if(n.length===0){const[,...t]=e;return startResolving(t)}e[0].possibleRequests=n;return startResolving(e)}}const IS_SPECIAL_MODULE_IMPORT=/^~[^/]+$/;const IS_NATIVE_WIN32_PATH=/^[a-z]:[/\\]|^\\\\/i;function getWebpackResolver(e,t,s=[]){const n=t&&(t.info.includes("dart-sass")||t.info.includes("sass-embedded"));const o=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 r=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(!n&&!_path.default.isAbsolute(e)){return Promise.reject()}const l=t;const p=l.slice(0,5).toLowerCase()==="file:";if(p){try{t=_url.default.fileURLToPath(l)}catch(e){t=t.slice(7)}}let u=[];const d=!IS_SPECIAL_MODULE_IMPORT.test(t)&&!IS_PKG_SCHEME.test(t)&&!p&&!l.startsWith("/")&&!IS_NATIVE_WIN32_PATH.test(l);if(s.length>0&&d){const a=getPossibleRequests(t,false,c);if(!n){u=u.concat({resolve:c?r:o,context:_path.default.dirname(e),possibleRequests:a})}u=u.concat(s.map((e=>({resolve:c?r:o,context:e,possibleRequests:a}))))}const f=getPossibleRequests(t,true,c);u=u.concat({resolve:c?i:a,context:_path.default.dirname(e),possibleRequests:f});return startResolving(u)}}const MATCH_CSS=/\.css$/i;function getModernWebpackImporter(e,t,s){const n=getWebpackResolver(e.getResolve,t,s);return{async canonicalize(t,s){const{fromImport:o}=s;const r=s.containingUrl?_url.default.fileURLToPath(s.containingUrl.toString()):e.resourcePath;let a;try{a=await n(r,t,o)}catch(e){return null}e.addDependency(_path.default.normalize(a));return _url.default.pathToFileURL(a)},async load(t){const s=_path.default.extname(t.pathname);let n;if(s&&s.toLowerCase()===".scss"){n="scss"}else if(s&&s.toLowerCase()===".sass"){n="indented"}else if(s&&s.toLowerCase()===".css"){n="css"}else{n="scss"}try{const s=await new Promise(((s,n)=>{const o=_url.default.fileURLToPath(t);e.fs.readFile(o,"utf8",((e,t)=>{if(e){n(e);return}s(t)}))}));return{contents:s,syntax:n}}catch(e){return null}}}}function getWebpackImporter(e,t,s){const n=getWebpackResolver(e.getResolve,t,s);return function importer(t,s,o){const{fromImport:r}=this;n(s,t,r).then((t=>{e.addDependency(_path.default.normalize(t));o({file:t.replace(MATCH_CSS,"")})})).catch((()=>{o({file:t})}))}}let nodeSassJobQueue=null;const sassModernCompilers=new WeakMap;function getCompileFn(e,t,s){const n=t.info.includes("dart-sass")||t.info.includes("sass-embedded");if(n){if(s.api==="modern"){return e=>{const{data:s,...n}=e;return t.compileStringAsync(s,n)}}if(s.api==="modern-compiler"){return async s=>{const n=e._compiler;const{data:o,...r}=s;if(n){if(!sassModernCompilers.has(n)){const e=await t.initAsyncCompiler();if(!sassModernCompilers.has(n)){sassModernCompilers.set(n,e);n.hooks.shutdown.tap("sass-loader",(()=>{e.dispose()}))}}return sassModernCompilers.get(n).compileStringAsync(o,r)}return t.compileStringAsync(o,r)}}return e=>new Promise(((s,n)=>{t.render(e,((e,t)=>{if(e){n(e);return}s(t)}))}))}if(s.api==="modern"||s.api==="modern-compiler"){throw new Error("Modern API is not supported for 'node-sass'")}if(nodeSassJobQueue===null){const e=Number(process.env.UV_THREADPOOL_SIZE||4);const s=__nccwpck_require__(175);nodeSassJobQueue=s.queue(t.render.bind(t),e-1)}return e=>new Promise(((t,s)=>{nodeSassJobQueue.push.bind(nodeSassJobQueue)(e,((e,n)=>{if(e){s(e);return}t(n)}))}))}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;if(typeof s.file!=="undefined"){delete s.file}s.sourceRoot="";s.sources=s.sources.map((e=>{const s=getURLType(e);if(s==="absolute"&&/^file:/i.test(e)){return _url.default.fileURLToPath(e)}else if(s==="path-relative"){return _path.default.resolve(t,_path.default.normalize(e))}return e}));return s}function errorFactory(e){let t;if(e.formatted){t=e.formatted.replace(/^Error: /,"")}else{({message:t}=e)}const s=new Error(t,{cause:e});s.stack=null;return s}},175:function(e){e.exports=require("next/dist/compiled/neo-async")},17:function(e){e.exports=require("path")},438:function(e){e.exports=require("sass")},310:function(e){e.exports=require("url")},713:function(e){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"}]},"api":{"description":"Switch between old and modern API for `sass` (`Dart Sass`) and `Sass Embedded` implementations.","link":"https://github.com/webpack-contrib/sass-loader#sassoptions","enum":["legacy","modern","modern-compiler"]},"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 n=true;try{__webpack_modules__[e](s,s.exports,__nccwpck_require__);n=false}finally{if(n)delete __webpack_module_cache__[e]}return s.exports}if(typeof __nccwpck_require__!=="undefined")__nccwpck_require__.ab=__dirname+"/";var __webpack_exports__=__nccwpck_require__(966);module.exports=__webpack_exports__})(); \ No newline at end of file +(function(){"use strict";var __webpack_modules__={555:function(e,t,s){const n=s(769);e.exports=n.default},769:function(e,t,s){Object.defineProperty(t,"__esModule",{value:true});t["default"]=void 0;var n=_interopRequireDefault(s(310));var o=_interopRequireDefault(s(17));var r=_interopRequireDefault(s(569));var a=s(135);function _interopRequireDefault(e){return e&&e.__esModule?e:{default:e}}async function loader(e){const t=this.getOptions(r.default);const s=this.async();let i;try{i=(0,a.getSassImplementation)(this,t.implementation)}catch(e){s(e);return}const c=typeof t.sourceMap==="boolean"?t.sourceMap:this.sourceMap;const l=typeof i.compileStringAsync==="undefined"?"legacy":typeof t.api==="undefined"?"modern":t.api;const p=await(0,a.getSassOptions)(this,t,e,i,c,l);const u=typeof t.webpackImporter==="boolean"?t.webpackImporter:true;if(u){const e=l==="modern"||l==="modern-compiler";if(!e){const{includePaths:e}=p;p.importer.push((0,a.getWebpackImporter)(this,i,e))}else{p.importers.push((0,a.getModernWebpackImporter)(this,i,[]))}}let d;try{d=(0,a.getCompileFn)(this,i,l)}catch(e){s(e);return}let f;try{f=await d(p)}catch(e){if(e.span&&typeof e.span.url!=="undefined"){this.addDependency(n.default.fileURLToPath(e.span.url))}else if(typeof e.file!=="undefined"){this.addDependency(o.default.normalize(e.file))}s((0,a.errorFactory)(e));return}let m=f.sourceMap?f.sourceMap:f.map?JSON.parse(f.map):null;if(m&&c){m=(0,a.normalizeSourceMap)(m,this.rootContext)}if(typeof f.loadedUrls!=="undefined"){f.loadedUrls.filter((e=>e.protocol==="file:")).forEach((e=>{const t=n.default.fileURLToPath(e);if(o.default.isAbsolute(t)){this.addDependency(t)}}))}else if(typeof f.stats!=="undefined"&&typeof f.stats.includedFiles!=="undefined"){f.stats.includedFiles.forEach((e=>{const t=o.default.normalize(e);if(o.default.isAbsolute(t)){this.addDependency(t)}}))}s(null,f.css.toString(),m)}var i=t["default"]=loader},135:function(__unused_webpack_module,exports,__nccwpck_require__){Object.defineProperty(exports,"__esModule",{value:true});exports.errorFactory=errorFactory;exports.getCompileFn=getCompileFn;exports.getModernWebpackImporter=getModernWebpackImporter;exports.getSassImplementation=getSassImplementation;exports.getSassOptions=getSassOptions;exports.getWebpackImporter=getWebpackImporter;exports.getWebpackResolver=getWebpackResolver;exports.normalizeSourceMap=normalizeSourceMap;var _url=_interopRequireDefault(__nccwpck_require__(310));var _path=_interopRequireDefault(__nccwpck_require__(17));function _interopRequireDefault(e){return e&&e.__esModule?e:{default:e}}function getDefaultSassImplementation(){let sassImplPkg="sass";try{require.resolve("sass-embedded");sassImplPkg="sass-embedded"}catch(ignoreError){try{eval("require").resolve("sass")}catch(_ignoreError){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){s=getDefaultSassImplementation()}if(typeof s==="string"){s=require(s)}const{info:n}=s;if(!n){throw new Error("Unknown Sass implementation.")}const o=n.split("\t");if(o.length<2){throw new Error(`Unknown Sass implementation "${n}".`)}const[r]=o;if(r==="dart-sass"){return s}else if(r==="node-sass"){return s}else if(r==="sass-embedded"){return s}throw new Error(`Unknown Sass implementation "${r}".`)}function isProductionLikeMode(e){return e.mode==="production"||!e.mode}function proxyCustomImporters(e,t){return[].concat(e).map((e=>function proxyImporter(...s){const n={...this,webpackLoaderContext:t};return e.apply(n,s)}))}async function getSassOptions(e,t,s,n,o,r){const a=t.sassOptions?typeof t.sassOptions==="function"?t.sassOptions(e)||{}:t.sassOptions:{};const i={...a,data:t.additionalData?typeof t.additionalData==="function"?await t.additionalData(s,e):`${t.additionalData}\n${s}`:s};if(!i.logger){const s=t.warnRuleAsWarning!==false;const n=e.getLogger("sass-loader");const formatSpan=e=>`Warning on line ${e.start.line}, column ${e.start.column} of ${e.url||"-"}:${e.start.line}:${e.start.column}:\n`;const formatDebugSpan=e=>`[debug:${e.start.line}:${e.start.column}] `;i.logger={debug(e,t){let s="";if(t.span){s=formatDebugSpan(t.span)}s+=e;n.debug(s)},warn(t,o){let r="";if(o.deprecation){r+="Deprecation "}if(o.span){r+=formatSpan(o.span)}r+=t;if(o.span&&o.span.context){r+=`\n\n${o.span.start.line} | ${o.span.context}`}if(o.stack&&o.stack!=="null"){r+=`\n\n${o.stack}`}if(s){const t=new Error(r);t.name="SassWarning";t.stack=null;e.emitWarning(t)}else{n.warn(r)}}}}const c=r==="modern"||r==="modern-compiler";const{resourcePath:l}=e;if(c){i.url=_url.default.pathToFileURL(l);if(!i.style&&isProductionLikeMode(e)){i.style="compressed"}if(o){i.sourceMap=true}if(typeof i.syntax==="undefined"){const e=_path.default.extname(l);if(e&&e.toLowerCase()===".scss"){i.syntax="scss"}else if(e&&e.toLowerCase()===".sass"){i.syntax="indented"}else if(e&&e.toLowerCase()===".css"){i.syntax="css"}}i.loadPaths=[].concat((i.loadPaths?i.loadPaths.slice():[]).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"?";":":"):[]);i.importers=i.importers?Array.isArray(i.importers)?i.importers.slice():[i.importers]:[]}else{i.file=l;if(!i.outputStyle&&isProductionLikeMode(e)){i.outputStyle="compressed"}if(o){i.sourceMap=true;i.outFile=_path.default.join(e.rootContext,"style.css.map");i.sourceMapContents=true;i.omitSourceMapUrl=true;i.sourceMapEmbed=false}const s=_path.default.extname(l);if(s&&s.toLowerCase()===".sass"&&typeof i.indentedSyntax==="undefined"){i.indentedSyntax=true}else{i.indentedSyntax=Boolean(i.indentedSyntax)}i.importer=i.importer?proxyCustomImporters(Array.isArray(i.importer)?i.importer.slice():[i.importer],e):[];if(t.webpackImporter===false&&i.importer.length===0){i.importer=undefined}i.includePaths=[].concat(process.cwd()).concat((i.includePaths?i.includePaths.slice():[]).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 i.charset==="undefined"){i.charset=true}}return i}const MODULE_REQUEST_REGEX=/^[^?]*~/;const IS_MODULE_IMPORT=/^~([^/]+|[^/]+\/|@[^/]+[/][^/]+|@[^/]+\/?|@[^/]+[/][^/]+\/)$/;const IS_PKG_SCHEME=/^pkg:/i;function getPossibleRequests(e,t=false,s=false){let n=e;if(t){if(MODULE_REQUEST_REGEX.test(e)){n=n.replace(MODULE_REQUEST_REGEX,"")}if(IS_PKG_SCHEME.test(e)){n=`${n.slice(4)}`;return[...new Set([n,e])]}if(IS_MODULE_IMPORT.test(e)||IS_PKG_SCHEME.test(e)){n=n[n.length-1]==="/"?n:`${n}/`;return[...new Set([n,e])]}}const o=_path.default.extname(n).toLowerCase();if(o===".css"){return[]}const r=_path.default.dirname(n);const a=r==="."?"":`${r}/`;const i=_path.default.basename(n);const c=_path.default.basename(n,o);return[...new Set([].concat(s?[`${a}_${c}.import${o}`,`${a}${c}.import${o}`]:[]).concat([`${a}_${i}`,`${a}${i}`]).concat(t?[e]:[]))]}function promiseResolve(e){return(t,s)=>new Promise(((n,o)=>{e(t,s,((e,t)=>{if(e){o(e)}else{n(t)}}))}))}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:n}]=e;try{return await s(n,t[0])}catch(s){const[,...n]=t;if(n.length===0){const[,...t]=e;return startResolving(t)}e[0].possibleRequests=n;return startResolving(e)}}const IS_SPECIAL_MODULE_IMPORT=/^~[^/]+$/;const IS_NATIVE_WIN32_PATH=/^[a-z]:[/\\]|^\\\\/i;function getWebpackResolver(e,t,s=[]){const n=t&&typeof t.compileStringAsync!=="undefined";const o=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 r=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(!n&&!_path.default.isAbsolute(e)){return Promise.reject()}const l=t;const p=l.slice(0,5).toLowerCase()==="file:";if(p){try{t=_url.default.fileURLToPath(l)}catch(e){t=t.slice(7)}}let u=[];const d=!IS_SPECIAL_MODULE_IMPORT.test(t)&&!IS_PKG_SCHEME.test(t)&&!p&&!l.startsWith("/")&&!IS_NATIVE_WIN32_PATH.test(l);if(s.length>0&&d){const a=getPossibleRequests(t,false,c);if(!n){u=u.concat({resolve:c?r:o,context:_path.default.dirname(e),possibleRequests:a})}u=u.concat(s.map((e=>({resolve:c?r:o,context:e,possibleRequests:a}))))}const f=getPossibleRequests(t,true,c);u=u.concat({resolve:c?i:a,context:_path.default.dirname(e),possibleRequests:f});return startResolving(u)}}const MATCH_CSS=/\.css$/i;function getModernWebpackImporter(e,t,s){const n=getWebpackResolver(e.getResolve,t,s);return{async canonicalize(t,s){const{fromImport:o}=s;const r=s.containingUrl?_url.default.fileURLToPath(s.containingUrl.toString()):e.resourcePath;let a;try{a=await n(r,t,o)}catch(e){return null}e.addDependency(_path.default.normalize(a));return _url.default.pathToFileURL(a)},async load(t){const s=_path.default.extname(t.pathname);let n;if(s&&s.toLowerCase()===".scss"){n="scss"}else if(s&&s.toLowerCase()===".sass"){n="indented"}else if(s&&s.toLowerCase()===".css"){n="css"}else{n="scss"}try{const s=await new Promise(((s,n)=>{const o=_url.default.fileURLToPath(t);e.fs.readFile(o,"utf8",((e,t)=>{if(e){n(e);return}s(t)}))}));return{contents:s,syntax:n}}catch(e){return null}}}}function getWebpackImporter(e,t,s){const n=getWebpackResolver(e.getResolve,t,s);return function importer(t,s,o){const{fromImport:r}=this;n(s,t,r).then((t=>{e.addDependency(_path.default.normalize(t));o({file:t.replace(MATCH_CSS,"")})})).catch((()=>{o({file:t})}))}}let nodeSassJobQueue=null;const sassModernCompilers=new WeakMap;function getCompileFn(e,t,s){if(typeof t.compileStringAsync!=="undefined"){if(s==="modern"){return e=>{const{data:s,...n}=e;return t.compileStringAsync(s,n)}}if(s==="modern-compiler"){return async s=>{const n=e._compiler;const{data:o,...r}=s;if(n){if(!sassModernCompilers.has(n)){const e=await t.initAsyncCompiler();if(!sassModernCompilers.has(n)){sassModernCompilers.set(n,e);n.hooks.shutdown.tap("sass-loader",(()=>{e.dispose()}))}}return sassModernCompilers.get(n).compileStringAsync(o,r)}return t.compileStringAsync(o,r)}}return e=>new Promise(((s,n)=>{t.render(e,((e,t)=>{if(e){n(e);return}s(t)}))}))}if(s==="modern"||s==="modern-compiler"){throw new Error("Modern API is not supported for 'node-sass'")}if(nodeSassJobQueue===null){const e=Number(process.env.UV_THREADPOOL_SIZE||4);const s=__nccwpck_require__(175);nodeSassJobQueue=s.queue(t.render.bind(t),e-1)}return e=>new Promise(((t,s)=>{nodeSassJobQueue.push.bind(nodeSassJobQueue)(e,((e,n)=>{if(e){s(e);return}t(n)}))}))}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;if(typeof s.file!=="undefined"){delete s.file}s.sourceRoot="";s.sources=s.sources.map((e=>{const s=getURLType(e);if(s==="absolute"&&/^file:/i.test(e)){return _url.default.fileURLToPath(e)}else if(s==="path-relative"){return _path.default.resolve(t,_path.default.normalize(e))}return e}));return s}function errorFactory(e){let t;if(e.formatted){t=e.formatted.replace(/^Error: /,"")}else{({message:t}=e)}const s=new Error(t,{cause:e});s.stack=null;return s}},175:function(e){e.exports=require("next/dist/compiled/neo-async")},17:function(e){e.exports=require("path")},438:function(e){e.exports=require("sass")},310:function(e){e.exports=require("url")},569:function(e){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"}]},"api":{"description":"Switch between old and modern API for `sass` (`Dart Sass`) and `Sass Embedded` implementations.","link":"https://github.com/webpack-contrib/sass-loader#sassoptions","enum":["legacy","modern","modern-compiler"]},"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 n=true;try{__webpack_modules__[e](s,s.exports,__nccwpck_require__);n=false}finally{if(n)delete __webpack_module_cache__[e]}return s.exports}if(typeof __nccwpck_require__!=="undefined")__nccwpck_require__.ab=__dirname+"/";var __webpack_exports__=__nccwpck_require__(555);module.exports=__webpack_exports__})(); \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 75c9e8b384b4e..f240a9ca0733e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1383,8 +1383,8 @@ importers: specifier: 0.13.4 version: 0.13.4 sass-loader: - specifier: 15.0.0 - version: 15.0.0(sass@1.77.8)(webpack@5.96.1(@swc/core@1.7.0-nightly-20240714.1(@swc/helpers@0.5.13))) + specifier: 16.0.0 + version: 16.0.0(sass@1.77.8)(webpack@5.96.1(@swc/core@1.7.0-nightly-20240714.1(@swc/helpers@0.5.13))) schema-utils2: specifier: npm:schema-utils@2.7.1 version: schema-utils@2.7.1 @@ -13511,8 +13511,8 @@ packages: safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} - sass-loader@15.0.0: - resolution: {integrity: sha512-mbXAL7sI/fgt3skXR6xHxtKkaGyxRrGf7zrU4hLLWxBDJEcAe0QsoNy92qKttCb3zfMniTkU2kD9yakUKtW7vQ==} + sass-loader@16.0.0: + resolution: {integrity: sha512-n13Z+3rU9A177dk4888czcVFiC8CL9dii4qpXWUg3YIIgZEvi9TCFKjOQcbK0kJM7DJu9VucrZFddvNfYCPwtw==} engines: {node: '>= 18.12.0'} peerDependencies: '@rspack/core': 0.x || 1.x @@ -30375,7 +30375,7 @@ snapshots: safer-buffer@2.1.2: {} - sass-loader@15.0.0(sass@1.77.8)(webpack@5.96.1(@swc/core@1.7.0-nightly-20240714.1(@swc/helpers@0.5.13))): + sass-loader@16.0.0(sass@1.77.8)(webpack@5.96.1(@swc/core@1.7.0-nightly-20240714.1(@swc/helpers@0.5.13))): dependencies: neo-async: 2.6.2 optionalDependencies: diff --git a/test/e2e/app-dir/scss/use-upgraded-loader/basic-module.test.ts b/test/e2e/app-dir/scss/use-upgraded-loader/basic-module.test.ts new file mode 100644 index 0000000000000..5914d91c2caf3 --- /dev/null +++ b/test/e2e/app-dir/scss/use-upgraded-loader/basic-module.test.ts @@ -0,0 +1,37 @@ +import { nextTestSetup } from 'e2e-utils' +import { colorToRgb } from 'next-test-utils' + +describe('Legacy sass-loader', () => { + const { next: nextWithLegacyLoader } = nextTestSetup({ + files: __dirname, + dependencies: { sass: '1.80.7' }, + }) + + it('should render the module for the legacy sass-loader', async () => { + const browser = await nextWithLegacyLoader.browser('/') + expect( + await browser.elementByCss('#verify-red').getComputedCss('color') + ).toBe(colorToRgb('red')) + }) +}) + +describe('Upgraded sass-loader', () => { + const { next: nextWithUpgradedLoader } = nextTestSetup({ + files: __dirname, + dependencies: { sass: '1.80.7' }, + nextConfig: { + sassOptions: { + experimental: { + useUpgradedLoader: true, + }, + }, + }, + }) + + it('should render the module for the upgraded sass-loader', async () => { + const browser = await nextWithUpgradedLoader.browser('/') + expect( + await browser.elementByCss('#verify-red').getComputedCss('color') + ).toBe(colorToRgb('red')) + }) +}) diff --git a/test/e2e/app-dir/scss/use-upgraded-loader/pages/index.js b/test/e2e/app-dir/scss/use-upgraded-loader/pages/index.js new file mode 100644 index 0000000000000..7b42859a67e32 --- /dev/null +++ b/test/e2e/app-dir/scss/use-upgraded-loader/pages/index.js @@ -0,0 +1,9 @@ +import { redText } from './index.module.scss' + +export default function Home() { + return ( +
+ This text should be red. +
+ ) +} diff --git a/test/e2e/app-dir/scss/use-upgraded-loader/pages/index.module.scss b/test/e2e/app-dir/scss/use-upgraded-loader/pages/index.module.scss new file mode 100644 index 0000000000000..eb8bb05c73aca --- /dev/null +++ b/test/e2e/app-dir/scss/use-upgraded-loader/pages/index.module.scss @@ -0,0 +1,4 @@ +$var: red; +.redText { + color: $var; +}