UNPKG

18.8 kBTypeScriptView Raw
1import {Syntax} from './options';
2import {PromiseOr} from './util/promise_or';
3
4/**
5 * Contextual information passed to {@link Importer.canonicalize} and {@link
6 * FileImporter.findFileUrl}. Not all importers will need this information to
7 * resolve loads, but some may find it useful.
8 */
9export interface CanonicalizeContext {
10 /**
11 * Whether this is being invoked because of a Sass
12 * `@import` rule, as opposed to a `@use` or `@forward` rule.
13 *
14 * This should *only* be used for determining whether or not to load
15 * [import-only files](https://sass-lang.com/documentation/at-rules/import#import-only-files).
16 */
17 fromImport: boolean;
18
19 /**
20 * The canonical URL of the file that contains the load, if that information
21 * is available.
22 *
23 * For an {@link Importer}, this is only passed when the `url` parameter is a
24 * relative URL _or_ when its [URL scheme] is included in {@link
25 * Importer.nonCanonicalScheme}. This ensures that canonical URLs are always
26 * resolved the same way regardless of context.
27 *
28 * [URL scheme]: https://developer.mozilla.org/en-US/docs/Learn/Common_questions/Web_mechanics/What_is_a_URL#scheme
29 *
30 * For a {@link FileImporter}, this is always available as long as Sass knows
31 * the canonical URL of the containing file.
32 */
33 containingUrl: URL | null;
34}
35
36/**
37 * A special type of importer that redirects all loads to existing files on
38 * disk. Although this is less powerful than a full {@link Importer}, it
39 * automatically takes care of Sass features like resolving partials and file
40 * extensions and of loading the file from disk.
41 *
42 * Like all importers, this implements custom Sass loading logic for [`@use`
43 * rules](https://sass-lang.com/documentation/at-rules/use) and [`@import`
44 * rules](https://sass-lang.com/documentation/at-rules/import). It can be passed
45 * to {@link Options.importers} or {@link StringOptions.importer}.
46 *
47 * @typeParam sync - A `FileImporter<'sync'>`'s {@link findFileUrl} must return
48 * synchronously, but in return it can be passed to {@link compile} and {@link
49 * compileString} in addition to {@link compileAsync} and {@link
50 * compileStringAsync}.
51 *
52 * A `FileImporter<'async'>`'s {@link findFileUrl} may either return
53 * synchronously or asynchronously, but it can only be used with {@link
54 * compileAsync} and {@link compileStringAsync}.
55 *
56 * @example
57 *
58 * ```js
59 * const {pathToFileURL} = require('url');
60 *
61 * sass.compile('style.scss', {
62 * importers: [{
63 * // An importer that redirects relative URLs starting with "~" to
64 * // `node_modules`.
65 * findFileUrl(url) {
66 * if (!url.startsWith('~')) return null;
67 * return new URL(url.substring(1), pathToFileURL('node_modules'));
68 * }
69 * }]
70 * });
71 * ```
72 *
73 * @category Importer
74 */
75export interface FileImporter<
76 sync extends 'sync' | 'async' = 'sync' | 'async'
77> {
78 /**
79 * A callback that's called to partially resolve a load (such as
80 * [`@use`](https://sass-lang.com/documentation/at-rules/use) or
81 * [`@import`](https://sass-lang.com/documentation/at-rules/import)) to a file
82 * on disk.
83 *
84 * Unlike an {@link Importer}, the compiler will automatically handle relative
85 * loads for a {@link FileImporter}. See {@link Options.importers} for more
86 * details on the way loads are resolved.
87 *
88 * @param url - The loaded URL. Since this might be relative, it's represented
89 * as a string rather than a {@link URL} object.
90 *
91 * @returns An absolute `file:` URL if this importer recognizes the `url`.
92 * This may be only partially resolved: the compiler will automatically look
93 * for [partials](https://sass-lang.com/documentation/at-rules/use#partials),
94 * [index files](https://sass-lang.com/documentation/at-rules/use#index-files),
95 * and file extensions based on the returned URL. An importer may also return
96 * a fully resolved URL if it so chooses.
97 *
98 * If this importer doesn't recognize the URL, it should return `null` instead
99 * to allow other importers or {@link Options.loadPaths | load paths} to
100 * handle it.
101 *
102 * This may also return a `Promise`, but if it does the importer may only be
103 * passed to {@link compileAsync} and {@link compileStringAsync}, not {@link
104 * compile} or {@link compileString}.
105 *
106 * @throws any - If this importer recognizes `url` but determines that it's
107 * invalid, it may throw an exception that will be wrapped by Sass. If the
108 * exception object has a `message` property, it will be used as the wrapped
109 * exception's message; otherwise, the exception object's `toString()` will be
110 * used. This means it's safe for importers to throw plain strings.
111 */
112 findFileUrl(
113 url: string,
114 context: CanonicalizeContext
115 ): PromiseOr<URL | null, sync>;
116
117 /** @hidden */
118 canonicalize?: never;
119}
120
121/**
122 * An object that implements custom Sass loading logic for [`@use`
123 * rules](https://sass-lang.com/documentation/at-rules/use) and [`@import`
124 * rules](https://sass-lang.com/documentation/at-rules/import). It can be passed
125 * to {@link Options.importers} or {@link StringOptions.importer}.
126 *
127 * Importers that simply redirect to files on disk are encouraged to use the
128 * {@link FileImporter} interface instead.
129 *
130 * ### Resolving a Load
131 *
132 * This is the process of resolving a load using a custom importer:
133 *
134 * - The compiler encounters `@use "db:foo/bar/baz"`.
135 * - It calls {@link canonicalize} with `"db:foo/bar/baz"`.
136 * - {@link canonicalize} returns `new URL("db:foo/bar/baz/_index.scss")`.
137 * - If the compiler has already loaded a stylesheet with this canonical URL, it
138 * re-uses the existing module.
139 * - Otherwise, it calls {@link load} with `new
140 * URL("db:foo/bar/baz/_index.scss")`.
141 * - {@link load} returns an {@link ImporterResult} that the compiler uses as
142 * the contents of the module.
143 *
144 * See {@link Options.importers} for more details on the way loads are resolved
145 * using multiple importers and load paths.
146 *
147 * @typeParam sync - An `Importer<'sync'>`'s {@link canonicalize} and {@link
148 * load} must return synchronously, but in return it can be passed to {@link
149 * compile} and {@link compileString} in addition to {@link compileAsync} and
150 * {@link compileStringAsync}.
151 *
152 * An `Importer<'async'>`'s {@link canonicalize} and {@link load} may either
153 * return synchronously or asynchronously, but it can only be used with {@link
154 * compileAsync} and {@link compileStringAsync}.
155 *
156 * @example
157 *
158 * ```js
159 * sass.compile('style.scss', {
160 * // An importer for URLs like `bgcolor:orange` that generates a
161 * // stylesheet with the given background color.
162 * importers: [{
163 * canonicalize(url) {
164 * if (!url.startsWith('bgcolor:')) return null;
165 * return new URL(url);
166 * },
167 * load(canonicalUrl) {
168 * return {
169 * contents: `body {background-color: ${canonicalUrl.pathname}}`,
170 * syntax: 'scss'
171 * };
172 * }
173 * }]
174 * });
175 * ```
176 *
177 * @category Importer
178 */
179export interface Importer<sync extends 'sync' | 'async' = 'sync' | 'async'> {
180 /**
181 * If `url` is recognized by this importer, returns its canonical format.
182 *
183 * If Sass has already loaded a stylesheet with the returned canonical URL, it
184 * re-uses the existing parse tree (and the loaded module for `@use`). This
185 * means that importers **must ensure** that the same canonical URL always
186 * refers to the same stylesheet, *even across different importers*. As such,
187 * importers are encouraged to use unique URL schemes to disambiguate between
188 * one another.
189 *
190 * As much as possible, custom importers should canonicalize URLs the same way
191 * as the built-in filesystem importer:
192 *
193 * - The importer should look for stylesheets by adding the prefix `_` to the
194 * URL's basename, and by adding the extensions `.sass` and `.scss` if the
195 * URL doesn't already have one of those extensions. For example, if the
196 * URL was `foo/bar/baz`, the importer would look for:
197 * - `foo/bar/baz.sass`
198 * - `foo/bar/baz.scss`
199 * - `foo/bar/_baz.sass`
200 * - `foo/bar/_baz.scss`
201 *
202 * If the URL was `foo/bar/baz.scss`, the importer would just look for:
203 * - `foo/bar/baz.scss`
204 * - `foo/bar/_baz.scss`
205 *
206 * If the importer finds a stylesheet at more than one of these URLs, it
207 * should throw an exception indicating that the URL is ambiguous. Note that
208 * if the extension is explicitly specified, a stylesheet with the opposite
209 * extension is allowed to exist.
210 *
211 * - If none of the possible paths is valid, the importer should perform the
212 * same resolution on the URL followed by `/index`. In the example above,
213 * it would look for:
214 * - `foo/bar/baz/index.sass`
215 * - `foo/bar/baz/index.scss`
216 * - `foo/bar/baz/_index.sass`
217 * - `foo/bar/baz/_index.scss`
218 *
219 * As above, if the importer finds a stylesheet at more than one of these
220 * URLs, it should throw an exception indicating that the import is
221 * ambiguous.
222 *
223 * If no stylesheets are found, the importer should return `null`.
224 *
225 * Calling {@link canonicalize} multiple times with the same URL must return
226 * the same result. Calling {@link canonicalize} with a URL returned by a
227 * previous call to {@link canonicalize} must return that URL.
228 *
229 * Relative loads in stylesheets loaded from an importer are handled by
230 * resolving the loaded URL relative to the canonical URL of the stylesheet
231 * that contains it, and passing that URL back to the importer's {@link
232 * canonicalize} method. For example, suppose the "Resolving a Load" example
233 * {@link Importer | above} returned a stylesheet that contained `@use
234 * "mixins"`:
235 *
236 * - The compiler resolves the URL `mixins` relative to the current
237 * stylesheet's canonical URL `db:foo/bar/baz/_index.scss` to get
238 * `db:foo/bar/baz/mixins`.
239 * - It calls {@link canonicalize} with `"db:foo/bar/baz/mixins"`.
240 * - {@link canonicalize} returns `new URL("db:foo/bar/baz/_mixins.scss")`.
241 *
242 * Because of this, {@link canonicalize} must return a meaningful result when
243 * called with a URL relative to one returned by an earlier call to {@link
244 * canonicalize}.
245 *
246 * @param url - The loaded URL. Since this might be relative, it's represented
247 * as a string rather than a {@link URL} object.
248 *
249 * @returns An absolute URL if this importer recognizes the `url`, or `null`
250 * if it doesn't. If this returns `null`, other importers or {@link
251 * Options.loadPaths | load paths} may handle the load.
252 *
253 * This may also return a `Promise`, but if it does the importer may only be
254 * passed to {@link compileAsync} and {@link compileStringAsync}, not {@link
255 * compile} or {@link compileString}.
256 *
257 * @throws any - If this importer recognizes `url` but determines that it's
258 * invalid, it may throw an exception that will be wrapped by Sass. If the
259 * exception object has a `message` property, it will be used as the wrapped
260 * exception's message; otherwise, the exception object's `toString()` will be
261 * used. This means it's safe for importers to throw plain strings.
262 */
263 canonicalize(
264 url: string,
265 context: CanonicalizeContext
266 ): PromiseOr<URL | null, sync>;
267
268 /**
269 * Loads the Sass text for the given `canonicalUrl`, or returns `null` if this
270 * importer can't find the stylesheet it refers to.
271 *
272 * @param canonicalUrl - The canonical URL of the stylesheet to load. This is
273 * guaranteed to come from a call to {@link canonicalize}, although not every
274 * call to {@link canonicalize} will result in a call to {@link load}.
275 *
276 * @returns The contents of the stylesheet at `canonicalUrl` if it can be
277 * loaded, or `null` if it can't.
278 *
279 * This may also return a `Promise`, but if it does the importer may only be
280 * passed to {@link compileAsync} and {@link compileStringAsync}, not {@link
281 * compile} or {@link compileString}.
282 *
283 * @throws any - If this importer finds a stylesheet at `url` but it fails to
284 * load for some reason, or if `url` is uniquely associated with this importer
285 * but doesn't refer to a real stylesheet, the importer may throw an exception
286 * that will be wrapped by Sass. If the exception object has a `message`
287 * property, it will be used as the wrapped exception's message; otherwise,
288 * the exception object's `toString()` will be used. This means it's safe for
289 * importers to throw plain strings.
290 */
291 load(canonicalUrl: URL): PromiseOr<ImporterResult | null, sync>;
292
293 /** @hidden */
294 findFileUrl?: never;
295
296 /**
297 * A URL scheme or set of schemes (without the `:`) that this importer
298 * promises never to use for URLs returned by {@link canonicalize}. If it does
299 * return a URL with one of these schemes, that's an error.
300 *
301 * If this is set, any call to canonicalize for a URL with a non-canonical
302 * scheme will be passed {@link CanonicalizeContext.containingUrl} if it's
303 * known.
304 *
305 * These schemes may only contain lowercase ASCII letters, ASCII numerals,
306 * `+`, `-`, and `.`. They may not be empty.
307 */
308 nonCanonicalScheme?: string | string[];
309}
310
311declare const nodePackageImporterKey: unique symbol;
312
313/**
314 * The built-in Node.js package importer. This loads pkg: URLs from node_modules
315 * according to the standard Node.js resolution algorithm.
316 *
317 * A Node.js package importer is exposed as a class that can be added to the
318 * `importers` option.
319 *
320 *```js
321 * const sass = require('sass');
322 * sass.compileString('@use "pkg:vuetify', {
323 * importers: [new sass.NodePackageImporter()]
324 * });
325 *```
326 *
327 * ## Writing Sass packages
328 *
329 * Package authors can control what is exposed to their users through their
330 * `package.json` manifest. The recommended method is to add a `sass`
331 * conditional export to `package.json`.
332 *
333 * ```json
334 * // node_modules/uicomponents/package.json
335 * {
336 * "exports": {
337 * ".": {
338 * "sass": "./src/scss/index.scss",
339 * "import": "./dist/js/index.mjs",
340 * "default": "./dist/js/index.js"
341 * }
342 * }
343 * }
344 * ```
345 *
346 * This allows a package user to write `@use "pkg:uicomponents"` to load the
347 * file at `node_modules/uicomponents/src/scss/index.scss`.
348 *
349 * The Node.js package importer supports the variety of formats supported by
350 * Node.js [package entry points], allowing authors to expose multiple subpaths.
351 *
352 * [package entry points]:
353 * https://nodejs.org/api/packages.html#package-entry-points
354 *
355 * ```json
356 * // node_modules/uicomponents/package.json
357 * {
358 * "exports": {
359 * ".": {
360 * "sass": "./src/scss/index.scss",
361 * },
362 * "./colors.scss": {
363 * "sass": "./src/scss/_colors.scss",
364 * },
365 * "./theme/*.scss": {
366 * "sass": "./src/scss/theme/*.scss",
367 * },
368 * }
369 * }
370 * ```
371 *
372 * This allows a package user to write:
373 *
374 * - `@use "pkg:uicomponents";` to import the root export.
375 * - `@use "pkg:uicomponents/colors";` to import the colors partial.
376 * - `@use "pkg:uicomponents/theme/purple";` to import a purple theme.
377 *
378 * Note that while library users can rely on the importer to resolve
379 * [partials](https://sass-lang.com/documentation/at-rules/use#partials), [index
380 * files](https://sass-lang.com/documentation/at-rules/use#index-files), and
381 * extensions, library authors must specify the entire file path in `exports`.
382 *
383 * In addition to the `sass` condition, the `style` condition is also
384 * acceptable. Sass will match the `default` condition if it's a relevant file
385 * type, but authors are discouraged from relying on this. Notably, the key
386 * order matters, and the importer will resolve to the first value with a key
387 * that is `sass`, `style`, or `default`, so you should always put `default`
388 * last.
389 *
390 * To help package authors who haven't transitioned to package entry points
391 * using the `exports` field, the Node.js package importer provides several
392 * fallback options. If the `pkg:` URL does not have a subpath, the Node.js
393 * package importer will look for a `sass` or `style` key at the root of
394 * `package.json`.
395 *
396 * ```json
397 * // node_modules/uicomponents/package.json
398 * {
399 * "sass": "./src/scss/index.scss",
400 * }
401 * ```
402 *
403 * This allows a user to write `@use "pkg:uicomponents";` to import the
404 * `index.scss` file.
405 *
406 * Finally, the Node.js package importer will look for an `index` file at the
407 * package root, resolving partials and extensions. For example, if the file
408 * `_index.scss` exists in the package root of `uicomponents`, a user can import
409 * that with `@use "pkg:uicomponents";`.
410 *
411 * If a `pkg:` URL includes a subpath that doesn't have a match in package entry
412 * points, the Node.js importer will attempt to find that file relative to the
413 * package root, resolving for file extensions, partials and index files. For
414 * example, if the file `src/sass/_colors.scss` exists in the `uicomponents`
415 * package, a user can import that file using `@use
416 * "pkg:uicomponents/src/sass/colors";`.
417 *
418 * @compatibility dart: "1.71.0", node: false
419 * @category Importer
420 */
421export class NodePackageImporter {
422 /** Used to distinguish this type from any arbitrary object. */
423 private readonly [nodePackageImporterKey]: true;
424
425 /**
426 * The NodePackageImporter has an optional `entryPointDirectory` option, which
427 * is the directory where the Node Package Importer should start when
428 * resolving `pkg:` URLs in sources other than files on disk. This will be
429 * used as the `parentURL` in the [Node Module
430 * Resolution](https://nodejs.org/api/esm.html#resolution-algorithm-specification)
431 * algorithm.
432 *
433 * In order to be found by the Node Package Importer, a package will need to
434 * be inside a node_modules folder located in the `entryPointDirectory`, or
435 * one of its parent directories, up to the filesystem root.
436 *
437 * Relative paths will be resolved relative to the current working directory.
438 * If a path is not provided, this defaults to the parent directory of the
439 * Node.js entrypoint. If that's not available, this will throw an error.
440 */
441 constructor(entryPointDirectory?: string);
442}
443
444/**
445 * The result of successfully loading a stylesheet with an {@link Importer}.
446 *
447 * @category Importer
448 */
449export interface ImporterResult {
450 /** The contents of the stylesheet. */
451 contents: string;
452
453 /** The syntax with which to parse {@link contents}. */
454 syntax: Syntax;
455
456 /**
457 * The URL to use to link to the loaded stylesheet's source code in source
458 * maps. A `file:` URL is ideal because it's accessible to both browsers and
459 * other build tools, but an `http:` URL is also acceptable.
460 *
461 * If this isn't set, it defaults to a `data:` URL that contains the contents
462 * of the loaded stylesheet.
463 */
464 sourceMapUrl?: URL;
465}