Ship a built-in package importer
See original GitHub issueThere’s currently no standard way for Sass users to import packages from dependencies. There are a number of domain-specific solutions, such as Webpack’s tilde-based imports, npm-sass
’s load-path-like functionality, or Dart Sass’s support for Dart’s package:
URLs.
Most of these have the disadvantage of needing to be explicitly opted-into, and all of them make it difficult to share stylesheets across different contexts. For example, if package A depends on package B, how can A import B in a way that will work in both Webpack and in Dart?
This is the kind of situation where centralization is a boon. If we can build into the Sass language a notion of a “package import” that’s flexible enough to work across contexts, we can make it usable by all stylesheets with confidence that even if they’re used by different build runners or even ported to a different package manager their dependencies will continue to work.
What does it look like?
The Sass specification talks about imports in terms of URLs. The current JS API deals with them as a combination of raw strings and filesystem paths, but I’d like to move away from that as part of #2509.
The current most popular solution for package imports is probably sass-loader
’s, which passes any import that begins with ~
through Webpack’s built-in resolution infrastructure. We could re-use this syntax, but it doesn’t work well with URL semantics. A string beginning with a tilde is syntactically a relative URL, which means we’d need to check for the relative path ~package
first before passing the URL to the package importer.
We’d have a similar problem if we automatically added node_modules
to the load path. Every instance of @use "bootstrap"
would need to check the relative path first, as well as potentially every load path, before checking the package importer. It also makes package stylesheets less visually distinctive, which can be confusing for readers.
As such, I propose that we use the URL scheme pkg:
to indicate Sass package imports, so users would write @use "pkg:bootstrap"
. This doesn’t conflict with any existing syntax and so producing backwards-compatibility headaches, and it nicely mirrors the syntax of Sass’s core libraries (@use "sass:color"
).
What does it do?
The purpose of a standard package importer is in fact to avoid specifying the exact behavior of the importer, so that it can do something sensible for each context. However, since Node.js is by far the most popular context in which Sass is used, we should figure out what it does in that particular case.
When resolving a URL, I think it should check node_modules
folders as described in Node’s documentation, beginning from the parent directory of the module in which Sass is invoked (if it’s invoked as a library from JS) or the parent directory of the entrypoint file (if Sass is invoked via the command line).
There’s a convention of npm packages declaring their Sass entrypoints using "style"
or "sass"
keys in their package.json
. While it’s definitely useful to be able to write @import "bootstrap"
rather than @import "bootstrap/scss/bootstrap"
, I actually think we shouldn’t support this. Instead, I think we should encourage packages to define an _index.scss
file that acts as the entrypoint. This will ensure that even if the package ends up on the load path, or installed through some way other than node_modules
, it can still be imported correctly without needing to parse package.json
.
Issue Analytics
- State:
- Created 4 years ago
- Reactions:35
- Comments:22 (7 by maintainers)
Top GitHub Comments
Supporting conditional exports would be nice too, so my library can specify
to make
@use 'library/styles'
load the scss file butimport 'library/styles'
load precompiled css instead.@Cleecanth One of the major goals of this plan is to avoid tightly coupling source files to particular host environments. If users are writing
npm:
ornode:
in their@use
URLs, then the files that contain those URLs are only usable in an npm or Node context.The point of a
pkg:
scheme is that it can be resolved differently in different contexts. If you’re using npm, it can be resolved using thenode_modules
directory, but if you’re using Ruby it can be resolved using Bundler logic or whatever.