canonicalize method
If url
is recognized by this importer, returns its canonical format.
Note that canonical URLs must be absolute, including a scheme. Returning
file:
URLs is encouraged if the imported stylesheet comes from a file on
disk.
If Sass has already loaded a stylesheet with the returned canonical URL, it re-uses the existing parse tree. This means that importers must ensure that the same canonical URL always refers to the same stylesheet, even across different importers.
This may return null
if url
isn't recognized by this importer.
If this importer's URL format supports file extensions, it should canonicalize them the same way as the default 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 wasfoo/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 import is ambiguous. Note that if the extension is explicitly specified, a stylesheet with the opposite extension may 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.sass
foo/bar/baz/_index.scss
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 canonicalize must return that URL. Calling canonicalize with a URL relative to one returned by canonicalize must return a meaningful result.
Implementation
@override
Uri? canonicalize(Uri url) {
if (url.scheme == 'file') return FilesystemImporter.cwd.canonicalize(url);
if (url.scheme != 'pkg') return null;
if (url.hasAuthority) {
throw "A pkg: URL must not have a host, port, username or password.";
} else if (p.url.isAbsolute(url.path)) {
throw "A pkg: URL's path must not begin with /.";
} else if (url.path.isEmpty) {
throw "A pkg: URL must not have an empty path.";
} else if (url.hasQuery || url.hasFragment) {
throw "A pkg: URL must not have a query or fragment.";
}
var baseDirectory = containingUrl?.scheme == 'file'
? p.dirname(p.fromUri(containingUrl!))
: _entryPointDirectory;
var (packageName, subpath) = _packageNameAndSubpath(url.path);
// If the package name is not a valid Node package name, return null in case
// another importer can handle.
if (packageName.startsWith('.') ||
packageName.contains('\\') ||
packageName.contains('%') ||
(packageName.startsWith('@') &&
!packageName.contains(p.url.separator))) {
return null;
}
var packageRoot = _resolvePackageRoot(packageName, baseDirectory);
if (packageRoot == null) return null;
var jsonPath = p.join(packageRoot, 'package.json');
var jsonString = readFile(jsonPath);
Map<String, dynamic> packageManifest;
try {
packageManifest = json.decode(jsonString) as Map<String, dynamic>;
} catch (e) {
throw "Failed to parse $jsonPath for \"pkg:$packageName\": $e";
}
if (_resolvePackageExports(
packageRoot, subpath, packageManifest, packageName)
case var resolved?) {
if (_validExtensions.contains(p.extension(resolved))) {
return p.toUri(p.canonicalize(resolved));
} else {
throw "The export for '${subpath ?? "root"}' in "
"'$packageName' resolved to '${resolved.toString()}', "
"which is not a '.scss', '.sass', or '.css' file.";
}
}
// If no subpath, attempt to resolve `sass` or `style` key in package.json,
// then `index` file at package root, resolved for file extensions and
// partials.
if (subpath == null) {
var rootPath = _resolvePackageRootValues(packageRoot, packageManifest);
return rootPath != null ? p.toUri(p.canonicalize(rootPath)) : null;
}
// If there is a subpath, attempt to resolve the path relative to the
// package root, and resolve for file extensions and partials.
var subpathInRoot = p.join(packageRoot, subpath);
return FilesystemImporter.cwd.canonicalize(p.toUri(subpathInRoot));
}