Skip to content

Commit

Permalink
Add NodePackageImporter by default if available
Browse files Browse the repository at this point in the history
  • Loading branch information
jgerigmeyer committed Feb 23, 2024
1 parent 876eccf commit b2c49ae
Show file tree
Hide file tree
Showing 4 changed files with 81 additions and 53 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
29 changes: 10 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand All @@ -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
Expand Down
11 changes: 11 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand Down
89 changes: 55 additions & 34 deletions test/main.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down Expand Up @@ -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',
});
};
Expand All @@ -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');
});
Expand All @@ -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]');
});
Expand All @@ -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,
Expand Down Expand Up @@ -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',
},
Expand All @@ -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');
Expand All @@ -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', () => {
Expand Down

0 comments on commit b2c49ae

Please sign in to comment.