From b2c49ae31680e9d29e8bc027bb6890a8b9aaa9c7 Mon Sep 17 00:00:00 2001 From: Jonny Gerig Meyer Date: Fri, 23 Feb 2024 11:00:08 -0500 Subject: [PATCH] Add NodePackageImporter by default if available --- CHANGELOG.md | 5 +++ README.md | 29 ++++++--------- src/index.ts | 11 ++++++ test/main.test.js | 89 +++++++++++++++++++++++++++++------------------ 4 files changed, 81 insertions(+), 53 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a376081..886ddd1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,11 @@ - FEATURE: Add True `sass` option (`string` or Sass implementation instance, defaults to `'sass'`) to allow using either `sass` or `embedded-sass`. +- FEATURE: Add the + [Node.js package importer](https://sass-lang.com/documentation/js-api/classes/nodepackageimporter/) + to the Sass `importers` option by default, if Dart Sass v1.71 or later is + available. Users can opt out by providing their own `importers` option, e.g. + `{ importers: [] }`. - BREAKING: Drop support for node < 18 - INTERNAL: Remove `sass` as a peer-dependency. - INTERNAL: Update dependencies diff --git a/README.md b/README.md index f7452c3..e4969d9 100644 --- a/README.md +++ b/README.md @@ -223,9 +223,16 @@ To pass in a source string (and use `sass.compileString`), add `sourceType: The third (optional) argument to `runSass` accepts the [same options](https://sass-lang.com/documentation/js-api/interfaces/Options) that -Sass' `compile` or `compileString` expect, and these are passed directly through -to Sass. The only modification `runSass` makes is to add True's sass path to the -`loadPaths` option, so `@use 'true';` works in your Sass test file. +Sass' `compile` or `compileString` expect (e.g. `importers`, `loadPaths`, or +`style`), and these are passed directly through to Sass. + +By default, True makes two modifications to these options. First, True's sass +path is added to the `loadPaths` option, so `@use 'true';` works in your Sass +test file. Second, if Dart Sass v1.71 or greater is installed, `importers` is +set to an array containing the [Node.js package importer][pkg-importer], which +supports `pkg:` imports to resolve `@use` and `@import` for external modules +installed via npm or Yarn. If `importers` is set (even as an empty array +`importers: []`), it will override this default importer. **Note:** True requires the [default Sass `'expanded'` output style](https://sass-lang.com/documentation/js-api/modules#OutputStyle), @@ -234,22 +241,6 @@ and will not work if `{ style: 'compressed' }` is used in the third argument to ### Custom Importers -If you use the Dart Sass [Node.js package importer][pkg-importer] (e.g. `@use -'pkg:accoutrement'`), you'll need to include `NodePackageImporter` in the -options passed to `runSass`: - -```js -const path = require('node:path'); -const { pathToFileURL } = require('node:url'); -const { NodePackageImporter } = require('sass'); -const sassTrue = require('sass-true'); - -const sassFile = path.join(__dirname, 'test.scss'); -sassTrue.runSass({ describe, it }, sassFile, { - importers: [new NodePackageImporter()], -}); -``` - If you use tilde notation (e.g. `@use '~accoutrement/sass/tools'`) or another method for importing Sass files from `node_modules`, you'll need to tell `runSass` how to handle that. That will require writing a [custom diff --git a/src/index.ts b/src/index.ts index edf616e..df0c28d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -73,6 +73,8 @@ export const runSass = function ( ) { const trueOpts = Object.assign({}, trueOptions); const sassOpts = Object.assign({}, sassOptions); + + // Add True's sass to `loadPaths` const sassPath = path.join(__dirname, '..', 'sass'); if (sassOpts.loadPaths) { sassOpts.loadPaths.push(sassPath); @@ -111,6 +113,15 @@ export const runSass = function ( } } + // Add the Sass Node.js package importer, if available + if (!sassOpts.importers) { + try { + sassOpts.importers = [new compiler.NodePackageImporter()]; + } catch (err) { + // skip if `NodePackageImporter` isn't available + } + } + const compilerFn = trueOpts.sourceType === 'string' ? 'compileString' : 'compile'; const parsedCss = compiler[compilerFn](src, sassOpts).css; diff --git a/test/main.test.js b/test/main.test.js index 8dfbeb3..260494d 100644 --- a/test/main.test.js +++ b/test/main.test.js @@ -14,6 +14,11 @@ if (process.env.USE_BUILT) { sassTrue = require('../src'); } +const mock = function (name, cb) { + cb(); +}; +const trueOpts = { describe: mock, it: mock }; + describe('#fail', () => { it('formats failure message', () => { const msg = sassTrue.formatFailureMessage({ @@ -49,11 +54,8 @@ describe('#runSass', () => { ' }', '}', ].join('\n'); - const mock = function (name, cb) { - cb(); - }; const attempt = function () { - sassTrue.runSass({ describe: mock, it: mock }, sass, { + sassTrue.runSass(trueOpts, sass, { style: 'compressed', }); }; @@ -78,11 +80,8 @@ describe('#runSass', () => { ' }', '}', ].join('\n'); - const mock = function (name, cb) { - cb(); - }; const attempt = function () { - sassTrue.runSass({ data: sass }, { describe: mock, it: mock }); + sassTrue.runSass({ data: sass }, trueOpts); }; expect(attempt).to.throw('do not match the new API'); }); @@ -96,14 +95,8 @@ describe('#runSass', () => { ' }', '}', ].join('\n'); - const mock = function (name, cb) { - cb(); - }; const attempt = function () { - sassTrue.runSass( - { describe: mock, it: mock, sourceType: 'string' }, - sass, - ); + sassTrue.runSass({ ...trueOpts, sourceType: 'string' }, sass); }; expect(attempt).to.throw('This test is meant to fail. [type: assert-true]'); }); @@ -125,14 +118,10 @@ describe('#runSass', () => { ' }', '}', ].join('\n'); - const mock = function (name, cb) { - cb(); - }; const attempt = function () { sassTrue.runSass( { - describe: mock, - it: mock, + ...trueOpts, sourceType: 'string', }, sass, @@ -160,14 +149,10 @@ describe('#runSass', () => { ' }', '}', ].join('\n'); - const mock = function (name, cb) { - cb(); - }; const attempt = function () { sassTrue.runSass( { - describe: mock, - it: mock, + ...trueOpts, sourceType: 'string', sass: 'sass-embedded', }, @@ -178,14 +163,10 @@ describe('#runSass', () => { }); it('can specify sass implementation to use [object]', () => { - const mock = function (name, cb) { - cb(); - }; const attempt = function () { sassTrue.runSass( { - describe: mock, - it: mock, + ...trueOpts, sass: { compile() { throw new Error('Custom sass implementation called'); @@ -199,14 +180,54 @@ describe('#runSass', () => { }); it('throws if sass implementation is not found', () => { - const mock = function (name, cb) { - cb(); - }; const attempt = function () { - sassTrue.runSass({ describe: mock, it: mock, sass: 'foobar' }, ''); + sassTrue.runSass({ ...trueOpts, sass: 'foobar' }, ''); }; expect(attempt).to.throw('Cannot find Dart Sass (`foobar`) dependency.'); }); + + it('adds NodePackageImporter by default', () => { + const attempt = function () { + sassTrue.runSass( + { + ...trueOpts, + sass: { + NodePackageImporter: class {}, + compile(src, opts) { + if (opts.importers[0] instanceof this.NodePackageImporter) { + throw new Error('NodePackageImporter added'); + } + throw new Error('failed'); + }, + }, + }, + '', + ); + }; + expect(attempt).to.throw('NodePackageImporter added'); + }); + + it('skips NodePackageImporter if `importers` option is provided', () => { + const attempt = function () { + sassTrue.runSass( + { + ...trueOpts, + sass: { + NodePackageImporter: class {}, + compile(src, opts) { + if (opts.importers[0] instanceof this.NodePackageImporter) { + throw new Error('NodePackageImporter added'); + } + throw new Error('failed'); + }, + }, + }, + '', + { importers: [] }, + ); + }; + expect(attempt).to.throw('failed'); + }); }); describe('#parse', () => {