马宇豪
2024-07-16 f591c27b57e2418c9495bc02ae8cfff84d35bc18
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
import {Syntax} from './options';
import {PromiseOr} from './util/promise_or';
 
/**
 * A special type of importer that redirects all loads to existing files on
 * disk. Although this is less powerful than a full [[Importer]], it
 * automatically takes care of Sass features like resolving partials and file
 * extensions and of loading the file from disk.
 *
 * Like all importers, this implements custom Sass loading logic for [`@use`
 * rules](https://sass-lang.com/documentation/at-rules/use) and [`@import`
 * rules](https://sass-lang.com/documentation/at-rules/import). It can be passed
 * to [[Options.importers]] or [[StringOptionsWithImporter.importer]].
 *
 * @typeParam sync - A `FileImporter<'sync'>`'s [[findFileUrl]] must return
 * synchronously, but in return it can be passed to [[compile]] and
 * [[compileString]] in addition to [[compileAsync]] and [[compileStringAsync]].
 *
 * A `FileImporter<'async'>`'s [[findFileUrl]] may either return synchronously
 * or asynchronously, but it can only be used with [[compileAsync]] and
 * [[compileStringAsync]].
 *
 * @example
 *
 * ```js
 * const {pathToFileURL} = require('url');
 *
 * sass.compile('style.scss', {
 *   importers: [{
 *     // An importer that redirects relative URLs starting with "~" to
 *     // `node_modules`.
 *     findFileUrl(url) {
 *       if (!url.startsWith('~')) return null;
 *       return new URL(url.substring(1), pathToFileURL('node_modules'));
 *     }
 *   }]
 * });
 * ```
 *
 * @category Importer
 */
export interface FileImporter<
  sync extends 'sync' | 'async' = 'sync' | 'async'
> {
  /**
   * A callback that's called to partially resolve a load (such as
   * [`@use`](https://sass-lang.com/documentation/at-rules/use) or
   * [`@import`](https://sass-lang.com/documentation/at-rules/import)) to a file
   * on disk.
   *
   * Unlike an [[Importer]], the compiler will automatically handle relative
   * loads for a [[FileImporter]]. See [[Options.importers]] for more details on
   * the way loads are resolved.
   *
   * @param url - The loaded URL. Since this might be relative, it's represented
   * as a string rather than a [[URL]] object.
   *
   * @param options.fromImport - Whether this is being invoked because of a Sass
   * `@import` rule, as opposed to a `@use` or `@forward` rule.
   *
   * This should *only* be used for determining whether or not to load
   * [import-only files](https://sass-lang.com/documentation/at-rules/import#import-only-files).
   *
   * @returns An absolute `file:` URL if this importer recognizes the `url`.
   * This may be only partially resolved: the compiler will automatically look
   * for [partials](https://sass-lang.com/documentation/at-rules/use#partials),
   * [index files](https://sass-lang.com/documentation/at-rules/use#index-files),
   * and file extensions based on the returned URL. An importer may also return
   * a fully resolved URL if it so chooses.
   *
   * If this importer doesn't recognize the URL, it should return `null` instead
   * to allow other importers or {@link Options.loadPaths | load paths} to
   * handle it.
   *
   * This may also return a `Promise`, but if it does the importer may only be
   * passed to [[compileAsync]] and [[compileStringAsync]], not [[compile]] or
   * [[compileString]].
   *
   * @throws any - If this importer recognizes `url` but determines that it's
   * invalid, it may throw an exception that will be wrapped by Sass. If the
   * exception object has a `message` property, it will be used as the wrapped
   * exception's message; otherwise, the exception object's `toString()` will be
   * used. This means it's safe for importers to throw plain strings.
   */
  findFileUrl(
    url: string,
    options: {fromImport: boolean}
  ): PromiseOr<URL | null, sync>;
 
  /** @hidden */
  canonicalize?: never;
}
 
/**
 * An object that implements custom Sass loading logic for [`@use`
 * rules](https://sass-lang.com/documentation/at-rules/use) and [`@import`
 * rules](https://sass-lang.com/documentation/at-rules/import). It can be passed
 * to [[Options.importers]] or [[StringOptionsWithImporter.importer]].
 *
 * Importers that simply redirect to files on disk are encouraged to use the
 * [[FileImporter]] interface instead.
 *
 * See [[Options.importers]] for more details on the way loads are resolved.
 *
 * @typeParam sync - An `Importer<'sync'>`'s [[canonicalize]] and [[load]] must
 * return synchronously, but in return it can be passed to [[compile]] and
 * [[compileString]] in addition to [[compileAsync]] and [[compileStringAsync]].
 *
 * An `Importer<'async'>`'s [[canonicalize]] and [[load]] may either return
 * synchronously or asynchronously, but it can only be used with
 * [[compileAsync]] and [[compileStringAsync]].
 *
 * @example Resolving a Load
 *
 * This is the process of resolving a load using a custom importer:
 *
 * - The compiler encounters `@use "db:foo/bar/baz"`.
 * - It calls [[canonicalize]] with `"db:foo/bar/baz"`.
 * - [[canonicalize]] returns `new URL("db:foo/bar/baz/_index.scss")`.
 * - If the compiler has already loaded a stylesheet with this canonical URL, it
 *   re-uses the existing module.
 * - Otherwise, it calls [[load]] with `new URL("db:foo/bar/baz/_index.scss")`.
 * - [[load]] returns an [[ImporterResult]] that the compiler uses as the
 *   contents of the module.
 *
 * @example Code Sample
 *
 * ```js
 * sass.compile('style.scss', {
 *   // An importer for URLs like `bgcolor:orange` that generates a
 *   // stylesheet with the given background color.
 *   importers: [{
 *     canonicalize(url) {
 *       if (!url.startsWith('bgcolor:')) return null;
 *       return new URL(url);
 *     },
 *     load(canonicalUrl) {
 *       return {
 *         contents: `body {background-color: ${canonicalUrl.pathname}}`,
 *         syntax: 'scss'
 *       };
 *     }
 *   }]
 * });
 * ```
 *
 * @category Importer
 */
export interface Importer<sync extends 'sync' | 'async' = 'sync' | 'async'> {
  /**
   * If `url` is recognized by this importer, returns its canonical format.
   *
   * If Sass has already loaded a stylesheet with the returned canonical URL, it
   * re-uses the existing parse tree (and the loaded module for `@use`). This
   * means that importers **must ensure** that the same canonical URL always
   * refers to the same stylesheet, *even across different importers*. As such,
   * importers are encouraged to use unique URL schemes to disambiguate between
   * one another.
   *
   * As much as possible, custom importers should canonicalize URLs the same way
   * as the built-in filesystem importer:
   *
   * - The importer should look for stylesheets by adding the prefix `_` to the
   *   URL's basename, and by adding the extensions `.sass` and `.scss` if the
   *   URL doesn't already have one of those extensions. For example, if the
   *   URL was `foo/bar/baz`, the importer would look for:
   *   - `foo/bar/baz.sass`
   *   - `foo/bar/baz.scss`
   *   - `foo/bar/_baz.sass`
   *   - `foo/bar/_baz.scss`
   *
   *   If the URL was `foo/bar/baz.scss`, the importer would just look for:
   *   - `foo/bar/baz.scss`
   *   - `foo/bar/_baz.scss`
   *
   *   If the importer finds a stylesheet at more than one of these URLs, it
   *   should throw an exception indicating that the URL is ambiguous. Note that
   *   if the extension is explicitly specified, a stylesheet with the opposite
   *   extension is allowed to exist.
   *
   * - If none of the possible paths is valid, the importer should perform the
   *   same resolution on the URL followed by `/index`. In the example above,
   *   it would look for:
   *   - `foo/bar/baz/index.sass`
   *   - `foo/bar/baz/index.scss`
   *   - `foo/bar/baz/_index.sass`
   *   - `foo/bar/baz/_index.scss`
   *
   *   As above, if the importer finds a stylesheet at more than one of these
   *   URLs, it should throw an exception indicating that the import is
   *   ambiguous.
   *
   * If no stylesheets are found, the importer should return `null`.
   *
   * Calling [[canonicalize]] multiple times with the same URL must return the
   * same result. Calling [[canonicalize]] with a URL returned by a previous
   * call to [[canonicalize]] must return that URL.
   *
   * Relative loads in stylesheets loaded from an importer are handled by
   * resolving the loaded URL relative to the canonical URL of the stylesheet
   * that contains it, and passing that URL back to the importer's
   * [[canonicalize]] method. For example, suppose the "Resolving a Load"
   * example {@link Importer | above} returned a stylesheet that contained
   * `@use "mixins"`:
   *
   * - The compiler resolves the URL `mixins` relative to the current
   *   stylesheet's canonical URL `db:foo/bar/baz/_index.scss` to get
   *   `db:foo/bar/baz/mixins`.
   * - It calls [[canonicalize]] with `"db:foo/bar/baz/mixins"`.
   * - [[canonicalize]] returns `new URL("db:foo/bar/baz/_mixins.scss")`.
   *
   * Because of this, [[canonicalize]] must return a meaningful result when
   * called with a URL relative to one returned by an earlier call to
   * [[canonicalize]].
   *
   * @param url - The loaded URL. Since this might be relative, it's represented
   * as a string rather than a [[URL]] object.
   *
   * @param options.fromImport - Whether this is being invoked because of a Sass
   * `@import` rule, as opposed to a `@use` or `@forward` rule.
   *
   * This should *only* be used for determining whether or not to load
   * [import-only files](https://sass-lang.com/documentation/at-rules/import#import-only-files).
   *
   * @returns An absolute URL if this importer recognizes the `url`, or `null`
   * if it doesn't. If this returns `null`, other importers or {@link
   * Options.loadPaths | load paths} may handle the load.
   *
   * This may also return a `Promise`, but if it does the importer may only be
   * passed to [[compileAsync]] and [[compileStringAsync]], not [[compile]] or
   * [[compileString]].
   *
   * @throws any - If this importer recognizes `url` but determines that it's
   * invalid, it may throw an exception that will be wrapped by Sass. If the
   * exception object has a `message` property, it will be used as the wrapped
   * exception's message; otherwise, the exception object's `toString()` will be
   * used. This means it's safe for importers to throw plain strings.
   */
  canonicalize(
    url: string,
    options: {fromImport: boolean}
  ): PromiseOr<URL | null, sync>;
 
  /**
   * Loads the Sass text for the given `canonicalUrl`, or returns `null` if this
   * importer can't find the stylesheet it refers to.
   *
   * @param canonicalUrl - The canonical URL of the stylesheet to load. This is
   * guaranteed to come from a call to [[canonicalize]], although not every call
   * to [[canonicalize]] will result in a call to [[load]].
   *
   * @returns The contents of the stylesheet at `canonicalUrl` if it can be
   * loaded, or `null` if it can't.
   *
   * This may also return a `Promise`, but if it does the importer may only be
   * passed to [[compileAsync]] and [[compileStringAsync]], not [[compile]] or
   * [[compileString]].
   *
   * @throws any - If this importer finds a stylesheet at `url` but it fails to
   * load for some reason, or if `url` is uniquely associated with this importer
   * but doesn't refer to a real stylesheet, the importer may throw an exception
   * that will be wrapped by Sass. If the exception object has a `message`
   * property, it will be used as the wrapped exception's message; otherwise,
   * the exception object's `toString()` will be used. This means it's safe for
   * importers to throw plain strings.
   */
  load(canonicalUrl: URL): PromiseOr<ImporterResult | null, sync>;
 
  /** @hidden */
  findFileUrl?: never;
}
 
/**
 * The result of successfully loading a stylesheet with an [[Importer]].
 *
 * @category Importer
 */
export interface ImporterResult {
  /** The contents of the stylesheet. */
  contents: string;
 
  /** The syntax with which to parse [[contents]]. */
  syntax: Syntax;
 
  /**
   * The URL to use to link to the loaded stylesheet's source code in source
   * maps. A `file:` URL is ideal because it's accessible to both browsers and
   * other build tools, but an `http:` URL is also acceptable.
   *
   * If this isn't set, it defaults to a `data:` URL that contains the contents
   * of the loaded stylesheet.
   */
  sourceMapUrl?: URL;
}