Skip to main
Article
Stacks of a variety of cardboard and food boxes, some leaning precariously.

Setting up Sass pkg: URLs

A quick guide to using the new Node.js package importer

Enabling pkg: URLs is quick and straightforward, and simplifies using packages from the node_modules folder.

Over the years, there have been a variety of methods for resolving third party libraries in Sass. I’ve used includePaths or loadPaths, a preceding ~, or even ../../../../node_modules/ (hoping I get the levels correct and the files never move around).

Sass 1.71 introduces a new standard way, with pkg: URLs. More can be found on the official blog post, and this article shows how quick it is to set it up.

I am currently working on a project creating components in Storybook, using Bootstrap as a base theme. Because of some project requirements, we’re still using Webpack with Storybook, and not the latest versions with Vite. Luckily, this setup makes it easy to use the new pkg: URLs.

First, I needed to install the latest version of Sass, using npm install sass.

In Storybook, Webpack is configured in .storybook/main.ts. There, we need to import the built-in Node.js package importer from sass:

import { NodePackageImporter } from 'sass';

Then, I updated the rule for sass-loader to use the new importer.

// File: .storybook/main.ts
{
  loader: require.resolve('sass-loader'),
  options: {
    implementation: require.resolve('sass'),
    sourceMap: true,
    sassOptions: {
      pkgImporter: new NodePackageImporter(),
    },
  },
},

By default, sass-loader uses the Legacy Sass API, which only supports a single importer. By adding api: 'modern', we can opt into the newer Sass API:

// File: .storybook/main.ts
{
  loader: require.resolve('sass-loader'),
  options: {
    implementation: require.resolve('sass'),
    sourceMap: true,
    api: 'modern',
    sassOptions: {
      importers: [new NodePackageImporter()],
    },
  },
},

Then, all that is left is updating the imported URLs in your Sass files.

Before:

/* File: ./src/styles/index.scss */
@import '../../node_modules/bootstrap/scss/bootstrap';

After:

/* File: ./src/styles/index.scss */
@import 'pkg:bootstrap';

Wait – what happened to the file path? The pkg: importer not only locates the node_modules folder, it also enables some smart path resolution. The key in this case is in Bootstrap’s package.json file:

// File: node_modules/bootstrap/package.json
...
"sass": "scss/bootstrap.scss",
...

The pkg: importer also understands Node.js’s Conditional Exports, which provide library authors a lot of control over how internal files are exposed to users.

Vite-based projects can also easily use the Node.js package importer. Again, I started by updating Sass using npm install sass.

This was a fairly simple Vite project, so I hadn’t created a configuration file at vite.config.ts yet. Once I created that, I made my changes to the css.preprocessorOptions.scss object.

Note:

Changes in the scss object only will affect .scss files. If you’re using the indent-based .sass format instead, you’ll need to add the Node.js package importer to css.preprocessorOptions.sass.

Vite currently only supports the legacy API, so I needed to set the pkgImporter option.

// ./vite.config.ts
import { NodePackageImporter } from 'sass';
import { defineConfig } from 'vite';

export default defineConfig({
  css: {
    preprocessorOptions: {
      scss: {
        pkgImporter: new NodePackageImporter(),
      },
    },
  },
});

With that in place, I can now update my Sass import.

Before:

/* File: ./src/styles/main.scss */
@use 'vuetify' with (

After:

/* File: ./src/styles/main.scss */
@use 'pkg:vuetify' with (

But wait, that’s actually more characters – why would I make that change?

Before, the location of vuetify was being determined by Vite. Adding pkg: moves that logic into Sass. This doesn’t have an immediate impact in this case, but it does make the code more portable. My styles could be shared with someone building with Webpack or ported to a future build tool. When more Package importers become available, this could even be used outside a Node.js module.

Updating your Sass to use pkg: URLs is a fairly straightforward quality of life improvement. I’m looking forward to seeing what can be achieved as more tools take advantage of the standardized syntax.

Recent Articles

  1. A dog zooming by the camera, up-close, body twisted and eyes wide as it circles a grass yard
    Article post type

    Zoom, zoom, and zoom

    The three types of browser (and CSS!) magnification

    I’m working on an article about fluid typography, and relative units. But instead, I fell down this rabbit hole – or a cleverly-disguised trap? – trying to understand ‘zoom’ in the browser (not Zoom™️ the software). Since I couldn’t find any up-to-date articles on the subject, I thought I shoul…

    see all Article posts
  2. A rusty anchor hanging with the sea in the background.
    Article post type

    Updates to the Anchor Position Polyfill

    Catching up to the spec

    Our sponsors are supporting the continued development of the CSS Anchor Positioning Polyfill. Here’s a summary of the latest updates.

    see all Article posts
  3. A back hoe on the bank of the Suez, trying to free the Ever Given cargo ship
    Article post type

    Learn Grid Now, Container Queries Can Wait

    Take your time with new CSS, but don’t sleep on the essentials

    Several people have asked recently why container queries aren’t being used more broadly in production. But I think we underestimate the level of legacy browser support that most companies require to re-write a code-base.

    see all Article posts