710d7c824745e428ab05e6487d14eb280d7ad48ed0664f9b84b7940648e9939252a45d053fa2cc2def68371f65b6215cfea7f779fe819bae4b1c19025ac00b 19 KB

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