diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d1e136..491a7b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # gulp-sass Changelog +## v6.0.0 + +* **Change** Migrate to use the [new Sass JS API](https://sass-lang.com/documentation/js-api/modules#compile) internally by default + ## v5.0.0 **June 25, 2021** diff --git a/README.md b/README.md index 53367aa..9e5711d 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Sass plugin for [Gulp](https://github.com/gulpjs/gulp). **_Before filing an issue, please make sure you have [updated to the latest version of `gulp-sass`](https://github.com/dlmanning/gulp-sass/wiki/Update-to-the-latest-Gulp-Sass) and have gone through our [Common Issues and Their Fixes](https://github.com/dlmanning/gulp-sass/wiki/Common-Issues-and-Their-Fixes) section._** -**Migrating your existing project to version 5? Please read our (short!) [migration guide](#migrating-to-version-5).** +**Migrating your existing project to version 5 or 6? Please read our (short!) [migration guides](#migrating-to-version-6).** ## Support @@ -12,7 +12,7 @@ Only [Active LTS and Current releases](https://github.com/nodejs/Release#release ## Installation -To use `gulp-sass`, you must install both `gulp-sass` itself *and* a Sass compiler. `gulp-sass` supports both [Dart Sass][] and [Node Sass][], although Node Sass is [deprecated](https://sass-lang.com/blog/libsass-is-deprecated). We recommend that you use Dart Sass for new projects, and migrate Node Sass projects to Dart Sass when possible. +To use `gulp-sass`, you must install both `gulp-sass` itself *and* a Sass compiler. `gulp-sass` supports both [Embedded Sass][], [Dart Sass][] and [Node Sass][], although Node Sass is [deprecated](https://sass-lang.com/blog/libsass-is-deprecated). We recommend that you use Dart Sass for new projects, and migrate Node Sass projects to Dart Sass or Embedded Sass when possible. Whichever compiler you choose, it's best to install these as dev dependencies: @@ -42,7 +42,7 @@ const sass = gulpSass(dartSass); `gulp-sass` must be used in a Gulp task. Your task can call `sass()` (to asynchronously render your CSS), or `sass.sync()` (to synchronously render your CSS). Then, export your task with the `export` keyword. We'll show some examples of how to do that. -**⚠️ Note:** When using Dart Sass, **synchronous rendering is twice as fast as asynchronous rendering**. The Sass team is exploring ways to improve asynchronous rendering with Dart Sass, but for now, you will get the best performance from `sass.sync()`. If performance is critical, you can use `node-sass` instead, but bear in mind that `node-sass` may not support modern Sass features you rely on. +**⚠️ Note:** When using Dart Sass, **synchronous rendering is twice as fast as asynchronous rendering**. The Sass team is exploring ways to improve asynchronous rendering with Dart Sass, but for now, you will get the best performance from `sass.sync()`. If performance is critical, you can use `sass-embedded` instead. ### Render your CSS @@ -78,17 +78,17 @@ function buildStyles() { ### Render with options -To change the final output of your CSS, you can pass an options object to your renderer. `gulp-sass` supports [Node Sass's render options](https://github.com/sass/node-sass#options), with two unsupported exceptions: +To change the final output of your CSS, you can pass an options object to your renderer. `gulp-sass` supports [Sass's JS API compile options](https://sass-lang.com/documentation/js-api/modules#compileString), with a few usage notes: -- The `data` option, which is used by `gulp-sass` internally. -- The `file` option, which has undefined behavior that may change without notice. +- The `syntax` option is set to `indented` automatically for files with the `.sass` extension +- The `sourceMap` and `sourceMapIncludeSources` options are set for you when using `gulp-sourcemaps` -For example, to compress your CSS, you can call `sass({outputStyle: 'compressed'}`. In the context of a Gulp task, that looks like this: +For example, to compress your CSS, you can call `sass({style: 'compressed'}`. In the context of a Gulp task, that looks like this: ```js function buildStyles() { return gulp.src('./sass/**/*.scss') - .pipe(sass({outputStyle: 'compressed'}).on('error', sass.logError)) + .pipe(sass({style: 'compressed'}).on('error', sass.logError)) .pipe(gulp.dest('./css')); }; @@ -100,7 +100,7 @@ Or this for synchronous rendering: ```js function buildStyles() { return gulp.src('./sass/**/*.scss') - .pipe(sass.sync({outputStyle: 'compressed'}).on('error', sass.logError)) + .pipe(sass.sync({style: 'compressed'}).on('error', sass.logError)) .pipe(gulp.dest('./css')); }; @@ -141,13 +141,34 @@ function buildStyles() { exports.buildStyles = buildStyles; ``` +

Migrating to version 6

+ +`gulp-sass` version 6 uses the new [compile](https://sass-lang.com/documentation/js-api/modules#compileString) function internally by default. If you use any options, for instance custom importers, please compare the [new options](https://sass-lang.com/documentation/js-api/modules#compileString) with the [legacy options](https://sass-lang.com/documentation/js-api/modules#render) in order to migrate. For instance, the `outputStyle` option is now called `style`. + +```diff + function buildStyles() { + return gulp.src('./sass/**/*.scss') +- .pipe(sass({outputStyle: 'compressed'}).on('error', sass.logError)) ++ .pipe(sass({style: 'compressed'}).on('error', sass.logError)) + .pipe(gulp.dest('./css')); + }; +``` + +If you want to keep using the legacy API while it's available, you can. + +```js +const sass = require('gulp-sass/legacy')(require('sass')); +``` + +If you use source maps, you may see the result change somewhat. The result will typically be absolute `file:` URLs, rather than relative ones. The result may also be the source itself, URL encoded. You can [optionally add custom importers](https://sass-lang.com/documentation/js-api/interfaces/CompileResult#sourceMap) to adjust the source maps according to your own needs. +

Migrating to version 5

`gulp-sass` version 5 requires Node.js 12 or later, and introduces some breaking changes. Additionally, changes in Node.js itself mean that Node fibers can no longer be used to speed up Dart Sass in Node.js 16. ### Setting a Sass compiler -As of version 5, `gulp-sass` _does not include a default Sass compiler_, so you must install one (either `node-sass` or `sass`) along with `gulp-sass`. +As of version 5, `gulp-sass` _does not include a default Sass compiler_, so you must install one (either `sass`, `sass-embedded`, or `node-sass`) along with `gulp-sass`. ```sh npm install sass gulp-sass --save-dev @@ -176,6 +197,28 @@ import dartSass from 'sass'; + const sass = gulpSass(dartSass); ``` +### Using the legacy Sass API + +If you need to use the deprecated `render` Sass API, `gulp-sass` still includes legacy support. + +```js +'use strict'; + +const gulp = require('gulp'); +const sass = require('gulp-sass/legacy')(require('sass')); + +function buildStyles() { + return gulp.src('./sass/**/*.scss') + .pipe(sass().on('error', sass.logError)) + .pipe(gulp.dest('./css')); +}; + +exports.buildStyles = buildStyles; +exports.watch = function () { + gulp.watch('./sass/**/*.scss', ['sass']); +}; +```` + ### What about fibers? We used to recommend Node fibers as a way to speed up asynchronous rendering with Dart Sass. Unfortunately, [Node fibers are discontinued](https://sass-lang.com/blog/node-fibers-discontinued) and will not work in Node.js 16. The Sass team is exploring its options for future performance improvements, but for now, you will get the best performance from `sass.sync()`. @@ -190,6 +233,7 @@ If you're having problems with the options you're passing in, it's likely a Dart We may, in the course of resolving issues, direct you to one of these other projects. If we do so, please follow up by searching that project's issue queue (both open and closed) for your problem and, if it doesn't exist, filing an issue with them. +[Embedded Sass]: https://github.com/sass/embedded-host-node [Dart Sass]: https://sass-lang.com/dart-sass [LibSass]: https://sass-lang.com/libsass [Node Sass]: https://github.com/sass/node-sass diff --git a/index.js b/index.js index 3a027bc..5e62096 100644 --- a/index.js +++ b/index.js @@ -24,11 +24,17 @@ const transfob = (transform) => new Transform({ transform, objectMode: true }); /** * Handles returning the file to the stream */ -const filePush = (file, sassObject, callback) => { +const filePush = (file, compileResult, callback) => { + file.contents = Buffer.from(compileResult.css); + file.path = replaceExtension(file.path, '.css'); + // Build Source Maps! - if (sassObject.map) { - // Transform map into JSON - const sassMap = JSON.parse(sassObject.map.toString()); + if (compileResult.sourceMap) { + const sassMap = compileResult.sourceMap; + if (!sassMap.file) { + sassMap.file = file.path; + } + // Grab the stdout and transform it into stdin const sassMapFile = sassMap.file.replace(/^stdout$/, 'stdin'); // Grab the base filename that's being worked on @@ -55,9 +61,6 @@ const filePush = (file, sassObject, callback) => { applySourceMap(file, sassMap); } - file.contents = sassObject.css; - file.path = replaceExtension(file.path, '.css'); - if (file.stat) { file.stat.atime = file.stat.mtime = file.stat.ctime = new Date(); } @@ -71,7 +74,7 @@ const filePush = (file, sassObject, callback) => { const handleError = (error, file, callback) => { const filePath = (error.file === 'stdin' ? file.path : error.file) || file.path; const relativePath = path.relative(process.cwd(), filePath); - const message = `${picocolors.underline(relativePath)}\n${error.formatted}`; + const message = `${picocolors.underline(relativePath)}\n${error.message}`; error.messageFormatted = message; error.messageOriginal = error.message; @@ -110,52 +113,48 @@ const gulpSass = (options, sync) => { } const opts = clonedeep(options || {}); - opts.data = file.contents.toString(); - // We set the file path here so that libsass can correctly resolve import paths - opts.file = file.path; - - // Ensure `indentedSyntax` is true if a `.sass` file + // Ensure `indented` if a `.sass` file if (path.extname(file.path) === '.sass') { - opts.indentedSyntax = true; + opts.syntax = 'indented'; } // Ensure file's parent directory in the include path - if (opts.includePaths) { - if (typeof opts.includePaths === 'string') { - opts.includePaths = [opts.includePaths]; + if (opts.loadPaths) { + if (typeof opts.loadPaths === 'string') { + opts.loadPaths = [opts.loadPaths]; } } else { - opts.includePaths = []; + opts.loadPaths = []; } - opts.includePaths.unshift(path.dirname(file.path)); + opts.loadPaths.unshift(path.dirname(file.path)); // Generate Source Maps if the source-map plugin is present if (file.sourceMap) { - opts.sourceMap = file.path; - opts.omitSourceMapUrl = true; - opts.sourceMapContents = true; + opts.sourceMap = true; + opts.sourceMapIncludeSources = true; } + const fileContents = file.contents.toString(); if (sync !== true) { /** - * Async Sass render + * Async Sass compile */ - gulpSass.compiler.render(opts, (error, obj) => { - if (error) { + gulpSass.compiler + .compileStringAsync(fileContents, opts) + .then((compileResult) => { + filePush(file, compileResult, callback); + }) + .catch((error) => { handleError(error, file, callback); - return; - } - - filePush(file, obj, callback); - }); + }); } else { /** - * Sync Sass render + * Sync Sass compile */ try { - filePush(file, gulpSass.compiler.renderSync(opts), callback); + filePush(file, gulpSass.compiler.compileString(fileContents, opts), callback); } catch (error) { handleError(error, file, callback); } @@ -164,7 +163,7 @@ const gulpSass = (options, sync) => { }; /** - * Sync Sass render + * Sync Sass compile */ gulpSass.sync = (options) => gulpSass(options, true); @@ -172,13 +171,13 @@ gulpSass.sync = (options) => gulpSass(options, true); * Log errors nicely */ gulpSass.logError = function logError(error) { - const message = new PluginError('sass', error.messageFormatted).toString(); + const message = new PluginError('sass', error).toString(); process.stderr.write(`${message}\n`); this.emit('end'); }; module.exports = (compiler) => { - if (!compiler || !compiler.render) { + if (!compiler || !compiler.compile) { const message = new PluginError( PLUGIN_NAME, MISSING_COMPILER_MESSAGE, diff --git a/legacy.js b/legacy.js new file mode 100644 index 0000000..3a027bc --- /dev/null +++ b/legacy.js @@ -0,0 +1,193 @@ +'use strict'; + +const path = require('path'); +const { Transform } = require('stream'); +const picocolors = require('picocolors'); +const PluginError = require('plugin-error'); +const replaceExtension = require('replace-ext'); +const stripAnsi = require('strip-ansi'); +const clonedeep = require('lodash.clonedeep'); +const applySourceMap = require('vinyl-sourcemaps-apply'); + +const PLUGIN_NAME = 'gulp-sass'; + +const MISSING_COMPILER_MESSAGE = ` +gulp-sass no longer has a default Sass compiler; please set one yourself. +Both the "sass" and "node-sass" packages are permitted. +For example, in your gulpfile: + + const sass = require('gulp-sass')(require('sass')); +`; + +const transfob = (transform) => new Transform({ transform, objectMode: true }); + +/** + * Handles returning the file to the stream + */ +const filePush = (file, sassObject, callback) => { + // Build Source Maps! + if (sassObject.map) { + // Transform map into JSON + const sassMap = JSON.parse(sassObject.map.toString()); + // Grab the stdout and transform it into stdin + const sassMapFile = sassMap.file.replace(/^stdout$/, 'stdin'); + // Grab the base filename that's being worked on + const sassFileSrc = file.relative; + // Grab the path portion of the file that's being worked on + const sassFileSrcPath = path.dirname(sassFileSrc); + + if (sassFileSrcPath) { + const sourceFileIndex = sassMap.sources.indexOf(sassMapFile); + // Prepend the path to all files in the sources array except the file that's being worked on + sassMap.sources = sassMap.sources.map((source, index) => ( + index === sourceFileIndex + ? source + : path.join(sassFileSrcPath, source) + )); + } + + // Remove 'stdin' from souces and replace with filenames! + sassMap.sources = sassMap.sources.filter((src) => src !== 'stdin' && src); + + // Replace the map file with the original filename (but new extension) + sassMap.file = replaceExtension(sassFileSrc, '.css'); + // Apply the map + applySourceMap(file, sassMap); + } + + file.contents = sassObject.css; + file.path = replaceExtension(file.path, '.css'); + + if (file.stat) { + file.stat.atime = file.stat.mtime = file.stat.ctime = new Date(); + } + + callback(null, file); +}; + +/** + * Handles error message + */ +const handleError = (error, file, callback) => { + const filePath = (error.file === 'stdin' ? file.path : error.file) || file.path; + const relativePath = path.relative(process.cwd(), filePath); + const message = `${picocolors.underline(relativePath)}\n${error.formatted}`; + + error.messageFormatted = message; + error.messageOriginal = error.message; + error.message = stripAnsi(message); + error.relativePath = relativePath; + + return callback(new PluginError(PLUGIN_NAME, error)); +}; + +/** + * Main Gulp Sass function + */ + +// eslint-disable-next-line arrow-body-style +const gulpSass = (options, sync) => { + return transfob((file, encoding, callback) => { + if (file.isNull()) { + callback(null, file); + return; + } + + if (file.isStream()) { + callback(new PluginError(PLUGIN_NAME, 'Streaming not supported')); + return; + } + + if (path.basename(file.path).startsWith('_')) { + callback(); + return; + } + + if (!file.contents.length) { + file.path = replaceExtension(file.path, '.css'); + callback(null, file); + return; + } + + const opts = clonedeep(options || {}); + opts.data = file.contents.toString(); + + // We set the file path here so that libsass can correctly resolve import paths + opts.file = file.path; + + // Ensure `indentedSyntax` is true if a `.sass` file + if (path.extname(file.path) === '.sass') { + opts.indentedSyntax = true; + } + + // Ensure file's parent directory in the include path + if (opts.includePaths) { + if (typeof opts.includePaths === 'string') { + opts.includePaths = [opts.includePaths]; + } + } else { + opts.includePaths = []; + } + + opts.includePaths.unshift(path.dirname(file.path)); + + // Generate Source Maps if the source-map plugin is present + if (file.sourceMap) { + opts.sourceMap = file.path; + opts.omitSourceMapUrl = true; + opts.sourceMapContents = true; + } + + if (sync !== true) { + /** + * Async Sass render + */ + gulpSass.compiler.render(opts, (error, obj) => { + if (error) { + handleError(error, file, callback); + return; + } + + filePush(file, obj, callback); + }); + } else { + /** + * Sync Sass render + */ + try { + filePush(file, gulpSass.compiler.renderSync(opts), callback); + } catch (error) { + handleError(error, file, callback); + } + } + }); +}; + +/** + * Sync Sass render + */ +gulpSass.sync = (options) => gulpSass(options, true); + +/** + * Log errors nicely + */ +gulpSass.logError = function logError(error) { + const message = new PluginError('sass', error.messageFormatted).toString(); + process.stderr.write(`${message}\n`); + this.emit('end'); +}; + +module.exports = (compiler) => { + if (!compiler || !compiler.render) { + const message = new PluginError( + PLUGIN_NAME, + MISSING_COMPILER_MESSAGE, + { showProperties: false }, + ).toString(); + process.stderr.write(`${message}\n`); + process.exit(1); + } + + gulpSass.compiler = compiler; + return gulpSass; +}; diff --git a/package-lock.json b/package-lock.json index 2dfc2e0..3213645 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,7 @@ "packages": { "": { "name": "gulp-sass", - "version": "5.0.0", + "version": "5.1.0", "license": "MIT", "dependencies": { "lodash.clonedeep": "^4.5.0", @@ -31,6 +31,7 @@ "postcss": "^8.4.5", "rimraf": "^3.0.2", "sass": "^1.45.1", + "sass-embedded": "^1.49.9", "vinyl": "^2.2.1" }, "engines": { @@ -322,12 +323,29 @@ "integrity": "sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==", "dev": true }, + "node_modules/@types/node": { + "version": "17.0.21", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.21.tgz", + "integrity": "sha512-DBZCJbhII3r90XbQxI8Y9IjjiiOGlZ0Hr32omXIZvwwZ7p4DMMXGrKXVyPfuoBOri9XNtL0UK69jYIBIsRX3QQ==", + "dev": true, + "optional": true + }, "node_modules/@types/normalize-package-data": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz", "integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==", "dev": true }, + "node_modules/@types/yauzl": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.9.2.tgz", + "integrity": "sha512-8uALY5LTvSuHgloDVUvWP3pIauILm+8/0pDMokuDYIoNsOkSwd5AiHBTSEJjKTDcZr5z8UpgOWZkxBF4iJftoA==", + "dev": true, + "optional": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@ungap/promise-all-settled": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", @@ -997,6 +1015,21 @@ "url": "https://opencollective.com/browserslist" } }, + "node_modules/buffer-builder": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/buffer-builder/-/buffer-builder-0.2.0.tgz", + "integrity": "sha1-MyLNMH2Cltqx9gRhhZOyYaP63o8=", + "dev": true + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=", + "dev": true, + "engines": { + "node": "*" + } + }, "node_modules/buffer-equal": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-1.0.0.tgz", @@ -2483,6 +2516,26 @@ "node": ">=0.10.0" } }, + "node_modules/extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + }, + "engines": { + "node": ">= 10.17.0" + }, + "optionalDependencies": { + "@types/yauzl": "^2.9.1" + } + }, "node_modules/extsprintf": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", @@ -2525,6 +2578,15 @@ "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", "dev": true }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=", + "dev": true, + "dependencies": { + "pend": "~1.2.0" + } + }, "node_modules/file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -2853,6 +2915,31 @@ "node": ">=0.10.0" } }, + "node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-stream/node_modules/pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, "node_modules/get-symbol-description": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", @@ -3063,6 +3150,12 @@ "node": ">= 0.10" } }, + "node_modules/google-protobuf": { + "version": "3.19.4", + "resolved": "https://registry.npmjs.org/google-protobuf/-/google-protobuf-3.19.4.tgz", + "integrity": "sha512-OIPNCxsG2lkIvf+P5FNfJ/Km95CsXOBecS9ZcAU6m2Rq3svc0Apl9nB3GMDNKfQ9asNv4KjyAqGwPQFrVle3Yg==", + "dev": true + }, "node_modules/graceful-fs": { "version": "4.2.8", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz", @@ -5320,6 +5413,26 @@ "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==", "dev": true }, + "node_modules/node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "dev": true, + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, "node_modules/node-gyp": { "version": "8.4.1", "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-8.4.1.tgz", @@ -5971,6 +6084,12 @@ "node": ">=0.10.0" } }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=", + "dev": true + }, "node_modules/performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", @@ -6751,6 +6870,15 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/rxjs": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.5.4.tgz", + "integrity": "sha512-h5M3Hk78r6wAheJF0a5YahB1yRQKCsZ4MsGdZ5O9ETbVtjPcScGfrMmoOq7EBsCRzd4BDkvDJ7ogP8Sz5tTFiQ==", + "dev": true, + "dependencies": { + "tslib": "^2.1.0" + } + }, "node_modules/safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", @@ -6789,6 +6917,43 @@ "node": ">=8.9.0" } }, + "node_modules/sass-embedded": { + "version": "1.49.9", + "resolved": "https://registry.npmjs.org/sass-embedded/-/sass-embedded-1.49.9.tgz", + "integrity": "sha512-FMzLl2Ts1gQBSu3zPKzu2oxAW/U6nbTjoGcn506M2o27BiN1N7aZZS6zHRSRaGlE9C/y2IuMNX5v5uswRJeQLQ==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "buffer-builder": "^0.2.0", + "extract-zip": "^2.0.1", + "google-protobuf": "^3.11.4", + "immutable": "^4.0.0", + "node-fetch": "^2.6.0", + "rxjs": "^7.4.0", + "semver": "^7.3.5", + "shelljs": "^0.8.4", + "supports-color": "^8.1.1", + "tar": "^6.0.5" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, "node_modules/sass-graph": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/sass-graph/-/sass-graph-4.0.0.tgz", @@ -7127,6 +7292,23 @@ "node": ">=8" } }, + "node_modules/shelljs": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.5.tgz", + "integrity": "sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==", + "dev": true, + "dependencies": { + "glob": "^7.0.0", + "interpret": "^1.0.0", + "rechoir": "^0.6.2" + }, + "bin": { + "shjs": "bin/shjs" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/side-channel": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", @@ -7956,6 +8138,12 @@ "node": ">=0.8" } }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=", + "dev": true + }, "node_modules/trim-newlines": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz", @@ -7986,6 +8174,12 @@ "strip-bom": "^3.0.0" } }, + "node_modules/tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", + "dev": true + }, "node_modules/tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", @@ -8399,6 +8593,22 @@ "node": ">= 0.10" } }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=", + "dev": true + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", + "dev": true, + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -8779,6 +8989,16 @@ "object.assign": "^4.1.0" } }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=", + "dev": true, + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", @@ -9023,12 +9243,29 @@ "integrity": "sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==", "dev": true }, + "@types/node": { + "version": "17.0.21", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.21.tgz", + "integrity": "sha512-DBZCJbhII3r90XbQxI8Y9IjjiiOGlZ0Hr32omXIZvwwZ7p4DMMXGrKXVyPfuoBOri9XNtL0UK69jYIBIsRX3QQ==", + "dev": true, + "optional": true + }, "@types/normalize-package-data": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz", "integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==", "dev": true }, + "@types/yauzl": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.9.2.tgz", + "integrity": "sha512-8uALY5LTvSuHgloDVUvWP3pIauILm+8/0pDMokuDYIoNsOkSwd5AiHBTSEJjKTDcZr5z8UpgOWZkxBF4iJftoA==", + "dev": true, + "optional": true, + "requires": { + "@types/node": "*" + } + }, "@ungap/promise-all-settled": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", @@ -9541,6 +9778,18 @@ "picocolors": "^1.0.0" } }, + "buffer-builder": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/buffer-builder/-/buffer-builder-0.2.0.tgz", + "integrity": "sha1-MyLNMH2Cltqx9gRhhZOyYaP63o8=", + "dev": true + }, + "buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=", + "dev": true + }, "buffer-equal": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-1.0.0.tgz", @@ -10775,6 +11024,18 @@ } } }, + "extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "dev": true, + "requires": { + "@types/yauzl": "^2.9.1", + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + } + }, "extsprintf": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", @@ -10811,6 +11072,15 @@ "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", "dev": true }, + "fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=", + "dev": true, + "requires": { + "pend": "~1.2.0" + } + }, "file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -11066,6 +11336,27 @@ "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=", "dev": true }, + "get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "requires": { + "pump": "^3.0.0" + }, + "dependencies": { + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + } + } + }, "get-symbol-description": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", @@ -11232,6 +11523,12 @@ "sparkles": "^1.0.0" } }, + "google-protobuf": { + "version": "3.19.4", + "resolved": "https://registry.npmjs.org/google-protobuf/-/google-protobuf-3.19.4.tgz", + "integrity": "sha512-OIPNCxsG2lkIvf+P5FNfJ/Km95CsXOBecS9ZcAU6m2Rq3svc0Apl9nB3GMDNKfQ9asNv4KjyAqGwPQFrVle3Yg==", + "dev": true + }, "graceful-fs": { "version": "4.2.8", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz", @@ -12963,6 +13260,15 @@ "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==", "dev": true }, + "node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "dev": true, + "requires": { + "whatwg-url": "^5.0.0" + } + }, "node-gyp": { "version": "8.4.1", "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-8.4.1.tgz", @@ -13457,6 +13763,12 @@ "pinkie-promise": "^2.0.0" } }, + "pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=", + "dev": true + }, "performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", @@ -14046,6 +14358,15 @@ "glob": "^7.1.3" } }, + "rxjs": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.5.4.tgz", + "integrity": "sha512-h5M3Hk78r6wAheJF0a5YahB1yRQKCsZ4MsGdZ5O9ETbVtjPcScGfrMmoOq7EBsCRzd4BDkvDJ7ogP8Sz5tTFiQ==", + "dev": true, + "requires": { + "tslib": "^2.1.0" + } + }, "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", @@ -14179,6 +14500,35 @@ } } }, + "sass-embedded": { + "version": "1.49.9", + "resolved": "https://registry.npmjs.org/sass-embedded/-/sass-embedded-1.49.9.tgz", + "integrity": "sha512-FMzLl2Ts1gQBSu3zPKzu2oxAW/U6nbTjoGcn506M2o27BiN1N7aZZS6zHRSRaGlE9C/y2IuMNX5v5uswRJeQLQ==", + "dev": true, + "requires": { + "buffer-builder": "^0.2.0", + "extract-zip": "^2.0.1", + "google-protobuf": "^3.11.4", + "immutable": "^4.0.0", + "node-fetch": "^2.6.0", + "rxjs": "^7.4.0", + "semver": "^7.3.5", + "shelljs": "^0.8.4", + "supports-color": "^8.1.1", + "tar": "^6.0.5" + }, + "dependencies": { + "supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, "sass-graph": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/sass-graph/-/sass-graph-4.0.0.tgz", @@ -14337,6 +14687,17 @@ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true }, + "shelljs": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.5.tgz", + "integrity": "sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==", + "dev": true, + "requires": { + "glob": "^7.0.0", + "interpret": "^1.0.0", + "rechoir": "^0.6.2" + } + }, "side-channel": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", @@ -15016,6 +15377,12 @@ "punycode": "^2.1.1" } }, + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=", + "dev": true + }, "trim-newlines": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz", @@ -15043,6 +15410,12 @@ "strip-bom": "^3.0.0" } }, + "tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", + "dev": true + }, "tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", @@ -15392,6 +15765,22 @@ } } }, + "webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=", + "dev": true + }, + "whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", + "dev": true, + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -15694,6 +16083,16 @@ } } }, + "yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=", + "dev": true, + "requires": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, "yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index e074d62..d97ea45 100644 --- a/package.json +++ b/package.json @@ -10,9 +10,11 @@ "lint": "eslint --report-unused-disable-directives --ignore-path .gitignore .", "fix": "npm run lint -- --fix", "mocha": "mocha", - "test": "npm run test:node-sass && npm run test:dart-sass", + "test": "npm run test:node-sass && npm run test:dart-sass && npm run test:legacy-dart-sass && npm run test:sass-embedded", "test:node-sass": "mocha", - "test:dart-sass": "mocha -- --sass" + "test:dart-sass": "mocha -- --sass", + "test:legacy-dart-sass": "mocha -- --sass --legacy", + "test:sass-embedded": "mocha -- --embedded" }, "repository": { "type": "git", @@ -30,7 +32,8 @@ }, "homepage": "https://github.com/dlmanning/gulp-sass#readme", "files": [ - "index.js" + "index.js", + "legacy.js" ], "dependencies": { "lodash.clonedeep": "^4.5.0", @@ -55,6 +58,7 @@ "postcss": "^8.4.5", "rimraf": "^3.0.2", "sass": "^1.45.1", + "sass-embedded": "^1.49.9", "vinyl": "^2.2.1" } } diff --git a/test/main.js b/test/main.js index b8bbdb6..2bc987a 100644 --- a/test/main.js +++ b/test/main.js @@ -12,12 +12,23 @@ const autoprefixer = require('autoprefixer'); const tap = require('gulp-tap'); const globule = require('globule'); -const COMPILER = process.argv.includes('--sass') ? 'sass' : 'node-sass'; +function getCompiler() { + if (process.argv.includes('--sass')) return 'sass'; + if (process.argv.includes('--embedded')) return 'sass-embedded'; + return 'node-sass'; +} -// eslint-disable-next-line import/no-dynamic-require -const sass = require('../index')(require(COMPILER)); +const COMPILER = getCompiler(); +const LEGACY_API = COMPILER === 'node-sass' || process.argv.includes('--legacy'); +const MODERN_COMPILER = COMPILER === 'sass' || COMPILER === 'sass-embedded'; -const expectedTestsPath = COMPILER === 'sass' ? 'expected-sass' : 'expected'; +/* eslint-disable import/no-dynamic-require */ +const sass = LEGACY_API + ? require('../legacy')(require(COMPILER)) + : require('../index')(require(COMPILER)); +/* eslint-enable import/no-dynamic-require */ + +const expectedTestsPath = MODERN_COMPILER ? 'expected-sass' : 'expected'; const createVinyl = (filename, contents) => { const base = path.join(__dirname, 'scss'); @@ -163,8 +174,8 @@ describe('gulp-sass -- async compile', () => { stream.on('error', (err) => { // Error must include message body - const messageBody = COMPILER === 'sass' - ? 'Error: expected "{"' + const messageBody = MODERN_COMPILER + ? 'expected "{"' : 'property "font" must be followed by a \':\''; assert.equal(err.message.includes(messageBody), true); // Error must include file error occurs in @@ -182,7 +193,7 @@ describe('gulp-sass -- async compile', () => { stream.on('error', (err) => { // Error must include original error message - const message = COMPILER === 'sass' + const message = MODERN_COMPILER ? 'expected "{"' : 'property "font" must be followed by a \':\''; assert.equal(err.messageOriginal.includes(message), true); @@ -245,16 +256,31 @@ describe('gulp-sass -- async compile', () => { + '}'; // Expected sources are relative to file.base - const expectedSources = [ + const legacyExpectedSources = [ 'inheritance.scss', 'includes/_cats.scss', 'includes/_dogs.sass', ]; + // Going forward the source map typically uses absolute file: URLs, + // although this can be controlled by custom importers + const expectedSources = [ + 'data:', + 'includes/_cats.scss', + 'includes/_dogs.sass', + ]; + const stream = sass(); stream.on('data', (cssFile) => { assert.ok(cssFile.sourceMap); - assert.deepEqual(cssFile.sourceMap.sources.sort(), expectedSources.sort()); + if (LEGACY_API) { + assert.deepEqual(cssFile.sourceMap.sources.sort(), legacyExpectedSources.sort()); + } else { + // look for partial matches since each test runner can have a different absolute path + assert.ok(cssFile.sourceMap.sources.every( + (source) => expectedSources.find((partial) => source.includes(partial)), + )); + } done(); }); stream.write(sassFile); @@ -411,8 +437,8 @@ describe('gulp-sass -- sync compile', () => { stream.on('error', (err) => { // Error must include message body - const messageBody = COMPILER === 'sass' - ? 'Error: expected "{"' + const messageBody = MODERN_COMPILER + ? 'expected "{"' : 'property "font" must be followed by a \':\''; assert.equal(err.message.includes(messageBody), true); assert.equal(err.relativePath, path.join('test', 'scss', 'error.scss')); @@ -434,12 +460,20 @@ describe('gulp-sass -- sync compile', () => { const sassFile = createVinyl('inheritance.scss'); // Expected sources are relative to file.base - const expectedSources = [ + const legacyExpectedSources = [ 'inheritance.scss', 'includes/_cats.scss', 'includes/_dogs.sass', ]; + // Going forward the source map typically uses absolute file: URLs, + // although this can be controlled by custom importers + const expectedSources = [ + 'data:', + 'includes/_cats.scss', + 'includes/_dogs.sass', + ]; + sassFile.sourceMap = '{' + '"version": 3,' + '"file": "scss/subdir/multilevelimport.scss",' @@ -452,40 +486,77 @@ describe('gulp-sass -- sync compile', () => { const stream = sass.sync(); stream.on('data', (cssFile) => { assert.ok(cssFile.sourceMap); - assert.deepEqual(cssFile.sourceMap.sources.sort(), expectedSources.sort()); + if (LEGACY_API) { + assert.deepEqual(cssFile.sourceMap.sources.sort(), legacyExpectedSources.sort()); + } else { + // look for partial matches since each test runner can have a different absolute path + assert.ok(cssFile.sourceMap.sources.every( + (source) => expectedSources.find((partial) => source.includes(partial)), + )); + } done(); }); stream.write(sassFile); }); it('should work with gulp-sourcemaps and autoprefixer', (done) => { - const expectedSourcesBefore = [ + const legacyExpectedSourcesBefore = [ 'inheritance.scss', 'includes/_cats.scss', 'includes/_dogs.sass', ]; - const expectedSourcesAfter = [ + const legacyExpectedSourcesAfter = [ 'includes/_cats.scss', 'includes/_dogs.sass', 'inheritance.scss', ]; - if (COMPILER === 'sass') expectedSourcesAfter.push('inheritance.css'); + const expectedSourcesBefore = [ + 'data:', + 'includes/_cats.scss', + 'includes/_dogs.sass', + ]; + + const expectedSourcesAfter = [ + 'includes/_cats.scss', + 'includes/_dogs.sass', + 'data:', + ]; + + if (MODERN_COMPILER) { + const result = 'inheritance.css'; + legacyExpectedSourcesAfter.push(result); + expectedSourcesAfter.push(result); + } gulp.src(path.join(__dirname, 'scss', 'inheritance.scss')) .pipe(sourcemaps.init()) .pipe(sass.sync()) .pipe(tap((file) => { assert.ok(file.sourceMap); - assert.deepEqual(file.sourceMap.sources.sort(), expectedSourcesBefore.sort()); + if (LEGACY_API) { + assert.deepEqual(file.sourceMap.sources.sort(), legacyExpectedSourcesBefore.sort()); + } else { + // look for partial matches since each test runner can have a different absolute path + assert.ok(file.sourceMap.sources.every( + (source) => expectedSourcesBefore.find((partial) => source.includes(partial)), + )); + } })) .pipe(postcss([autoprefixer()])) .pipe(sourcemaps.write()) .pipe(gulp.dest(path.join(__dirname, 'results'))) .pipe(tap((file) => { assert.ok(file.sourceMap); - assert.deepEqual(file.sourceMap.sources.sort(), expectedSourcesAfter.sort()); + if (LEGACY_API) { + assert.deepEqual(file.sourceMap.sources.sort(), legacyExpectedSourcesAfter.sort()); + } else { + // look for partial matches since each test runner can have a different absolute path + assert.ok(file.sourceMap.sources.every( + (source) => expectedSourcesAfter.find((partial) => source.includes(partial)), + )); + } })); done(); }); @@ -505,39 +576,77 @@ describe('gulp-sass -- sync compile', () => { .pipe(sass.sync()) .pipe(tap((file) => { assert.ok(file.sourceMap); - const actual = normaliseEOL(file.sourceMap.sourcesContent[0]); - const expected = normaliseEOL(filesContent[path.normalize(file.sourceMap.sources[0])]); - assert.deepEqual(actual, expected); + if (LEGACY_API) { + const actual = normaliseEOL(file.sourceMap.sourcesContent[0]); + const expected = normaliseEOL(filesContent[path.normalize(file.sourceMap.sources[0])]); + assert.deepEqual(actual, expected); + } else { + const sourceMap = file.sourceMap.sources[0]; + const source = decodeURI(sourceMap.split('data:;charset=utf-8,')[1]); + const actual = normaliseEOL(source); + const expected = normaliseEOL(filesContent[path.normalize(file.sourceMap.file.replace('.css', '.scss'))]); + assert.deepEqual(actual, expected); + } })); done(); }); it('should work with gulp-sourcemaps and autoprefixer with different file.base', (done) => { - const expectedSourcesBefore = [ + const legacyExpectedSourcesBefore = [ 'scss/inheritance.scss', 'scss/includes/_cats.scss', 'scss/includes/_dogs.sass', ]; - const expectedSourcesAfter = [ + const legacyExpectedSourcesAfter = [ 'scss/includes/_cats.scss', 'scss/includes/_dogs.sass', 'scss/inheritance.scss', ]; - if (COMPILER === 'sass') expectedSourcesAfter.push('scss/inheritance.css'); + const expectedSourcesBefore = [ + 'scss/data:', + 'scss/includes/_cats.scss', + 'scss/includes/_dogs.sass', + ]; + + const expectedSourcesAfter = [ + 'scss/includes/_cats.scss', + 'scss/includes/_dogs.sass', + 'scss/data:', + ]; + + if (MODERN_COMPILER) { + const result = 'scss/inheritance.css'; + legacyExpectedSourcesAfter.push(result); + expectedSourcesAfter.push(result); + } gulp.src(path.join(__dirname, 'scss', 'inheritance.scss'), { base: 'test' }) .pipe(sourcemaps.init()) .pipe(sass.sync()) .pipe(tap((file) => { assert.ok(file.sourceMap); - assert.deepEqual(file.sourceMap.sources.sort(), expectedSourcesBefore.sort()); + if (LEGACY_API) { + assert.deepEqual(file.sourceMap.sources.sort(), legacyExpectedSourcesBefore.sort()); + } else { + // look for partial matches since each test runner can have a different absolute path + assert.ok(file.sourceMap.sources.every( + (source) => expectedSourcesBefore.find((partial) => source.includes(partial)), + )); + } })) .pipe(postcss([autoprefixer()])) .pipe(tap((file) => { assert.ok(file.sourceMap); - assert.deepEqual(file.sourceMap.sources.sort(), expectedSourcesAfter.sort()); + if (LEGACY_API) { + assert.deepEqual(file.sourceMap.sources.sort(), legacyExpectedSourcesAfter.sort()); + } else { + // look for partial matches since each test runner can have a different absolute path + assert.ok(file.sourceMap.sources.every( + (source) => expectedSourcesAfter.find((partial) => source.includes(partial)), + )); + } })); done(); });