node-sass custom importer for file from the web not working

Viewed 371

I am trying to import a stylesheet from Google Fonts into a stylesheet to avoid the eytra request later on.

My main.scss file starts with

@import "https://fonts.googleapis.com/css2?family=Lato:ital,wght@0,400;1,400;1,700;1,900&family=Source+Code+Pro:wght@400;700&display=swap";

and I expect the resulting main.css to start with

@font-face {
  font-family: 'Lato';
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url(https://fonts.gstatic.com/s/lato/v17/S6u8w4BMUTPHjxswWw.ttf) format('truetype');
}
/* etc */

For this I have the following script:

var scss = require("node-sass");
var https = require('https');

function compile(filePath, dirs, callback){
  scss.render({
    file: filePath,
    includePaths: dirs,
    outputStyle: 'expanded',
    sourceMap: false,
    importer: [
      function(url, prev, done) {
        if (url.startsWith('https')) {
          https.get(url, (res) => {
            let contents = '';
            res.on('data', (d) => contents += d);
            res.on('end', () => {
              console.log('scss')
              done({contents})
            });         
          });
        }
        return null;
      }
    ]
  }, function (e, css) {
    if (error) {
      return callback(error)
    }
    callback(null, css.css)
  });
}

compile('main.scss', null, (e, d)=>{console.log(d.toString())})

The node-sass spec says the content should be delivered back as

An object with the key contents whose value is the contents of a stylesheet (in SCSS syntax). This causes Sass to load that stylesheet’s contents.

I can see that the importer function is called, resolves the request and has the wanted stylesheet text in the contents variable. The output is written to the console, but still has the original @import line.

Where does this go wrong?

Edit: If using node-sass, node crashes with the message Speicherzugriffsfehler. After changing the used library to sass, the crash does not happen, but the error of the not-resolved import remains.

Equally, trying to use a dummy static rule .bla {--blubb: green;} as the returned contents did not work.

1 Answers

I found a solution after examining the dart-sass library a bit closer. It seems URLs that start with the http(s):// protocol are never passed to the importer function.

So you have to trick it a bit, and this is not a generalized solution.

In the main.scss file, shorten the URL of the protocol:

@import "fonts.googleapis.com/css2?family=Lato:ital,wght@0,400;1,400;1,700;1,900&family=Source+Code+Pro:wght@400;700&display=swap";

Then, catch the fonts at the begining in the importer function:

    importer: function(url, prev, done) {
      if (url.startsWith('fonts')) {
        https.get('https://' + url, (res) => {
          let contents = '';
          res.on('data', (d) => contents += d);
          res.on('end', () => done({contents}));
        });
      }
    },

Note an important difference: There is no return null; after the if clause. This was causing the importer result to be passed, despite it loading the file contents correctly.

The whole solution only works with (dart-)sass, not with node-sass, which ends in an infinite loop for some reason.

Related