From 5e570c560ce519d4c976212842e9fc0f849f685c Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Wed, 25 Oct 2023 13:19:09 -0400 Subject: [PATCH 001/113] Export nodePackageImporter --- lib/src/js.dart | 1 + lib/src/js/compile.dart | 7 +++++++ lib/src/js/exports.dart | 2 ++ 3 files changed, 10 insertions(+) diff --git a/lib/src/js.dart b/lib/src/js.dart index 9a2c51c06..aaf390f0e 100644 --- a/lib/src/js.dart +++ b/lib/src/js.dart @@ -45,6 +45,7 @@ void main() { silent: JSLogger( warn: allowInteropNamed('sass.Logger.silent.warn', (_, __) {}), debug: allowInteropNamed('sass.Logger.silent.debug', (_, __) {}))); + exports.nodePackageImporter = nodePackageImporter; exports.info = "dart-sass\t${const String.fromEnvironment('version')}\t(Sass Compiler)\t" diff --git a/lib/src/js/compile.dart b/lib/src/js/compile.dart index b0a192a9e..9f4764fc2 100644 --- a/lib/src/js/compile.dart +++ b/lib/src/js/compile.dart @@ -321,3 +321,10 @@ List _parseFunctions(Object? functions, {bool asynch = false}) { }); return result; } + +const NodePackageImporter nodePackageImporter = NodePackageImporter(); + +class NodePackageImporter { + final String nodePackageImporterBrand = ''; + const NodePackageImporter(); +} diff --git a/lib/src/js/exports.dart b/lib/src/js/exports.dart index ee5a74471..39b90266a 100644 --- a/lib/src/js/exports.dart +++ b/lib/src/js/exports.dart @@ -11,6 +11,7 @@ import '../value.dart' as value; import 'legacy/types.dart'; import 'logger.dart'; import 'reflection.dart'; +import 'compile.dart'; @JS() class Exports { @@ -22,6 +23,7 @@ class Exports { external set info(String info); external set Exception(JSClass function); external set Logger(LoggerNamespace namespace); + external set nodePackageImporter(NodePackageImporter nodePackageImporter); // Value APIs external set Value(JSClass function); From d01b7acbdbf5cecc40f47f5035d99def8a5ae8f8 Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Wed, 25 Oct 2023 15:31:43 -0400 Subject: [PATCH 002/113] Initial Node Package Importer --- lib/src/importer/node_package.dart | 180 +++++++++++++++++++++++++++++ lib/src/js/compile.dart | 10 ++ 2 files changed, 190 insertions(+) create mode 100644 lib/src/importer/node_package.dart diff --git a/lib/src/importer/node_package.dart b/lib/src/importer/node_package.dart new file mode 100644 index 000000000..9e2ac05e4 --- /dev/null +++ b/lib/src/importer/node_package.dart @@ -0,0 +1,180 @@ +// Copyright 2023 Google Inc. Use of this source code is governed by an +// MIT-style license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +import '../importer.dart'; +import './utils.dart'; +import 'dart:io'; +import 'dart:convert'; +import 'package:path/path.dart' as p; + +/// A filesystem importer to use for load implementation details, and for +/// canonicalizing paths not defined in package.json. +/// +final _filesystemImporter = FilesystemImporter('.'); + +// An importer that resolves `pkg:` URLs using the Node resolution algorithm. +class NodePackageImporterInternal extends Importer { + final Uri entryPointURL; + // Creates an importer with the associated entry point url + NodePackageImporterInternal(this.entryPointURL); + + @override + Uri? canonicalize(Uri url) { + if (url.scheme != 'pkg') return null; + if (url.path.startsWith('/')) { + throw "pkg: URL $url must not be an absolute path"; + } + if (url.path.isEmpty) { + throw "pkg: URL $url must not have an empty path"; + } + if (url.userInfo != '' || url.hasPort || url.hasQuery || url.hasFragment) { + throw "Invalid URL $url"; + } + var baseURL = + containingUrl?.scheme == 'file:' ? containingUrl! : entryPointURL; + + var (packageName, subpath) = packageNameAndSubpath(url.path); + var packageRoot = resolvePackageRoot(packageName, baseURL); + if (packageRoot == null) { + throw "Node Package $packageName could not be found."; + } + + // Attempt to resolve using conditional exports + var jsonString = + File(packageRoot.path + '/package.json').readAsStringSync(); + var packageManifest = jsonDecode(jsonString) as Map; + + var resolved = resolvePackageExports( + packageRoot, subpath, packageManifest, packageName); + + if (resolved != null && + resolved.scheme == 'file:' && + ['scss', 'sass', 'css'].contains(p.extension(resolved.path))) { + return resolved; + } else if (resolved != null) { + throw "The export for $subpath in $packageName is not a valid Sass 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 == '') { + return resolvePackageRootValues(packageRoot.path, packageManifest); + } + + // If there is a subpath, attempt to resolve the path relative to the package root, and + // resolve for file extensions and partials. + return _filesystemImporter + .canonicalize(Uri.parse("${packageRoot.path}/$subpath")); + } + + @override + ImporterResult? load(Uri url) => _filesystemImporter.load(url); +} + +// Takes a string, `path`, and returns a tuple with the package name and the +// subpath if it is present. +(String, String) packageNameAndSubpath(String path) { + var parts = path.split('/'); + var name = parts.removeAt(0); + if (name.startsWith('@')) { + name = '$name/${parts.removeAt(0)}'; + } + return (name, parts.isNotEmpty ? parts.join('/') : '.'); +} + +// Takes a string, `packageName`, and an absolute URL `baseURL`, and returns an +// absolute URL to the root directory for the most proximate installed +// `packageName`. +Uri? resolvePackageRoot(String packageName, Uri baseURL) { + // TODO implement + return null; +} + +// Takes a string `packagePath`, which is the root directory for a package, and +// `packageManifest`, which is the contents of that package's `package.json` +// file, and returns a file URL. +Uri? resolvePackageRootValues( + String packageRoot, Map packageManifest) { + var extensions = ['scss', 'sass', 'css']; + + var sassValue = packageManifest['sass'] as String?; + if (sassValue != null && extensions.contains(p.extension(sassValue))) { + return Uri.file('$packageRoot/$sassValue'); + } + var styleValue = packageManifest['style'] as String?; + if (styleValue != null && extensions.contains(p.extension(styleValue))) { + return Uri.file('$packageRoot/$styleValue'); + } + + var result = resolveImportPath('$packageRoot/index'); + if (result != null) return Uri.parse(result); + return null; +} + +// Takes a package.json value `packageManifest`, a directory URL `packageRoot` +// and a relative URL path `subpath`. It returns a file URL or null. +// `packageName` is used for error reporting only. +Uri? resolvePackageExports(Uri packageRoot, String subpath, + Map packageManifest, String packageName) { + if (packageManifest['exports'] == null) return null; + var exports = packageManifest['exports'] as Map; + var subpathVariants = exportLoadPaths(subpath); + var resolvedPaths = + nodePackageExportsResolve(packageRoot, subpathVariants, exports); + + if (resolvedPaths.length == 1) return resolvedPaths.first; + if (resolvedPaths.length > 1) { + throw "Unable to determine which of multiple potential" + "resolutions found for $subpath in $packageName should be used."; + } + if (p.extension(subpath).isNotEmpty) return null; + + var subpathIndexVariants = exportLoadPaths("$subpath/index"); + + var resolvedIndexpaths = + nodePackageExportsResolve(packageRoot, subpathIndexVariants, exports); + + if (resolvedIndexpaths.length == 1) return resolvedPaths.first; + if (resolvedIndexpaths.length > 1) { + throw "Unable to determine which of multiple potential" + "resolutions found for $subpath in $packageName should be used."; + } + + return null; +} + +// Takes a package.json value `packageManifest`, a directory URL `packageRoot` +// and a list of relative URL paths `subpathVariants`. It returns a list of all +// subpaths present in the package Manifest exports. +List nodePackageExportsResolve(Uri packageRoot, + List subpathVariants, Map exports) { + // TODO implement + return []; +} + +// Given a string `subpath`, returns a list of all possible variations with +// extensions and partials. +List exportLoadPaths(String subpath) { + List paths = []; + + if (['scss', 'sass', 'css'].any((ext) => subpath.endsWith(ext))) { + paths.add(subpath); + } else { + paths.addAll([ + '$subpath.scss', + '$subpath.sass', + '$subpath.css', + ]); + } + var basename = Uri.parse(subpath).pathSegments.last; + if (!basename.startsWith('_')) { + List prefixedPaths = []; + for (final path in paths) { + prefixedPaths.add('_$path'); + } + paths.addAll(prefixedPaths); + } + + return paths; +} diff --git a/lib/src/js/compile.dart b/lib/src/js/compile.dart index 9f4764fc2..d715fa0fc 100644 --- a/lib/src/js/compile.dart +++ b/lib/src/js/compile.dart @@ -13,6 +13,7 @@ import '../importer/js_to_dart/async.dart'; import '../importer/js_to_dart/async_file.dart'; import '../importer/js_to_dart/file.dart'; import '../importer/js_to_dart/sync.dart'; +import '../importer/node_package.dart'; import '../io.dart'; import '../logger/js_to_dart.dart'; import '../util/nullable.dart'; @@ -207,6 +208,15 @@ AsyncImporter _parseAsyncImporter(Object? importer) { /// Converts [importer] into a synchronous [Importer]. Importer _parseImporter(Object? importer) { + if (importer == nodePackageImporter) { + // TODO(jamesnw) Is this the correct check? + if (isBrowser) { + jsThrow(JsError( + "The Node Package Importer can not be used without a filesystem.")); + } + Uri entryPointURL = Uri.directory('.'); + importer = NodePackageImporterInternal(entryPointURL); + } if (importer == null) jsThrow(JsError("Importers may not be null.")); importer as JSImporter; From 2360346860d4ccc5c5e1b0415ea49cae630c4298 Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Thu, 26 Oct 2023 15:19:04 -0400 Subject: [PATCH 003/113] Add case for NodePackageImporter so embedded host compiles --- lib/src/embedded/dispatcher.dart | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/src/embedded/dispatcher.dart b/lib/src/embedded/dispatcher.dart index 40e81863d..6f0012e87 100644 --- a/lib/src/embedded/dispatcher.dart +++ b/lib/src/embedded/dispatcher.dart @@ -223,6 +223,10 @@ final class Dispatcher { case InboundMessage_CompileRequest_Importer_Importer.notSet: _checkNoNonCanonicalScheme(importer); return null; + + // TODO(jamesnw) replace with node package importer + case InboundMessage_CompileRequest_Importer_Importer.nodePackageImporter: + return HostImporter(this, importer.importerId, ['pkg']); } } From f1ce3a1941af9e5de92ae86b2c637b4aed6ade2a Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Fri, 27 Oct 2023 15:23:02 -0400 Subject: [PATCH 004/113] Hook up embedded --- lib/src/embedded/dispatcher.dart | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/src/embedded/dispatcher.dart b/lib/src/embedded/dispatcher.dart index 6f0012e87..33a6514ce 100644 --- a/lib/src/embedded/dispatcher.dart +++ b/lib/src/embedded/dispatcher.dart @@ -11,6 +11,7 @@ import 'package:native_synchronization/mailbox.dart'; import 'package:path/path.dart' as p; import 'package:protobuf/protobuf.dart'; import 'package:sass/sass.dart' as sass; +import 'package:sass/src/importer/node_package.dart'; import '../value/function.dart'; import '../value/mixin.dart'; @@ -224,9 +225,10 @@ final class Dispatcher { _checkNoNonCanonicalScheme(importer); return null; - // TODO(jamesnw) replace with node package importer case InboundMessage_CompileRequest_Importer_Importer.nodePackageImporter: - return HostImporter(this, importer.importerId, ['pkg']); + var entryPointURL = + Uri.parse(importer.nodePackageImporter.entryPointUrl); + return NodePackageImporterInternal(entryPointURL); } } From 2609525a5c6027bc47788521828a9c9ff97a8fe0 Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Fri, 27 Oct 2023 16:36:56 -0400 Subject: [PATCH 005/113] Resolving package root, return absolute paths --- lib/src/importer/node_package.dart | 25 ++++++++++++++++--------- lib/src/js/compile.dart | 9 ++++++--- 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/lib/src/importer/node_package.dart b/lib/src/importer/node_package.dart index 9e2ac05e4..cfda0ee4b 100644 --- a/lib/src/importer/node_package.dart +++ b/lib/src/importer/node_package.dart @@ -4,8 +4,8 @@ import '../importer.dart'; import './utils.dart'; -import 'dart:io'; import 'dart:convert'; +import '../io.dart'; import 'package:path/path.dart' as p; /// A filesystem importer to use for load implementation details, and for @@ -37,12 +37,11 @@ class NodePackageImporterInternal extends Importer { var (packageName, subpath) = packageNameAndSubpath(url.path); var packageRoot = resolvePackageRoot(packageName, baseURL); if (packageRoot == null) { - throw "Node Package $packageName could not be found."; + throw "Node Package '$packageName' could not be found."; } // Attempt to resolve using conditional exports - var jsonString = - File(packageRoot.path + '/package.json').readAsStringSync(); + var jsonString = readFile(packageRoot.path + '/package.json'); var packageManifest = jsonDecode(jsonString) as Map; var resolved = resolvePackageExports( @@ -65,7 +64,7 @@ class NodePackageImporterInternal extends Importer { // If there is a subpath, attempt to resolve the path relative to the package root, and // resolve for file extensions and partials. return _filesystemImporter - .canonicalize(Uri.parse("${packageRoot.path}/$subpath")); + .canonicalize(Uri.file("${packageRoot.path}/$subpath")); } @override @@ -80,14 +79,22 @@ class NodePackageImporterInternal extends Importer { if (name.startsWith('@')) { name = '$name/${parts.removeAt(0)}'; } - return (name, parts.isNotEmpty ? parts.join('/') : '.'); + return (name, parts.isNotEmpty ? parts.join('/') : ''); } // Takes a string, `packageName`, and an absolute URL `baseURL`, and returns an // absolute URL to the root directory for the most proximate installed // `packageName`. Uri? resolvePackageRoot(String packageName, Uri baseURL) { - // TODO implement + var baseDirectory = p.dirname(baseURL.toString()); + + // TODO make recursive + var potentialPackage = + p.joinAll([baseDirectory, 'node_modules', packageName]); + + if (dirExists(potentialPackage)) { + return Uri.parse(potentialPackage); + } return null; } @@ -96,7 +103,7 @@ Uri? resolvePackageRoot(String packageName, Uri baseURL) { // file, and returns a file URL. Uri? resolvePackageRootValues( String packageRoot, Map packageManifest) { - var extensions = ['scss', 'sass', 'css']; + var extensions = ['.scss', '.sass', '.css']; var sassValue = packageManifest['sass'] as String?; if (sassValue != null && extensions.contains(p.extension(sassValue))) { @@ -108,7 +115,7 @@ Uri? resolvePackageRootValues( } var result = resolveImportPath('$packageRoot/index'); - if (result != null) return Uri.parse(result); + if (result != null) return Uri.file(result); return null; } diff --git a/lib/src/js/compile.dart b/lib/src/js/compile.dart index d715fa0fc..326e4a839 100644 --- a/lib/src/js/compile.dart +++ b/lib/src/js/compile.dart @@ -5,6 +5,7 @@ import 'package:cli_pkg/js.dart'; import 'package:node_interop/js.dart'; import 'package:node_interop/util.dart' hide futureToPromise; +import 'package:path/path.dart' as p; import 'package:term_glyph/term_glyph.dart' as glyph; import '../../sass.dart'; @@ -214,8 +215,9 @@ Importer _parseImporter(Object? importer) { jsThrow(JsError( "The Node Package Importer can not be used without a filesystem.")); } - Uri entryPointURL = Uri.directory('.'); - importer = NodePackageImporterInternal(entryPointURL); + // TODO(jamesnw) Can we get an actual filename for parity? Is it needed? + Uri entryPointURL = Uri.parse(p.absolute('./index.js')); + return NodePackageImporterInternal(entryPointURL); } if (importer == null) jsThrow(JsError("Importers may not be null.")); @@ -335,6 +337,7 @@ List _parseFunctions(Object? functions, {bool asynch = false}) { const NodePackageImporter nodePackageImporter = NodePackageImporter(); class NodePackageImporter { - final String nodePackageImporterBrand = ''; + // ignore: unused_field + final String _nodePackageImporterBrand = ''; const NodePackageImporter(); } From b0b8dab72f12478ab5a88eb97d58a489ee7559c9 Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Mon, 30 Oct 2023 14:30:33 -0400 Subject: [PATCH 006/113] Add recursive parent directory lookup for resolving package root --- lib/src/importer/node_package.dart | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/lib/src/importer/node_package.dart b/lib/src/importer/node_package.dart index cfda0ee4b..1adac561c 100644 --- a/lib/src/importer/node_package.dart +++ b/lib/src/importer/node_package.dart @@ -31,6 +31,7 @@ class NodePackageImporterInternal extends Importer { if (url.userInfo != '' || url.hasPort || url.hasQuery || url.hasFragment) { throw "Invalid URL $url"; } + // TODO(jamesnw) `containingUrl` seems to be always null var baseURL = containingUrl?.scheme == 'file:' ? containingUrl! : entryPointURL; @@ -88,14 +89,21 @@ class NodePackageImporterInternal extends Importer { Uri? resolvePackageRoot(String packageName, Uri baseURL) { var baseDirectory = p.dirname(baseURL.toString()); - // TODO make recursive - var potentialPackage = - p.joinAll([baseDirectory, 'node_modules', packageName]); + Uri? recurseUpFrom(String entry) { + var potentialPackage = p.joinAll([entry, 'node_modules', packageName]); - if (dirExists(potentialPackage)) { - return Uri.parse(potentialPackage); + if (dirExists(potentialPackage)) { + return Uri.file(potentialPackage); + } + List parentDirectoryParts = List.from(Uri.parse(entry).pathSegments) + ..removeLast(); + + if (parentDirectoryParts.isEmpty) return null; + + return recurseUpFrom(p.joinAll(parentDirectoryParts)); } - return null; + + return recurseUpFrom(baseDirectory); } // Takes a string `packagePath`, which is the root directory for a package, and From b7f50f4d14a21cc3d8bf2a3cd78dd2adb3bd476c Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Mon, 30 Oct 2023 14:54:45 -0400 Subject: [PATCH 007/113] FilesystemImporter constructor should be internal --- lib/src/importer/filesystem.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/src/importer/filesystem.dart b/lib/src/importer/filesystem.dart index 31af69829..9872ad112 100644 --- a/lib/src/importer/filesystem.dart +++ b/lib/src/importer/filesystem.dart @@ -20,6 +20,8 @@ class FilesystemImporter extends Importer { final String _loadPath; /// Creates an importer that loads files relative to [loadPath]. + /// @nodoc + @internal FilesystemImporter(String loadPath) : _loadPath = p.absolute(loadPath); Uri? canonicalize(Uri url) { From 6895b57d003ba3c3b6783ba77512d949d579a25a Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Wed, 1 Nov 2023 10:26:38 -0400 Subject: [PATCH 008/113] Start package exports resolve implementation --- lib/src/importer/node_package.dart | 89 +++++++++++++++++++++++++++--- 1 file changed, 80 insertions(+), 9 deletions(-) diff --git a/lib/src/importer/node_package.dart b/lib/src/importer/node_package.dart index 1adac561c..887419532 100644 --- a/lib/src/importer/node_package.dart +++ b/lib/src/importer/node_package.dart @@ -2,6 +2,8 @@ // MIT-style license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT. +import 'package:collection/collection.dart'; + import '../importer.dart'; import './utils.dart'; import 'dart:convert'; @@ -49,10 +51,11 @@ class NodePackageImporterInternal extends Importer { packageRoot, subpath, packageManifest, packageName); if (resolved != null && - resolved.scheme == 'file:' && - ['scss', 'sass', 'css'].contains(p.extension(resolved.path))) { + resolved.scheme == 'file' && + ['.scss', '.sass', '.css'].contains(p.extension(resolved.path))) { return resolved; } else if (resolved != null) { + // TODO(jamesnw) handle empty subpath throw "The export for $subpath in $packageName is not a valid Sass file."; } // If no subpath, attempt to resolve `sass` or `style` key in package.json, @@ -133,10 +136,10 @@ Uri? resolvePackageRootValues( Uri? resolvePackageExports(Uri packageRoot, String subpath, Map packageManifest, String packageName) { if (packageManifest['exports'] == null) return null; - var exports = packageManifest['exports'] as Map; + var exports = packageManifest['exports'] as Object; var subpathVariants = exportLoadPaths(subpath); var resolvedPaths = - nodePackageExportsResolve(packageRoot, subpathVariants, exports); + _nodePackageExportsResolve(packageRoot, subpathVariants, exports); if (resolvedPaths.length == 1) return resolvedPaths.first; if (resolvedPaths.length > 1) { @@ -148,7 +151,7 @@ Uri? resolvePackageExports(Uri packageRoot, String subpath, var subpathIndexVariants = exportLoadPaths("$subpath/index"); var resolvedIndexpaths = - nodePackageExportsResolve(packageRoot, subpathIndexVariants, exports); + _nodePackageExportsResolve(packageRoot, subpathIndexVariants, exports); if (resolvedIndexpaths.length == 1) return resolvedPaths.first; if (resolvedIndexpaths.length > 1) { @@ -162,16 +165,84 @@ Uri? resolvePackageExports(Uri packageRoot, String subpath, // Takes a package.json value `packageManifest`, a directory URL `packageRoot` // and a list of relative URL paths `subpathVariants`. It returns a list of all // subpaths present in the package Manifest exports. -List nodePackageExportsResolve(Uri packageRoot, - List subpathVariants, Map exports) { - // TODO implement - return []; +List _nodePackageExportsResolve( + Uri packageRoot, List subpathVariants, Object exports) { + Uri? processVariant(String subpath) { + if (subpath == '') { + // main export + Object? mainExport = _getMainExport(exports); + if (mainExport == null) return null; + return _packageTargetResolve(subpath, exports, packageRoot); + } + return null; + } + + return subpathVariants.map(processVariant).whereNotNull().toList(); +} + +Uri? _packageTargetResolve(String subpath, Object exports, Uri packageRoot) { + switch (exports) { + case String string: + if (!string.startsWith('./')) { + throw "Invalid Package Target"; + } + return Uri.parse("$packageRoot/$string"); + case Map map: + var conditions = ['sass', 'style', 'default']; + for (var key in map.keys) { + if (conditions.contains(key)) { + var result = + _packageTargetResolve(subpath, map[key] as Object, packageRoot); + if (result != null) { + return result; + } + } + } + return null; + case List array: + if (array.isEmpty) return null; + + for (var value in array) { + var result = + _packageTargetResolve(subpath, value as Object, packageRoot); + if (result != null) { + return result; + } + } + + return null; + default: + break; + } + return null; +} + +Object? _getMainExport(Object exports) { + switch (exports) { + case String string: + return string; + + case List list: + return list; + + case Map map: + if (!map.keys.any((key) => key.startsWith('.'))) { + return map; + } else if (map.containsKey('.')) { + return map['.'] as Object; + } + break; + default: + break; + } + return null; } // Given a string `subpath`, returns a list of all possible variations with // extensions and partials. List exportLoadPaths(String subpath) { List paths = []; + if (subpath == '') return [subpath]; if (['scss', 'sass', 'css'].any((ext) => subpath.endsWith(ext))) { paths.add(subpath); From 880ad5a86c15db1d46bdab3d8aa5ca201d639ec5 Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Wed, 1 Nov 2023 16:23:21 -0400 Subject: [PATCH 009/113] Exports algorithm --- lib/src/importer/node_package.dart | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/lib/src/importer/node_package.dart b/lib/src/importer/node_package.dart index 887419532..eb8012ed9 100644 --- a/lib/src/importer/node_package.dart +++ b/lib/src/importer/node_package.dart @@ -153,7 +153,7 @@ Uri? resolvePackageExports(Uri packageRoot, String subpath, var resolvedIndexpaths = _nodePackageExportsResolve(packageRoot, subpathIndexVariants, exports); - if (resolvedIndexpaths.length == 1) return resolvedPaths.first; + if (resolvedIndexpaths.length == 1) return resolvedIndexpaths.first; if (resolvedIndexpaths.length > 1) { throw "Unable to determine which of multiple potential" "resolutions found for $subpath in $packageName should be used."; @@ -169,10 +169,18 @@ List _nodePackageExportsResolve( Uri packageRoot, List subpathVariants, Object exports) { Uri? processVariant(String subpath) { if (subpath == '') { - // main export Object? mainExport = _getMainExport(exports); if (mainExport == null) return null; - return _packageTargetResolve(subpath, exports, packageRoot); + return _packageTargetResolve(subpath, mainExport, packageRoot); + } else { + if (exports is Map && + exports.keys.every((key) => key.startsWith('.'))) { + var matchKey = "./$subpath"; + if (exports.containsKey(matchKey)) { + return _packageTargetResolve( + matchKey, exports[matchKey] as Object, packageRoot); + } + } } return null; } From 02f6bccc6201a06201e4b2db12fa7fb454a34269 Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Thu, 2 Nov 2023 11:22:42 -0400 Subject: [PATCH 010/113] Exports parsing --- lib/src/importer/node_package.dart | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/lib/src/importer/node_package.dart b/lib/src/importer/node_package.dart index eb8012ed9..d415fee5c 100644 --- a/lib/src/importer/node_package.dart +++ b/lib/src/importer/node_package.dart @@ -143,19 +143,19 @@ Uri? resolvePackageExports(Uri packageRoot, String subpath, if (resolvedPaths.length == 1) return resolvedPaths.first; if (resolvedPaths.length > 1) { - throw "Unable to determine which of multiple potential" + throw "Unable to determine which of multiple potential " "resolutions found for $subpath in $packageName should be used."; } if (p.extension(subpath).isNotEmpty) return null; - var subpathIndexVariants = exportLoadPaths("$subpath/index"); + var subpathIndexVariants = exportLoadPaths(subpath, true); var resolvedIndexpaths = _nodePackageExportsResolve(packageRoot, subpathIndexVariants, exports); if (resolvedIndexpaths.length == 1) return resolvedIndexpaths.first; if (resolvedIndexpaths.length > 1) { - throw "Unable to determine which of multiple potential" + throw "Unable to determine which of multiple potential " "resolutions found for $subpath in $packageName should be used."; } @@ -175,7 +175,7 @@ List _nodePackageExportsResolve( } else { if (exports is Map && exports.keys.every((key) => key.startsWith('.'))) { - var matchKey = "./$subpath"; + var matchKey = subpath.startsWith('/') ? ".$subpath" : "./$subpath"; if (exports.containsKey(matchKey)) { return _packageTargetResolve( matchKey, exports[matchKey] as Object, packageRoot); @@ -248,9 +248,14 @@ Object? _getMainExport(Object exports) { // Given a string `subpath`, returns a list of all possible variations with // extensions and partials. -List exportLoadPaths(String subpath) { +List exportLoadPaths(String subpath, [bool addIndex = false]) { List paths = []; - if (subpath == '') return [subpath]; + if (subpath.isEmpty && !addIndex) return [subpath]; + if (subpath.isEmpty && addIndex) { + subpath = 'index'; + } else if (subpath.isNotEmpty && addIndex) { + subpath = "$subpath/index"; + } if (['scss', 'sass', 'css'].any((ext) => subpath.endsWith(ext))) { paths.add(subpath); @@ -261,11 +266,18 @@ List exportLoadPaths(String subpath) { '$subpath.css', ]); } - var basename = Uri.parse(subpath).pathSegments.last; + var subpathSegments = Uri.parse(subpath).pathSegments; + var basename = subpathSegments.last; + var dirPath = subpathSegments.sublist(0, subpathSegments.length - 1); if (!basename.startsWith('_')) { List prefixedPaths = []; for (final path in paths) { - prefixedPaths.add('_$path'); + var pathBasename = Uri.parse(path).pathSegments.last; + if (dirPath.isEmpty) { + prefixedPaths.add('_$pathBasename'); + } else { + prefixedPaths.add('$dirPath/_$pathBasename'); + } } paths.addAll(prefixedPaths); } From c1929f0046d0c3d0a4bdee45b9ceb4dc0ed46734 Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Thu, 2 Nov 2023 11:34:55 -0400 Subject: [PATCH 011/113] Move subprocedures inside class, make private --- lib/src/importer/node_package.dart | 347 ++++++++++++++--------------- 1 file changed, 173 insertions(+), 174 deletions(-) diff --git a/lib/src/importer/node_package.dart b/lib/src/importer/node_package.dart index d415fee5c..c31bbb676 100644 --- a/lib/src/importer/node_package.dart +++ b/lib/src/importer/node_package.dart @@ -15,7 +15,7 @@ import 'package:path/path.dart' as p; /// final _filesystemImporter = FilesystemImporter('.'); -// An importer that resolves `pkg:` URLs using the Node resolution algorithm. +/// An importer that resolves `pkg:` URLs using the Node resolution algorithm. class NodePackageImporterInternal extends Importer { final Uri entryPointURL; // Creates an importer with the associated entry point url @@ -37,8 +37,8 @@ class NodePackageImporterInternal extends Importer { var baseURL = containingUrl?.scheme == 'file:' ? containingUrl! : entryPointURL; - var (packageName, subpath) = packageNameAndSubpath(url.path); - var packageRoot = resolvePackageRoot(packageName, baseURL); + var (packageName, subpath) = _packageNameAndSubpath(url.path); + var packageRoot = _resolvePackageRoot(packageName, baseURL); if (packageRoot == null) { throw "Node Package '$packageName' could not be found."; } @@ -47,7 +47,7 @@ class NodePackageImporterInternal extends Importer { var jsonString = readFile(packageRoot.path + '/package.json'); var packageManifest = jsonDecode(jsonString) as Map; - var resolved = resolvePackageExports( + var resolved = _resolvePackageExports( packageRoot, subpath, packageManifest, packageName); if (resolved != null && @@ -62,7 +62,7 @@ class NodePackageImporterInternal extends Importer { // then `index` file at package root, resolved for file extensions and // partials. if (subpath == '') { - return resolvePackageRootValues(packageRoot.path, packageManifest); + return _resolvePackageRootValues(packageRoot.path, packageManifest); } // If there is a subpath, attempt to resolve the path relative to the package root, and @@ -73,214 +73,213 @@ class NodePackageImporterInternal extends Importer { @override ImporterResult? load(Uri url) => _filesystemImporter.load(url); -} -// Takes a string, `path`, and returns a tuple with the package name and the -// subpath if it is present. -(String, String) packageNameAndSubpath(String path) { - var parts = path.split('/'); - var name = parts.removeAt(0); - if (name.startsWith('@')) { - name = '$name/${parts.removeAt(0)}'; + // Takes a string, `path`, and returns a tuple with the package name and the + // subpath if it is present. + (String, String) _packageNameAndSubpath(String path) { + var parts = path.split('/'); + var name = parts.removeAt(0); + if (name.startsWith('@')) { + name = '$name/${parts.removeAt(0)}'; + } + return (name, parts.isNotEmpty ? parts.join('/') : ''); } - return (name, parts.isNotEmpty ? parts.join('/') : ''); -} -// Takes a string, `packageName`, and an absolute URL `baseURL`, and returns an -// absolute URL to the root directory for the most proximate installed -// `packageName`. -Uri? resolvePackageRoot(String packageName, Uri baseURL) { - var baseDirectory = p.dirname(baseURL.toString()); + // Takes a string, `packageName`, and an absolute URL `baseURL`, and returns an + // absolute URL to the root directory for the most proximate installed + // `packageName`. + Uri? _resolvePackageRoot(String packageName, Uri baseURL) { + var baseDirectory = p.dirname(baseURL.toString()); - Uri? recurseUpFrom(String entry) { - var potentialPackage = p.joinAll([entry, 'node_modules', packageName]); + Uri? recurseUpFrom(String entry) { + var potentialPackage = p.joinAll([entry, 'node_modules', packageName]); - if (dirExists(potentialPackage)) { - return Uri.file(potentialPackage); - } - List parentDirectoryParts = List.from(Uri.parse(entry).pathSegments) - ..removeLast(); + if (dirExists(potentialPackage)) { + return Uri.file(potentialPackage); + } + List parentDirectoryParts = + List.from(Uri.parse(entry).pathSegments)..removeLast(); + + if (parentDirectoryParts.isEmpty) return null; - if (parentDirectoryParts.isEmpty) return null; + return recurseUpFrom(p.joinAll(parentDirectoryParts)); + } - return recurseUpFrom(p.joinAll(parentDirectoryParts)); + return recurseUpFrom(baseDirectory); } - return recurseUpFrom(baseDirectory); -} + // Takes a string `packagePath`, which is the root directory for a package, and + // `packageManifest`, which is the contents of that package's `package.json` + // file, and returns a file URL. + Uri? _resolvePackageRootValues( + String packageRoot, Map packageManifest) { + var extensions = ['.scss', '.sass', '.css']; -// Takes a string `packagePath`, which is the root directory for a package, and -// `packageManifest`, which is the contents of that package's `package.json` -// file, and returns a file URL. -Uri? resolvePackageRootValues( - String packageRoot, Map packageManifest) { - var extensions = ['.scss', '.sass', '.css']; + var sassValue = packageManifest['sass'] as String?; + if (sassValue != null && extensions.contains(p.extension(sassValue))) { + return Uri.file('$packageRoot/$sassValue'); + } + var styleValue = packageManifest['style'] as String?; + if (styleValue != null && extensions.contains(p.extension(styleValue))) { + return Uri.file('$packageRoot/$styleValue'); + } - var sassValue = packageManifest['sass'] as String?; - if (sassValue != null && extensions.contains(p.extension(sassValue))) { - return Uri.file('$packageRoot/$sassValue'); - } - var styleValue = packageManifest['style'] as String?; - if (styleValue != null && extensions.contains(p.extension(styleValue))) { - return Uri.file('$packageRoot/$styleValue'); + var result = resolveImportPath('$packageRoot/index'); + if (result != null) return Uri.file(result); + return null; } - var result = resolveImportPath('$packageRoot/index'); - if (result != null) return Uri.file(result); - return null; -} + // Takes a package.json value `packageManifest`, a directory URL `packageRoot` + // and a relative URL path `subpath`. It returns a file URL or null. + // `packageName` is used for error reporting only. + Uri? _resolvePackageExports(Uri packageRoot, String subpath, + Map packageManifest, String packageName) { + if (packageManifest['exports'] == null) return null; + var exports = packageManifest['exports'] as Object; + var subpathVariants = _exportLoadPaths(subpath); + var resolvedPaths = + _nodePackageExportsResolve(packageRoot, subpathVariants, exports); + + if (resolvedPaths.length == 1) return resolvedPaths.first; + if (resolvedPaths.length > 1) { + throw "Unable to determine which of multiple potential " + "resolutions found for $subpath in $packageName should be used."; + } + if (p.extension(subpath).isNotEmpty) return null; -// Takes a package.json value `packageManifest`, a directory URL `packageRoot` -// and a relative URL path `subpath`. It returns a file URL or null. -// `packageName` is used for error reporting only. -Uri? resolvePackageExports(Uri packageRoot, String subpath, - Map packageManifest, String packageName) { - if (packageManifest['exports'] == null) return null; - var exports = packageManifest['exports'] as Object; - var subpathVariants = exportLoadPaths(subpath); - var resolvedPaths = - _nodePackageExportsResolve(packageRoot, subpathVariants, exports); - - if (resolvedPaths.length == 1) return resolvedPaths.first; - if (resolvedPaths.length > 1) { - throw "Unable to determine which of multiple potential " - "resolutions found for $subpath in $packageName should be used."; - } - if (p.extension(subpath).isNotEmpty) return null; + var subpathIndexVariants = _exportLoadPaths(subpath, true); - var subpathIndexVariants = exportLoadPaths(subpath, true); + var resolvedIndexpaths = + _nodePackageExportsResolve(packageRoot, subpathIndexVariants, exports); - var resolvedIndexpaths = - _nodePackageExportsResolve(packageRoot, subpathIndexVariants, exports); + if (resolvedIndexpaths.length == 1) return resolvedIndexpaths.first; + if (resolvedIndexpaths.length > 1) { + throw "Unable to determine which of multiple potential " + "resolutions found for $subpath in $packageName should be used."; + } - if (resolvedIndexpaths.length == 1) return resolvedIndexpaths.first; - if (resolvedIndexpaths.length > 1) { - throw "Unable to determine which of multiple potential " - "resolutions found for $subpath in $packageName should be used."; + return null; } - return null; -} - -// Takes a package.json value `packageManifest`, a directory URL `packageRoot` -// and a list of relative URL paths `subpathVariants`. It returns a list of all -// subpaths present in the package Manifest exports. -List _nodePackageExportsResolve( - Uri packageRoot, List subpathVariants, Object exports) { - Uri? processVariant(String subpath) { - if (subpath == '') { - Object? mainExport = _getMainExport(exports); - if (mainExport == null) return null; - return _packageTargetResolve(subpath, mainExport, packageRoot); - } else { - if (exports is Map && - exports.keys.every((key) => key.startsWith('.'))) { - var matchKey = subpath.startsWith('/') ? ".$subpath" : "./$subpath"; - if (exports.containsKey(matchKey)) { - return _packageTargetResolve( - matchKey, exports[matchKey] as Object, packageRoot); + // Takes a package.json value `packageManifest`, a directory URL `packageRoot` + // and a list of relative URL paths `subpathVariants`. It returns a list of all + // subpaths present in the package Manifest exports. + List _nodePackageExportsResolve( + Uri packageRoot, List subpathVariants, Object exports) { + Uri? processVariant(String subpath) { + if (subpath == '') { + Object? mainExport = _getMainExport(exports); + if (mainExport == null) return null; + return _packageTargetResolve(subpath, mainExport, packageRoot); + } else { + if (exports is Map && + exports.keys.every((key) => key.startsWith('.'))) { + var matchKey = subpath.startsWith('/') ? ".$subpath" : "./$subpath"; + if (exports.containsKey(matchKey)) { + return _packageTargetResolve( + matchKey, exports[matchKey] as Object, packageRoot); + } } } + return null; } - return null; + + return subpathVariants.map(processVariant).whereNotNull().toList(); } - return subpathVariants.map(processVariant).whereNotNull().toList(); -} + Uri? _packageTargetResolve(String subpath, Object exports, Uri packageRoot) { + switch (exports) { + case String string: + if (!string.startsWith('./')) { + throw "Invalid Package Target"; + } + return Uri.parse("$packageRoot/$string"); + case Map map: + var conditions = ['sass', 'style', 'default']; + for (var key in map.keys) { + if (conditions.contains(key)) { + var result = + _packageTargetResolve(subpath, map[key] as Object, packageRoot); + if (result != null) { + return result; + } + } + } + return null; + case List array: + if (array.isEmpty) return null; -Uri? _packageTargetResolve(String subpath, Object exports, Uri packageRoot) { - switch (exports) { - case String string: - if (!string.startsWith('./')) { - throw "Invalid Package Target"; - } - return Uri.parse("$packageRoot/$string"); - case Map map: - var conditions = ['sass', 'style', 'default']; - for (var key in map.keys) { - if (conditions.contains(key)) { + for (var value in array) { var result = - _packageTargetResolve(subpath, map[key] as Object, packageRoot); + _packageTargetResolve(subpath, value as Object, packageRoot); if (result != null) { return result; } } - } - return null; - case List array: - if (array.isEmpty) return null; - - for (var value in array) { - var result = - _packageTargetResolve(subpath, value as Object, packageRoot); - if (result != null) { - return result; - } - } - return null; - default: - break; + return null; + default: + break; + } + return null; } - return null; -} -Object? _getMainExport(Object exports) { - switch (exports) { - case String string: - return string; + Object? _getMainExport(Object exports) { + switch (exports) { + case String string: + return string; - case List list: - return list; + case List list: + return list; - case Map map: - if (!map.keys.any((key) => key.startsWith('.'))) { - return map; - } else if (map.containsKey('.')) { - return map['.'] as Object; - } - break; - default: - break; + case Map map: + if (!map.keys.any((key) => key.startsWith('.'))) { + return map; + } else if (map.containsKey('.')) { + return map['.'] as Object; + } + break; + default: + break; + } + return null; } - return null; -} -// Given a string `subpath`, returns a list of all possible variations with -// extensions and partials. -List exportLoadPaths(String subpath, [bool addIndex = false]) { - List paths = []; - if (subpath.isEmpty && !addIndex) return [subpath]; - if (subpath.isEmpty && addIndex) { - subpath = 'index'; - } else if (subpath.isNotEmpty && addIndex) { - subpath = "$subpath/index"; - } + // Given a string `subpath`, returns a list of all possible variations with + // extensions and partials. + List _exportLoadPaths(String subpath, [bool addIndex = false]) { + List paths = []; + if (subpath.isEmpty && !addIndex) return [subpath]; + if (subpath.isEmpty && addIndex) { + subpath = 'index'; + } else if (subpath.isNotEmpty && addIndex) { + subpath = "$subpath/index"; + } - if (['scss', 'sass', 'css'].any((ext) => subpath.endsWith(ext))) { - paths.add(subpath); - } else { - paths.addAll([ - '$subpath.scss', - '$subpath.sass', - '$subpath.css', - ]); - } - var subpathSegments = Uri.parse(subpath).pathSegments; - var basename = subpathSegments.last; - var dirPath = subpathSegments.sublist(0, subpathSegments.length - 1); - if (!basename.startsWith('_')) { - List prefixedPaths = []; - for (final path in paths) { - var pathBasename = Uri.parse(path).pathSegments.last; - if (dirPath.isEmpty) { - prefixedPaths.add('_$pathBasename'); - } else { - prefixedPaths.add('$dirPath/_$pathBasename'); + if (['scss', 'sass', 'css'].any((ext) => subpath.endsWith(ext))) { + paths.add(subpath); + } else { + paths.addAll([ + '$subpath.scss', + '$subpath.sass', + '$subpath.css', + ]); + } + var subpathSegments = Uri.parse(subpath).pathSegments; + var basename = subpathSegments.last; + var dirPath = subpathSegments.sublist(0, subpathSegments.length - 1); + if (!basename.startsWith('_')) { + List prefixedPaths = []; + for (final path in paths) { + var pathBasename = Uri.parse(path).pathSegments.last; + if (dirPath.isEmpty) { + prefixedPaths.add('_$pathBasename'); + } else { + prefixedPaths.add('$dirPath/_$pathBasename'); + } } + paths.addAll(prefixedPaths); } - paths.addAll(prefixedPaths); + return paths; } - - return paths; } From ef994ab2709c9901a5c98b0a769a84ac67a830ca Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Thu, 2 Nov 2023 15:39:04 -0400 Subject: [PATCH 012/113] Hook up package importers for legacy --- lib/src/async_compile.dart | 3 +- lib/src/js/legacy.dart | 60 ++++++++++++++++++++------- lib/src/js/legacy/render_options.dart | 2 + lib/src/visitor/async_evaluate.dart | 7 ++-- lib/src/visitor/evaluate.dart | 9 ++-- 5 files changed, 56 insertions(+), 25 deletions(-) diff --git a/lib/src/async_compile.dart b/lib/src/async_compile.dart index daa2233db..94c60c8ae 100644 --- a/lib/src/async_compile.dart +++ b/lib/src/async_compile.dart @@ -26,7 +26,8 @@ import 'visitor/serialize.dart'; /// Like [compileAsync] in `lib/sass.dart`, but provides more options to support /// the node-sass compatible API and the executable. /// -/// At most one of `importCache` and `nodeImporter` may be provided at once. +/// If both `importCache` and `nodeImporter` are provided, the importers in +/// `importCache` will be evaluated before `nodeImporter`. Future compileAsync(String path, {Syntax? syntax, Logger? logger, diff --git a/lib/src/js/legacy.dart b/lib/src/js/legacy.dart index 5c5ad533a..67a46fa79 100644 --- a/lib/src/js/legacy.dart +++ b/lib/src/js/legacy.dart @@ -10,6 +10,9 @@ import 'dart:typed_data'; import 'package:cli_pkg/js.dart'; import 'package:node_interop/js.dart'; import 'package:path/path.dart' as p; +import '../async_import_cache.dart'; +import '../import_cache.dart'; +import '../importer/node_package.dart'; import '../callable.dart'; import '../compile.dart'; @@ -74,24 +77,27 @@ Future _renderAsync(RenderOptions options) async { var file = options.file.andThen(p.absolute); if (options.data case var data?) { - result = await compileStringAsync(data, - nodeImporter: _parseImporter(options, start), - functions: _parseFunctions(options, start, asynch: true), - syntax: isTruthy(options.indentedSyntax) ? Syntax.sass : null, - style: _parseOutputStyle(options.outputStyle), - useSpaces: options.indentType != 'tab', - indentWidth: _parseIndentWidth(options.indentWidth), - lineFeed: _parseLineFeed(options.linefeed), - url: file == null ? 'stdin' : p.toUri(file).toString(), - quietDeps: options.quietDeps ?? false, - verbose: options.verbose ?? false, - charset: options.charset ?? true, - sourceMap: _enableSourceMaps(options), - logger: - JSToDartLogger(options.logger, Logger.stderr(color: hasTerminal))); + result = await compileStringAsync( + data, + nodeImporter: _parseImporter(options, start), + importCache: _parsePackageImportersAsync(options, start), + functions: _parseFunctions(options, start, asynch: true), + syntax: isTruthy(options.indentedSyntax) ? Syntax.sass : null, + style: _parseOutputStyle(options.outputStyle), + useSpaces: options.indentType != 'tab', + indentWidth: _parseIndentWidth(options.indentWidth), + lineFeed: _parseLineFeed(options.linefeed), + url: file == null ? 'stdin' : p.toUri(file).toString(), + quietDeps: options.quietDeps ?? false, + verbose: options.verbose ?? false, + charset: options.charset ?? true, + sourceMap: _enableSourceMaps(options), + logger: JSToDartLogger(options.logger, Logger.stderr(color: hasTerminal)), + ); } else if (file != null) { result = await compileAsync(file, nodeImporter: _parseImporter(options, start), + importCache: _parsePackageImportersAsync(options, start), functions: _parseFunctions(options, start, asynch: true), syntax: isTruthy(options.indentedSyntax) ? Syntax.sass : null, style: _parseOutputStyle(options.outputStyle), @@ -129,6 +135,7 @@ RenderResult renderSync(RenderOptions options) { if (options.data case var data?) { result = compileString(data, nodeImporter: _parseImporter(options, start), + importCache: _parsePackageImporters(options, start), functions: _parseFunctions(options, start).cast(), syntax: isTruthy(options.indentedSyntax) ? Syntax.sass : null, style: _parseOutputStyle(options.outputStyle), @@ -145,6 +152,7 @@ RenderResult renderSync(RenderOptions options) { } else if (file != null) { result = compile(file, nodeImporter: _parseImporter(options, start), + importCache: _parsePackageImporters(options, start), functions: _parseFunctions(options, start).cast(), syntax: isTruthy(options.indentedSyntax) ? Syntax.sass : null, style: _parseOutputStyle(options.outputStyle), @@ -289,6 +297,28 @@ NodeImporter _parseImporter(RenderOptions options, DateTime start) { return NodeImporter(contextOptions, includePaths, importers); } +/// Creates an [AsyncImportCache] for Package Importers. +AsyncImportCache? _parsePackageImportersAsync( + RenderOptions options, DateTime start) { + if (options.pkgImporter case 'node') { + // TODO(jamesnw) Can we get an actual filename for parity? Is it needed? + Uri entryPointURL = Uri.parse(p.absolute('./index.js')); + return AsyncImportCache( + importers: [NodePackageImporterInternal(entryPointURL)]); + } + return null; +} + +/// Creates an [ImportCache] for Package Importers. +ImportCache? _parsePackageImporters(RenderOptions options, DateTime start) { + if (options.pkgImporter case 'node') { + // TODO(jamesnw) Can we get an actual filename for parity? Is it needed? + Uri entryPointURL = Uri.parse(p.absolute('./index.js')); + return ImportCache(importers: [NodePackageImporterInternal(entryPointURL)]); + } + return null; +} + /// Creates the [RenderContextOptions] for the `this` context in which custom /// functions and importers will be evaluated. RenderContextOptions _contextOptions(RenderOptions options, DateTime start) { diff --git a/lib/src/js/legacy/render_options.dart b/lib/src/js/legacy/render_options.dart index 3357166de..33a56fbe6 100644 --- a/lib/src/js/legacy/render_options.dart +++ b/lib/src/js/legacy/render_options.dart @@ -13,6 +13,7 @@ class RenderOptions { external String? get file; external String? get data; external Object? get importer; + external String? get pkgImporter; external Object? get functions; external List? get includePaths; external bool? get indentedSyntax; @@ -36,6 +37,7 @@ class RenderOptions { {String? file, String? data, Object? importer, + String? pkgImporter, Object? functions, List? includePaths, bool? indentedSyntax, diff --git a/lib/src/visitor/async_evaluate.dart b/lib/src/visitor/async_evaluate.dart index 767d2393b..53492e18c 100644 --- a/lib/src/visitor/async_evaluate.dart +++ b/lib/src/visitor/async_evaluate.dart @@ -337,9 +337,7 @@ final class _EvaluateVisitor Logger? logger, bool quietDeps = false, bool sourceMap = false}) - : _importCache = nodeImporter == null - ? importCache ?? AsyncImportCache.none(logger: logger) - : null, + : _importCache = importCache ?? AsyncImportCache.none(logger: logger), _nodeImporter = nodeImporter, _logger = logger ?? const Logger.stderr(), _quietDeps = quietDeps, @@ -1705,7 +1703,8 @@ final class _EvaluateVisitor return (stylesheet, importer: importer, isDependency: isDependency); } } - } else { + } + if (_nodeImporter != null) { if (await _importLikeNode( url, baseUrl ?? _stylesheet.span.sourceUrl, forImport) case var result?) { diff --git a/lib/src/visitor/evaluate.dart b/lib/src/visitor/evaluate.dart index 50b2cce78..e1cb3ac35 100644 --- a/lib/src/visitor/evaluate.dart +++ b/lib/src/visitor/evaluate.dart @@ -5,7 +5,7 @@ // DO NOT EDIT. This file was generated from async_evaluate.dart. // See tool/grind/synchronize.dart for details. // -// Checksum: 58ef9912c6a9d9cfe9c3f5d991f625ab1a627e7a +// Checksum: 243938b731f0196c3df9a7d6f8fce9cb84a6de60 // // ignore_for_file: unused_import @@ -345,9 +345,7 @@ final class _EvaluateVisitor Logger? logger, bool quietDeps = false, bool sourceMap = false}) - : _importCache = nodeImporter == null - ? importCache ?? ImportCache.none(logger: logger) - : null, + : _importCache = importCache ?? ImportCache.none(logger: logger), _nodeImporter = nodeImporter, _logger = logger ?? const Logger.stderr(), _quietDeps = quietDeps, @@ -1702,7 +1700,8 @@ final class _EvaluateVisitor return (stylesheet, importer: importer, isDependency: isDependency); } } - } else { + } + if (_nodeImporter != null) { if (_importLikeNode( url, baseUrl ?? _stylesheet.span.sourceUrl, forImport) case var result?) { From 99990b1c002f92c68c66562e575766570433be9d Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Fri, 3 Nov 2023 09:23:10 -0400 Subject: [PATCH 013/113] Handle entrypoint cases --- lib/src/compile.dart | 5 +++-- lib/src/importer/node_package.dart | 15 ++++++++++----- lib/src/js/compile.dart | 1 - 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/lib/src/compile.dart b/lib/src/compile.dart index b951c8036..b5427304a 100644 --- a/lib/src/compile.dart +++ b/lib/src/compile.dart @@ -5,7 +5,7 @@ // DO NOT EDIT. This file was generated from async_compile.dart. // See tool/grind/synchronize.dart for details. // -// Checksum: 5178e366228bde7854df12221393857bb3022628 +// Checksum: 23b059beeb519469aa45fead0956a1a25f0c814e // // ignore_for_file: unused_import @@ -35,7 +35,8 @@ import 'visitor/serialize.dart'; /// Like [compile] in `lib/sass.dart`, but provides more options to support /// the node-sass compatible API and the executable. /// -/// At most one of `importCache` and `nodeImporter` may be provided at once. +/// If both `importCache` and `nodeImporter` are provided, the importers in +/// `importCache` will be evaluated before `nodeImporter`. CompileResult compile(String path, {Syntax? syntax, Logger? logger, diff --git a/lib/src/importer/node_package.dart b/lib/src/importer/node_package.dart index c31bbb676..b60e59b9f 100644 --- a/lib/src/importer/node_package.dart +++ b/lib/src/importer/node_package.dart @@ -21,6 +21,11 @@ class NodePackageImporterInternal extends Importer { // Creates an importer with the associated entry point url NodePackageImporterInternal(this.entryPointURL); + @override + bool isNonCanonicalScheme(String scheme) { + return scheme == 'pkg'; + } + @override Uri? canonicalize(Uri url) { if (url.scheme != 'pkg') return null; @@ -33,9 +38,8 @@ class NodePackageImporterInternal extends Importer { if (url.userInfo != '' || url.hasPort || url.hasQuery || url.hasFragment) { throw "Invalid URL $url"; } - // TODO(jamesnw) `containingUrl` seems to be always null var baseURL = - containingUrl?.scheme == 'file:' ? containingUrl! : entryPointURL; + containingUrl?.scheme == 'file' ? containingUrl! : entryPointURL; var (packageName, subpath) = _packageNameAndSubpath(url.path); var packageRoot = _resolvePackageRoot(packageName, baseURL); @@ -55,8 +59,8 @@ class NodePackageImporterInternal extends Importer { ['.scss', '.sass', '.css'].contains(p.extension(resolved.path))) { return resolved; } else if (resolved != null) { - // TODO(jamesnw) handle empty subpath - throw "The export for $subpath in $packageName is not a valid Sass file."; + throw "The export for '${subpath == '' ? "root" : subpath}' in " + "'$packageName' is not a valid Sass 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 @@ -89,9 +93,10 @@ class NodePackageImporterInternal extends Importer { // absolute URL to the root directory for the most proximate installed // `packageName`. Uri? _resolvePackageRoot(String packageName, Uri baseURL) { - var baseDirectory = p.dirname(baseURL.toString()); + var baseDirectory = p.dirname(baseURL.toFilePath()); Uri? recurseUpFrom(String entry) { + if (!entry.startsWith("/")) entry = "/$entry"; var potentialPackage = p.joinAll([entry, 'node_modules', packageName]); if (dirExists(potentialPackage)) { diff --git a/lib/src/js/compile.dart b/lib/src/js/compile.dart index 326e4a839..f2cf5a4e6 100644 --- a/lib/src/js/compile.dart +++ b/lib/src/js/compile.dart @@ -210,7 +210,6 @@ AsyncImporter _parseAsyncImporter(Object? importer) { /// Converts [importer] into a synchronous [Importer]. Importer _parseImporter(Object? importer) { if (importer == nodePackageImporter) { - // TODO(jamesnw) Is this the correct check? if (isBrowser) { jsThrow(JsError( "The Node Package Importer can not be used without a filesystem.")); From 8518c6704463efd41bf480d3cd6802c284d0aa4d Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Fri, 3 Nov 2023 12:16:46 -0400 Subject: [PATCH 014/113] Add node package importer to async --- lib/src/js/compile.dart | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/lib/src/js/compile.dart b/lib/src/js/compile.dart index f2cf5a4e6..da86a1f26 100644 --- a/lib/src/js/compile.dart +++ b/lib/src/js/compile.dart @@ -184,6 +184,15 @@ OutputStyle _parseOutputStyle(String? style) => switch (style) { /// Converts [importer] into an [AsyncImporter] that can be used with /// [compileAsync] or [compileStringAsync]. AsyncImporter _parseAsyncImporter(Object? importer) { + if (importer == nodePackageImporter) { + if (isBrowser) { + jsThrow(JsError( + "The Node Package Importer can not be used without a filesystem.")); + } + // TODO(jamesnw) Can we get an actual filename for parity? Is it needed? + Uri entryPointURL = Uri.parse(p.absolute('./index.js')); + return NodePackageImporterInternal(entryPointURL); + } if (importer == null) jsThrow(JsError("Importers may not be null.")); importer as JSImporter; From aed5ea45b721e635b0d10d83e6735470f15cb459 Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Fri, 3 Nov 2023 14:08:46 -0400 Subject: [PATCH 015/113] Add logs for debugging tests --- lib/src/importer/node_package.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/src/importer/node_package.dart b/lib/src/importer/node_package.dart index b60e59b9f..8d544cae6 100644 --- a/lib/src/importer/node_package.dart +++ b/lib/src/importer/node_package.dart @@ -40,6 +40,8 @@ class NodePackageImporterInternal extends Importer { } var baseURL = containingUrl?.scheme == 'file' ? containingUrl! : entryPointURL; + print("url: $url, baseURL: $baseURL, containingUrl: " + "$containingUrl, entryPointURL: $entryPointURL"); var (packageName, subpath) = _packageNameAndSubpath(url.path); var packageRoot = _resolvePackageRoot(packageName, baseURL); From e22bfef80add5e325186e32092cd95fb0ccc97bc Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Fri, 3 Nov 2023 15:06:53 -0400 Subject: [PATCH 016/113] Use platform-specific separators --- lib/src/importer/node_package.dart | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/src/importer/node_package.dart b/lib/src/importer/node_package.dart index 8d544cae6..3a516c86c 100644 --- a/lib/src/importer/node_package.dart +++ b/lib/src/importer/node_package.dart @@ -29,19 +29,19 @@ class NodePackageImporterInternal extends Importer { @override Uri? canonicalize(Uri url) { if (url.scheme != 'pkg') return null; + // TODO(jamesnw) Can these errors even be thrown? Or are these cases + // filtered out before this? if (url.path.startsWith('/')) { - throw "pkg: URL $url must not be an absolute path"; + throw "pkg: URL $url must not be an absolute path."; } if (url.path.isEmpty) { - throw "pkg: URL $url must not have an empty path"; + throw "pkg: URL $url must not have an empty path."; } if (url.userInfo != '' || url.hasPort || url.hasQuery || url.hasFragment) { throw "Invalid URL $url"; } var baseURL = containingUrl?.scheme == 'file' ? containingUrl! : entryPointURL; - print("url: $url, baseURL: $baseURL, containingUrl: " - "$containingUrl, entryPointURL: $entryPointURL"); var (packageName, subpath) = _packageNameAndSubpath(url.path); var packageRoot = _resolvePackageRoot(packageName, baseURL); @@ -74,7 +74,7 @@ class NodePackageImporterInternal extends Importer { // If there is a subpath, attempt to resolve the path relative to the package root, and // resolve for file extensions and partials. return _filesystemImporter - .canonicalize(Uri.file("${packageRoot.path}/$subpath")); + .canonicalize(Uri.file("${packageRoot.path}${p.separator}$subpath")); } @override @@ -98,7 +98,7 @@ class NodePackageImporterInternal extends Importer { var baseDirectory = p.dirname(baseURL.toFilePath()); Uri? recurseUpFrom(String entry) { - if (!entry.startsWith("/")) entry = "/$entry"; + if (!entry.startsWith(p.separator)) entry = "${p.separator}$entry"; var potentialPackage = p.joinAll([entry, 'node_modules', packageName]); if (dirExists(potentialPackage)) { @@ -128,10 +128,10 @@ class NodePackageImporterInternal extends Importer { } var styleValue = packageManifest['style'] as String?; if (styleValue != null && extensions.contains(p.extension(styleValue))) { - return Uri.file('$packageRoot/$styleValue'); + return Uri.file('$packageRoot${p.separator}$styleValue'); } - var result = resolveImportPath('$packageRoot/index'); + var result = resolveImportPath('$packageRoot${p.separator}index'); if (result != null) return Uri.file(result); return null; } From bc2d0a73159921f18f484794a8d04d3c0e28c55f Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Fri, 3 Nov 2023 15:38:53 -0400 Subject: [PATCH 017/113] Handling parsing windows paths to file name --- lib/src/importer/node_package.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/importer/node_package.dart b/lib/src/importer/node_package.dart index 3a516c86c..215e8f728 100644 --- a/lib/src/importer/node_package.dart +++ b/lib/src/importer/node_package.dart @@ -95,7 +95,7 @@ class NodePackageImporterInternal extends Importer { // absolute URL to the root directory for the most proximate installed // `packageName`. Uri? _resolvePackageRoot(String packageName, Uri baseURL) { - var baseDirectory = p.dirname(baseURL.toFilePath()); + var baseDirectory = p.dirname(Uri.file(baseURL.toString()).toFilePath()); Uri? recurseUpFrom(String entry) { if (!entry.startsWith(p.separator)) entry = "${p.separator}$entry"; From 7718e8a6a3021d76016892509afafc666b1c6727 Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Fri, 3 Nov 2023 15:48:26 -0400 Subject: [PATCH 018/113] Add logs for Windows troubleshooting --- lib/src/importer/node_package.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/importer/node_package.dart b/lib/src/importer/node_package.dart index 215e8f728..f1911ee84 100644 --- a/lib/src/importer/node_package.dart +++ b/lib/src/importer/node_package.dart @@ -96,11 +96,11 @@ class NodePackageImporterInternal extends Importer { // `packageName`. Uri? _resolvePackageRoot(String packageName, Uri baseURL) { var baseDirectory = p.dirname(Uri.file(baseURL.toString()).toFilePath()); - + print("baseDirectory: $baseDirectory, baseURL: $baseURL"); Uri? recurseUpFrom(String entry) { if (!entry.startsWith(p.separator)) entry = "${p.separator}$entry"; var potentialPackage = p.joinAll([entry, 'node_modules', packageName]); - + print("potentialPackage: $potentialPackage"); if (dirExists(potentialPackage)) { return Uri.file(potentialPackage); } From 1bcfb4cfd844d89c48b96be0c33a4dbf1ac76ccd Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Sat, 4 Nov 2023 13:24:34 -0400 Subject: [PATCH 019/113] Only prepend on non-Windows --- lib/src/importer/node_package.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/src/importer/node_package.dart b/lib/src/importer/node_package.dart index f1911ee84..dd15f8007 100644 --- a/lib/src/importer/node_package.dart +++ b/lib/src/importer/node_package.dart @@ -98,7 +98,9 @@ class NodePackageImporterInternal extends Importer { var baseDirectory = p.dirname(Uri.file(baseURL.toString()).toFilePath()); print("baseDirectory: $baseDirectory, baseURL: $baseURL"); Uri? recurseUpFrom(String entry) { - if (!entry.startsWith(p.separator)) entry = "${p.separator}$entry"; + if (!isWindows && !entry.startsWith(p.separator)) { + entry = "${p.separator}$entry"; + } var potentialPackage = p.joinAll([entry, 'node_modules', packageName]); print("potentialPackage: $potentialPackage"); if (dirExists(potentialPackage)) { From e84ff351bcd332b6c85d6401417ee729b78419a1 Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Mon, 6 Nov 2023 12:59:54 -0500 Subject: [PATCH 020/113] Add wildcard expansion --- lib/src/importer/node_package.dart | 66 +++++++++++++++++++++++++++--- 1 file changed, 60 insertions(+), 6 deletions(-) diff --git a/lib/src/importer/node_package.dart b/lib/src/importer/node_package.dart index dd15f8007..cad977b1a 100644 --- a/lib/src/importer/node_package.dart +++ b/lib/src/importer/node_package.dart @@ -184,10 +184,39 @@ class NodePackageImporterInternal extends Importer { if (exports is Map && exports.keys.every((key) => key.startsWith('.'))) { var matchKey = subpath.startsWith('/') ? ".$subpath" : "./$subpath"; - if (exports.containsKey(matchKey)) { + if (exports.containsKey(matchKey) && !matchKey.contains('*')) { return _packageTargetResolve( matchKey, exports[matchKey] as Object, packageRoot); } + var expansionKeys = exports.keys.where( + (key) => key.split('').where((char) => char == '*').length == 1); + expansionKeys = _sortExpansionKeys(expansionKeys.toList()); + + for (var expansionKey in expansionKeys) { + var parts = expansionKey.split('*'); + var patternBase = parts[0]; + if (matchKey.startsWith(patternBase) && matchKey != patternBase) { + var patternTrailer = parts[1]; + if (patternTrailer.isEmpty || + (matchKey.endsWith(patternTrailer) && + matchKey.length >= expansionKey.length)) { + var target = exports[expansionKey] as Object; + var patternMatch = matchKey.substring(patternBase.length, + matchKey.length - patternTrailer.length); + return _packageTargetResolve( + subpath, target, packageRoot, patternMatch); + } + } + } + +// For each key expansionKey in expansionKeys, do +// Let patternBase be the substring of expansionKey up to but excluding the first "*" character. +// If matchKey starts with but is not equal to patternBase, then +// Let patternTrailer be the substring of expansionKey from the index after the first "*" character. +// If patternTrailer has zero length, or if matchKey ends with patternTrailer and the length of matchKey is greater than or equal to the length of expansionKey, then +// Let target be the value of matchObj[expansionKey]. +// Let patternMatch be the substring of matchKey starting at the index of the length of patternBase up to the length of matchKey minus the length of patternTrailer. +// Return the result of PACKAGE_TARGET_RESOLVE(packageURL, target, patternMatch, isImports, conditions). } } return null; @@ -196,19 +225,44 @@ class NodePackageImporterInternal extends Importer { return subpathVariants.map(processVariant).whereNotNull().toList(); } - Uri? _packageTargetResolve(String subpath, Object exports, Uri packageRoot) { + /// Implementation of the `PATTERN_KEY_COMPARE` algorithm from + /// https://nodejs.org/api/esm.html#resolution-algorithm-specification. + List _sortExpansionKeys(List keys) { + int sorter(String keyA, String keyB) { + var baseLengthA = + keyA.contains('*') ? keyA.indexOf('*') + 1 : keyA.length; + var baseLengthB = + keyB.contains('*') ? keyB.indexOf('*') + 1 : keyB.length; + if (baseLengthA > baseLengthB) return -1; + if (baseLengthB > baseLengthA) return 1; + if (!keyA.contains("*")) return 1; + if (!keyB.contains("*")) return -1; + if (keyA.length > keyB.length) return -1; + if (keyB.length > keyA.length) return 1; + return 0; + } + + keys.sort(sorter); + return keys; + } + + Uri? _packageTargetResolve(String subpath, Object exports, Uri packageRoot, + [String? patternMatch]) { switch (exports) { case String string: if (!string.startsWith('./')) { throw "Invalid Package Target"; } + if (patternMatch != null) { + string = string.replaceAll(RegExp(r'\*'), patternMatch); + } return Uri.parse("$packageRoot/$string"); case Map map: var conditions = ['sass', 'style', 'default']; for (var key in map.keys) { if (conditions.contains(key)) { - var result = - _packageTargetResolve(subpath, map[key] as Object, packageRoot); + var result = _packageTargetResolve( + subpath, map[key] as Object, packageRoot, patternMatch); if (result != null) { return result; } @@ -219,8 +273,8 @@ class NodePackageImporterInternal extends Importer { if (array.isEmpty) return null; for (var value in array) { - var result = - _packageTargetResolve(subpath, value as Object, packageRoot); + var result = _packageTargetResolve( + subpath, value as Object, packageRoot, patternMatch); if (result != null) { return result; } From d5763c416aa81a5ca54865e6fed125f23d987924 Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Mon, 6 Nov 2023 15:17:40 -0500 Subject: [PATCH 021/113] Disable wildcard matching --- lib/src/importer/node_package.dart | 48 ++++++++++++++---------------- 1 file changed, 22 insertions(+), 26 deletions(-) diff --git a/lib/src/importer/node_package.dart b/lib/src/importer/node_package.dart index cad977b1a..d3efadc57 100644 --- a/lib/src/importer/node_package.dart +++ b/lib/src/importer/node_package.dart @@ -26,6 +26,9 @@ class NodePackageImporterInternal extends Importer { return scheme == 'pkg'; } + // Disable while determining path forward. + bool useWildcard = false; + @override Uri? canonicalize(Uri url) { if (url.scheme != 'pkg') return null; @@ -188,35 +191,28 @@ class NodePackageImporterInternal extends Importer { return _packageTargetResolve( matchKey, exports[matchKey] as Object, packageRoot); } - var expansionKeys = exports.keys.where( - (key) => key.split('').where((char) => char == '*').length == 1); - expansionKeys = _sortExpansionKeys(expansionKeys.toList()); - - for (var expansionKey in expansionKeys) { - var parts = expansionKey.split('*'); - var patternBase = parts[0]; - if (matchKey.startsWith(patternBase) && matchKey != patternBase) { - var patternTrailer = parts[1]; - if (patternTrailer.isEmpty || - (matchKey.endsWith(patternTrailer) && - matchKey.length >= expansionKey.length)) { - var target = exports[expansionKey] as Object; - var patternMatch = matchKey.substring(patternBase.length, - matchKey.length - patternTrailer.length); - return _packageTargetResolve( - subpath, target, packageRoot, patternMatch); + if (useWildcard) { + var expansionKeys = exports.keys.where((key) => + key.split('').where((char) => char == '*').length == 1); + expansionKeys = _sortExpansionKeys(expansionKeys.toList()); + + for (var expansionKey in expansionKeys) { + var parts = expansionKey.split('*'); + var patternBase = parts[0]; + if (matchKey.startsWith(patternBase) && matchKey != patternBase) { + var patternTrailer = parts[1]; + if (patternTrailer.isEmpty || + (matchKey.endsWith(patternTrailer) && + matchKey.length >= expansionKey.length)) { + var target = exports[expansionKey] as Object; + var patternMatch = matchKey.substring(patternBase.length, + matchKey.length - patternTrailer.length); + return _packageTargetResolve( + subpath, target, packageRoot, patternMatch); + } } } } - -// For each key expansionKey in expansionKeys, do -// Let patternBase be the substring of expansionKey up to but excluding the first "*" character. -// If matchKey starts with but is not equal to patternBase, then -// Let patternTrailer be the substring of expansionKey from the index after the first "*" character. -// If patternTrailer has zero length, or if matchKey ends with patternTrailer and the length of matchKey is greater than or equal to the length of expansionKey, then -// Let target be the value of matchObj[expansionKey]. -// Let patternMatch be the substring of matchKey starting at the index of the length of patternBase up to the length of matchKey minus the length of patternTrailer. -// Return the result of PACKAGE_TARGET_RESOLVE(packageURL, target, patternMatch, isImports, conditions). } } return null; From d24da96a22cdc893aeb269455e60759d4e2dc62e Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Mon, 6 Nov 2023 16:34:55 -0500 Subject: [PATCH 022/113] Revert to resolvePackageRoot working on Posix --- lib/src/importer/node_package.dart | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/lib/src/importer/node_package.dart b/lib/src/importer/node_package.dart index d3efadc57..dbebc80ac 100644 --- a/lib/src/importer/node_package.dart +++ b/lib/src/importer/node_package.dart @@ -98,12 +98,10 @@ class NodePackageImporterInternal extends Importer { // absolute URL to the root directory for the most proximate installed // `packageName`. Uri? _resolvePackageRoot(String packageName, Uri baseURL) { - var baseDirectory = p.dirname(Uri.file(baseURL.toString()).toFilePath()); - print("baseDirectory: $baseDirectory, baseURL: $baseURL"); + var baseDirectory = p.dirname(baseURL.toFilePath()); + Uri? recurseUpFrom(String entry) { - if (!isWindows && !entry.startsWith(p.separator)) { - entry = "${p.separator}$entry"; - } + if (!entry.startsWith("/")) entry = "/$entry"; var potentialPackage = p.joinAll([entry, 'node_modules', packageName]); print("potentialPackage: $potentialPackage"); if (dirExists(potentialPackage)) { From 535481d34d6c56be1a2aab3c1dbfd7e935abbd6e Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Mon, 6 Nov 2023 17:14:48 -0500 Subject: [PATCH 023/113] Windows pathing --- lib/src/importer/node_package.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/importer/node_package.dart b/lib/src/importer/node_package.dart index dbebc80ac..73ec07432 100644 --- a/lib/src/importer/node_package.dart +++ b/lib/src/importer/node_package.dart @@ -101,14 +101,14 @@ class NodePackageImporterInternal extends Importer { var baseDirectory = p.dirname(baseURL.toFilePath()); Uri? recurseUpFrom(String entry) { - if (!entry.startsWith("/")) entry = "/$entry"; + if (!isWindows && !entry.startsWith("/")) entry = "/$entry"; var potentialPackage = p.joinAll([entry, 'node_modules', packageName]); print("potentialPackage: $potentialPackage"); if (dirExists(potentialPackage)) { return Uri.file(potentialPackage); } List parentDirectoryParts = - List.from(Uri.parse(entry).pathSegments)..removeLast(); + List.from(Uri.file(entry).pathSegments)..removeLast(); if (parentDirectoryParts.isEmpty) return null; From ada3782761f70e212cefca99d9deeb8cc0ec8458 Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Mon, 6 Nov 2023 20:38:10 -0500 Subject: [PATCH 024/113] Move parentDir logic to io --- lib/src/importer/node_package.dart | 6 ++++-- lib/src/io/interface.dart | 2 ++ lib/src/io/js.dart | 2 ++ lib/src/io/vm.dart | 2 ++ 4 files changed, 10 insertions(+), 2 deletions(-) diff --git a/lib/src/importer/node_package.dart b/lib/src/importer/node_package.dart index 73ec07432..a92f48244 100644 --- a/lib/src/importer/node_package.dart +++ b/lib/src/importer/node_package.dart @@ -107,12 +107,14 @@ class NodePackageImporterInternal extends Importer { if (dirExists(potentialPackage)) { return Uri.file(potentialPackage); } + + var parent = parentDir(entry); List parentDirectoryParts = - List.from(Uri.file(entry).pathSegments)..removeLast(); + List.from(Uri.file(parent).pathSegments); if (parentDirectoryParts.isEmpty) return null; - return recurseUpFrom(p.joinAll(parentDirectoryParts)); + return recurseUpFrom(parent); } return recurseUpFrom(baseDirectory); diff --git a/lib/src/io/interface.dart b/lib/src/io/interface.dart index 29182eb45..ede97b358 100644 --- a/lib/src/io/interface.dart +++ b/lib/src/io/interface.dart @@ -92,3 +92,5 @@ set exitCode(int value) => throw ''; /// periodically rather than using a native filesystem monitoring API. Future> watchDir(String path, {bool poll = false}) => throw ''; + +String parentDir(String path) => throw ''; diff --git a/lib/src/io/js.dart b/lib/src/io/js.dart index 94e4f1a13..74cb685c4 100644 --- a/lib/src/io/js.dart +++ b/lib/src/io/js.dart @@ -285,3 +285,5 @@ Future> watchDir(String path, {bool poll = false}) { return completer.future; } + +String parentDir(String path) => p.dirname(path); diff --git a/lib/src/io/vm.dart b/lib/src/io/vm.dart index 1d5c7b561..b14b44f69 100644 --- a/lib/src/io/vm.dart +++ b/lib/src/io/vm.dart @@ -114,3 +114,5 @@ Future> watchDir(String path, {bool poll = false}) async { return stream; } + +String parentDir(String path) => io.FileSystemEntity.parentOf(path); From 60f9892e2eb28473d4d7fac4efd24fb0d191bbbf Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Mon, 6 Nov 2023 22:16:49 -0500 Subject: [PATCH 025/113] Try separate pathing for windows --- lib/src/importer/node_package.dart | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/src/importer/node_package.dart b/lib/src/importer/node_package.dart index a92f48244..a04b05835 100644 --- a/lib/src/importer/node_package.dart +++ b/lib/src/importer/node_package.dart @@ -98,10 +98,11 @@ class NodePackageImporterInternal extends Importer { // absolute URL to the root directory for the most proximate installed // `packageName`. Uri? _resolvePackageRoot(String packageName, Uri baseURL) { - var baseDirectory = p.dirname(baseURL.toFilePath()); + var baseDirectory = isWindows + ? p.dirname(Uri.file(baseURL.toString()).toFilePath()) + : p.dirname(baseURL.toFilePath()); Uri? recurseUpFrom(String entry) { - if (!isWindows && !entry.startsWith("/")) entry = "/$entry"; var potentialPackage = p.joinAll([entry, 'node_modules', packageName]); print("potentialPackage: $potentialPackage"); if (dirExists(potentialPackage)) { @@ -112,7 +113,7 @@ class NodePackageImporterInternal extends Importer { List parentDirectoryParts = List.from(Uri.file(parent).pathSegments); - if (parentDirectoryParts.isEmpty) return null; + if (parentDirectoryParts.length == 1) return null; return recurseUpFrom(parent); } From 51e5bba3dc13eee9d953da21c4ae943c0f14ab26 Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Tue, 7 Nov 2023 16:37:45 -0500 Subject: [PATCH 026/113] Enable wildcards --- lib/src/importer/node_package.dart | 48 ++++++++++++++++-------------- 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/lib/src/importer/node_package.dart b/lib/src/importer/node_package.dart index a04b05835..bbc72854f 100644 --- a/lib/src/importer/node_package.dart +++ b/lib/src/importer/node_package.dart @@ -26,9 +26,6 @@ class NodePackageImporterInternal extends Importer { return scheme == 'pkg'; } - // Disable while determining path forward. - bool useWildcard = false; - @override Uri? canonicalize(Uri url) { if (url.scheme != 'pkg') return null; @@ -192,25 +189,24 @@ class NodePackageImporterInternal extends Importer { return _packageTargetResolve( matchKey, exports[matchKey] as Object, packageRoot); } - if (useWildcard) { - var expansionKeys = exports.keys.where((key) => - key.split('').where((char) => char == '*').length == 1); - expansionKeys = _sortExpansionKeys(expansionKeys.toList()); - - for (var expansionKey in expansionKeys) { - var parts = expansionKey.split('*'); - var patternBase = parts[0]; - if (matchKey.startsWith(patternBase) && matchKey != patternBase) { - var patternTrailer = parts[1]; - if (patternTrailer.isEmpty || - (matchKey.endsWith(patternTrailer) && - matchKey.length >= expansionKey.length)) { - var target = exports[expansionKey] as Object; - var patternMatch = matchKey.substring(patternBase.length, - matchKey.length - patternTrailer.length); - return _packageTargetResolve( - subpath, target, packageRoot, patternMatch); - } + + var expansionKeys = exports.keys.where( + (key) => key.split('').where((char) => char == '*').length == 1); + expansionKeys = _sortExpansionKeys(expansionKeys.toList()); + + for (var expansionKey in expansionKeys) { + var parts = expansionKey.split('*'); + var patternBase = parts[0]; + if (matchKey.startsWith(patternBase) && matchKey != patternBase) { + var patternTrailer = parts[1]; + if (patternTrailer.isEmpty || + (matchKey.endsWith(patternTrailer) && + matchKey.length >= expansionKey.length)) { + var target = exports[expansionKey] as Object; + var patternMatch = matchKey.substring(patternBase.length, + matchKey.length - patternTrailer.length); + return _packageTargetResolve( + subpath, target, packageRoot, patternMatch); } } } @@ -252,6 +248,12 @@ class NodePackageImporterInternal extends Importer { } if (patternMatch != null) { string = string.replaceAll(RegExp(r'\*'), patternMatch); + var path = p.normalize("${packageRoot.toFilePath()}/$string"); + if (fileExists(path)) { + return Uri.parse('$packageRoot/$string'); + } else { + return null; + } } return Uri.parse("$packageRoot/$string"); case Map map: @@ -335,7 +337,7 @@ class NodePackageImporterInternal extends Importer { if (dirPath.isEmpty) { prefixedPaths.add('_$pathBasename'); } else { - prefixedPaths.add('$dirPath/_$pathBasename'); + prefixedPaths.add('${dirPath.join(p.separator)}/_$pathBasename'); } } paths.addAll(prefixedPaths); From c391990c248eda8b4530e9d178c51651496b880d Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Tue, 7 Nov 2023 17:04:24 -0500 Subject: [PATCH 027/113] Document functions --- lib/src/importer/node_package.dart | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/src/importer/node_package.dart b/lib/src/importer/node_package.dart index bbc72854f..89ea35207 100644 --- a/lib/src/importer/node_package.dart +++ b/lib/src/importer/node_package.dart @@ -218,8 +218,8 @@ class NodePackageImporterInternal extends Importer { return subpathVariants.map(processVariant).whereNotNull().toList(); } - /// Implementation of the `PATTERN_KEY_COMPARE` algorithm from - /// https://nodejs.org/api/esm.html#resolution-algorithm-specification. + // Implementation of the `PATTERN_KEY_COMPARE` algorithm from + // https://nodejs.org/api/esm.html#resolution-algorithm-specification. List _sortExpansionKeys(List keys) { int sorter(String keyA, String keyB) { var baseLengthA = @@ -239,6 +239,7 @@ class NodePackageImporterInternal extends Importer { return keys; } + // Recurses through `exports` object to find match for `subpath`. Uri? _packageTargetResolve(String subpath, Object exports, Uri packageRoot, [String? patternMatch]) { switch (exports) { @@ -286,6 +287,8 @@ class NodePackageImporterInternal extends Importer { return null; } + // Given an `exports` object, returns the entry for an export without a + // subpath. Object? _getMainExport(Object exports) { switch (exports) { case String string: From 787d261fc322642e7b4552ed37df516117c250ba Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Wed, 8 Nov 2023 11:20:02 -0500 Subject: [PATCH 028/113] Revert incorrect @internal, add CacheImporter.only --- lib/src/async_import_cache.dart | 6 ++++++ lib/src/import_cache.dart | 8 +++++++- lib/src/importer/filesystem.dart | 2 -- lib/src/js/legacy.dart | 5 +++-- 4 files changed, 16 insertions(+), 5 deletions(-) diff --git a/lib/src/async_import_cache.dart b/lib/src/async_import_cache.dart index 9b08e5597..fafe4080a 100644 --- a/lib/src/async_import_cache.dart +++ b/lib/src/async_import_cache.dart @@ -104,6 +104,12 @@ final class AsyncImportCache { : _importers = const [], _logger = logger ?? const Logger.stderr(); + /// Creates an import cache without any globally-available importers, and only + /// the passed in importers. + AsyncImportCache.only({Iterable? importers, Logger? logger}) + : _importers = [...?importers], + _logger = logger ?? const Logger.stderr(); + /// Converts the user's [importers], [loadPaths], and [packageConfig] /// options into a single list of importers. static List _toImporters(Iterable? importers, diff --git a/lib/src/import_cache.dart b/lib/src/import_cache.dart index 397e676aa..c1ddc3131 100644 --- a/lib/src/import_cache.dart +++ b/lib/src/import_cache.dart @@ -5,7 +5,7 @@ // DO NOT EDIT. This file was generated from async_import_cache.dart. // See tool/grind/synchronize.dart for details. // -// Checksum: 342e907cf10e1dd80d7045fc32db43c74376654e +// Checksum: 039ce81351c314df5ec550096ab034ac3595916b // // ignore_for_file: unused_import @@ -106,6 +106,12 @@ final class ImportCache { : _importers = const [], _logger = logger ?? const Logger.stderr(); + /// Creates an import cache without any globally-available importers, and only + /// the passed in importers. + ImportCache.only({Iterable? importers, Logger? logger}) + : _importers = [...?importers], + _logger = logger ?? const Logger.stderr(); + /// Converts the user's [importers], [loadPaths], and [packageConfig] /// options into a single list of importers. static List _toImporters(Iterable? importers, diff --git a/lib/src/importer/filesystem.dart b/lib/src/importer/filesystem.dart index 9872ad112..31af69829 100644 --- a/lib/src/importer/filesystem.dart +++ b/lib/src/importer/filesystem.dart @@ -20,8 +20,6 @@ class FilesystemImporter extends Importer { final String _loadPath; /// Creates an importer that loads files relative to [loadPath]. - /// @nodoc - @internal FilesystemImporter(String loadPath) : _loadPath = p.absolute(loadPath); Uri? canonicalize(Uri url) { diff --git a/lib/src/js/legacy.dart b/lib/src/js/legacy.dart index 67a46fa79..c23d881c4 100644 --- a/lib/src/js/legacy.dart +++ b/lib/src/js/legacy.dart @@ -303,7 +303,7 @@ AsyncImportCache? _parsePackageImportersAsync( if (options.pkgImporter case 'node') { // TODO(jamesnw) Can we get an actual filename for parity? Is it needed? Uri entryPointURL = Uri.parse(p.absolute('./index.js')); - return AsyncImportCache( + return AsyncImportCache.only( importers: [NodePackageImporterInternal(entryPointURL)]); } return null; @@ -314,7 +314,8 @@ ImportCache? _parsePackageImporters(RenderOptions options, DateTime start) { if (options.pkgImporter case 'node') { // TODO(jamesnw) Can we get an actual filename for parity? Is it needed? Uri entryPointURL = Uri.parse(p.absolute('./index.js')); - return ImportCache(importers: [NodePackageImporterInternal(entryPointURL)]); + return ImportCache.only( + importers: [NodePackageImporterInternal(entryPointURL)]); } return null; } From 851fa6f41502ce0beb503980409e590f9fb2b9cd Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Wed, 8 Nov 2023 12:23:47 -0500 Subject: [PATCH 029/113] Fix legacy importer order bug --- lib/src/visitor/async_evaluate.dart | 5 ++++- lib/src/visitor/evaluate.dart | 5 +++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/src/visitor/async_evaluate.dart b/lib/src/visitor/async_evaluate.dart index 53492e18c..600afb80a 100644 --- a/lib/src/visitor/async_evaluate.dart +++ b/lib/src/visitor/async_evaluate.dart @@ -337,7 +337,10 @@ final class _EvaluateVisitor Logger? logger, bool quietDeps = false, bool sourceMap = false}) - : _importCache = importCache ?? AsyncImportCache.none(logger: logger), + : _importCache = importCache ?? + (nodeImporter == null + ? AsyncImportCache.none(logger: logger) + : null), _nodeImporter = nodeImporter, _logger = logger ?? const Logger.stderr(), _quietDeps = quietDeps, diff --git a/lib/src/visitor/evaluate.dart b/lib/src/visitor/evaluate.dart index e1cb3ac35..795ebbd01 100644 --- a/lib/src/visitor/evaluate.dart +++ b/lib/src/visitor/evaluate.dart @@ -5,7 +5,7 @@ // DO NOT EDIT. This file was generated from async_evaluate.dart. // See tool/grind/synchronize.dart for details. // -// Checksum: 243938b731f0196c3df9a7d6f8fce9cb84a6de60 +// Checksum: 17c4d5069f1c6a7f159c7a228cdc9802b1b0707d // // ignore_for_file: unused_import @@ -345,7 +345,8 @@ final class _EvaluateVisitor Logger? logger, bool quietDeps = false, bool sourceMap = false}) - : _importCache = importCache ?? ImportCache.none(logger: logger), + : _importCache = importCache ?? + (nodeImporter == null ? ImportCache.none(logger: logger) : null), _nodeImporter = nodeImporter, _logger = logger ?? const Logger.stderr(), _quietDeps = quietDeps, From 99d7ad283a6bcb64f8ac0448884a9b08010340f4 Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Wed, 8 Nov 2023 13:43:06 -0500 Subject: [PATCH 030/113] Remove debug print --- lib/src/importer/node_package.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/src/importer/node_package.dart b/lib/src/importer/node_package.dart index 89ea35207..f1da5f432 100644 --- a/lib/src/importer/node_package.dart +++ b/lib/src/importer/node_package.dart @@ -101,7 +101,6 @@ class NodePackageImporterInternal extends Importer { Uri? recurseUpFrom(String entry) { var potentialPackage = p.joinAll([entry, 'node_modules', packageName]); - print("potentialPackage: $potentialPackage"); if (dirExists(potentialPackage)) { return Uri.file(potentialPackage); } From e63fb1bad61a49b35b18f7062585f469adfe7baa Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Wed, 8 Nov 2023 14:55:14 -0500 Subject: [PATCH 031/113] Don't throw if pkg not found, load package.json with windows-friendly path --- lib/src/importer/node_package.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/importer/node_package.dart b/lib/src/importer/node_package.dart index f1da5f432..b68761ea3 100644 --- a/lib/src/importer/node_package.dart +++ b/lib/src/importer/node_package.dart @@ -46,11 +46,11 @@ class NodePackageImporterInternal extends Importer { var (packageName, subpath) = _packageNameAndSubpath(url.path); var packageRoot = _resolvePackageRoot(packageName, baseURL); if (packageRoot == null) { - throw "Node Package '$packageName' could not be found."; + return null; } // Attempt to resolve using conditional exports - var jsonString = readFile(packageRoot.path + '/package.json'); + var jsonString = readFile(p.join(packageRoot.path, 'package.json')); var packageManifest = jsonDecode(jsonString) as Map; var resolved = _resolvePackageExports( From ce5caea85c9163e1ad965c0903b2c05395fb67ec Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Wed, 8 Nov 2023 15:08:43 -0500 Subject: [PATCH 032/113] Add logging statements --- lib/src/importer/node_package.dart | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/src/importer/node_package.dart b/lib/src/importer/node_package.dart index b68761ea3..98c8c214b 100644 --- a/lib/src/importer/node_package.dart +++ b/lib/src/importer/node_package.dart @@ -46,15 +46,18 @@ class NodePackageImporterInternal extends Importer { var (packageName, subpath) = _packageNameAndSubpath(url.path); var packageRoot = _resolvePackageRoot(packageName, baseURL); if (packageRoot == null) { + print("resolving packageRoot Failed"); return null; } // Attempt to resolve using conditional exports var jsonString = readFile(p.join(packageRoot.path, 'package.json')); var packageManifest = jsonDecode(jsonString) as Map; + print("loaded jsonString, packageManifest"); var resolved = _resolvePackageExports( packageRoot, subpath, packageManifest, packageName); + print("resolved $resolved"); if (resolved != null && resolved.scheme == 'file' && @@ -68,8 +71,10 @@ class NodePackageImporterInternal extends Importer { // then `index` file at package root, resolved for file extensions and // partials. if (subpath == '') { + print("subpath"); return _resolvePackageRootValues(packageRoot.path, packageManifest); } + print('filesystem'); // If there is a subpath, attempt to resolve the path relative to the package root, and // resolve for file extensions and partials. From 69ec0abaced745fd569178a0c4dda689385c3d22 Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Wed, 8 Nov 2023 15:14:21 -0500 Subject: [PATCH 033/113] Add logging statements, v2 --- lib/src/importer/node_package.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/src/importer/node_package.dart b/lib/src/importer/node_package.dart index 98c8c214b..29a503d75 100644 --- a/lib/src/importer/node_package.dart +++ b/lib/src/importer/node_package.dart @@ -103,9 +103,11 @@ class NodePackageImporterInternal extends Importer { var baseDirectory = isWindows ? p.dirname(Uri.file(baseURL.toString()).toFilePath()) : p.dirname(baseURL.toFilePath()); + print("resolve $baseDirectory"); Uri? recurseUpFrom(String entry) { var potentialPackage = p.joinAll([entry, 'node_modules', packageName]); + print("recurse $entry $potentialPackage"); if (dirExists(potentialPackage)) { return Uri.file(potentialPackage); } From c0e85a8988e36e455c9e743a03bb1cc77f23846a Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Wed, 8 Nov 2023 15:30:10 -0500 Subject: [PATCH 034/113] Add logging statements, v3 --- lib/src/importer/node_package.dart | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/src/importer/node_package.dart b/lib/src/importer/node_package.dart index 29a503d75..bf319a831 100644 --- a/lib/src/importer/node_package.dart +++ b/lib/src/importer/node_package.dart @@ -49,6 +49,7 @@ class NodePackageImporterInternal extends Importer { print("resolving packageRoot Failed"); return null; } + print("resolving packageRoot worked"); // Attempt to resolve using conditional exports var jsonString = readFile(p.join(packageRoot.path, 'package.json')); @@ -111,13 +112,17 @@ class NodePackageImporterInternal extends Importer { if (dirExists(potentialPackage)) { return Uri.file(potentialPackage); } + print("directory doesn't exist"); var parent = parentDir(entry); List parentDirectoryParts = List.from(Uri.file(parent).pathSegments); + print("parent $parent ${parentDirectoryParts.join(p.separator)}"); if (parentDirectoryParts.length == 1) return null; + print("length longer than 1, recurse again."); + return recurseUpFrom(parent); } From f1936d9e10679e5fc697dd70fd9364c8d7cefc85 Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Wed, 8 Nov 2023 20:36:14 -0500 Subject: [PATCH 035/113] Add logging statements, v4 --- .github/workflows/ci.yml | 26 ++------------------------ lib/src/importer/node_package.dart | 8 +++++++- 2 files changed, 9 insertions(+), 25 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0e55102c9..b0021f102 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -94,20 +94,9 @@ jobs: strategy: fail-fast: false matrix: - os: [ubuntu-latest, windows-latest, macos-latest] + os: [windows-latest] dart_channel: [stable] node-version: [18] - include: - # Include LTS versions on Ubuntu - - os: ubuntu-latest - dart_channel: stable - node-version: 16 - - os: ubuntu-latest - dart_channel: stable - node-version: 14 - - os: ubuntu-latest - dart_channel: dev - node-version: 18 steps: - uses: actions/checkout@v4 @@ -263,20 +252,9 @@ jobs: fail-fast: false matrix: - os: [ubuntu-latest, windows-latest, macos-latest] + os: [windows-latest] dart_channel: [stable] node-version: [18] - include: - # Include LTS versions on Ubuntu - - os: ubuntu-latest - dart_channel: stable - node-version: 16 - - os: ubuntu-latest - dart_channel: stable - node-version: 14 - - os: ubuntu-latest - dart_channel: dev - node-version: 18 steps: - uses: actions/checkout@v4 - uses: ./.github/util/initialize diff --git a/lib/src/importer/node_package.dart b/lib/src/importer/node_package.dart index bf319a831..3ae552f8b 100644 --- a/lib/src/importer/node_package.dart +++ b/lib/src/importer/node_package.dart @@ -52,7 +52,9 @@ class NodePackageImporterInternal extends Importer { print("resolving packageRoot worked"); // Attempt to resolve using conditional exports - var jsonString = readFile(p.join(packageRoot.path, 'package.json')); + var jsonPath = p.join(packageRoot.path, 'package.json'); + print("jsonPath $jsonPath"); + var jsonString = readFile(jsonPath); var packageManifest = jsonDecode(jsonString) as Map; print("loaded jsonString, packageManifest"); @@ -105,8 +107,12 @@ class NodePackageImporterInternal extends Importer { ? p.dirname(Uri.file(baseURL.toString()).toFilePath()) : p.dirname(baseURL.toFilePath()); print("resolve $baseDirectory"); + var lastEntry = ''; Uri? recurseUpFrom(String entry) { + // prevent infinite recursion + if (entry == lastEntry) return null; + lastEntry = entry; var potentialPackage = p.joinAll([entry, 'node_modules', packageName]); print("recurse $entry $potentialPackage"); if (dirExists(potentialPackage)) { From 8da28952b6e3f920a69498374749fd1f31a64808 Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Wed, 8 Nov 2023 22:15:41 -0500 Subject: [PATCH 036/113] Uri.file > Uri.directory --- lib/src/importer/node_package.dart | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/src/importer/node_package.dart b/lib/src/importer/node_package.dart index 3ae552f8b..c6267c484 100644 --- a/lib/src/importer/node_package.dart +++ b/lib/src/importer/node_package.dart @@ -49,7 +49,7 @@ class NodePackageImporterInternal extends Importer { print("resolving packageRoot Failed"); return null; } - print("resolving packageRoot worked"); + print("resolving packageRoot worked - ${packageRoot.path}"); // Attempt to resolve using conditional exports var jsonPath = p.join(packageRoot.path, 'package.json'); @@ -104,7 +104,7 @@ class NodePackageImporterInternal extends Importer { // `packageName`. Uri? _resolvePackageRoot(String packageName, Uri baseURL) { var baseDirectory = isWindows - ? p.dirname(Uri.file(baseURL.toString()).toFilePath()) + ? p.dirname(Uri.directory(baseURL.toString()).toFilePath()) : p.dirname(baseURL.toFilePath()); print("resolve $baseDirectory"); var lastEntry = ''; @@ -116,13 +116,13 @@ class NodePackageImporterInternal extends Importer { var potentialPackage = p.joinAll([entry, 'node_modules', packageName]); print("recurse $entry $potentialPackage"); if (dirExists(potentialPackage)) { - return Uri.file(potentialPackage); + return Uri.directory(potentialPackage); } print("directory doesn't exist"); var parent = parentDir(entry); List parentDirectoryParts = - List.from(Uri.file(parent).pathSegments); + List.from(Uri.directory(parent).pathSegments); print("parent $parent ${parentDirectoryParts.join(p.separator)}"); if (parentDirectoryParts.length == 1) return null; From 168853199519051a98a836b9032130e2b26a4b61 Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Wed, 8 Nov 2023 22:30:44 -0500 Subject: [PATCH 037/113] Convert path to Uri to filestring --- lib/src/importer/node_package.dart | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/src/importer/node_package.dart b/lib/src/importer/node_package.dart index c6267c484..d9dda8726 100644 --- a/lib/src/importer/node_package.dart +++ b/lib/src/importer/node_package.dart @@ -54,7 +54,11 @@ class NodePackageImporterInternal extends Importer { // Attempt to resolve using conditional exports var jsonPath = p.join(packageRoot.path, 'package.json'); print("jsonPath $jsonPath"); - var jsonString = readFile(jsonPath); + + var jsonFile = Uri.file(jsonPath).toFilePath(); + print("jsonFile $jsonFile"); + + var jsonString = readFile(jsonFile); var packageManifest = jsonDecode(jsonString) as Map; print("loaded jsonString, packageManifest"); From a16263694e585735f5c3438ecebafb0cb89d22fc Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Thu, 9 Nov 2023 09:23:43 -0500 Subject: [PATCH 038/113] Use file path --- lib/src/importer/node_package.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/importer/node_package.dart b/lib/src/importer/node_package.dart index d9dda8726..94aad94ad 100644 --- a/lib/src/importer/node_package.dart +++ b/lib/src/importer/node_package.dart @@ -52,7 +52,7 @@ class NodePackageImporterInternal extends Importer { print("resolving packageRoot worked - ${packageRoot.path}"); // Attempt to resolve using conditional exports - var jsonPath = p.join(packageRoot.path, 'package.json'); + var jsonPath = p.join(packageRoot.toFilePath(), 'package.json'); print("jsonPath $jsonPath"); var jsonFile = Uri.file(jsonPath).toFilePath(); From e44215bada3cdb5e2b9fa58e76b23de28dc240fa Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Thu, 9 Nov 2023 09:45:42 -0500 Subject: [PATCH 039/113] More filepath usage --- lib/src/importer/node_package.dart | 25 +++++++------------------ 1 file changed, 7 insertions(+), 18 deletions(-) diff --git a/lib/src/importer/node_package.dart b/lib/src/importer/node_package.dart index 94aad94ad..08e49d25e 100644 --- a/lib/src/importer/node_package.dart +++ b/lib/src/importer/node_package.dart @@ -46,21 +46,15 @@ class NodePackageImporterInternal extends Importer { var (packageName, subpath) = _packageNameAndSubpath(url.path); var packageRoot = _resolvePackageRoot(packageName, baseURL); if (packageRoot == null) { - print("resolving packageRoot Failed"); return null; } - print("resolving packageRoot worked - ${packageRoot.path}"); // Attempt to resolve using conditional exports var jsonPath = p.join(packageRoot.toFilePath(), 'package.json'); - print("jsonPath $jsonPath"); - var jsonFile = Uri.file(jsonPath).toFilePath(); - print("jsonFile $jsonFile"); var jsonString = readFile(jsonFile); var packageManifest = jsonDecode(jsonString) as Map; - print("loaded jsonString, packageManifest"); var resolved = _resolvePackageExports( packageRoot, subpath, packageManifest, packageName); @@ -78,15 +72,16 @@ class NodePackageImporterInternal extends Importer { // then `index` file at package root, resolved for file extensions and // partials. if (subpath == '') { - print("subpath"); - return _resolvePackageRootValues(packageRoot.path, packageManifest); + print("no subpath"); + return _resolvePackageRootValues( + packageRoot.toFilePath(), packageManifest); } - print('filesystem'); // If there is a subpath, attempt to resolve the path relative to the package root, and // resolve for file extensions and partials. - return _filesystemImporter - .canonicalize(Uri.file("${packageRoot.path}${p.separator}$subpath")); + var relativeSubpath = "${packageRoot.toFilePath()}${p.separator}$subpath"; + print("filesystem $relativeSubpath"); + return _filesystemImporter.canonicalize(Uri.file(relativeSubpath)); } @override @@ -110,7 +105,6 @@ class NodePackageImporterInternal extends Importer { var baseDirectory = isWindows ? p.dirname(Uri.directory(baseURL.toString()).toFilePath()) : p.dirname(baseURL.toFilePath()); - print("resolve $baseDirectory"); var lastEntry = ''; Uri? recurseUpFrom(String entry) { @@ -118,21 +112,16 @@ class NodePackageImporterInternal extends Importer { if (entry == lastEntry) return null; lastEntry = entry; var potentialPackage = p.joinAll([entry, 'node_modules', packageName]); - print("recurse $entry $potentialPackage"); + if (dirExists(potentialPackage)) { return Uri.directory(potentialPackage); } - print("directory doesn't exist"); var parent = parentDir(entry); List parentDirectoryParts = List.from(Uri.directory(parent).pathSegments); - print("parent $parent ${parentDirectoryParts.join(p.separator)}"); if (parentDirectoryParts.length == 1) return null; - - print("length longer than 1, recurse again."); - return recurseUpFrom(parent); } From e69ce0c442ffd768b234efafb3a10edf1e562a0e Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Thu, 9 Nov 2023 10:17:42 -0500 Subject: [PATCH 040/113] Inspect entry values --- lib/src/importer/node_package.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/src/importer/node_package.dart b/lib/src/importer/node_package.dart index 08e49d25e..a2a161361 100644 --- a/lib/src/importer/node_package.dart +++ b/lib/src/importer/node_package.dart @@ -42,6 +42,8 @@ class NodePackageImporterInternal extends Importer { } var baseURL = containingUrl?.scheme == 'file' ? containingUrl! : entryPointURL; + print( + "resolving $url, containing $containingUrl, entry $entryPointURL, base $baseURL"); var (packageName, subpath) = _packageNameAndSubpath(url.path); var packageRoot = _resolvePackageRoot(packageName, baseURL); @@ -72,7 +74,7 @@ class NodePackageImporterInternal extends Importer { // then `index` file at package root, resolved for file extensions and // partials. if (subpath == '') { - print("no subpath"); + print("no subpath $packageRoot"); return _resolvePackageRootValues( packageRoot.toFilePath(), packageManifest); } From afc32bb977655514fe7997c3cf02dea8bedd1f27 Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Thu, 9 Nov 2023 10:30:04 -0500 Subject: [PATCH 041/113] Remove duplicated separators --- lib/src/importer/node_package.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/src/importer/node_package.dart b/lib/src/importer/node_package.dart index a2a161361..820e6c440 100644 --- a/lib/src/importer/node_package.dart +++ b/lib/src/importer/node_package.dart @@ -139,14 +139,14 @@ class NodePackageImporterInternal extends Importer { var sassValue = packageManifest['sass'] as String?; if (sassValue != null && extensions.contains(p.extension(sassValue))) { - return Uri.file('$packageRoot/$sassValue'); + return Uri.file('$packageRoot$sassValue'); } var styleValue = packageManifest['style'] as String?; if (styleValue != null && extensions.contains(p.extension(styleValue))) { - return Uri.file('$packageRoot${p.separator}$styleValue'); + return Uri.file('$packageRoot$styleValue'); } - var result = resolveImportPath('$packageRoot${p.separator}index'); + var result = resolveImportPath('${packageRoot}index'); if (result != null) return Uri.file(result); return null; } From aa7c5f449bdc261b6ec24bf3ec5d70640f0d6b63 Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Thu, 9 Nov 2023 10:40:55 -0500 Subject: [PATCH 042/113] Remove scheme from containing url --- lib/src/importer/node_package.dart | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/src/importer/node_package.dart b/lib/src/importer/node_package.dart index 820e6c440..69f05463f 100644 --- a/lib/src/importer/node_package.dart +++ b/lib/src/importer/node_package.dart @@ -40,8 +40,10 @@ class NodePackageImporterInternal extends Importer { if (url.userInfo != '' || url.hasPort || url.hasQuery || url.hasFragment) { throw "Invalid URL $url"; } - var baseURL = - containingUrl?.scheme == 'file' ? containingUrl! : entryPointURL; + + var baseURL = containingUrl?.scheme == 'file' + ? Uri.parse(containingUrl!.toFilePath()) + : entryPointURL; print( "resolving $url, containing $containingUrl, entry $entryPointURL, base $baseURL"); From 0549d2fcb6a4ea5a884ef181e631c041751ff85e Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Thu, 9 Nov 2023 10:48:18 -0500 Subject: [PATCH 043/113] Remove log statements --- .github/workflows/ci.yml | 26 ++++++++++++++++++++++++-- lib/src/importer/node_package.dart | 5 ----- 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b0021f102..0e55102c9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -94,9 +94,20 @@ jobs: strategy: fail-fast: false matrix: - os: [windows-latest] + os: [ubuntu-latest, windows-latest, macos-latest] dart_channel: [stable] node-version: [18] + include: + # Include LTS versions on Ubuntu + - os: ubuntu-latest + dart_channel: stable + node-version: 16 + - os: ubuntu-latest + dart_channel: stable + node-version: 14 + - os: ubuntu-latest + dart_channel: dev + node-version: 18 steps: - uses: actions/checkout@v4 @@ -252,9 +263,20 @@ jobs: fail-fast: false matrix: - os: [windows-latest] + os: [ubuntu-latest, windows-latest, macos-latest] dart_channel: [stable] node-version: [18] + include: + # Include LTS versions on Ubuntu + - os: ubuntu-latest + dart_channel: stable + node-version: 16 + - os: ubuntu-latest + dart_channel: stable + node-version: 14 + - os: ubuntu-latest + dart_channel: dev + node-version: 18 steps: - uses: actions/checkout@v4 - uses: ./.github/util/initialize diff --git a/lib/src/importer/node_package.dart b/lib/src/importer/node_package.dart index 69f05463f..7db52976a 100644 --- a/lib/src/importer/node_package.dart +++ b/lib/src/importer/node_package.dart @@ -44,8 +44,6 @@ class NodePackageImporterInternal extends Importer { var baseURL = containingUrl?.scheme == 'file' ? Uri.parse(containingUrl!.toFilePath()) : entryPointURL; - print( - "resolving $url, containing $containingUrl, entry $entryPointURL, base $baseURL"); var (packageName, subpath) = _packageNameAndSubpath(url.path); var packageRoot = _resolvePackageRoot(packageName, baseURL); @@ -62,7 +60,6 @@ class NodePackageImporterInternal extends Importer { var resolved = _resolvePackageExports( packageRoot, subpath, packageManifest, packageName); - print("resolved $resolved"); if (resolved != null && resolved.scheme == 'file' && @@ -76,7 +73,6 @@ class NodePackageImporterInternal extends Importer { // then `index` file at package root, resolved for file extensions and // partials. if (subpath == '') { - print("no subpath $packageRoot"); return _resolvePackageRootValues( packageRoot.toFilePath(), packageManifest); } @@ -84,7 +80,6 @@ class NodePackageImporterInternal extends Importer { // If there is a subpath, attempt to resolve the path relative to the package root, and // resolve for file extensions and partials. var relativeSubpath = "${packageRoot.toFilePath()}${p.separator}$subpath"; - print("filesystem $relativeSubpath"); return _filesystemImporter.canonicalize(Uri.file(relativeSubpath)); } From 757309e6dac78283f4d11edb9f04854448b7e915 Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Mon, 13 Nov 2023 09:15:55 -0500 Subject: [PATCH 044/113] Documentation comments --- lib/src/importer/node_package.dart | 52 +++++++++++++++--------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/lib/src/importer/node_package.dart b/lib/src/importer/node_package.dart index 7db52976a..fee1f7b09 100644 --- a/lib/src/importer/node_package.dart +++ b/lib/src/importer/node_package.dart @@ -12,13 +12,13 @@ import 'package:path/path.dart' as p; /// A filesystem importer to use for load implementation details, and for /// canonicalizing paths not defined in package.json. -/// final _filesystemImporter = FilesystemImporter('.'); -/// An importer that resolves `pkg:` URLs using the Node resolution algorithm. +/// An [Importer] that resolves `pkg:` URLs using the Node resolution algorithm. class NodePackageImporterInternal extends Importer { final Uri entryPointURL; - // Creates an importer with the associated entry point url + + /// Creates a Node Package Importer with the associated entry point url NodePackageImporterInternal(this.entryPointURL); @override @@ -77,8 +77,8 @@ class NodePackageImporterInternal extends Importer { packageRoot.toFilePath(), packageManifest); } - // If there is a subpath, attempt to resolve the path relative to the package root, and - // resolve for file extensions and partials. + // If there is a subpath, attempt to resolve the path relative to the + // package root, and resolve for file extensions and partials. var relativeSubpath = "${packageRoot.toFilePath()}${p.separator}$subpath"; return _filesystemImporter.canonicalize(Uri.file(relativeSubpath)); } @@ -86,8 +86,8 @@ class NodePackageImporterInternal extends Importer { @override ImporterResult? load(Uri url) => _filesystemImporter.load(url); - // Takes a string, `path`, and returns a tuple with the package name and the - // subpath if it is present. + /// Takes a string, `path`, and returns a tuple with the package name and the + /// subpath if it is present. (String, String) _packageNameAndSubpath(String path) { var parts = path.split('/'); var name = parts.removeAt(0); @@ -97,9 +97,9 @@ class NodePackageImporterInternal extends Importer { return (name, parts.isNotEmpty ? parts.join('/') : ''); } - // Takes a string, `packageName`, and an absolute URL `baseURL`, and returns an - // absolute URL to the root directory for the most proximate installed - // `packageName`. + /// Takes a string, `packageName`, and an absolute URL `baseURL`, and returns + /// an absolute URL to the root directory for the most proximate installed + /// `packageName`. Uri? _resolvePackageRoot(String packageName, Uri baseURL) { var baseDirectory = isWindows ? p.dirname(Uri.directory(baseURL.toString()).toFilePath()) @@ -127,9 +127,9 @@ class NodePackageImporterInternal extends Importer { return recurseUpFrom(baseDirectory); } - // Takes a string `packagePath`, which is the root directory for a package, and - // `packageManifest`, which is the contents of that package's `package.json` - // file, and returns a file URL. + /// Takes a string `packagePath`, which is the root directory for a package, + /// and `packageManifest`, which is the contents of that package's + /// `package.json` file, and returns a file URL. Uri? _resolvePackageRootValues( String packageRoot, Map packageManifest) { var extensions = ['.scss', '.sass', '.css']; @@ -148,9 +148,9 @@ class NodePackageImporterInternal extends Importer { return null; } - // Takes a package.json value `packageManifest`, a directory URL `packageRoot` - // and a relative URL path `subpath`. It returns a file URL or null. - // `packageName` is used for error reporting only. + /// Takes a package.json value `packageManifest`, a directory URL + /// `packageRoot` and a relative URL path `subpath`. It returns a file URL or + /// null. `packageName` is used for error reporting only. Uri? _resolvePackageExports(Uri packageRoot, String subpath, Map packageManifest, String packageName) { if (packageManifest['exports'] == null) return null; @@ -180,9 +180,9 @@ class NodePackageImporterInternal extends Importer { return null; } - // Takes a package.json value `packageManifest`, a directory URL `packageRoot` - // and a list of relative URL paths `subpathVariants`. It returns a list of all - // subpaths present in the package Manifest exports. + /// Takes a package.json value `packageManifest`, a directory URL + /// `packageRoot` and a list of relative URL paths `subpathVariants`. It + /// returns a list of all subpaths present in the package manifest exports. List _nodePackageExportsResolve( Uri packageRoot, List subpathVariants, Object exports) { Uri? processVariant(String subpath) { @@ -227,8 +227,8 @@ class NodePackageImporterInternal extends Importer { return subpathVariants.map(processVariant).whereNotNull().toList(); } - // Implementation of the `PATTERN_KEY_COMPARE` algorithm from - // https://nodejs.org/api/esm.html#resolution-algorithm-specification. + /// Implementation of the `PATTERN_KEY_COMPARE` algorithm from + /// https://nodejs.org/api/esm.html#resolution-algorithm-specification. List _sortExpansionKeys(List keys) { int sorter(String keyA, String keyB) { var baseLengthA = @@ -248,7 +248,7 @@ class NodePackageImporterInternal extends Importer { return keys; } - // Recurses through `exports` object to find match for `subpath`. + /// Recurses through `exports` object to find match for `subpath`. Uri? _packageTargetResolve(String subpath, Object exports, Uri packageRoot, [String? patternMatch]) { switch (exports) { @@ -296,8 +296,8 @@ class NodePackageImporterInternal extends Importer { return null; } - // Given an `exports` object, returns the entry for an export without a - // subpath. + /// Given an `exports` object, returns the entry for an export without a + /// subpath. Object? _getMainExport(Object exports) { switch (exports) { case String string: @@ -319,8 +319,8 @@ class NodePackageImporterInternal extends Importer { return null; } - // Given a string `subpath`, returns a list of all possible variations with - // extensions and partials. + /// Given a string `subpath`, returns a list of all possible variations with + /// extensions and partials. List _exportLoadPaths(String subpath, [bool addIndex = false]) { List paths = []; if (subpath.isEmpty && !addIndex) return [subpath]; From 75de009b48f3e426d3776a6b0ed22a35939a0ebb Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Thu, 16 Nov 2023 11:24:48 -0500 Subject: [PATCH 045/113] Use FilesystemImporter.canonicalize for file: URLs --- lib/src/importer/node_package.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/src/importer/node_package.dart b/lib/src/importer/node_package.dart index fee1f7b09..93ca36cee 100644 --- a/lib/src/importer/node_package.dart +++ b/lib/src/importer/node_package.dart @@ -28,6 +28,7 @@ class NodePackageImporterInternal extends Importer { @override Uri? canonicalize(Uri url) { + if (url.scheme == 'file') return _filesystemImporter.canonicalize(url); if (url.scheme != 'pkg') return null; // TODO(jamesnw) Can these errors even be thrown? Or are these cases // filtered out before this? From ece578e8fcd66ebdf28ce45824bc47e2d031c918 Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Fri, 17 Nov 2023 10:01:10 -0500 Subject: [PATCH 046/113] Handle invalid URL --- lib/src/importer/node_package.dart | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/lib/src/importer/node_package.dart b/lib/src/importer/node_package.dart index 93ca36cee..1e1ac9b7e 100644 --- a/lib/src/importer/node_package.dart +++ b/lib/src/importer/node_package.dart @@ -30,16 +30,18 @@ class NodePackageImporterInternal extends Importer { Uri? canonicalize(Uri url) { if (url.scheme == 'file') return _filesystemImporter.canonicalize(url); if (url.scheme != 'pkg') return null; - // TODO(jamesnw) Can these errors even be thrown? Or are these cases - // filtered out before this? - if (url.path.startsWith('/')) { + + if (url.hasAuthority) { + throw "pkg: URL $url must not have a host, port, username or password."; + } + if (p.isAbsolute(url.path)) { throw "pkg: URL $url must not be an absolute path."; } if (url.path.isEmpty) { throw "pkg: URL $url must not have an empty path."; } - if (url.userInfo != '' || url.hasPort || url.hasQuery || url.hasFragment) { - throw "Invalid URL $url"; + if (url.hasQuery || url.hasFragment) { + throw "pkg: URL $url must not have a query or fragment."; } var baseURL = containingUrl?.scheme == 'file' From c22c8ca32c0df0660da66c9c0c740b3aead195d2 Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Fri, 17 Nov 2023 10:12:10 -0500 Subject: [PATCH 047/113] Create FilesystemImporter cwd singleton --- lib/src/async_compile.dart | 6 +++--- lib/src/compile.dart | 8 ++++---- lib/src/executable/compile_stylesheet.dart | 6 +++--- lib/src/executable/repl.dart | 2 +- lib/src/executable/watch.dart | 6 +++--- lib/src/importer/filesystem.dart | 3 +++ lib/src/importer/js_to_dart/async_file.dart | 2 +- lib/src/importer/js_to_dart/file.dart | 2 +- lib/src/importer/node_package.dart | 2 +- lib/src/importer/package.dart | 2 +- 10 files changed, 21 insertions(+), 18 deletions(-) diff --git a/lib/src/async_compile.dart b/lib/src/async_compile.dart index 94c60c8ae..a940d3f26 100644 --- a/lib/src/async_compile.dart +++ b/lib/src/async_compile.dart @@ -57,7 +57,7 @@ Future compileAsync(String path, (syntax == null || syntax == Syntax.forPath(path))) { importCache ??= AsyncImportCache.none(logger: logger); stylesheet = (await importCache.importCanonical( - FilesystemImporter('.'), p.toUri(canonicalize(path)), + FilesystemImporter.cwd, p.toUri(canonicalize(path)), originalUrl: p.toUri(path)))!; } else { stylesheet = Stylesheet.parse( @@ -70,7 +70,7 @@ Future compileAsync(String path, logger, importCache, nodeImporter, - FilesystemImporter('.'), + FilesystemImporter.cwd, functions, style, useSpaces, @@ -122,7 +122,7 @@ Future compileStringAsync(String source, logger, importCache, nodeImporter, - importer ?? (isBrowser ? NoOpImporter() : FilesystemImporter('.')), + importer ?? (isBrowser ? NoOpImporter() : FilesystemImporter.cwd), functions, style, useSpaces, diff --git a/lib/src/compile.dart b/lib/src/compile.dart index b5427304a..94221405d 100644 --- a/lib/src/compile.dart +++ b/lib/src/compile.dart @@ -5,7 +5,7 @@ // DO NOT EDIT. This file was generated from async_compile.dart. // See tool/grind/synchronize.dart for details. // -// Checksum: 23b059beeb519469aa45fead0956a1a25f0c814e +// Checksum: a9421a2975e79ad591ae32474cd076e1379d0e75 // // ignore_for_file: unused_import @@ -66,7 +66,7 @@ CompileResult compile(String path, (syntax == null || syntax == Syntax.forPath(path))) { importCache ??= ImportCache.none(logger: logger); stylesheet = importCache.importCanonical( - FilesystemImporter('.'), p.toUri(canonicalize(path)), + FilesystemImporter.cwd, p.toUri(canonicalize(path)), originalUrl: p.toUri(path))!; } else { stylesheet = Stylesheet.parse( @@ -79,7 +79,7 @@ CompileResult compile(String path, logger, importCache, nodeImporter, - FilesystemImporter('.'), + FilesystemImporter.cwd, functions, style, useSpaces, @@ -131,7 +131,7 @@ CompileResult compileString(String source, logger, importCache, nodeImporter, - importer ?? (isBrowser ? NoOpImporter() : FilesystemImporter('.')), + importer ?? (isBrowser ? NoOpImporter() : FilesystemImporter.cwd), functions, style, useSpaces, diff --git a/lib/src/executable/compile_stylesheet.dart b/lib/src/executable/compile_stylesheet.dart index 70b52ba10..ba85610af 100644 --- a/lib/src/executable/compile_stylesheet.dart +++ b/lib/src/executable/compile_stylesheet.dart @@ -68,7 +68,7 @@ Future<(int, String, String?)?> compileStylesheet(ExecutableOptions options, Future _compileStylesheetWithoutErrorHandling(ExecutableOptions options, StylesheetGraph graph, String? source, String? destination, {bool ifModified = false}) async { - var importer = FilesystemImporter('.'); + var importer = FilesystemImporter.cwd; if (ifModified) { try { if (source != null && @@ -102,7 +102,7 @@ Future _compileStylesheetWithoutErrorHandling(ExecutableOptions options, syntax: syntax, logger: options.logger, importCache: importCache, - importer: FilesystemImporter('.'), + importer: FilesystemImporter.cwd, style: options.style, quietDeps: options.quietDeps, verbose: options.verbose, @@ -127,7 +127,7 @@ Future _compileStylesheetWithoutErrorHandling(ExecutableOptions options, syntax: syntax, logger: options.logger, importCache: graph.importCache, - importer: FilesystemImporter('.'), + importer: FilesystemImporter.cwd, style: options.style, quietDeps: options.quietDeps, verbose: options.verbose, diff --git a/lib/src/executable/repl.dart b/lib/src/executable/repl.dart index d460b40e0..6e0124bde 100644 --- a/lib/src/executable/repl.dart +++ b/lib/src/executable/repl.dart @@ -22,7 +22,7 @@ Future repl(ExecutableOptions options) async { var repl = Repl(prompt: '>> '); var logger = TrackingLogger(options.logger); var evaluator = Evaluator( - importer: FilesystemImporter('.'), + importer: FilesystemImporter.cwd, importCache: ImportCache(loadPaths: options.loadPaths, logger: logger), logger: logger); await for (String line in repl.runAsync()) { diff --git a/lib/src/executable/watch.dart b/lib/src/executable/watch.dart index c8a222b0b..9e1db78e9 100644 --- a/lib/src/executable/watch.dart +++ b/lib/src/executable/watch.dart @@ -39,7 +39,7 @@ Future watch(ExecutableOptions options, StylesheetGraph graph) async { var sourcesToDestinations = _sourcesToDestinations(options); for (var source in sourcesToDestinations.keys) { graph.addCanonical( - FilesystemImporter('.'), p.toUri(canonicalize(source)), p.toUri(source), + FilesystemImporter.cwd, p.toUri(canonicalize(source)), p.toUri(source), recanonicalize: false); } var success = await compileStylesheets(options, graph, sourcesToDestinations, @@ -130,7 +130,7 @@ final class _Watcher { await compileStylesheets(_options, _graph, {path: destination}, ifModified: true); var downstream = _graph.addCanonical( - FilesystemImporter('.'), _canonicalize(path), p.toUri(path)); + FilesystemImporter.cwd, _canonicalize(path), p.toUri(path)); return await _recompileDownstream(downstream) && success; } @@ -144,7 +144,7 @@ final class _Watcher { if (_destinationFor(path) case var destination?) _delete(destination); } - var downstream = _graph.remove(FilesystemImporter('.'), url); + var downstream = _graph.remove(FilesystemImporter.cwd, url); return await _recompileDownstream(downstream); } diff --git a/lib/src/importer/filesystem.dart b/lib/src/importer/filesystem.dart index 31af69829..2560f9d7c 100644 --- a/lib/src/importer/filesystem.dart +++ b/lib/src/importer/filesystem.dart @@ -22,6 +22,9 @@ class FilesystemImporter extends Importer { /// Creates an importer that loads files relative to [loadPath]. FilesystemImporter(String loadPath) : _loadPath = p.absolute(loadPath); + /// Creates an importer relative to the current working directory. + static FilesystemImporter cwd = FilesystemImporter('.'); + Uri? canonicalize(Uri url) { if (url.scheme != 'file' && url.scheme != '') return null; return resolveImportPath(p.join(_loadPath, p.fromUri(url))) diff --git a/lib/src/importer/js_to_dart/async_file.dart b/lib/src/importer/js_to_dart/async_file.dart index e984531dc..c3e5f4294 100644 --- a/lib/src/importer/js_to_dart/async_file.dart +++ b/lib/src/importer/js_to_dart/async_file.dart @@ -21,7 +21,7 @@ import '../utils.dart'; /// [JSToDartAsyncFileImporter]. /// /// This allows us to avoid duplicating logic between the two importers. -final _filesystemImporter = FilesystemImporter('.'); +final _filesystemImporter = FilesystemImporter.cwd; /// A wrapper for a potentially-asynchronous JS API file importer that exposes /// it as a Dart [AsyncImporter]. diff --git a/lib/src/importer/js_to_dart/file.dart b/lib/src/importer/js_to_dart/file.dart index 9ad474d00..a66682690 100644 --- a/lib/src/importer/js_to_dart/file.dart +++ b/lib/src/importer/js_to_dart/file.dart @@ -16,7 +16,7 @@ import '../utils.dart'; /// [JSToDartAsyncFileImporter]. /// /// This allows us to avoid duplicating logic between the two importers. -final _filesystemImporter = FilesystemImporter('.'); +final _filesystemImporter = FilesystemImporter.cwd; /// A wrapper for a potentially-asynchronous JS API file importer that exposes /// it as a Dart [AsyncImporter]. diff --git a/lib/src/importer/node_package.dart b/lib/src/importer/node_package.dart index 1e1ac9b7e..6bf44217c 100644 --- a/lib/src/importer/node_package.dart +++ b/lib/src/importer/node_package.dart @@ -12,7 +12,7 @@ import 'package:path/path.dart' as p; /// A filesystem importer to use for load implementation details, and for /// canonicalizing paths not defined in package.json. -final _filesystemImporter = FilesystemImporter('.'); +final _filesystemImporter = FilesystemImporter.cwd; /// An [Importer] that resolves `pkg:` URLs using the Node resolution algorithm. class NodePackageImporterInternal extends Importer { diff --git a/lib/src/importer/package.dart b/lib/src/importer/package.dart index 21f41509f..c5738599c 100644 --- a/lib/src/importer/package.dart +++ b/lib/src/importer/package.dart @@ -11,7 +11,7 @@ import '../importer.dart'; /// /// This allows us to avoid duplicating the logic for choosing an extension and /// looking for partials. -final _filesystemImporter = FilesystemImporter('.'); +final _filesystemImporter = FilesystemImporter.cwd; /// An importer that loads stylesheets from `package:` imports. /// From 091ed1f0a0675916b4fb58367528fb469c705a32 Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Fri, 17 Nov 2023 10:31:33 -0500 Subject: [PATCH 048/113] Add missed FilesystemImporter cwd's --- lib/src/embedded/importer/file.dart | 2 +- lib/src/importer/filesystem.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/embedded/importer/file.dart b/lib/src/embedded/importer/file.dart index edd0d3537..2cf0e8ba6 100644 --- a/lib/src/embedded/importer/file.dart +++ b/lib/src/embedded/importer/file.dart @@ -11,7 +11,7 @@ import 'base.dart'; /// [FileImporter]. /// /// This allows us to avoid duplicating logic between the two importers. -final _filesystemImporter = FilesystemImporter('.'); +final _filesystemImporter = FilesystemImporter.cwd; /// An importer that asks the host to resolve imports in a simplified, /// file-system-centric way. diff --git a/lib/src/importer/filesystem.dart b/lib/src/importer/filesystem.dart index 2560f9d7c..c4f7fb023 100644 --- a/lib/src/importer/filesystem.dart +++ b/lib/src/importer/filesystem.dart @@ -23,7 +23,7 @@ class FilesystemImporter extends Importer { FilesystemImporter(String loadPath) : _loadPath = p.absolute(loadPath); /// Creates an importer relative to the current working directory. - static FilesystemImporter cwd = FilesystemImporter('.'); + static FilesystemImporter cwd = FilesystemImporter.cwd; Uri? canonicalize(Uri url) { if (url.scheme != 'file' && url.scheme != '') return null; From e0e42b6bbfa21e77a03d04056c4c737029cb17f1 Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Fri, 17 Nov 2023 10:58:56 -0500 Subject: [PATCH 049/113] Undo accidental self reference --- lib/src/importer/filesystem.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/importer/filesystem.dart b/lib/src/importer/filesystem.dart index c4f7fb023..2560f9d7c 100644 --- a/lib/src/importer/filesystem.dart +++ b/lib/src/importer/filesystem.dart @@ -23,7 +23,7 @@ class FilesystemImporter extends Importer { FilesystemImporter(String loadPath) : _loadPath = p.absolute(loadPath); /// Creates an importer relative to the current working directory. - static FilesystemImporter cwd = FilesystemImporter.cwd; + static FilesystemImporter cwd = FilesystemImporter('.'); Uri? canonicalize(Uri url) { if (url.scheme != 'file' && url.scheme != '') return null; From 6175ce9cc084cfd6e1d8ab088be21f0bc4f26df8 Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Fri, 17 Nov 2023 11:52:02 -0500 Subject: [PATCH 050/113] Switch NodePackageImporter type to JSSymbol --- lib/src/js/compile.dart | 21 +++++++++++++-------- lib/src/js/exports.dart | 2 +- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/lib/src/js/compile.dart b/lib/src/js/compile.dart index da86a1f26..a15fce3d8 100644 --- a/lib/src/js/compile.dart +++ b/lib/src/js/compile.dart @@ -3,9 +3,11 @@ // https://opensource.org/licenses/MIT. import 'package:cli_pkg/js.dart'; +import 'package:js/js.dart'; import 'package:node_interop/js.dart'; import 'package:node_interop/util.dart' hide futureToPromise; import 'package:path/path.dart' as p; +import 'package:sass/src/js/function.dart'; import 'package:term_glyph/term_glyph.dart' as glyph; import '../../sass.dart'; @@ -184,7 +186,8 @@ OutputStyle _parseOutputStyle(String? style) => switch (style) { /// Converts [importer] into an [AsyncImporter] that can be used with /// [compileAsync] or [compileStringAsync]. AsyncImporter _parseAsyncImporter(Object? importer) { - if (importer == nodePackageImporter) { + var jsEquals = JSFunction('a', 'b', 'return a===b'); + if (jsEquals.call(importer, nodePackageImporter) == true) { if (isBrowser) { jsThrow(JsError( "The Node Package Importer can not be used without a filesystem.")); @@ -218,7 +221,8 @@ AsyncImporter _parseAsyncImporter(Object? importer) { /// Converts [importer] into a synchronous [Importer]. Importer _parseImporter(Object? importer) { - if (importer == nodePackageImporter) { + var jsEquals = JSFunction('a', 'b', 'return a===b'); + if (jsEquals.call(importer, nodePackageImporter) == true) { if (isBrowser) { jsThrow(JsError( "The Node Package Importer can not be used without a filesystem.")); @@ -227,6 +231,7 @@ Importer _parseImporter(Object? importer) { Uri entryPointURL = Uri.parse(p.absolute('./index.js')); return NodePackageImporterInternal(entryPointURL); } + if (importer == null) jsThrow(JsError("Importers may not be null.")); importer as JSImporter; @@ -342,10 +347,10 @@ List _parseFunctions(Object? functions, {bool asynch = false}) { return result; } -const NodePackageImporter nodePackageImporter = NodePackageImporter(); +@JS("Symbol") +class JSSymbol {} -class NodePackageImporter { - // ignore: unused_field - final String _nodePackageImporterBrand = ''; - const NodePackageImporter(); -} +@JS("Symbol") +external JSSymbol createJSSymbol(); + +final nodePackageImporter = createJSSymbol(); diff --git a/lib/src/js/exports.dart b/lib/src/js/exports.dart index 39b90266a..969976b48 100644 --- a/lib/src/js/exports.dart +++ b/lib/src/js/exports.dart @@ -23,7 +23,7 @@ class Exports { external set info(String info); external set Exception(JSClass function); external set Logger(LoggerNamespace namespace); - external set nodePackageImporter(NodePackageImporter nodePackageImporter); + external set nodePackageImporter(JSSymbol nodePackageImporter); // Value APIs external set Value(JSClass function); From c0a47cdc453888f841f39c0a557b06ab14404dda Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Wed, 22 Nov 2023 18:46:07 -0500 Subject: [PATCH 051/113] Change entry point logic --- lib/src/js/compile.dart | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/lib/src/js/compile.dart b/lib/src/js/compile.dart index a15fce3d8..c91dd614a 100644 --- a/lib/src/js/compile.dart +++ b/lib/src/js/compile.dart @@ -47,7 +47,8 @@ NodeCompileResult compile(String path, [CompileOptions? options]) { sourceMap: options?.sourceMap ?? false, logger: JSToDartLogger(options?.logger, Logger.stderr(color: color), ascii: ascii), - importers: options?.importers?.map(_parseImporter), + importers: options?.importers + ?.map((importer) => _parseImporter(importer, Uri.parse(path))), functions: _parseFunctions(options?.functions).cast()); return _convertResult(result, includeSourceContents: options?.sourceMapIncludeSources ?? false); @@ -63,10 +64,11 @@ NodeCompileResult compile(String path, [CompileOptions? options]) { NodeCompileResult compileString(String text, [CompileStringOptions? options]) { var color = options?.alertColor ?? hasTerminal; var ascii = options?.alertAscii ?? glyph.ascii; + var url = options?.url.andThen(jsToDartUrl); try { var result = compileStringToResult(text, syntax: parseSyntax(options?.syntax), - url: options?.url.andThen(jsToDartUrl), + url: url, color: color, loadPaths: options?.loadPaths, quietDeps: options?.quietDeps ?? false, @@ -76,7 +78,8 @@ NodeCompileResult compileString(String text, [CompileStringOptions? options]) { sourceMap: options?.sourceMap ?? false, logger: JSToDartLogger(options?.logger, Logger.stderr(color: color), ascii: ascii), - importers: options?.importers?.map(_parseImporter), + importers: options?.importers + ?.map((importer) => _parseImporter(importer, url)), importer: options?.importer.andThen(_parseImporter) ?? (options?.url == null ? NoOpImporter() : null), functions: _parseFunctions(options?.functions).cast()); @@ -109,7 +112,7 @@ Promise compileAsync(String path, [CompileOptions? options]) { logger: JSToDartLogger(options?.logger, Logger.stderr(color: color), ascii: ascii), importers: options?.importers - ?.map((importer) => _parseAsyncImporter(importer)), + ?.map((importer) => _parseAsyncImporter(importer, Uri.parse(path))), functions: _parseFunctions(options?.functions, asynch: true)); return _convertResult(result, includeSourceContents: options?.sourceMapIncludeSources ?? false); @@ -123,10 +126,11 @@ Promise compileAsync(String path, [CompileOptions? options]) { Promise compileStringAsync(String text, [CompileStringOptions? options]) { var color = options?.alertColor ?? hasTerminal; var ascii = options?.alertAscii ?? glyph.ascii; + var url = options?.url.andThen(jsToDartUrl); return _wrapAsyncSassExceptions(futureToPromise(() async { var result = await compileStringToResultAsync(text, syntax: parseSyntax(options?.syntax), - url: options?.url.andThen(jsToDartUrl), + url: url, color: color, loadPaths: options?.loadPaths, quietDeps: options?.quietDeps ?? false, @@ -139,7 +143,7 @@ Promise compileStringAsync(String text, [CompileStringOptions? options]) { importers: options?.importers ?.map((importer) => _parseAsyncImporter(importer)), importer: options?.importer - .andThen((importer) => _parseAsyncImporter(importer)) ?? + .andThen((importer) => _parseAsyncImporter(importer, url)) ?? (options?.url == null ? NoOpImporter() : null), functions: _parseFunctions(options?.functions, asynch: true)); return _convertResult(result, @@ -185,15 +189,14 @@ OutputStyle _parseOutputStyle(String? style) => switch (style) { /// Converts [importer] into an [AsyncImporter] that can be used with /// [compileAsync] or [compileStringAsync]. -AsyncImporter _parseAsyncImporter(Object? importer) { +AsyncImporter _parseAsyncImporter(Object? importer, [Uri? entryPointURL]) { var jsEquals = JSFunction('a', 'b', 'return a===b'); if (jsEquals.call(importer, nodePackageImporter) == true) { if (isBrowser) { jsThrow(JsError( "The Node Package Importer can not be used without a filesystem.")); } - // TODO(jamesnw) Can we get an actual filename for parity? Is it needed? - Uri entryPointURL = Uri.parse(p.absolute('./index.js')); + entryPointURL = entryPointURL ?? Uri.parse(p.absolute('./index.scss')); return NodePackageImporterInternal(entryPointURL); } if (importer == null) jsThrow(JsError("Importers may not be null.")); @@ -220,15 +223,14 @@ AsyncImporter _parseAsyncImporter(Object? importer) { } /// Converts [importer] into a synchronous [Importer]. -Importer _parseImporter(Object? importer) { +Importer _parseImporter(Object? importer, [Uri? entryPointURL]) { var jsEquals = JSFunction('a', 'b', 'return a===b'); if (jsEquals.call(importer, nodePackageImporter) == true) { if (isBrowser) { jsThrow(JsError( "The Node Package Importer can not be used without a filesystem.")); } - // TODO(jamesnw) Can we get an actual filename for parity? Is it needed? - Uri entryPointURL = Uri.parse(p.absolute('./index.js')); + entryPointURL = entryPointURL ?? Uri.parse(p.absolute('./index.scss')); return NodePackageImporterInternal(entryPointURL); } From d3431ea9152b9b3c50c8147cbfde25f16cec9ab3 Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Fri, 1 Dec 2023 10:25:17 -0500 Subject: [PATCH 052/113] Address review --- lib/src/async_import_cache.dart | 4 +-- lib/src/embedded/compilation_dispatcher.dart | 4 +-- lib/src/importer/node_package.dart | 17 +++------ lib/src/js/compile.dart | 13 +++---- lib/src/js/exports.dart | 2 +- lib/src/js/legacy.dart | 36 +++++++++----------- lib/src/js/utils.dart | 11 ++++++ 7 files changed, 42 insertions(+), 45 deletions(-) diff --git a/lib/src/async_import_cache.dart b/lib/src/async_import_cache.dart index fafe4080a..0deb6285f 100644 --- a/lib/src/async_import_cache.dart +++ b/lib/src/async_import_cache.dart @@ -106,8 +106,8 @@ final class AsyncImportCache { /// Creates an import cache without any globally-available importers, and only /// the passed in importers. - AsyncImportCache.only({Iterable? importers, Logger? logger}) - : _importers = [...?importers], + AsyncImportCache.only(Iterable importers, {Logger? logger}) + : _importers = List.unmodifiable(importers), _logger = logger ?? const Logger.stderr(); /// Converts the user's [importers], [loadPaths], and [packageConfig] diff --git a/lib/src/embedded/compilation_dispatcher.dart b/lib/src/embedded/compilation_dispatcher.dart index 1bd66b50b..9d69f7901 100644 --- a/lib/src/embedded/compilation_dispatcher.dart +++ b/lib/src/embedded/compilation_dispatcher.dart @@ -226,9 +226,9 @@ final class CompilationDispatcher { return null; case InboundMessage_CompileRequest_Importer_Importer.nodePackageImporter: - var entryPointURL = + var entryPointUrl = Uri.parse(importer.nodePackageImporter.entryPointUrl); - return NodePackageImporterInternal(entryPointURL); + return NodePackageImporterInternal(entryPointUrl); } } diff --git a/lib/src/importer/node_package.dart b/lib/src/importer/node_package.dart index 6bf44217c..ecf47e144 100644 --- a/lib/src/importer/node_package.dart +++ b/lib/src/importer/node_package.dart @@ -22,9 +22,7 @@ class NodePackageImporterInternal extends Importer { NodePackageImporterInternal(this.entryPointURL); @override - bool isNonCanonicalScheme(String scheme) { - return scheme == 'pkg'; - } + bool isNonCanonicalScheme(String scheme) => scheme == 'pkg'; @override Uri? canonicalize(Uri url) { @@ -50,11 +48,8 @@ class NodePackageImporterInternal extends Importer { var (packageName, subpath) = _packageNameAndSubpath(url.path); var packageRoot = _resolvePackageRoot(packageName, baseURL); - if (packageRoot == null) { - return null; - } - // Attempt to resolve using conditional exports + if (packageRoot == null) return null; var jsonPath = p.join(packageRoot.toFilePath(), 'package.json'); var jsonFile = Uri.file(jsonPath).toFilePath(); @@ -202,15 +197,13 @@ class NodePackageImporterInternal extends Importer { matchKey, exports[matchKey] as Object, packageRoot); } - var expansionKeys = exports.keys.where( - (key) => key.split('').where((char) => char == '*').length == 1); + var expansionKeys = + exports.keys.where((key) => '*'.allMatches(key).length == 1); expansionKeys = _sortExpansionKeys(expansionKeys.toList()); for (var expansionKey in expansionKeys) { - var parts = expansionKey.split('*'); - var patternBase = parts[0]; + var [patternBase, patternTrailer] = expansionKey.split('*'); if (matchKey.startsWith(patternBase) && matchKey != patternBase) { - var patternTrailer = parts[1]; if (patternTrailer.isEmpty || (matchKey.endsWith(patternTrailer) && matchKey.length >= expansionKey.length)) { diff --git a/lib/src/js/compile.dart b/lib/src/js/compile.dart index c91dd614a..6788e54fd 100644 --- a/lib/src/js/compile.dart +++ b/lib/src/js/compile.dart @@ -190,11 +190,10 @@ OutputStyle _parseOutputStyle(String? style) => switch (style) { /// Converts [importer] into an [AsyncImporter] that can be used with /// [compileAsync] or [compileStringAsync]. AsyncImporter _parseAsyncImporter(Object? importer, [Uri? entryPointURL]) { - var jsEquals = JSFunction('a', 'b', 'return a===b'); - if (jsEquals.call(importer, nodePackageImporter) == true) { + if (jsEquals(importer, nodePackageImporter)) { if (isBrowser) { jsThrow(JsError( - "The Node Package Importer can not be used without a filesystem.")); + "The Node Package Importer cannot be used without a filesystem.")); } entryPointURL = entryPointURL ?? Uri.parse(p.absolute('./index.scss')); return NodePackageImporterInternal(entryPointURL); @@ -349,10 +348,6 @@ List _parseFunctions(Object? functions, {bool asynch = false}) { return result; } -@JS("Symbol") -class JSSymbol {} - -@JS("Symbol") -external JSSymbol createJSSymbol(); - +/// The exported `nodePackageImporter` that can be added to the `importers` +/// option to enable loading `pkg:` URLs from `node_modules`. final nodePackageImporter = createJSSymbol(); diff --git a/lib/src/js/exports.dart b/lib/src/js/exports.dart index 969976b48..6973a148c 100644 --- a/lib/src/js/exports.dart +++ b/lib/src/js/exports.dart @@ -11,7 +11,7 @@ import '../value.dart' as value; import 'legacy/types.dart'; import 'logger.dart'; import 'reflection.dart'; -import 'compile.dart'; +import 'utils.dart'; @JS() class Exports { diff --git a/lib/src/js/legacy.dart b/lib/src/js/legacy.dart index c23d881c4..a4ff7e8d5 100644 --- a/lib/src/js/legacy.dart +++ b/lib/src/js/legacy.dart @@ -77,23 +77,22 @@ Future _renderAsync(RenderOptions options) async { var file = options.file.andThen(p.absolute); if (options.data case var data?) { - result = await compileStringAsync( - data, - nodeImporter: _parseImporter(options, start), - importCache: _parsePackageImportersAsync(options, start), - functions: _parseFunctions(options, start, asynch: true), - syntax: isTruthy(options.indentedSyntax) ? Syntax.sass : null, - style: _parseOutputStyle(options.outputStyle), - useSpaces: options.indentType != 'tab', - indentWidth: _parseIndentWidth(options.indentWidth), - lineFeed: _parseLineFeed(options.linefeed), - url: file == null ? 'stdin' : p.toUri(file).toString(), - quietDeps: options.quietDeps ?? false, - verbose: options.verbose ?? false, - charset: options.charset ?? true, - sourceMap: _enableSourceMaps(options), - logger: JSToDartLogger(options.logger, Logger.stderr(color: hasTerminal)), - ); + result = await compileStringAsync(data, + nodeImporter: _parseImporter(options, start), + importCache: _parsePackageImportersAsync(options, start), + functions: _parseFunctions(options, start, asynch: true), + syntax: isTruthy(options.indentedSyntax) ? Syntax.sass : null, + style: _parseOutputStyle(options.outputStyle), + useSpaces: options.indentType != 'tab', + indentWidth: _parseIndentWidth(options.indentWidth), + lineFeed: _parseLineFeed(options.linefeed), + url: file == null ? 'stdin' : p.toUri(file).toString(), + quietDeps: options.quietDeps ?? false, + verbose: options.verbose ?? false, + charset: options.charset ?? true, + sourceMap: _enableSourceMaps(options), + logger: + JSToDartLogger(options.logger, Logger.stderr(color: hasTerminal))); } else if (file != null) { result = await compileAsync(file, nodeImporter: _parseImporter(options, start), @@ -303,8 +302,7 @@ AsyncImportCache? _parsePackageImportersAsync( if (options.pkgImporter case 'node') { // TODO(jamesnw) Can we get an actual filename for parity? Is it needed? Uri entryPointURL = Uri.parse(p.absolute('./index.js')); - return AsyncImportCache.only( - importers: [NodePackageImporterInternal(entryPointURL)]); + return AsyncImportCache.only([NodePackageImporterInternal(entryPointURL)]); } return null; } diff --git a/lib/src/js/utils.dart b/lib/src/js/utils.dart index 687484c9a..bdc84fe06 100644 --- a/lib/src/js/utils.dart +++ b/lib/src/js/utils.dart @@ -33,6 +33,17 @@ external JSClass get jsErrorClass; /// Returns whether [value] is a JS Error object. bool isJSError(Object value) => instanceof(value, jsErrorClass); +final _jsEquals = JSFunction('a', 'b', 'return a===b'); + +/// Returns where [a] and [b] are considered equal in JS. +bool jsEquals(Object? a, Object? b) => _jsEquals.call(a, b) == true; + +@JS("Symbol") +class JSSymbol {} + +@JS("Symbol") +external JSSymbol createJSSymbol(); + /// Attaches [trace] to [error] as its stack trace. void attachJsStack(JsError error, StackTrace trace) { // Stack traces in v8 contain the error message itself as well as the stack From 4cdd953a9d677ffb698bcab6ed0b4ec4d768ec92 Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Fri, 1 Dec 2023 10:31:56 -0500 Subject: [PATCH 053/113] Remove separate _filesystemImporter fields for FilesystemImporter.cwd --- lib/src/embedded/importer/file.dart | 12 +++--------- lib/src/importer/js_to_dart/async_file.dart | 16 +++++----------- lib/src/importer/js_to_dart/file.dart | 16 +++++----------- lib/src/importer/node_package.dart | 10 +++------- lib/src/importer/package.dart | 17 ++++++----------- 5 files changed, 22 insertions(+), 49 deletions(-) diff --git a/lib/src/embedded/importer/file.dart b/lib/src/embedded/importer/file.dart index 2cf0e8ba6..5fe9a0222 100644 --- a/lib/src/embedded/importer/file.dart +++ b/lib/src/embedded/importer/file.dart @@ -7,12 +7,6 @@ import '../compilation_dispatcher.dart'; import '../embedded_sass.pb.dart' hide SourceSpan; import 'base.dart'; -/// A filesystem importer to use for most implementation details of -/// [FileImporter]. -/// -/// This allows us to avoid duplicating logic between the two importers. -final _filesystemImporter = FilesystemImporter.cwd; - /// An importer that asks the host to resolve imports in a simplified, /// file-system-centric way. final class FileImporter extends ImporterBase { @@ -23,7 +17,7 @@ final class FileImporter extends ImporterBase { : super(dispatcher); Uri? canonicalize(Uri url) { - if (url.scheme == 'file') return _filesystemImporter.canonicalize(url); + if (url.scheme == 'file') return FilesystemImporter.cwd.canonicalize(url); var request = OutboundMessage_FileImportRequest() ..importerId = _importerId @@ -41,7 +35,7 @@ final class FileImporter extends ImporterBase { throw 'The file importer must return a file: URL, was "$url"'; } - return _filesystemImporter.canonicalize(url); + return FilesystemImporter.cwd.canonicalize(url); case InboundMessage_FileImportResponse_Result.error: throw response.error; @@ -51,7 +45,7 @@ final class FileImporter extends ImporterBase { } } - ImporterResult? load(Uri url) => _filesystemImporter.load(url); + ImporterResult? load(Uri url) => FilesystemImporter.cwd.load(url); bool isNonCanonicalScheme(String scheme) => scheme != 'file'; diff --git a/lib/src/importer/js_to_dart/async_file.dart b/lib/src/importer/js_to_dart/async_file.dart index c3e5f4294..7be4b9461 100644 --- a/lib/src/importer/js_to_dart/async_file.dart +++ b/lib/src/importer/js_to_dart/async_file.dart @@ -17,12 +17,6 @@ import '../filesystem.dart'; import '../result.dart'; import '../utils.dart'; -/// A filesystem importer to use for most implementation details of -/// [JSToDartAsyncFileImporter]. -/// -/// This allows us to avoid duplicating logic between the two importers. -final _filesystemImporter = FilesystemImporter.cwd; - /// A wrapper for a potentially-asynchronous JS API file importer that exposes /// it as a Dart [AsyncImporter]. final class JSToDartAsyncFileImporter extends AsyncImporter { @@ -32,7 +26,7 @@ final class JSToDartAsyncFileImporter extends AsyncImporter { JSToDartAsyncFileImporter(this._findFileUrl); FutureOr canonicalize(Uri url) async { - if (url.scheme == 'file') return _filesystemImporter.canonicalize(url); + if (url.scheme == 'file') return FilesystemImporter.cwd.canonicalize(url); var result = wrapJSExceptions(() => _findFileUrl( url.toString(), @@ -52,16 +46,16 @@ final class JSToDartAsyncFileImporter extends AsyncImporter { '"$url".')); } - return _filesystemImporter.canonicalize(resultUrl); + return FilesystemImporter.cwd.canonicalize(resultUrl); } - ImporterResult? load(Uri url) => _filesystemImporter.load(url); + ImporterResult? load(Uri url) => FilesystemImporter.cwd.load(url); DateTime modificationTime(Uri url) => - _filesystemImporter.modificationTime(url); + FilesystemImporter.cwd.modificationTime(url); bool couldCanonicalize(Uri url, Uri canonicalUrl) => - _filesystemImporter.couldCanonicalize(url, canonicalUrl); + FilesystemImporter.cwd.couldCanonicalize(url, canonicalUrl); bool isNonCanonicalScheme(String scheme) => scheme != 'file'; } diff --git a/lib/src/importer/js_to_dart/file.dart b/lib/src/importer/js_to_dart/file.dart index a66682690..e3302f881 100644 --- a/lib/src/importer/js_to_dart/file.dart +++ b/lib/src/importer/js_to_dart/file.dart @@ -12,12 +12,6 @@ import '../../js/utils.dart'; import '../../util/nullable.dart'; import '../utils.dart'; -/// A filesystem importer to use for most implementation details of -/// [JSToDartAsyncFileImporter]. -/// -/// This allows us to avoid duplicating logic between the two importers. -final _filesystemImporter = FilesystemImporter.cwd; - /// A wrapper for a potentially-asynchronous JS API file importer that exposes /// it as a Dart [AsyncImporter]. final class JSToDartFileImporter extends Importer { @@ -27,7 +21,7 @@ final class JSToDartFileImporter extends Importer { JSToDartFileImporter(this._findFileUrl); Uri? canonicalize(Uri url) { - if (url.scheme == 'file') return _filesystemImporter.canonicalize(url); + if (url.scheme == 'file') return FilesystemImporter.cwd.canonicalize(url); var result = wrapJSExceptions(() => _findFileUrl( url.toString(), @@ -51,16 +45,16 @@ final class JSToDartFileImporter extends Importer { '"$url".')); } - return _filesystemImporter.canonicalize(resultUrl); + return FilesystemImporter.cwd.canonicalize(resultUrl); } - ImporterResult? load(Uri url) => _filesystemImporter.load(url); + ImporterResult? load(Uri url) => FilesystemImporter.cwd.load(url); DateTime modificationTime(Uri url) => - _filesystemImporter.modificationTime(url); + FilesystemImporter.cwd.modificationTime(url); bool couldCanonicalize(Uri url, Uri canonicalUrl) => - _filesystemImporter.couldCanonicalize(url, canonicalUrl); + FilesystemImporter.cwd.couldCanonicalize(url, canonicalUrl); bool isNonCanonicalScheme(String scheme) => scheme != 'file'; } diff --git a/lib/src/importer/node_package.dart b/lib/src/importer/node_package.dart index ecf47e144..2fb2d304b 100644 --- a/lib/src/importer/node_package.dart +++ b/lib/src/importer/node_package.dart @@ -10,10 +10,6 @@ import 'dart:convert'; import '../io.dart'; import 'package:path/path.dart' as p; -/// A filesystem importer to use for load implementation details, and for -/// canonicalizing paths not defined in package.json. -final _filesystemImporter = FilesystemImporter.cwd; - /// An [Importer] that resolves `pkg:` URLs using the Node resolution algorithm. class NodePackageImporterInternal extends Importer { final Uri entryPointURL; @@ -26,7 +22,7 @@ class NodePackageImporterInternal extends Importer { @override Uri? canonicalize(Uri url) { - if (url.scheme == 'file') return _filesystemImporter.canonicalize(url); + if (url.scheme == 'file') return FilesystemImporter.cwd.canonicalize(url); if (url.scheme != 'pkg') return null; if (url.hasAuthority) { @@ -78,11 +74,11 @@ class NodePackageImporterInternal extends Importer { // If there is a subpath, attempt to resolve the path relative to the // package root, and resolve for file extensions and partials. var relativeSubpath = "${packageRoot.toFilePath()}${p.separator}$subpath"; - return _filesystemImporter.canonicalize(Uri.file(relativeSubpath)); + return FilesystemImporter.cwd.canonicalize(Uri.file(relativeSubpath)); } @override - ImporterResult? load(Uri url) => _filesystemImporter.load(url); + ImporterResult? load(Uri url) => FilesystemImporter.cwd.load(url); /// Takes a string, `path`, and returns a tuple with the package name and the /// subpath if it is present. diff --git a/lib/src/importer/package.dart b/lib/src/importer/package.dart index c5738599c..39d09ac63 100644 --- a/lib/src/importer/package.dart +++ b/lib/src/importer/package.dart @@ -7,12 +7,6 @@ import 'package:package_config/package_config_types.dart'; import '../importer.dart'; -/// A filesystem importer to use when resolving the results of `package:` URLs. -/// -/// This allows us to avoid duplicating the logic for choosing an extension and -/// looking for partials. -final _filesystemImporter = FilesystemImporter.cwd; - /// An importer that loads stylesheets from `package:` imports. /// /// {@category Importer} @@ -29,7 +23,7 @@ class PackageImporter extends Importer { PackageImporter(PackageConfig packageConfig) : _packageConfig = packageConfig; Uri? canonicalize(Uri url) { - if (url.scheme == 'file') return _filesystemImporter.canonicalize(url); + if (url.scheme == 'file') return FilesystemImporter.cwd.canonicalize(url); if (url.scheme != 'package') return null; var resolved = _packageConfig.resolve(url); @@ -39,17 +33,18 @@ class PackageImporter extends Importer { throw "Unsupported URL $resolved."; } - return _filesystemImporter.canonicalize(resolved); + return FilesystemImporter.cwd.canonicalize(resolved); } - ImporterResult? load(Uri url) => _filesystemImporter.load(url); + ImporterResult? load(Uri url) => FilesystemImporter.cwd.load(url); DateTime modificationTime(Uri url) => - _filesystemImporter.modificationTime(url); + FilesystemImporter.cwd.modificationTime(url); bool couldCanonicalize(Uri url, Uri canonicalUrl) => (url.scheme == 'file' || url.scheme == 'package' || url.scheme == '') && - _filesystemImporter.couldCanonicalize(Uri(path: url.path), canonicalUrl); + FilesystemImporter.cwd + .couldCanonicalize(Uri(path: url.path), canonicalUrl); String toString() => "package:..."; } From e847921009d21324bed5292c7fe303221a2f2c62 Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Fri, 1 Dec 2023 10:34:00 -0500 Subject: [PATCH 054/113] Remove unneeded import --- lib/src/js/compile.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/src/js/compile.dart b/lib/src/js/compile.dart index 6788e54fd..385936d15 100644 --- a/lib/src/js/compile.dart +++ b/lib/src/js/compile.dart @@ -3,7 +3,6 @@ // https://opensource.org/licenses/MIT. import 'package:cli_pkg/js.dart'; -import 'package:js/js.dart'; import 'package:node_interop/js.dart'; import 'package:node_interop/util.dart' hide futureToPromise; import 'package:path/path.dart' as p; From 5e637de185110400ceb9bd51fcc5a4ef8fa3885a Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Fri, 1 Dec 2023 10:36:58 -0500 Subject: [PATCH 055/113] Sync --- lib/src/import_cache.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/src/import_cache.dart b/lib/src/import_cache.dart index c1ddc3131..e34f0a7ee 100644 --- a/lib/src/import_cache.dart +++ b/lib/src/import_cache.dart @@ -5,7 +5,7 @@ // DO NOT EDIT. This file was generated from async_import_cache.dart. // See tool/grind/synchronize.dart for details. // -// Checksum: 039ce81351c314df5ec550096ab034ac3595916b +// Checksum: d157b83599dbc07a80ac6cb5ffdf5dde03b60376 // // ignore_for_file: unused_import @@ -108,8 +108,8 @@ final class ImportCache { /// Creates an import cache without any globally-available importers, and only /// the passed in importers. - ImportCache.only({Iterable? importers, Logger? logger}) - : _importers = [...?importers], + ImportCache.only(Iterable importers, {Logger? logger}) + : _importers = List.unmodifiable(importers), _logger = logger ?? const Logger.stderr(); /// Converts the user's [importers], [loadPaths], and [packageConfig] From 2575b86d72aaad0f7a784a1e648152a81aeba7ad Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Fri, 1 Dec 2023 11:17:35 -0500 Subject: [PATCH 056/113] Address review --- lib/src/importer/node_package.dart | 47 ++++++++++++++---------------- lib/src/js/legacy.dart | 3 +- 2 files changed, 23 insertions(+), 27 deletions(-) diff --git a/lib/src/importer/node_package.dart b/lib/src/importer/node_package.dart index 2fb2d304b..43ee07335 100644 --- a/lib/src/importer/node_package.dart +++ b/lib/src/importer/node_package.dart @@ -17,6 +17,8 @@ class NodePackageImporterInternal extends Importer { /// Creates a Node Package Importer with the associated entry point url NodePackageImporterInternal(this.entryPointURL); + static const validExtensions = {'.scss', '.sass', '.css'}; + @override bool isNonCanonicalScheme(String scheme) => scheme == 'pkg'; @@ -52,16 +54,17 @@ class NodePackageImporterInternal extends Importer { var jsonString = readFile(jsonFile); var packageManifest = jsonDecode(jsonString) as Map; - var resolved = _resolvePackageExports( - packageRoot, subpath, packageManifest, packageName); - - if (resolved != null && - resolved.scheme == 'file' && - ['.scss', '.sass', '.css'].contains(p.extension(resolved.path))) { - return resolved; - } else if (resolved != null) { - throw "The export for '${subpath == '' ? "root" : subpath}' in " - "'$packageName' is not a valid Sass file."; + if (_resolvePackageExports( + packageRoot, subpath, packageManifest, packageName) + case var resolved?) { + if (resolved.scheme == 'file' && + validExtensions.contains(p.url.extension(resolved.path))) { + return resolved; + } else { + throw "The export for '${subpath == '' ? "root" : subpath}' 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 @@ -121,19 +124,18 @@ class NodePackageImporterInternal extends Importer { return recurseUpFrom(baseDirectory); } - /// Takes a string `packagePath`, which is the root directory for a package, + /// Takes a string `packageRoot`, which is the root directory for a package, /// and `packageManifest`, which is the contents of that package's /// `package.json` file, and returns a file URL. Uri? _resolvePackageRootValues( String packageRoot, Map packageManifest) { - var extensions = ['.scss', '.sass', '.css']; - var sassValue = packageManifest['sass'] as String?; - if (sassValue != null && extensions.contains(p.extension(sassValue))) { + if (sassValue != null && validExtensions.contains(p.extension(sassValue))) { return Uri.file('$packageRoot$sassValue'); } var styleValue = packageManifest['style'] as String?; - if (styleValue != null && extensions.contains(p.extension(styleValue))) { + if (styleValue != null && + validExtensions.contains(p.extension(styleValue))) { return Uri.file('$packageRoot$styleValue'); } @@ -162,11 +164,11 @@ class NodePackageImporterInternal extends Importer { var subpathIndexVariants = _exportLoadPaths(subpath, true); - var resolvedIndexpaths = + var resolvedIndexPaths = _nodePackageExportsResolve(packageRoot, subpathIndexVariants, exports); - if (resolvedIndexpaths.length == 1) return resolvedIndexpaths.first; - if (resolvedIndexpaths.length > 1) { + if (resolvedIndexPaths.length == 1) return resolvedIndexPaths.first; + if (resolvedIndexPaths.length > 1) { throw "Unable to determine which of multiple potential " "resolutions found for $subpath in $packageName should be used."; } @@ -251,11 +253,7 @@ class NodePackageImporterInternal extends Importer { if (patternMatch != null) { string = string.replaceAll(RegExp(r'\*'), patternMatch); var path = p.normalize("${packageRoot.toFilePath()}/$string"); - if (fileExists(path)) { - return Uri.parse('$packageRoot/$string'); - } else { - return null; - } + return fileExists(path) ? Uri.parse('$packageRoot/$string') : null; } return Uri.parse("$packageRoot/$string"); case Map map: @@ -283,9 +281,8 @@ class NodePackageImporterInternal extends Importer { return null; default: - break; + return null; } - return null; } /// Given an `exports` object, returns the entry for an export without a diff --git a/lib/src/js/legacy.dart b/lib/src/js/legacy.dart index a4ff7e8d5..4e399f203 100644 --- a/lib/src/js/legacy.dart +++ b/lib/src/js/legacy.dart @@ -312,8 +312,7 @@ ImportCache? _parsePackageImporters(RenderOptions options, DateTime start) { if (options.pkgImporter case 'node') { // TODO(jamesnw) Can we get an actual filename for parity? Is it needed? Uri entryPointURL = Uri.parse(p.absolute('./index.js')); - return ImportCache.only( - importers: [NodePackageImporterInternal(entryPointURL)]); + return ImportCache.only([NodePackageImporterInternal(entryPointURL)]); } return null; } From f0bd0c044355823940d47211a27e6920566ee853 Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Fri, 1 Dec 2023 14:04:16 -0500 Subject: [PATCH 057/113] Use switch expression --- lib/src/importer/node_package.dart | 32 ++++++++++++++---------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/lib/src/importer/node_package.dart b/lib/src/importer/node_package.dart index 43ee07335..f3beed867 100644 --- a/lib/src/importer/node_package.dart +++ b/lib/src/importer/node_package.dart @@ -288,24 +288,22 @@ class NodePackageImporterInternal extends Importer { /// Given an `exports` object, returns the entry for an export without a /// subpath. Object? _getMainExport(Object exports) { - switch (exports) { - case String string: - return string; - - case List list: - return list; - - case Map map: - if (!map.keys.any((key) => key.startsWith('.'))) { - return map; - } else if (map.containsKey('.')) { - return map['.'] as Object; - } - break; - default: - break; + Object? parseMap(Map map) { + if (!map.keys.any((key) => key.startsWith('.'))) { + return map; + } + if (map.containsKey('.')) { + return map['.'] as Object; + } + return null; } - return null; + + return switch (exports) { + String string => string, + List list => list, + Map map => parseMap(map), + _ => null + }; } /// Given a string `subpath`, returns a list of all possible variations with From 3916b23335df90f5b8957990bc819838eee68a3a Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Fri, 1 Dec 2023 14:08:25 -0500 Subject: [PATCH 058/113] More review --- lib/src/importer/node_package.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/src/importer/node_package.dart b/lib/src/importer/node_package.dart index f3beed867..3f080f09e 100644 --- a/lib/src/importer/node_package.dart +++ b/lib/src/importer/node_package.dart @@ -151,7 +151,7 @@ class NodePackageImporterInternal extends Importer { Map packageManifest, String packageName) { if (packageManifest['exports'] == null) return null; var exports = packageManifest['exports'] as Object; - var subpathVariants = _exportLoadPaths(subpath); + var subpathVariants = _exportsToCheck(subpath); var resolvedPaths = _nodePackageExportsResolve(packageRoot, subpathVariants, exports); @@ -162,7 +162,7 @@ class NodePackageImporterInternal extends Importer { } if (p.extension(subpath).isNotEmpty) return null; - var subpathIndexVariants = _exportLoadPaths(subpath, true); + var subpathIndexVariants = _exportsToCheck(subpath, addIndex: true); var resolvedIndexPaths = _nodePackageExportsResolve(packageRoot, subpathIndexVariants, exports); @@ -308,7 +308,7 @@ class NodePackageImporterInternal extends Importer { /// Given a string `subpath`, returns a list of all possible variations with /// extensions and partials. - List _exportLoadPaths(String subpath, [bool addIndex = false]) { + List _exportsToCheck(String subpath, {bool addIndex = false}) { List paths = []; if (subpath.isEmpty && !addIndex) return [subpath]; if (subpath.isEmpty && addIndex) { From b4f43422b6d6278c7ce6db4a2ba196284beff728 Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Fri, 1 Dec 2023 15:20:11 -0500 Subject: [PATCH 059/113] Handle invalid json, update _resolvePackageRootValues --- lib/src/importer/node_package.dart | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/lib/src/importer/node_package.dart b/lib/src/importer/node_package.dart index 3f080f09e..e683386a5 100644 --- a/lib/src/importer/node_package.dart +++ b/lib/src/importer/node_package.dart @@ -52,7 +52,12 @@ class NodePackageImporterInternal extends Importer { var jsonFile = Uri.file(jsonPath).toFilePath(); var jsonString = readFile(jsonFile); - var packageManifest = jsonDecode(jsonString) as Map; + Map packageManifest; + try { + packageManifest = json.decode(jsonString) as Map; + } catch (e) { + throw "'package.json' in 'pkg:$packageName' cannot be parsed."; + } if (_resolvePackageExports( packageRoot, subpath, packageManifest, packageName) @@ -129,17 +134,19 @@ class NodePackageImporterInternal extends Importer { /// `package.json` file, and returns a file URL. Uri? _resolvePackageRootValues( String packageRoot, Map packageManifest) { - var sassValue = packageManifest['sass'] as String?; - if (sassValue != null && validExtensions.contains(p.extension(sassValue))) { - return Uri.file('$packageRoot$sassValue'); + if (packageManifest['sass'] case String sassValue) { + if (validExtensions.contains(p.extension(sassValue))) { + return Uri.file(p.url.join(packageRoot, sassValue)); + } } - var styleValue = packageManifest['style'] as String?; - if (styleValue != null && - validExtensions.contains(p.extension(styleValue))) { - return Uri.file('$packageRoot$styleValue'); + + if (packageManifest['style'] case String styleValue) { + if (validExtensions.contains(p.extension(styleValue))) { + return Uri.file(p.url.join(packageRoot, styleValue)); + } } - var result = resolveImportPath('${packageRoot}index'); + var result = resolveImportPath(p.url.join(packageRoot, 'index')); if (result != null) return Uri.file(result); return null; } @@ -309,7 +316,7 @@ class NodePackageImporterInternal extends Importer { /// Given a string `subpath`, returns a list of all possible variations with /// extensions and partials. List _exportsToCheck(String subpath, {bool addIndex = false}) { - List paths = []; + var paths = []; if (subpath.isEmpty && !addIndex) return [subpath]; if (subpath.isEmpty && addIndex) { subpath = 'index'; From fadd73ad0031498b430dccc883f6d2279a9864a6 Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Fri, 1 Dec 2023 15:38:53 -0500 Subject: [PATCH 060/113] Use null for empty subpath --- lib/src/importer/node_package.dart | 31 +++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/lib/src/importer/node_package.dart b/lib/src/importer/node_package.dart index e683386a5..0d38d760e 100644 --- a/lib/src/importer/node_package.dart +++ b/lib/src/importer/node_package.dart @@ -66,7 +66,7 @@ class NodePackageImporterInternal extends Importer { validExtensions.contains(p.url.extension(resolved.path))) { return resolved; } else { - throw "The export for '${subpath == '' ? "root" : subpath}' in " + throw "The export for '${subpath ?? "root"}' in " "'$packageName' resolved to '${resolved.toString()}', " "which is not a '.scss', '.sass', or '.css' file."; } @@ -74,7 +74,7 @@ class NodePackageImporterInternal extends Importer { // 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 == '') { + if (subpath == null) { return _resolvePackageRootValues( packageRoot.toFilePath(), packageManifest); } @@ -90,13 +90,13 @@ class NodePackageImporterInternal extends Importer { /// Takes a string, `path`, and returns a tuple with the package name and the /// subpath if it is present. - (String, String) _packageNameAndSubpath(String path) { + (String, String?) _packageNameAndSubpath(String path) { var parts = path.split('/'); var name = parts.removeAt(0); if (name.startsWith('@')) { name = '$name/${parts.removeAt(0)}'; } - return (name, parts.isNotEmpty ? parts.join('/') : ''); + return (name, parts.isNotEmpty ? parts.join('/') : null); } /// Takes a string, `packageName`, and an absolute URL `baseURL`, and returns @@ -154,7 +154,7 @@ class NodePackageImporterInternal extends Importer { /// Takes a package.json value `packageManifest`, a directory URL /// `packageRoot` and a relative URL path `subpath`. It returns a file URL or /// null. `packageName` is used for error reporting only. - Uri? _resolvePackageExports(Uri packageRoot, String subpath, + Uri? _resolvePackageExports(Uri packageRoot, String? subpath, Map packageManifest, String packageName) { if (packageManifest['exports'] == null) return null; var exports = packageManifest['exports'] as Object; @@ -167,7 +167,7 @@ class NodePackageImporterInternal extends Importer { throw "Unable to determine which of multiple potential " "resolutions found for $subpath in $packageName should be used."; } - if (p.extension(subpath).isNotEmpty) return null; + if (subpath != null && p.extension(subpath).isNotEmpty) return null; var subpathIndexVariants = _exportsToCheck(subpath, addIndex: true); @@ -187,9 +187,9 @@ class NodePackageImporterInternal extends Importer { /// `packageRoot` and a list of relative URL paths `subpathVariants`. It /// returns a list of all subpaths present in the package manifest exports. List _nodePackageExportsResolve( - Uri packageRoot, List subpathVariants, Object exports) { - Uri? processVariant(String subpath) { - if (subpath == '') { + Uri packageRoot, List subpathVariants, Object exports) { + Uri? processVariant(String? subpath) { + if (subpath == null) { Object? mainExport = _getMainExport(exports); if (mainExport == null) return null; return _packageTargetResolve(subpath, mainExport, packageRoot); @@ -250,7 +250,7 @@ class NodePackageImporterInternal extends Importer { } /// Recurses through `exports` object to find match for `subpath`. - Uri? _packageTargetResolve(String subpath, Object exports, Uri packageRoot, + Uri? _packageTargetResolve(String? subpath, Object exports, Uri packageRoot, [String? patternMatch]) { switch (exports) { case String string: @@ -315,16 +315,17 @@ class NodePackageImporterInternal extends Importer { /// Given a string `subpath`, returns a list of all possible variations with /// extensions and partials. - List _exportsToCheck(String subpath, {bool addIndex = false}) { + List _exportsToCheck(String? subpath, {bool addIndex = false}) { var paths = []; - if (subpath.isEmpty && !addIndex) return [subpath]; - if (subpath.isEmpty && addIndex) { + + if (subpath == null && addIndex) { subpath = 'index'; - } else if (subpath.isNotEmpty && addIndex) { + } else if (subpath != null && addIndex) { subpath = "$subpath/index"; } + if (subpath == null) return [null]; - if (['scss', 'sass', 'css'].any((ext) => subpath.endsWith(ext))) { + if (['scss', 'sass', 'css'].any((ext) => subpath!.endsWith(ext))) { paths.add(subpath); } else { paths.addAll([ From ac5886351a464dcb2a267422a10cef5d6fea790b Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Fri, 1 Dec 2023 18:47:28 -0500 Subject: [PATCH 061/113] List the multiple matches in error --- lib/src/importer/node_package.dart | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/lib/src/importer/node_package.dart b/lib/src/importer/node_package.dart index 0d38d760e..a0d03b479 100644 --- a/lib/src/importer/node_package.dart +++ b/lib/src/importer/node_package.dart @@ -151,9 +151,10 @@ class NodePackageImporterInternal extends Importer { return null; } - /// Takes a package.json value `packageManifest`, a directory URL - /// `packageRoot` and a relative URL path `subpath`. It returns a file URL or - /// null. `packageName` is used for error reporting only. + /// Returns a path specified in the `exports` section of package.json. Takes a + /// package.json value `packageManifest`, a directory URL `packageRoot` and a + /// relative URL path `subpath`. `packageName` is used for error reporting + /// only. Uri? _resolvePackageExports(Uri packageRoot, String? subpath, Map packageManifest, String packageName) { if (packageManifest['exports'] == null) return null; @@ -164,8 +165,10 @@ class NodePackageImporterInternal extends Importer { if (resolvedPaths.length == 1) return resolvedPaths.first; if (resolvedPaths.length > 1) { - throw "Unable to determine which of multiple potential " - "resolutions found for $subpath in $packageName should be used."; + throw "Unable to determine which of multiple potential resolutions " + "found for ${subpath ?? 'root'} in $packageName should be used. " + "\n\nFound:\n" + "${resolvedPaths.join('\n')}"; } if (subpath != null && p.extension(subpath).isNotEmpty) return null; @@ -176,8 +179,10 @@ class NodePackageImporterInternal extends Importer { if (resolvedIndexPaths.length == 1) return resolvedIndexPaths.first; if (resolvedIndexPaths.length > 1) { - throw "Unable to determine which of multiple potential " - "resolutions found for $subpath in $packageName should be used."; + throw "Unable to determine which of multiple potential resolutions " + "found for ${subpath ?? 'root'} in $packageName should be used. " + "\n\nFound:\n" + "${resolvedIndexPaths.join('\n')}"; } return null; From 6f8aacce8df221ef1495027442812ac02099bec1 Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Fri, 1 Dec 2023 18:57:49 -0500 Subject: [PATCH 062/113] Refactor multiple match logic --- lib/src/importer/node_package.dart | 49 ++++++++++++++++-------------- 1 file changed, 27 insertions(+), 22 deletions(-) diff --git a/lib/src/importer/node_package.dart b/lib/src/importer/node_package.dart index a0d03b479..e5a5716ef 100644 --- a/lib/src/importer/node_package.dart +++ b/lib/src/importer/node_package.dart @@ -160,29 +160,19 @@ class NodePackageImporterInternal extends Importer { if (packageManifest['exports'] == null) return null; var exports = packageManifest['exports'] as Object; var subpathVariants = _exportsToCheck(subpath); - var resolvedPaths = - _nodePackageExportsResolve(packageRoot, subpathVariants, exports); - - if (resolvedPaths.length == 1) return resolvedPaths.first; - if (resolvedPaths.length > 1) { - throw "Unable to determine which of multiple potential resolutions " - "found for ${subpath ?? 'root'} in $packageName should be used. " - "\n\nFound:\n" - "${resolvedPaths.join('\n')}"; + if (_nodePackageExportsResolve( + packageRoot, subpathVariants, exports, subpath, packageName) + case Uri uri) { + return uri; } + if (subpath != null && p.extension(subpath).isNotEmpty) return null; var subpathIndexVariants = _exportsToCheck(subpath, addIndex: true); - - var resolvedIndexPaths = - _nodePackageExportsResolve(packageRoot, subpathIndexVariants, exports); - - if (resolvedIndexPaths.length == 1) return resolvedIndexPaths.first; - if (resolvedIndexPaths.length > 1) { - throw "Unable to determine which of multiple potential resolutions " - "found for ${subpath ?? 'root'} in $packageName should be used. " - "\n\nFound:\n" - "${resolvedIndexPaths.join('\n')}"; + if (_nodePackageExportsResolve( + packageRoot, subpathIndexVariants, exports, subpath, packageName) + case Uri uri) { + return uri; } return null; @@ -191,8 +181,12 @@ class NodePackageImporterInternal extends Importer { /// Takes a package.json value `packageManifest`, a directory URL /// `packageRoot` and a list of relative URL paths `subpathVariants`. It /// returns a list of all subpaths present in the package manifest exports. - List _nodePackageExportsResolve( - Uri packageRoot, List subpathVariants, Object exports) { + Uri? _nodePackageExportsResolve( + Uri packageRoot, + List subpathVariants, + Object exports, + String? subpath, + String packageName) { Uri? processVariant(String? subpath) { if (subpath == null) { Object? mainExport = _getMainExport(exports); @@ -230,7 +224,18 @@ class NodePackageImporterInternal extends Importer { return null; } - return subpathVariants.map(processVariant).whereNotNull().toList(); + var matches = subpathVariants.map(processVariant).whereNotNull().toList(); + + switch (matches) { + case [var path]: + return path; + case [_, _, ...] && var paths: + throw "Unable to determine which of multiple potential resolutions " + "found for ${subpath ?? 'root'} in $packageName should be used. " + "\n\nFound:\n" + "${paths.join('\n')}"; + } + return null; } /// Implementation of the `PATTERN_KEY_COMPARE` algorithm from From 2f49a3421861d543ae7f3d87af39242d4580af63 Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Mon, 4 Dec 2023 09:25:43 -0600 Subject: [PATCH 063/113] Refactor _nodePackageExportsResolve --- lib/src/importer/node_package.dart | 58 +++++++++++++++--------------- 1 file changed, 30 insertions(+), 28 deletions(-) diff --git a/lib/src/importer/node_package.dart b/lib/src/importer/node_package.dart index e5a5716ef..787de6406 100644 --- a/lib/src/importer/node_package.dart +++ b/lib/src/importer/node_package.dart @@ -3,6 +3,7 @@ // https://opensource.org/licenses/MIT. import 'package:collection/collection.dart'; +import 'package:sass/src/util/nullable.dart'; import '../importer.dart'; import './utils.dart'; @@ -189,38 +190,39 @@ class NodePackageImporterInternal extends Importer { String packageName) { Uri? processVariant(String? subpath) { if (subpath == null) { - Object? mainExport = _getMainExport(exports); - if (mainExport == null) return null; - return _packageTargetResolve(subpath, mainExport, packageRoot); - } else { - if (exports is Map && - exports.keys.every((key) => key.startsWith('.'))) { - var matchKey = subpath.startsWith('/') ? ".$subpath" : "./$subpath"; - if (exports.containsKey(matchKey) && !matchKey.contains('*')) { - return _packageTargetResolve( - matchKey, exports[matchKey] as Object, packageRoot); - } + return _getMainExport(exports).andThen((mainExport) => + _packageTargetResolve(subpath, mainExport, packageRoot)); + } + if (exports is! Map || + exports.keys.every((key) => !key.startsWith('.'))) { + return null; + } + var matchKey = subpath.startsWith('/') ? ".$subpath" : "./$subpath"; + if (exports.containsKey(matchKey) && !matchKey.contains('*')) { + return _packageTargetResolve( + matchKey, exports[matchKey] as Object, packageRoot); + } - var expansionKeys = - exports.keys.where((key) => '*'.allMatches(key).length == 1); - expansionKeys = _sortExpansionKeys(expansionKeys.toList()); - - for (var expansionKey in expansionKeys) { - var [patternBase, patternTrailer] = expansionKey.split('*'); - if (matchKey.startsWith(patternBase) && matchKey != patternBase) { - if (patternTrailer.isEmpty || - (matchKey.endsWith(patternTrailer) && - matchKey.length >= expansionKey.length)) { - var target = exports[expansionKey] as Object; - var patternMatch = matchKey.substring(patternBase.length, - matchKey.length - patternTrailer.length); - return _packageTargetResolve( - subpath, target, packageRoot, patternMatch); - } - } + var expansionKeys = _sortExpansionKeys([ + for (var key in exports.keys) + if (key.split('').where((char) => char == '*').length == 1) key + ]); + + for (var expansionKey in expansionKeys) { + var [patternBase, patternTrailer] = expansionKey.split('*'); + if (matchKey.startsWith(patternBase) && matchKey != patternBase) { + if (patternTrailer.isEmpty || + (matchKey.endsWith(patternTrailer) && + matchKey.length >= expansionKey.length)) { + var target = exports[expansionKey] as Object; + var patternMatch = matchKey.substring( + patternBase.length, matchKey.length - patternTrailer.length); + return _packageTargetResolve( + subpath, target, packageRoot, patternMatch); } } } + return null; } From b4015ee87087891d3ab1a18c086a9dd0a9165cb0 Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Mon, 4 Dec 2023 10:49:19 -0600 Subject: [PATCH 064/113] Adjust _packageNameAndSubpath comment --- lib/src/importer/node_package.dart | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/lib/src/importer/node_package.dart b/lib/src/importer/node_package.dart index 787de6406..462e2ff46 100644 --- a/lib/src/importer/node_package.dart +++ b/lib/src/importer/node_package.dart @@ -89,10 +89,14 @@ class NodePackageImporterInternal extends Importer { @override ImporterResult? load(Uri url) => FilesystemImporter.cwd.load(url); - /// Takes a string, `path`, and returns a tuple with the package name and the - /// subpath if it is present. - (String, String?) _packageNameAndSubpath(String path) { - var parts = path.split('/'); + /// Splits a [bare import + /// specifier](https://nodejs.org/api/esm.html#import-specifiers) `specifier` + /// into its package name and subpath, if one exists. + /// + /// Because this is a bare import specifier and not a path, we always use `/` + /// to avoid invalid values on non-Posix machines. + (String, String?) _packageNameAndSubpath(String specifier) { + var parts = specifier.split('/'); var name = parts.removeAt(0); if (name.startsWith('@')) { name = '$name/${parts.removeAt(0)}'; From 0571ae717823afc31fc3cb2ff462cffc5eb87b93 Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Mon, 4 Dec 2023 11:01:39 -0600 Subject: [PATCH 065/113] Adjust _resolvePackateRoot --- lib/src/importer/node_package.dart | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/lib/src/importer/node_package.dart b/lib/src/importer/node_package.dart index 462e2ff46..2d37a99be 100644 --- a/lib/src/importer/node_package.dart +++ b/lib/src/importer/node_package.dart @@ -111,23 +111,20 @@ class NodePackageImporterInternal extends Importer { var baseDirectory = isWindows ? p.dirname(Uri.directory(baseURL.toString()).toFilePath()) : p.dirname(baseURL.toFilePath()); - var lastEntry = ''; Uri? recurseUpFrom(String entry) { + var potentialPackage = p.join(entry, 'node_modules', packageName); + + if (dirExists(potentialPackage)) return Uri.directory(potentialPackage); + var parent = parentDir(entry); + // prevent infinite recursion - if (entry == lastEntry) return null; - lastEntry = entry; - var potentialPackage = p.joinAll([entry, 'node_modules', packageName]); + if (entry == parent) return null; - if (dirExists(potentialPackage)) { - return Uri.directory(potentialPackage); - } + var rootLength = isWindows ? 1 : 0; - var parent = parentDir(entry); - List parentDirectoryParts = - List.from(Uri.directory(parent).pathSegments); + if (Uri.directory(parent).pathSegments.length == rootLength) return null; - if (parentDirectoryParts.length == 1) return null; return recurseUpFrom(parent); } From e18ca15ac2f7a629947a0e4d39a77d388d7ce25c Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Mon, 4 Dec 2023 11:38:03 -0600 Subject: [PATCH 066/113] Throw error if exports has both path and non-path keys --- lib/src/importer/node_package.dart | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/src/importer/node_package.dart b/lib/src/importer/node_package.dart index 2d37a99be..c78ede3d3 100644 --- a/lib/src/importer/node_package.dart +++ b/lib/src/importer/node_package.dart @@ -189,6 +189,12 @@ class NodePackageImporterInternal extends Importer { Object exports, String? subpath, String packageName) { + if (exports is Map) { + if (exports.keys.any((key) => key.startsWith('.')) && + exports.keys.any((key) => !key.startsWith('.'))) { + throw 'Invalid Package Configuration'; + } + } Uri? processVariant(String? subpath) { if (subpath == null) { return _getMainExport(exports).andThen((mainExport) => From d95d7fcb73e48944a83a34ce86c17b230f61857a Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Mon, 4 Dec 2023 11:46:02 -0600 Subject: [PATCH 067/113] Reduce indent --- lib/src/importer/node_package.dart | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/lib/src/importer/node_package.dart b/lib/src/importer/node_package.dart index c78ede3d3..1b76de5c6 100644 --- a/lib/src/importer/node_package.dart +++ b/lib/src/importer/node_package.dart @@ -217,16 +217,16 @@ class NodePackageImporterInternal extends Importer { for (var expansionKey in expansionKeys) { var [patternBase, patternTrailer] = expansionKey.split('*'); - if (matchKey.startsWith(patternBase) && matchKey != patternBase) { - if (patternTrailer.isEmpty || - (matchKey.endsWith(patternTrailer) && - matchKey.length >= expansionKey.length)) { - var target = exports[expansionKey] as Object; - var patternMatch = matchKey.substring( - patternBase.length, matchKey.length - patternTrailer.length); - return _packageTargetResolve( - subpath, target, packageRoot, patternMatch); - } + if (!matchKey.startsWith(patternBase)) continue; + if (matchKey == patternBase) continue; + if (patternTrailer.isEmpty || + (matchKey.endsWith(patternTrailer) && + matchKey.length >= expansionKey.length)) { + var target = exports[expansionKey] as Object; + var patternMatch = matchKey.substring( + patternBase.length, matchKey.length - patternTrailer.length); + return _packageTargetResolve( + subpath, target, packageRoot, patternMatch); } } From 123b6939f571c19bf2a08df4626bc53152ba26df Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Mon, 4 Dec 2023 16:55:45 -0600 Subject: [PATCH 068/113] Clean up file paths --- lib/src/importer/node_package.dart | 52 ++++++++++++++++-------------- 1 file changed, 27 insertions(+), 25 deletions(-) diff --git a/lib/src/importer/node_package.dart b/lib/src/importer/node_package.dart index 1b76de5c6..3f1a664d1 100644 --- a/lib/src/importer/node_package.dart +++ b/lib/src/importer/node_package.dart @@ -3,6 +3,7 @@ // https://opensource.org/licenses/MIT. import 'package:collection/collection.dart'; +import 'package:sass/src/util/map.dart'; import 'package:sass/src/util/nullable.dart'; import '../importer.dart'; @@ -41,16 +42,15 @@ class NodePackageImporterInternal extends Importer { throw "pkg: URL $url must not have a query or fragment."; } - var baseURL = containingUrl?.scheme == 'file' - ? Uri.parse(containingUrl!.toFilePath()) - : entryPointURL; + var baseURL = + containingUrl?.scheme == 'file' ? containingUrl! : entryPointURL; var (packageName, subpath) = _packageNameAndSubpath(url.path); var packageRoot = _resolvePackageRoot(packageName, baseURL); if (packageRoot == null) return null; - var jsonPath = p.join(packageRoot.toFilePath(), 'package.json'); - var jsonFile = Uri.file(jsonPath).toFilePath(); + var jsonPath = p.join(packageRoot, 'package.json'); + var jsonFile = p.fromUri(Uri.file(jsonPath)); var jsonString = readFile(jsonFile); Map packageManifest; @@ -61,7 +61,7 @@ class NodePackageImporterInternal extends Importer { } if (_resolvePackageExports( - packageRoot, subpath, packageManifest, packageName) + Uri.file(packageRoot), subpath, packageManifest, packageName) case var resolved?) { if (resolved.scheme == 'file' && validExtensions.contains(p.url.extension(resolved.path))) { @@ -76,13 +76,12 @@ class NodePackageImporterInternal extends Importer { // then `index` file at package root, resolved for file extensions and // partials. if (subpath == null) { - return _resolvePackageRootValues( - packageRoot.toFilePath(), packageManifest); + return _resolvePackageRootValues(packageRoot, packageManifest); } // If there is a subpath, attempt to resolve the path relative to the // package root, and resolve for file extensions and partials. - var relativeSubpath = "${packageRoot.toFilePath()}${p.separator}$subpath"; + var relativeSubpath = p.join(packageRoot, subpath); return FilesystemImporter.cwd.canonicalize(Uri.file(relativeSubpath)); } @@ -107,10 +106,10 @@ class NodePackageImporterInternal extends Importer { /// Takes a string, `packageName`, and an absolute URL `baseURL`, and returns /// an absolute URL to the root directory for the most proximate installed /// `packageName`. - Uri? _resolvePackageRoot(String packageName, Uri baseURL) { + String? _resolvePackageRoot(String packageName, Uri baseURL) { var baseDirectory = isWindows - ? p.dirname(Uri.directory(baseURL.toString()).toFilePath()) - : p.dirname(baseURL.toFilePath()); + ? p.dirname(p.fromUri(Uri.directory(baseURL.toString()))) + : p.dirname(p.fromUri(baseURL)); Uri? recurseUpFrom(String entry) { var potentialPackage = p.join(entry, 'node_modules', packageName); @@ -128,7 +127,9 @@ class NodePackageImporterInternal extends Importer { return recurseUpFrom(parent); } - return recurseUpFrom(baseDirectory); + var directory = recurseUpFrom(baseDirectory); + if (directory == null) return null; + return p.fromUri(directory); } /// Takes a string `packageRoot`, which is the root directory for a package, @@ -277,26 +278,27 @@ class NodePackageImporterInternal extends Importer { throw "Invalid Package Target"; } if (patternMatch != null) { - string = string.replaceAll(RegExp(r'\*'), patternMatch); - var path = p.normalize("${packageRoot.toFilePath()}/$string"); - return fileExists(path) ? Uri.parse('$packageRoot/$string') : null; + var replaced = string.replaceAll(RegExp(r'\*'), patternMatch); + var path = p.normalize(p.join(p.fromUri(packageRoot), replaced)); + return fileExists(path) ? Uri.parse('$packageRoot/$replaced') : null; } return Uri.parse("$packageRoot/$string"); case Map map: var conditions = ['sass', 'style', 'default']; - for (var key in map.keys) { - if (conditions.contains(key)) { - var result = _packageTargetResolve( - subpath, map[key] as Object, packageRoot, patternMatch); - if (result != null) { - return result; - } + for (var (key, value) in map.pairs) { + if (!conditions.contains(key)) continue; + if (_packageTargetResolve( + subpath, value as Object, packageRoot, patternMatch) + case var result?) { + return result; } } return null; - case List array: - if (array.isEmpty) return null; + case []: + return null; + + case List array: for (var value in array) { var result = _packageTargetResolve( subpath, value as Object, packageRoot, patternMatch); From b1b78ad9084a60d58379c151846cc82ad01118cc Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Mon, 4 Dec 2023 17:21:06 -0600 Subject: [PATCH 069/113] Revert to naive concat for Windows tests --- lib/src/importer/node_package.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/importer/node_package.dart b/lib/src/importer/node_package.dart index 3f1a664d1..1a2c01673 100644 --- a/lib/src/importer/node_package.dart +++ b/lib/src/importer/node_package.dart @@ -81,7 +81,7 @@ class NodePackageImporterInternal extends Importer { // If there is a subpath, attempt to resolve the path relative to the // package root, and resolve for file extensions and partials. - var relativeSubpath = p.join(packageRoot, subpath); + var relativeSubpath = "$packageRoot${p.separator}$subpath"; return FilesystemImporter.cwd.canonicalize(Uri.file(relativeSubpath)); } From afbfade874a6739185fdd86b93ebbd3f7d986962 Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Mon, 4 Dec 2023 17:28:01 -0600 Subject: [PATCH 070/113] Another Windows attempt --- lib/src/importer/node_package.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/importer/node_package.dart b/lib/src/importer/node_package.dart index 1a2c01673..12e7dd7d4 100644 --- a/lib/src/importer/node_package.dart +++ b/lib/src/importer/node_package.dart @@ -81,7 +81,7 @@ class NodePackageImporterInternal extends Importer { // If there is a subpath, attempt to resolve the path relative to the // package root, and resolve for file extensions and partials. - var relativeSubpath = "$packageRoot${p.separator}$subpath"; + var relativeSubpath = p.join(packageRoot, subpath); return FilesystemImporter.cwd.canonicalize(Uri.file(relativeSubpath)); } @@ -108,7 +108,7 @@ class NodePackageImporterInternal extends Importer { /// `packageName`. String? _resolvePackageRoot(String packageName, Uri baseURL) { var baseDirectory = isWindows - ? p.dirname(p.fromUri(Uri.directory(baseURL.toString()))) + ? p.dirname(Uri.directory(baseURL.toString()).toFilePath()) : p.dirname(p.fromUri(baseURL)); Uri? recurseUpFrom(String entry) { From 6167fd88309649cba54d0b3c92fec9f898e8f699 Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Mon, 4 Dec 2023 17:45:54 -0600 Subject: [PATCH 071/113] Try parsing containing URL for windows --- lib/src/importer/node_package.dart | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/src/importer/node_package.dart b/lib/src/importer/node_package.dart index 12e7dd7d4..f80e6dab0 100644 --- a/lib/src/importer/node_package.dart +++ b/lib/src/importer/node_package.dart @@ -42,8 +42,9 @@ class NodePackageImporterInternal extends Importer { throw "pkg: URL $url must not have a query or fragment."; } - var baseURL = - containingUrl?.scheme == 'file' ? containingUrl! : entryPointURL; + var baseURL = containingUrl?.scheme == 'file' + ? Uri.parse(containingUrl!.toFilePath()) + : entryPointURL; var (packageName, subpath) = _packageNameAndSubpath(url.path); var packageRoot = _resolvePackageRoot(packageName, baseURL); @@ -108,7 +109,7 @@ class NodePackageImporterInternal extends Importer { /// `packageName`. String? _resolvePackageRoot(String packageName, Uri baseURL) { var baseDirectory = isWindows - ? p.dirname(Uri.directory(baseURL.toString()).toFilePath()) + ? p.dirname(p.fromUri(Uri.directory(baseURL.toString()))) : p.dirname(p.fromUri(baseURL)); Uri? recurseUpFrom(String entry) { From e3fc5926f743547249c21b34e86db64dc26a4b81 Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Mon, 4 Dec 2023 18:08:24 -0600 Subject: [PATCH 072/113] Remove unneeded parentDir --- lib/src/importer/node_package.dart | 2 +- lib/src/io/interface.dart | 2 -- lib/src/io/js.dart | 2 -- lib/src/io/vm.dart | 2 -- 4 files changed, 1 insertion(+), 7 deletions(-) diff --git a/lib/src/importer/node_package.dart b/lib/src/importer/node_package.dart index f80e6dab0..672286f69 100644 --- a/lib/src/importer/node_package.dart +++ b/lib/src/importer/node_package.dart @@ -116,7 +116,7 @@ class NodePackageImporterInternal extends Importer { var potentialPackage = p.join(entry, 'node_modules', packageName); if (dirExists(potentialPackage)) return Uri.directory(potentialPackage); - var parent = parentDir(entry); + var parent = p.dirname(entry); // prevent infinite recursion if (entry == parent) return null; diff --git a/lib/src/io/interface.dart b/lib/src/io/interface.dart index ede97b358..29182eb45 100644 --- a/lib/src/io/interface.dart +++ b/lib/src/io/interface.dart @@ -92,5 +92,3 @@ set exitCode(int value) => throw ''; /// periodically rather than using a native filesystem monitoring API. Future> watchDir(String path, {bool poll = false}) => throw ''; - -String parentDir(String path) => throw ''; diff --git a/lib/src/io/js.dart b/lib/src/io/js.dart index 74cb685c4..94e4f1a13 100644 --- a/lib/src/io/js.dart +++ b/lib/src/io/js.dart @@ -285,5 +285,3 @@ Future> watchDir(String path, {bool poll = false}) { return completer.future; } - -String parentDir(String path) => p.dirname(path); diff --git a/lib/src/io/vm.dart b/lib/src/io/vm.dart index b14b44f69..1d5c7b561 100644 --- a/lib/src/io/vm.dart +++ b/lib/src/io/vm.dart @@ -114,5 +114,3 @@ Future> watchDir(String path, {bool poll = false}) async { return stream; } - -String parentDir(String path) => io.FileSystemEntity.parentOf(path); From c9c64732da35af810072223f5bca2461a63a2a9d Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Fri, 8 Dec 2023 08:59:26 -0600 Subject: [PATCH 073/113] Link to Node spec --- lib/src/importer/filesystem.dart | 2 +- lib/src/importer/node_package.dart | 13 ++++++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/lib/src/importer/filesystem.dart b/lib/src/importer/filesystem.dart index 2560f9d7c..47b0ae288 100644 --- a/lib/src/importer/filesystem.dart +++ b/lib/src/importer/filesystem.dart @@ -23,7 +23,7 @@ class FilesystemImporter extends Importer { FilesystemImporter(String loadPath) : _loadPath = p.absolute(loadPath); /// Creates an importer relative to the current working directory. - static FilesystemImporter cwd = FilesystemImporter('.'); + static final cwd = FilesystemImporter('.'); Uri? canonicalize(Uri url) { if (url.scheme != 'file' && url.scheme != '') return null; diff --git a/lib/src/importer/node_package.dart b/lib/src/importer/node_package.dart index 672286f69..8a4437d57 100644 --- a/lib/src/importer/node_package.dart +++ b/lib/src/importer/node_package.dart @@ -107,6 +107,9 @@ class NodePackageImporterInternal extends Importer { /// Takes a string, `packageName`, and an absolute URL `baseURL`, and returns /// an absolute URL to the root directory for the most proximate installed /// `packageName`. + /// + /// Implementation of `PACKAGE_RESOLVE` from the [Resolution Algorithm + /// Specification](https://nodejs.org/api/esm.html#resolution-algorithm-specification). String? _resolvePackageRoot(String packageName, Uri baseURL) { var baseDirectory = isWindows ? p.dirname(p.fromUri(Uri.directory(baseURL.toString()))) @@ -185,6 +188,9 @@ class NodePackageImporterInternal extends Importer { /// Takes a package.json value `packageManifest`, a directory URL /// `packageRoot` and a list of relative URL paths `subpathVariants`. It /// returns a list of all subpaths present in the package manifest exports. + /// + /// Implementation of `PACKAGE_EXPORTS_RESOLVE` from the [Resolution Algorithm + /// Specification](https://nodejs.org/api/esm.html#resolution-algorithm-specification). Uri? _nodePackageExportsResolve( Uri packageRoot, List subpathVariants, @@ -270,7 +276,12 @@ class NodePackageImporterInternal extends Importer { return keys; } - /// Recurses through `exports` object to find match for `subpath`. + /// Recurses through `exports` object to find match for `subpath`, verifying + /// the file exists relative to `packageRoot`. Instances of `*` will be + /// replaced with `patternMatch`. + /// + /// Implementation of `PACKAGE_TARGET_RESOLVE` from the [Resolution Algorithm + /// Specification](https://nodejs.org/api/esm.html#resolution-algorithm-specification). Uri? _packageTargetResolve(String? subpath, Object exports, Uri packageRoot, [String? patternMatch]) { switch (exports) { From 7f6d15a9de432ac49d6f4254f369f2752a17faa7 Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Fri, 8 Dec 2023 11:44:23 -0600 Subject: [PATCH 074/113] Clean up errors --- lib/src/importer/node_package.dart | 40 +++++++++++++----------------- 1 file changed, 17 insertions(+), 23 deletions(-) diff --git a/lib/src/importer/node_package.dart b/lib/src/importer/node_package.dart index 8a4437d57..17effa178 100644 --- a/lib/src/importer/node_package.dart +++ b/lib/src/importer/node_package.dart @@ -212,16 +212,16 @@ class NodePackageImporterInternal extends Importer { exports.keys.every((key) => !key.startsWith('.'))) { return null; } - var matchKey = subpath.startsWith('/') ? ".$subpath" : "./$subpath"; + var matchKey = "./$subpath"; if (exports.containsKey(matchKey) && !matchKey.contains('*')) { return _packageTargetResolve( matchKey, exports[matchKey] as Object, packageRoot); } - var expansionKeys = _sortExpansionKeys([ + var expansionKeys = [ for (var key in exports.keys) if (key.split('').where((char) => char == '*').length == 1) key - ]); + ]..sort(_compareExpansionKeys); for (var expansionKey in expansionKeys) { var [patternBase, patternTrailer] = expansionKey.split('*'); @@ -255,25 +255,18 @@ class NodePackageImporterInternal extends Importer { return null; } - /// Implementation of the `PATTERN_KEY_COMPARE` algorithm from + /// Implementation of the `PATTERN_KEY_COMPARE` comparator from /// https://nodejs.org/api/esm.html#resolution-algorithm-specification. - List _sortExpansionKeys(List keys) { - int sorter(String keyA, String keyB) { - var baseLengthA = - keyA.contains('*') ? keyA.indexOf('*') + 1 : keyA.length; - var baseLengthB = - keyB.contains('*') ? keyB.indexOf('*') + 1 : keyB.length; - if (baseLengthA > baseLengthB) return -1; - if (baseLengthB > baseLengthA) return 1; - if (!keyA.contains("*")) return 1; - if (!keyB.contains("*")) return -1; - if (keyA.length > keyB.length) return -1; - if (keyB.length > keyA.length) return 1; - return 0; - } - - keys.sort(sorter); - return keys; + int _compareExpansionKeys(String keyA, String keyB) { + var baseLengthA = keyA.contains('*') ? keyA.indexOf('*') + 1 : keyA.length; + var baseLengthB = keyB.contains('*') ? keyB.indexOf('*') + 1 : keyB.length; + if (baseLengthA > baseLengthB) return -1; + if (baseLengthB > baseLengthA) return 1; + if (!keyA.contains("*")) return 1; + if (!keyB.contains("*")) return -1; + if (keyA.length > keyB.length) return -1; + if (keyB.length > keyA.length) return 1; + return 0; } /// Recurses through `exports` object to find match for `subpath`, verifying @@ -287,7 +280,7 @@ class NodePackageImporterInternal extends Importer { switch (exports) { case String string: if (!string.startsWith('./')) { - throw "Invalid Package Target"; + throw "Export '$string' must be a path relative to the package root at '$packageRoot'."; } if (patternMatch != null) { var replaced = string.replaceAll(RegExp(r'\*'), patternMatch); @@ -320,8 +313,9 @@ class NodePackageImporterInternal extends Importer { } return null; + default: - return null; + throw "Invalid 'exports' value in ${p.join(packageRoot.toFilePath(), 'package.json')}"; } } From f3f43d424befce6fa85f6ed89d25c257f9e3c216 Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Tue, 19 Dec 2023 15:18:21 -0500 Subject: [PATCH 075/113] Move to NodePackageImporter as class --- lib/src/js.dart | 2 +- lib/src/js/compile.dart | 72 ++++++++++++++++++--------- lib/src/js/exports.dart | 3 +- lib/src/js/legacy.dart | 33 +++++++++--- lib/src/js/legacy/render_options.dart | 11 +++- lib/src/js/utils.dart | 4 ++ pubspec.yaml | 2 +- 7 files changed, 89 insertions(+), 38 deletions(-) diff --git a/lib/src/js.dart b/lib/src/js.dart index aaf390f0e..74cddfae9 100644 --- a/lib/src/js.dart +++ b/lib/src/js.dart @@ -45,7 +45,7 @@ void main() { silent: JSLogger( warn: allowInteropNamed('sass.Logger.silent.warn', (_, __) {}), debug: allowInteropNamed('sass.Logger.silent.debug', (_, __) {}))); - exports.nodePackageImporter = nodePackageImporter; + exports.NodePackageImporter = nodePackageImporterClass; exports.info = "dart-sass\t${const String.fromEnvironment('version')}\t(Sass Compiler)\t" diff --git a/lib/src/js/compile.dart b/lib/src/js/compile.dart index 385936d15..41c051b47 100644 --- a/lib/src/js/compile.dart +++ b/lib/src/js/compile.dart @@ -6,7 +6,6 @@ import 'package:cli_pkg/js.dart'; import 'package:node_interop/js.dart'; import 'package:node_interop/util.dart' hide futureToPromise; import 'package:path/path.dart' as p; -import 'package:sass/src/js/function.dart'; import 'package:term_glyph/term_glyph.dart' as glyph; import '../../sass.dart'; @@ -23,6 +22,7 @@ import 'compile_options.dart'; import 'compile_result.dart'; import 'exception.dart'; import 'importer.dart'; +import 'reflection.dart'; import 'utils.dart'; /// The JS API `compile` function. @@ -46,8 +46,7 @@ NodeCompileResult compile(String path, [CompileOptions? options]) { sourceMap: options?.sourceMap ?? false, logger: JSToDartLogger(options?.logger, Logger.stderr(color: color), ascii: ascii), - importers: options?.importers - ?.map((importer) => _parseImporter(importer, Uri.parse(path))), + importers: options?.importers?.map(_parseImporter), functions: _parseFunctions(options?.functions).cast()); return _convertResult(result, includeSourceContents: options?.sourceMapIncludeSources ?? false); @@ -63,11 +62,10 @@ NodeCompileResult compile(String path, [CompileOptions? options]) { NodeCompileResult compileString(String text, [CompileStringOptions? options]) { var color = options?.alertColor ?? hasTerminal; var ascii = options?.alertAscii ?? glyph.ascii; - var url = options?.url.andThen(jsToDartUrl); try { var result = compileStringToResult(text, syntax: parseSyntax(options?.syntax), - url: url, + url: options?.url.andThen(jsToDartUrl), color: color, loadPaths: options?.loadPaths, quietDeps: options?.quietDeps ?? false, @@ -77,8 +75,7 @@ NodeCompileResult compileString(String text, [CompileStringOptions? options]) { sourceMap: options?.sourceMap ?? false, logger: JSToDartLogger(options?.logger, Logger.stderr(color: color), ascii: ascii), - importers: options?.importers - ?.map((importer) => _parseImporter(importer, url)), + importers: options?.importers?.map(_parseImporter), importer: options?.importer.andThen(_parseImporter) ?? (options?.url == null ? NoOpImporter() : null), functions: _parseFunctions(options?.functions).cast()); @@ -111,7 +108,7 @@ Promise compileAsync(String path, [CompileOptions? options]) { logger: JSToDartLogger(options?.logger, Logger.stderr(color: color), ascii: ascii), importers: options?.importers - ?.map((importer) => _parseAsyncImporter(importer, Uri.parse(path))), + ?.map((importer) => _parseAsyncImporter(importer)), functions: _parseFunctions(options?.functions, asynch: true)); return _convertResult(result, includeSourceContents: options?.sourceMapIncludeSources ?? false); @@ -125,11 +122,10 @@ Promise compileAsync(String path, [CompileOptions? options]) { Promise compileStringAsync(String text, [CompileStringOptions? options]) { var color = options?.alertColor ?? hasTerminal; var ascii = options?.alertAscii ?? glyph.ascii; - var url = options?.url.andThen(jsToDartUrl); return _wrapAsyncSassExceptions(futureToPromise(() async { var result = await compileStringToResultAsync(text, syntax: parseSyntax(options?.syntax), - url: url, + url: options?.url.andThen(jsToDartUrl), color: color, loadPaths: options?.loadPaths, quietDeps: options?.quietDeps ?? false, @@ -142,7 +138,7 @@ Promise compileStringAsync(String text, [CompileStringOptions? options]) { importers: options?.importers ?.map((importer) => _parseAsyncImporter(importer)), importer: options?.importer - .andThen((importer) => _parseAsyncImporter(importer, url)) ?? + .andThen((importer) => _parseAsyncImporter(importer)) ?? (options?.url == null ? NoOpImporter() : null), functions: _parseFunctions(options?.functions, asynch: true)); return _convertResult(result, @@ -188,14 +184,23 @@ OutputStyle _parseOutputStyle(String? style) => switch (style) { /// Converts [importer] into an [AsyncImporter] that can be used with /// [compileAsync] or [compileStringAsync]. -AsyncImporter _parseAsyncImporter(Object? importer, [Uri? entryPointURL]) { - if (jsEquals(importer, nodePackageImporter)) { +AsyncImporter _parseAsyncImporter(Object? importer) { + if (importer is NodePackageImporterClass) { if (isBrowser) { jsThrow(JsError( "The Node Package Importer cannot be used without a filesystem.")); } - entryPointURL = entryPointURL ?? Uri.parse(p.absolute('./index.scss')); - return NodePackageImporterInternal(entryPointURL); + var entryPointURL = importer.entryPointPath != null + ? p.join(p.current, importer.entryPointPath) + : requireMainFilename; + + if (entryPointURL == null) { + jsThrow(JsError( + "The Node Package Importer cannot determine an entry point." + "Please provide an `entryPointPath` to the Node Package Importer.")); + } + + return NodePackageImporterInternal(Uri.file(entryPointURL)); } if (importer == null) jsThrow(JsError("Importers may not be null.")); @@ -221,15 +226,23 @@ AsyncImporter _parseAsyncImporter(Object? importer, [Uri? entryPointURL]) { } /// Converts [importer] into a synchronous [Importer]. -Importer _parseImporter(Object? importer, [Uri? entryPointURL]) { - var jsEquals = JSFunction('a', 'b', 'return a===b'); - if (jsEquals.call(importer, nodePackageImporter) == true) { +Importer _parseImporter(Object? importer) { + if (importer is NodePackageImporterClass) { if (isBrowser) { jsThrow(JsError( - "The Node Package Importer can not be used without a filesystem.")); + "The Node Package Importer cannot be used without a filesystem.")); + } + var entryPointURL = importer.entryPointPath != null + ? p.join(p.current, importer.entryPointPath) + : requireMainFilename; + + if (entryPointURL == null) { + jsThrow(JsError( + "The Node Package Importer cannot determine an entry point." + "Please provide an `entryPointPath` to the Node Package Importer.")); } - entryPointURL = entryPointURL ?? Uri.parse(p.absolute('./index.scss')); - return NodePackageImporterInternal(entryPointURL); + + return NodePackageImporterInternal(Uri.file(entryPointURL)); } if (importer == null) jsThrow(JsError("Importers may not be null.")); @@ -347,6 +360,17 @@ List _parseFunctions(Object? functions, {bool asynch = false}) { return result; } -/// The exported `nodePackageImporter` that can be added to the `importers` -/// option to enable loading `pkg:` URLs from `node_modules`. -final nodePackageImporter = createJSSymbol(); +class NodePackageImporterClass { + final String? entryPointPath; + NodePackageImporterClass(this.entryPointPath); +} + +/// The exported `NodePackageImporter` class that can be added to the +/// `importers` option to enable loading `pkg:` URLs from `node_modules`. +final JSClass nodePackageImporterClass = () { + var jsClass = createJSClass( + 'sass.NodePackageImporter', + (Object self, [String? entryPointPath]) => + NodePackageImporterClass(entryPointPath)); + return jsClass; +}(); diff --git a/lib/src/js/exports.dart b/lib/src/js/exports.dart index 6973a148c..33037489b 100644 --- a/lib/src/js/exports.dart +++ b/lib/src/js/exports.dart @@ -11,7 +11,6 @@ import '../value.dart' as value; import 'legacy/types.dart'; import 'logger.dart'; import 'reflection.dart'; -import 'utils.dart'; @JS() class Exports { @@ -23,7 +22,7 @@ class Exports { external set info(String info); external set Exception(JSClass function); external set Logger(LoggerNamespace namespace); - external set nodePackageImporter(JSSymbol nodePackageImporter); + external set NodePackageImporter(JSClass function); // Value APIs external set Value(JSClass function); diff --git a/lib/src/js/legacy.dart b/lib/src/js/legacy.dart index 4e399f203..deea244c6 100644 --- a/lib/src/js/legacy.dart +++ b/lib/src/js/legacy.dart @@ -299,20 +299,37 @@ NodeImporter _parseImporter(RenderOptions options, DateTime start) { /// Creates an [AsyncImportCache] for Package Importers. AsyncImportCache? _parsePackageImportersAsync( RenderOptions options, DateTime start) { - if (options.pkgImporter case 'node') { - // TODO(jamesnw) Can we get an actual filename for parity? Is it needed? - Uri entryPointURL = Uri.parse(p.absolute('./index.js')); - return AsyncImportCache.only([NodePackageImporterInternal(entryPointURL)]); + if (options.pkgImporter?.type case 'node') { + var entryPointURL = options.pkgImporter?.entryPointPath != null + ? p.join(p.current, options.pkgImporter?.entryPointPath) + : requireMainFilename; + + if (entryPointURL == null) { + jsThrow(JsError( + "The Node Package Importer cannot determine an entry point." + "Please provide an `entryPointPath` to the Node Package Importer.")); + } + + return AsyncImportCache.only( + [NodePackageImporterInternal(Uri.file(entryPointURL))]); } return null; } /// Creates an [ImportCache] for Package Importers. ImportCache? _parsePackageImporters(RenderOptions options, DateTime start) { - if (options.pkgImporter case 'node') { - // TODO(jamesnw) Can we get an actual filename for parity? Is it needed? - Uri entryPointURL = Uri.parse(p.absolute('./index.js')); - return ImportCache.only([NodePackageImporterInternal(entryPointURL)]); + if (options.pkgImporter?.type case 'node') { + var entryPointURL = options.pkgImporter?.entryPointPath != null + ? p.join(p.current, options.pkgImporter?.entryPointPath) + : requireMainFilename; + + if (entryPointURL == null) { + jsThrow(JsError( + "The Node Package Importer cannot determine an entry point." + "Please provide an `entryPointPath` to the Node Package Importer.")); + } + return ImportCache.only( + [NodePackageImporterInternal(Uri.file(entryPointURL))]); } return null; } diff --git a/lib/src/js/legacy/render_options.dart b/lib/src/js/legacy/render_options.dart index 33a56fbe6..0ca3b4533 100644 --- a/lib/src/js/legacy/render_options.dart +++ b/lib/src/js/legacy/render_options.dart @@ -7,13 +7,20 @@ import 'package:js/js.dart'; import '../logger.dart'; import 'fiber.dart'; +@JS() +@anonymous +class PackageImporterOptions { + external String get type; + external String? get entryPointPath; +} + @JS() @anonymous class RenderOptions { external String? get file; external String? get data; external Object? get importer; - external String? get pkgImporter; + external PackageImporterOptions? get pkgImporter; external Object? get functions; external List? get includePaths; external bool? get indentedSyntax; @@ -37,7 +44,7 @@ class RenderOptions { {String? file, String? data, Object? importer, - String? pkgImporter, + PackageImporterOptions? pkgImporter, Object? functions, List? includePaths, bool? indentedSyntax, diff --git a/lib/src/js/utils.dart b/lib/src/js/utils.dart index bdc84fe06..71927ac81 100644 --- a/lib/src/js/utils.dart +++ b/lib/src/js/utils.dart @@ -244,3 +244,7 @@ Syntax parseSyntax(String? syntax) => switch (syntax) { 'css' => Syntax.css, _ => jsThrow(JsError('Unknown syntax "$syntax".')) }; + +/// The value of require.main.filename +@JS("require.main.filename") +external String? get requireMainFilename; diff --git a/pubspec.yaml b/pubspec.yaml index c42d2133e..e72bc905d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -14,7 +14,7 @@ dependencies: args: ^2.0.0 async: ^2.5.0 charcode: ^1.2.0 - cli_pkg: ^2.7.0 + cli_pkg: ^2.7.1 cli_repl: ^0.2.1 collection: ^1.16.0 http: "^1.1.0" From 485a71fc0ebcf40be68b676882112d421a03ddc2 Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Tue, 19 Dec 2023 15:31:04 -0500 Subject: [PATCH 076/113] Fix embedded param --- lib/src/embedded/compilation_dispatcher.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/embedded/compilation_dispatcher.dart b/lib/src/embedded/compilation_dispatcher.dart index 9d69f7901..c73f87d33 100644 --- a/lib/src/embedded/compilation_dispatcher.dart +++ b/lib/src/embedded/compilation_dispatcher.dart @@ -227,7 +227,7 @@ final class CompilationDispatcher { case InboundMessage_CompileRequest_Importer_Importer.nodePackageImporter: var entryPointUrl = - Uri.parse(importer.nodePackageImporter.entryPointUrl); + Uri.parse(importer.nodePackageImporter.entryPointPath); return NodePackageImporterInternal(entryPointUrl); } } From ed585906ec0ee6b57518d83d03c5e872e6958402 Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Wed, 20 Dec 2023 11:41:56 -0500 Subject: [PATCH 077/113] Switch posix url context for bare import specifiers --- lib/src/importer/node_package.dart | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/lib/src/importer/node_package.dart b/lib/src/importer/node_package.dart index 17effa178..d589e564e 100644 --- a/lib/src/importer/node_package.dart +++ b/lib/src/importer/node_package.dart @@ -96,12 +96,28 @@ class NodePackageImporterInternal extends Importer { /// Because this is a bare import specifier and not a path, we always use `/` /// to avoid invalid values on non-Posix machines. (String, String?) _packageNameAndSubpath(String specifier) { - var parts = specifier.split('/'); + var parts = p.posix.split(specifier); var name = parts.removeAt(0); + + if (name.startsWith('.')) { + throw "pkg: name $name must not start with a '.'."; + } + if (name.contains('\\')) { + throw "pkg: name $name must not contain a '\\'."; + } + if (name.contains('%')) { + throw "pkg: name $name must not contain a '%'."; + } + if (name.startsWith('@')) { - name = '$name/${parts.removeAt(0)}'; + if (parts.isEmpty) { + throw "pkg: name $name is an invalid package name." + "Scoped packages, which start with '@', must have a second segment."; + } + name = p.posix.join(name, parts.removeAt(0)); } - return (name, parts.isNotEmpty ? parts.join('/') : null); + + return (name, parts.isNotEmpty ? p.posix.joinAll(parts) : null); } /// Takes a string, `packageName`, and an absolute URL `baseURL`, and returns From da3694a4cb3faa9577187611dc672288d6a55980 Mon Sep 17 00:00:00 2001 From: Jonny Gerig Meyer Date: Wed, 20 Dec 2023 12:29:56 -0500 Subject: [PATCH 078/113] Try using containingURL directly --- lib/src/importer/node_package.dart | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/src/importer/node_package.dart b/lib/src/importer/node_package.dart index d589e564e..9ee81ad48 100644 --- a/lib/src/importer/node_package.dart +++ b/lib/src/importer/node_package.dart @@ -42,9 +42,8 @@ class NodePackageImporterInternal extends Importer { throw "pkg: URL $url must not have a query or fragment."; } - var baseURL = containingUrl?.scheme == 'file' - ? Uri.parse(containingUrl!.toFilePath()) - : entryPointURL; + var baseURL = + containingUrl?.scheme == 'file' ? containingUrl! : entryPointURL; var (packageName, subpath) = _packageNameAndSubpath(url.path); var packageRoot = _resolvePackageRoot(packageName, baseURL); From ed7fe62976383cd5dac6a6ae2c05c32cdcaf85fc Mon Sep 17 00:00:00 2001 From: Jonny Gerig Meyer Date: Wed, 20 Dec 2023 13:41:06 -0500 Subject: [PATCH 079/113] temp try logging path for windows --- lib/src/importer/node_package.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/src/importer/node_package.dart b/lib/src/importer/node_package.dart index 9ee81ad48..87a3c4928 100644 --- a/lib/src/importer/node_package.dart +++ b/lib/src/importer/node_package.dart @@ -126,6 +126,7 @@ class NodePackageImporterInternal extends Importer { /// Implementation of `PACKAGE_RESOLVE` from the [Resolution Algorithm /// Specification](https://nodejs.org/api/esm.html#resolution-algorithm-specification). String? _resolvePackageRoot(String packageName, Uri baseURL) { + print(baseURL.toString()); var baseDirectory = isWindows ? p.dirname(p.fromUri(Uri.directory(baseURL.toString()))) : p.dirname(p.fromUri(baseURL)); From c8406a4330c8f96d363c9ebb4e47b7c4d2b8eeb7 Mon Sep 17 00:00:00 2001 From: Jonny Gerig Meyer Date: Wed, 20 Dec 2023 13:57:23 -0500 Subject: [PATCH 080/113] More temp code for Windows debugging --- lib/src/importer/node_package.dart | 71 +++++++++++++++++++++++++++++- 1 file changed, 70 insertions(+), 1 deletion(-) diff --git a/lib/src/importer/node_package.dart b/lib/src/importer/node_package.dart index 87a3c4928..6e94cfff8 100644 --- a/lib/src/importer/node_package.dart +++ b/lib/src/importer/node_package.dart @@ -126,7 +126,76 @@ class NodePackageImporterInternal extends Importer { /// Implementation of `PACKAGE_RESOLVE` from the [Resolution Algorithm /// Specification](https://nodejs.org/api/esm.html#resolution-algorithm-specification). String? _resolvePackageRoot(String packageName, Uri baseURL) { - print(baseURL.toString()); + print("baseURL: " + baseURL.toString()); + var path = baseURL.toString(); + if (path.startsWith(r"\\?\")) { + if (path.startsWith(r"UNC\", 4)) { + path = path.replaceRange(0, 7, r'\'); + } else { + path = path.substring(4); + if (path.length < 3 || + path.codeUnitAt(1) != 0x3A || + path.codeUnitAt(2) != 0x5C) { + throw ArgumentError.value( + path, "path", r"Windows paths with \\?\ prefix must be absolute"); + } + } + } else { + path = path.replaceAll("/", r'\'); + } + print("path: " + path); + const String sep = r'\'; + if (path.length > 1 && path.codeUnitAt(1) == 0x3A) { + // _checkWindowsDriveLetter(path.codeUnitAt(0), true); + if (path.length == 2 || path.codeUnitAt(2) != 0x5C) { + throw ArgumentError.value( + path, "path", "Windows paths with drive letter must be absolute"); + } + // Absolute file://C:/ URI. + var pathSegments = path.split(sep); + if (pathSegments.last.isNotEmpty) { + pathSegments.add(""); // Extra separator at end. + } + print(pathSegments.skip(1)); + // _checkWindowsPathReservedCharacters(pathSegments, true, 1); + // return Uri(scheme: "file", pathSegments: pathSegments); + } + + if (path.startsWith(sep)) { + if (path.startsWith(sep, 1)) { + // Absolute file:// URI with host. + int pathStart = path.indexOf(r'\', 2); + // String hostPart = + // (pathStart < 0) ? path.substring(2) : path.substring(2, pathStart); + String pathPart = (pathStart < 0) ? "" : path.substring(pathStart + 1); + var pathSegments = pathPart.split(sep); + print(pathSegments); + // _checkWindowsPathReservedCharacters(pathSegments, true); + if (pathSegments.last.isNotEmpty) { + pathSegments.add(""); // Extra separator at end. + } + // return Uri(scheme: "file", host: hostPart, pathSegments: pathSegments); + } else { + // Absolute file:// URI. + var pathSegments = path.split(sep); + if (pathSegments.last.isNotEmpty) { + pathSegments.add(""); // Extra separator at end. + } + print(pathSegments); + // _checkWindowsPathReservedCharacters(pathSegments, true); + // return Uri(scheme: "file", pathSegments: pathSegments); + } + } else { + // Relative URI. + var pathSegments = path.split(sep); + print(pathSegments); + // _checkWindowsPathReservedCharacters(pathSegments, true); + if (pathSegments.isNotEmpty && pathSegments.last.isNotEmpty) { + pathSegments.add(""); // Extra separator at end. + } + // return Uri(pathSegments: pathSegments); + } + var baseDirectory = isWindows ? p.dirname(p.fromUri(Uri.directory(baseURL.toString()))) : p.dirname(p.fromUri(baseURL)); From ade6fe26df3123e826718e3ece9ebd4752cbe1bc Mon Sep 17 00:00:00 2001 From: Jonny Gerig Meyer Date: Wed, 20 Dec 2023 14:15:55 -0500 Subject: [PATCH 081/113] Try using baseUrl.path --- lib/src/importer/node_package.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/importer/node_package.dart b/lib/src/importer/node_package.dart index 6e94cfff8..afcba3619 100644 --- a/lib/src/importer/node_package.dart +++ b/lib/src/importer/node_package.dart @@ -127,7 +127,7 @@ class NodePackageImporterInternal extends Importer { /// Specification](https://nodejs.org/api/esm.html#resolution-algorithm-specification). String? _resolvePackageRoot(String packageName, Uri baseURL) { print("baseURL: " + baseURL.toString()); - var path = baseURL.toString(); + var path = baseURL.path; if (path.startsWith(r"\\?\")) { if (path.startsWith(r"UNC\", 4)) { path = path.replaceRange(0, 7, r'\'); @@ -197,7 +197,7 @@ class NodePackageImporterInternal extends Importer { } var baseDirectory = isWindows - ? p.dirname(p.fromUri(Uri.directory(baseURL.toString()))) + ? p.dirname(p.fromUri(Uri.directory(baseURL.path))) : p.dirname(p.fromUri(baseURL)); Uri? recurseUpFrom(String entry) { From 5481ca6a0da5161c82929b2013aa49dd08ab5798 Mon Sep 17 00:00:00 2001 From: Jonny Gerig Meyer Date: Wed, 20 Dec 2023 14:47:25 -0500 Subject: [PATCH 082/113] manual attempt to coerce windows path --- lib/src/importer/node_package.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/importer/node_package.dart b/lib/src/importer/node_package.dart index afcba3619..e22669cda 100644 --- a/lib/src/importer/node_package.dart +++ b/lib/src/importer/node_package.dart @@ -127,7 +127,7 @@ class NodePackageImporterInternal extends Importer { /// Specification](https://nodejs.org/api/esm.html#resolution-algorithm-specification). String? _resolvePackageRoot(String packageName, Uri baseURL) { print("baseURL: " + baseURL.toString()); - var path = baseURL.path; + var path = baseURL.path.substring(1); if (path.startsWith(r"\\?\")) { if (path.startsWith(r"UNC\", 4)) { path = path.replaceRange(0, 7, r'\'); @@ -197,7 +197,7 @@ class NodePackageImporterInternal extends Importer { } var baseDirectory = isWindows - ? p.dirname(p.fromUri(Uri.directory(baseURL.path))) + ? p.dirname(p.fromUri(Uri.directory(baseURL.path.substring(1)))) : p.dirname(p.fromUri(baseURL)); Uri? recurseUpFrom(String entry) { From b106be19bbe98cb89561c00f171fad7ac60cae28 Mon Sep 17 00:00:00 2001 From: Jonny Gerig Meyer Date: Wed, 20 Dec 2023 14:59:07 -0500 Subject: [PATCH 083/113] Try skipping Uri.directory --- lib/src/importer/node_package.dart | 74 +----------------------------- 1 file changed, 1 insertion(+), 73 deletions(-) diff --git a/lib/src/importer/node_package.dart b/lib/src/importer/node_package.dart index e22669cda..ca35d6bb3 100644 --- a/lib/src/importer/node_package.dart +++ b/lib/src/importer/node_package.dart @@ -126,79 +126,7 @@ class NodePackageImporterInternal extends Importer { /// Implementation of `PACKAGE_RESOLVE` from the [Resolution Algorithm /// Specification](https://nodejs.org/api/esm.html#resolution-algorithm-specification). String? _resolvePackageRoot(String packageName, Uri baseURL) { - print("baseURL: " + baseURL.toString()); - var path = baseURL.path.substring(1); - if (path.startsWith(r"\\?\")) { - if (path.startsWith(r"UNC\", 4)) { - path = path.replaceRange(0, 7, r'\'); - } else { - path = path.substring(4); - if (path.length < 3 || - path.codeUnitAt(1) != 0x3A || - path.codeUnitAt(2) != 0x5C) { - throw ArgumentError.value( - path, "path", r"Windows paths with \\?\ prefix must be absolute"); - } - } - } else { - path = path.replaceAll("/", r'\'); - } - print("path: " + path); - const String sep = r'\'; - if (path.length > 1 && path.codeUnitAt(1) == 0x3A) { - // _checkWindowsDriveLetter(path.codeUnitAt(0), true); - if (path.length == 2 || path.codeUnitAt(2) != 0x5C) { - throw ArgumentError.value( - path, "path", "Windows paths with drive letter must be absolute"); - } - // Absolute file://C:/ URI. - var pathSegments = path.split(sep); - if (pathSegments.last.isNotEmpty) { - pathSegments.add(""); // Extra separator at end. - } - print(pathSegments.skip(1)); - // _checkWindowsPathReservedCharacters(pathSegments, true, 1); - // return Uri(scheme: "file", pathSegments: pathSegments); - } - - if (path.startsWith(sep)) { - if (path.startsWith(sep, 1)) { - // Absolute file:// URI with host. - int pathStart = path.indexOf(r'\', 2); - // String hostPart = - // (pathStart < 0) ? path.substring(2) : path.substring(2, pathStart); - String pathPart = (pathStart < 0) ? "" : path.substring(pathStart + 1); - var pathSegments = pathPart.split(sep); - print(pathSegments); - // _checkWindowsPathReservedCharacters(pathSegments, true); - if (pathSegments.last.isNotEmpty) { - pathSegments.add(""); // Extra separator at end. - } - // return Uri(scheme: "file", host: hostPart, pathSegments: pathSegments); - } else { - // Absolute file:// URI. - var pathSegments = path.split(sep); - if (pathSegments.last.isNotEmpty) { - pathSegments.add(""); // Extra separator at end. - } - print(pathSegments); - // _checkWindowsPathReservedCharacters(pathSegments, true); - // return Uri(scheme: "file", pathSegments: pathSegments); - } - } else { - // Relative URI. - var pathSegments = path.split(sep); - print(pathSegments); - // _checkWindowsPathReservedCharacters(pathSegments, true); - if (pathSegments.isNotEmpty && pathSegments.last.isNotEmpty) { - pathSegments.add(""); // Extra separator at end. - } - // return Uri(pathSegments: pathSegments); - } - - var baseDirectory = isWindows - ? p.dirname(p.fromUri(Uri.directory(baseURL.path.substring(1)))) - : p.dirname(p.fromUri(baseURL)); + var baseDirectory = p.dirname(p.fromUri(baseURL)); Uri? recurseUpFrom(String entry) { var potentialPackage = p.join(entry, 'node_modules', packageName); From 0d2f8595df115d8789b9859fd871b31fec0a8c8b Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Wed, 3 Jan 2024 16:09:52 -0500 Subject: [PATCH 084/113] Remove Internal from class --- lib/src/embedded/compilation_dispatcher.dart | 2 +- lib/src/importer/node_package.dart | 4 ++-- lib/src/js/compile.dart | 4 ++-- lib/src/js/legacy.dart | 5 ++--- 4 files changed, 7 insertions(+), 8 deletions(-) diff --git a/lib/src/embedded/compilation_dispatcher.dart b/lib/src/embedded/compilation_dispatcher.dart index c73f87d33..cacd9b0a4 100644 --- a/lib/src/embedded/compilation_dispatcher.dart +++ b/lib/src/embedded/compilation_dispatcher.dart @@ -228,7 +228,7 @@ final class CompilationDispatcher { case InboundMessage_CompileRequest_Importer_Importer.nodePackageImporter: var entryPointUrl = Uri.parse(importer.nodePackageImporter.entryPointPath); - return NodePackageImporterInternal(entryPointUrl); + return NodePackageImporter(entryPointUrl); } } diff --git a/lib/src/importer/node_package.dart b/lib/src/importer/node_package.dart index ca35d6bb3..3d65d795d 100644 --- a/lib/src/importer/node_package.dart +++ b/lib/src/importer/node_package.dart @@ -13,11 +13,11 @@ import '../io.dart'; import 'package:path/path.dart' as p; /// An [Importer] that resolves `pkg:` URLs using the Node resolution algorithm. -class NodePackageImporterInternal extends Importer { +class NodePackageImporter extends Importer { final Uri entryPointURL; /// Creates a Node Package Importer with the associated entry point url - NodePackageImporterInternal(this.entryPointURL); + NodePackageImporter(this.entryPointURL); static const validExtensions = {'.scss', '.sass', '.css'}; diff --git a/lib/src/js/compile.dart b/lib/src/js/compile.dart index 41c051b47..17a679cd3 100644 --- a/lib/src/js/compile.dart +++ b/lib/src/js/compile.dart @@ -200,7 +200,7 @@ AsyncImporter _parseAsyncImporter(Object? importer) { "Please provide an `entryPointPath` to the Node Package Importer.")); } - return NodePackageImporterInternal(Uri.file(entryPointURL)); + return NodePackageImporter(Uri.file(entryPointURL)); } if (importer == null) jsThrow(JsError("Importers may not be null.")); @@ -242,7 +242,7 @@ Importer _parseImporter(Object? importer) { "Please provide an `entryPointPath` to the Node Package Importer.")); } - return NodePackageImporterInternal(Uri.file(entryPointURL)); + return NodePackageImporter(Uri.file(entryPointURL)); } if (importer == null) jsThrow(JsError("Importers may not be null.")); diff --git a/lib/src/js/legacy.dart b/lib/src/js/legacy.dart index deea244c6..344b38940 100644 --- a/lib/src/js/legacy.dart +++ b/lib/src/js/legacy.dart @@ -311,7 +311,7 @@ AsyncImportCache? _parsePackageImportersAsync( } return AsyncImportCache.only( - [NodePackageImporterInternal(Uri.file(entryPointURL))]); + [NodePackageImporter(Uri.file(entryPointURL))]); } return null; } @@ -328,8 +328,7 @@ ImportCache? _parsePackageImporters(RenderOptions options, DateTime start) { "The Node Package Importer cannot determine an entry point." "Please provide an `entryPointPath` to the Node Package Importer.")); } - return ImportCache.only( - [NodePackageImporterInternal(Uri.file(entryPointURL))]); + return ImportCache.only([NodePackageImporter(Uri.file(entryPointURL))]); } return null; } From b686cc134b845344403352014736bf48806f8734 Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Wed, 3 Jan 2024 21:20:22 -0500 Subject: [PATCH 085/113] Progress on path/uri cleanup --- lib/src/embedded/compilation_dispatcher.dart | 7 +- lib/src/importer/node_package.dart | 75 ++++++++++---------- lib/src/js/compile.dart | 12 ++-- lib/src/js/legacy.dart | 13 ++-- 4 files changed, 52 insertions(+), 55 deletions(-) diff --git a/lib/src/embedded/compilation_dispatcher.dart b/lib/src/embedded/compilation_dispatcher.dart index cacd9b0a4..a97f0312d 100644 --- a/lib/src/embedded/compilation_dispatcher.dart +++ b/lib/src/embedded/compilation_dispatcher.dart @@ -11,7 +11,7 @@ import 'package:native_synchronization/mailbox.dart'; import 'package:path/path.dart' as p; import 'package:protobuf/protobuf.dart'; import 'package:sass/sass.dart' as sass; -import 'package:sass/src/importer/node_package.dart'; +import 'package:sass/src/importer/node_package.dart' as npi; import '../value/function.dart'; import '../value/mixin.dart'; @@ -226,9 +226,8 @@ final class CompilationDispatcher { return null; case InboundMessage_CompileRequest_Importer_Importer.nodePackageImporter: - var entryPointUrl = - Uri.parse(importer.nodePackageImporter.entryPointPath); - return NodePackageImporter(entryPointUrl); + return npi.NodePackageImporter( + importer.nodePackageImporter.entryPointPath); } } diff --git a/lib/src/importer/node_package.dart b/lib/src/importer/node_package.dart index 3d65d795d..3eb449f7f 100644 --- a/lib/src/importer/node_package.dart +++ b/lib/src/importer/node_package.dart @@ -14,10 +14,10 @@ import 'package:path/path.dart' as p; /// An [Importer] that resolves `pkg:` URLs using the Node resolution algorithm. class NodePackageImporter extends Importer { - final Uri entryPointURL; + final String _entryPointPath; /// Creates a Node Package Importer with the associated entry point url - NodePackageImporter(this.entryPointURL); + NodePackageImporter(this._entryPointPath); static const validExtensions = {'.scss', '.sass', '.css'}; @@ -42,17 +42,17 @@ class NodePackageImporter extends Importer { throw "pkg: URL $url must not have a query or fragment."; } - var baseURL = - containingUrl?.scheme == 'file' ? containingUrl! : entryPointURL; + var basePath = containingUrl?.scheme == 'file' + ? p.fromUri(containingUrl!) + : _entryPointPath; var (packageName, subpath) = _packageNameAndSubpath(url.path); - var packageRoot = _resolvePackageRoot(packageName, baseURL); + var packageRoot = _resolvePackageRoot(packageName, basePath); if (packageRoot == null) return null; var jsonPath = p.join(packageRoot, 'package.json'); - var jsonFile = p.fromUri(Uri.file(jsonPath)); - var jsonString = readFile(jsonFile); + var jsonString = readFile(jsonPath); Map packageManifest; try { packageManifest = json.decode(jsonString) as Map; @@ -61,11 +61,10 @@ class NodePackageImporter extends Importer { } if (_resolvePackageExports( - Uri.file(packageRoot), subpath, packageManifest, packageName) + packageRoot, subpath, packageManifest, packageName) case var resolved?) { - if (resolved.scheme == 'file' && - validExtensions.contains(p.url.extension(resolved.path))) { - return resolved; + if (validExtensions.contains(p.extension(resolved))) { + return p.toUri(resolved); } else { throw "The export for '${subpath ?? "root"}' in " "'$packageName' resolved to '${resolved.toString()}', " @@ -82,7 +81,7 @@ class NodePackageImporter extends Importer { // If there is a subpath, attempt to resolve the path relative to the // package root, and resolve for file extensions and partials. var relativeSubpath = p.join(packageRoot, subpath); - return FilesystemImporter.cwd.canonicalize(Uri.file(relativeSubpath)); + return FilesystemImporter.cwd.canonicalize(p.toUri(relativeSubpath)); } @override @@ -125,13 +124,13 @@ class NodePackageImporter extends Importer { /// /// Implementation of `PACKAGE_RESOLVE` from the [Resolution Algorithm /// Specification](https://nodejs.org/api/esm.html#resolution-algorithm-specification). - String? _resolvePackageRoot(String packageName, Uri baseURL) { - var baseDirectory = p.dirname(p.fromUri(baseURL)); + String? _resolvePackageRoot(String packageName, String basePath) { + var baseDirectory = p.dirname(basePath); - Uri? recurseUpFrom(String entry) { + String? recurseUpFrom(String entry) { var potentialPackage = p.join(entry, 'node_modules', packageName); - if (dirExists(potentialPackage)) return Uri.directory(potentialPackage); + if (dirExists(potentialPackage)) return potentialPackage; var parent = p.dirname(entry); // prevent infinite recursion @@ -144,9 +143,7 @@ class NodePackageImporter extends Importer { return recurseUpFrom(parent); } - var directory = recurseUpFrom(baseDirectory); - if (directory == null) return null; - return p.fromUri(directory); + return recurseUpFrom(baseDirectory); } /// Takes a string `packageRoot`, which is the root directory for a package, @@ -171,19 +168,20 @@ class NodePackageImporter extends Importer { return null; } - /// Returns a path specified in the `exports` section of package.json. Takes a - /// package.json value `packageManifest`, a directory URL `packageRoot` and a - /// relative URL path `subpath`. `packageName` is used for error reporting - /// only. - Uri? _resolvePackageExports(Uri packageRoot, String? subpath, + /// Returns a file path specified in the `exports` section of package.json. + /// + /// Takes a package.json value `packageManifest`, a directory URL + /// `packageRoot` and a relative URL path `subpath`. `packageName` is used for + /// error reporting only. + String? _resolvePackageExports(String packageRoot, String? subpath, Map packageManifest, String packageName) { if (packageManifest['exports'] == null) return null; var exports = packageManifest['exports'] as Object; var subpathVariants = _exportsToCheck(subpath); if (_nodePackageExportsResolve( packageRoot, subpathVariants, exports, subpath, packageName) - case Uri uri) { - return uri; + case String path) { + return path; } if (subpath != null && p.extension(subpath).isNotEmpty) return null; @@ -191,8 +189,8 @@ class NodePackageImporter extends Importer { var subpathIndexVariants = _exportsToCheck(subpath, addIndex: true); if (_nodePackageExportsResolve( packageRoot, subpathIndexVariants, exports, subpath, packageName) - case Uri uri) { - return uri; + case String path) { + return path; } return null; @@ -204,8 +202,8 @@ class NodePackageImporter extends Importer { /// /// Implementation of `PACKAGE_EXPORTS_RESOLVE` from the [Resolution Algorithm /// Specification](https://nodejs.org/api/esm.html#resolution-algorithm-specification). - Uri? _nodePackageExportsResolve( - Uri packageRoot, + String? _nodePackageExportsResolve( + String packageRoot, List subpathVariants, Object exports, String? subpath, @@ -216,7 +214,7 @@ class NodePackageImporter extends Importer { throw 'Invalid Package Configuration'; } } - Uri? processVariant(String? subpath) { + String? processVariant(String? subpath) { if (subpath == null) { return _getMainExport(exports).andThen((mainExport) => _packageTargetResolve(subpath, mainExport, packageRoot)); @@ -288,7 +286,8 @@ class NodePackageImporter extends Importer { /// /// Implementation of `PACKAGE_TARGET_RESOLVE` from the [Resolution Algorithm /// Specification](https://nodejs.org/api/esm.html#resolution-algorithm-specification). - Uri? _packageTargetResolve(String? subpath, Object exports, Uri packageRoot, + String? _packageTargetResolve( + String? subpath, Object exports, String packageRoot, [String? patternMatch]) { switch (exports) { case String string: @@ -297,10 +296,10 @@ class NodePackageImporter extends Importer { } if (patternMatch != null) { var replaced = string.replaceAll(RegExp(r'\*'), patternMatch); - var path = p.normalize(p.join(p.fromUri(packageRoot), replaced)); - return fileExists(path) ? Uri.parse('$packageRoot/$replaced') : null; + var path = p.normalize(p.join(packageRoot, replaced)); + return fileExists(path) ? path : null; } - return Uri.parse("$packageRoot/$string"); + return p.join(packageRoot, string); case Map map: var conditions = ['sass', 'style', 'default']; for (var (key, value) in map.pairs) { @@ -328,7 +327,7 @@ class NodePackageImporter extends Importer { return null; default: - throw "Invalid 'exports' value in ${p.join(packageRoot.toFilePath(), 'package.json')}"; + throw "Invalid 'exports' value in ${p.join(packageRoot, 'package.json')}"; } } @@ -374,13 +373,13 @@ class NodePackageImporter extends Importer { '$subpath.css', ]); } - var subpathSegments = Uri.parse(subpath).pathSegments; + var subpathSegments = p.toUri(subpath).pathSegments; var basename = subpathSegments.last; var dirPath = subpathSegments.sublist(0, subpathSegments.length - 1); if (!basename.startsWith('_')) { List prefixedPaths = []; for (final path in paths) { - var pathBasename = Uri.parse(path).pathSegments.last; + var pathBasename = p.toUri(path).pathSegments.last; if (dirPath.isEmpty) { prefixedPaths.add('_$pathBasename'); } else { diff --git a/lib/src/js/compile.dart b/lib/src/js/compile.dart index 17a679cd3..9e37d462e 100644 --- a/lib/src/js/compile.dart +++ b/lib/src/js/compile.dart @@ -190,17 +190,17 @@ AsyncImporter _parseAsyncImporter(Object? importer) { jsThrow(JsError( "The Node Package Importer cannot be used without a filesystem.")); } - var entryPointURL = importer.entryPointPath != null + var entryPointPath = importer.entryPointPath != null ? p.join(p.current, importer.entryPointPath) : requireMainFilename; - if (entryPointURL == null) { + if (entryPointPath == null) { jsThrow(JsError( "The Node Package Importer cannot determine an entry point." "Please provide an `entryPointPath` to the Node Package Importer.")); } - return NodePackageImporter(Uri.file(entryPointURL)); + return NodePackageImporter(entryPointPath); } if (importer == null) jsThrow(JsError("Importers may not be null.")); @@ -232,17 +232,17 @@ Importer _parseImporter(Object? importer) { jsThrow(JsError( "The Node Package Importer cannot be used without a filesystem.")); } - var entryPointURL = importer.entryPointPath != null + var entryPointPath = importer.entryPointPath != null ? p.join(p.current, importer.entryPointPath) : requireMainFilename; - if (entryPointURL == null) { + if (entryPointPath == null) { jsThrow(JsError( "The Node Package Importer cannot determine an entry point." "Please provide an `entryPointPath` to the Node Package Importer.")); } - return NodePackageImporter(Uri.file(entryPointURL)); + return NodePackageImporter(entryPointPath); } if (importer == null) jsThrow(JsError("Importers may not be null.")); diff --git a/lib/src/js/legacy.dart b/lib/src/js/legacy.dart index 344b38940..38ea0a664 100644 --- a/lib/src/js/legacy.dart +++ b/lib/src/js/legacy.dart @@ -300,18 +300,17 @@ NodeImporter _parseImporter(RenderOptions options, DateTime start) { AsyncImportCache? _parsePackageImportersAsync( RenderOptions options, DateTime start) { if (options.pkgImporter?.type case 'node') { - var entryPointURL = options.pkgImporter?.entryPointPath != null + var entryPointPath = options.pkgImporter?.entryPointPath != null ? p.join(p.current, options.pkgImporter?.entryPointPath) : requireMainFilename; - if (entryPointURL == null) { + if (entryPointPath == null) { jsThrow(JsError( "The Node Package Importer cannot determine an entry point." "Please provide an `entryPointPath` to the Node Package Importer.")); } - return AsyncImportCache.only( - [NodePackageImporter(Uri.file(entryPointURL))]); + return AsyncImportCache.only([NodePackageImporter(entryPointPath)]); } return null; } @@ -319,16 +318,16 @@ AsyncImportCache? _parsePackageImportersAsync( /// Creates an [ImportCache] for Package Importers. ImportCache? _parsePackageImporters(RenderOptions options, DateTime start) { if (options.pkgImporter?.type case 'node') { - var entryPointURL = options.pkgImporter?.entryPointPath != null + var entryPointPath = options.pkgImporter?.entryPointPath != null ? p.join(p.current, options.pkgImporter?.entryPointPath) : requireMainFilename; - if (entryPointURL == null) { + if (entryPointPath == null) { jsThrow(JsError( "The Node Package Importer cannot determine an entry point." "Please provide an `entryPointPath` to the Node Package Importer.")); } - return ImportCache.only([NodePackageImporter(Uri.file(entryPointURL))]); + return ImportCache.only([NodePackageImporter(entryPointPath)]); } return null; } From 038b2f4f13fde0ed120aee6a6205745b1b146217 Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Wed, 3 Jan 2024 21:39:07 -0500 Subject: [PATCH 086/113] Use paths in _exportsToCheck --- lib/src/importer/node_package.dart | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/src/importer/node_package.dart b/lib/src/importer/node_package.dart index 3eb449f7f..077771c23 100644 --- a/lib/src/importer/node_package.dart +++ b/lib/src/importer/node_package.dart @@ -360,7 +360,7 @@ class NodePackageImporter extends Importer { if (subpath == null && addIndex) { subpath = 'index'; } else if (subpath != null && addIndex) { - subpath = "$subpath/index"; + subpath = p.posix.join(subpath, 'index'); } if (subpath == null) return [null]; @@ -375,15 +375,16 @@ class NodePackageImporter extends Importer { } var subpathSegments = p.toUri(subpath).pathSegments; var basename = subpathSegments.last; - var dirPath = subpathSegments.sublist(0, subpathSegments.length - 1); if (!basename.startsWith('_')) { List prefixedPaths = []; for (final path in paths) { + var dirPath = subpathSegments.sublist(0, subpathSegments.length - 1); var pathBasename = p.toUri(path).pathSegments.last; if (dirPath.isEmpty) { prefixedPaths.add('_$pathBasename'); } else { - prefixedPaths.add('${dirPath.join(p.separator)}/_$pathBasename'); + dirPath.add('_$pathBasename'); + prefixedPaths.add(p.posix.joinAll(dirPath)); } } paths.addAll(prefixedPaths); From 924612acd873d9f321dce6425803ad15c341034b Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Thu, 4 Jan 2024 12:29:47 -0500 Subject: [PATCH 087/113] Update docs --- lib/src/importer/node_package.dart | 60 ++++++++++++++++-------------- 1 file changed, 32 insertions(+), 28 deletions(-) diff --git a/lib/src/importer/node_package.dart b/lib/src/importer/node_package.dart index 077771c23..f932b9a8a 100644 --- a/lib/src/importer/node_package.dart +++ b/lib/src/importer/node_package.dart @@ -14,9 +14,10 @@ import 'package:path/path.dart' as p; /// An [Importer] that resolves `pkg:` URLs using the Node resolution algorithm. class NodePackageImporter extends Importer { + /// The starting path for canonicalizations without a containing URL. final String _entryPointPath; - /// Creates a Node Package Importer with the associated entry point url + /// Creates a Node Package Importer with the associated entry point. NodePackageImporter(this._entryPointPath); static const validExtensions = {'.scss', '.sass', '.css'}; @@ -75,7 +76,8 @@ class NodePackageImporter extends Importer { // then `index` file at package root, resolved for file extensions and // partials. if (subpath == null) { - return _resolvePackageRootValues(packageRoot, packageManifest); + var rootPath = _resolvePackageRootValues(packageRoot, packageManifest); + return rootPath != null ? p.toUri(rootPath) : null; } // If there is a subpath, attempt to resolve the path relative to the @@ -118,9 +120,8 @@ class NodePackageImporter extends Importer { return (name, parts.isNotEmpty ? p.posix.joinAll(parts) : null); } - /// Takes a string, `packageName`, and an absolute URL `baseURL`, and returns - /// an absolute URL to the root directory for the most proximate installed - /// `packageName`. + /// Returns an absolute path to the root directory for the most proximate + /// installed `packageName`. /// /// Implementation of `PACKAGE_RESOLVE` from the [Resolution Algorithm /// Specification](https://nodejs.org/api/esm.html#resolution-algorithm-specification). @@ -146,33 +147,31 @@ class NodePackageImporter extends Importer { return recurseUpFrom(baseDirectory); } - /// Takes a string `packageRoot`, which is the root directory for a package, - /// and `packageManifest`, which is the contents of that package's - /// `package.json` file, and returns a file URL. - Uri? _resolvePackageRootValues( + /// Returns a file path specified by the `sass` or `style` values in a package + /// manifest, or an `index` file relative to the package root. + String? _resolvePackageRootValues( String packageRoot, Map packageManifest) { if (packageManifest['sass'] case String sassValue) { if (validExtensions.contains(p.extension(sassValue))) { - return Uri.file(p.url.join(packageRoot, sassValue)); + return p.url.join(packageRoot, sassValue); } } if (packageManifest['style'] case String styleValue) { if (validExtensions.contains(p.extension(styleValue))) { - return Uri.file(p.url.join(packageRoot, styleValue)); + return p.url.join(packageRoot, styleValue); } } var result = resolveImportPath(p.url.join(packageRoot, 'index')); - if (result != null) return Uri.file(result); + if (result != null) return result; return null; } - /// Returns a file path specified in the `exports` section of package.json. + /// Returns a file path specified by a `subpath` in the `exports` section of + /// package.json. /// - /// Takes a package.json value `packageManifest`, a directory URL - /// `packageRoot` and a relative URL path `subpath`. `packageName` is used for - /// error reporting only. + /// `packageName` is used for error reporting. String? _resolvePackageExports(String packageRoot, String? subpath, Map packageManifest, String packageName) { if (packageManifest['exports'] == null) return null; @@ -196,9 +195,11 @@ class NodePackageImporter extends Importer { return null; } - /// Takes a package.json value `packageManifest`, a directory URL - /// `packageRoot` and a list of relative URL paths `subpathVariants`. It - /// returns a list of all subpaths present in the package manifest exports. + /// Returns the path to one subpath variants, resolved in the `exports` of a + /// package manifest. + /// + /// Throws an error if multiple `subpathVariants` match, and null if none + /// match. /// /// Implementation of `PACKAGE_EXPORTS_RESOLVE` from the [Resolution Algorithm /// Specification](https://nodejs.org/api/esm.html#resolution-algorithm-specification). @@ -280,9 +281,10 @@ class NodePackageImporter extends Importer { return 0; } - /// Recurses through `exports` object to find match for `subpath`, verifying - /// the file exists relative to `packageRoot`. Instances of `*` will be - /// replaced with `patternMatch`. + /// Returns a file path to `subpath`, resolved in the `exports` object. + /// + /// Verifies the file exists relative to `packageRoot`. Instances of `*` + /// will be replaced with `patternMatch`. /// /// Implementation of `PACKAGE_TARGET_RESOLVE` from the [Resolution Algorithm /// Specification](https://nodejs.org/api/esm.html#resolution-algorithm-specification). @@ -331,8 +333,7 @@ class NodePackageImporter extends Importer { } } - /// Given an `exports` object, returns the entry for an export without a - /// subpath. + /// Returns a path to a package's export without a subpath. Object? _getMainExport(Object exports) { Object? parseMap(Map map) { if (!map.keys.any((key) => key.startsWith('.'))) { @@ -352,8 +353,11 @@ class NodePackageImporter extends Importer { }; } - /// Given a string `subpath`, returns a list of all possible variations with - /// extensions and partials. + /// Returns a list of all possible variations of `subpath` with extensions and + /// partials. + /// + /// `subpath` is part of the [bare import specifier], so we use the `/` + /// separator. List _exportsToCheck(String? subpath, {bool addIndex = false}) { var paths = []; @@ -373,13 +377,13 @@ class NodePackageImporter extends Importer { '$subpath.css', ]); } - var subpathSegments = p.toUri(subpath).pathSegments; + var subpathSegments = p.posix.split(subpath); var basename = subpathSegments.last; if (!basename.startsWith('_')) { List prefixedPaths = []; for (final path in paths) { var dirPath = subpathSegments.sublist(0, subpathSegments.length - 1); - var pathBasename = p.toUri(path).pathSegments.last; + var pathBasename = p.posix.split(path).last; if (dirPath.isEmpty) { prefixedPaths.add('_$pathBasename'); } else { From 31b59f40a8612a8a187a78ea6a7e8cddbadc6be1 Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Fri, 5 Jan 2024 10:46:23 -0500 Subject: [PATCH 088/113] Update exportsToCheck, packageNameAndSubpath --- lib/src/importer/node_package.dart | 61 +++++++++++++----------------- 1 file changed, 26 insertions(+), 35 deletions(-) diff --git a/lib/src/importer/node_package.dart b/lib/src/importer/node_package.dart index f932b9a8a..88205759f 100644 --- a/lib/src/importer/node_package.dart +++ b/lib/src/importer/node_package.dart @@ -32,14 +32,11 @@ class NodePackageImporter extends Importer { if (url.hasAuthority) { throw "pkg: URL $url must not have a host, port, username or password."; - } - if (p.isAbsolute(url.path)) { + } else if (p.isAbsolute(url.path)) { throw "pkg: URL $url must not be an absolute path."; - } - if (url.path.isEmpty) { + } else if (url.path.isEmpty) { throw "pkg: URL $url must not have an empty path."; - } - if (url.hasQuery || url.hasFragment) { + } else if (url.hasQuery || url.hasFragment) { throw "pkg: URL $url must not have a query or fragment."; } @@ -96,16 +93,14 @@ class NodePackageImporter extends Importer { /// Because this is a bare import specifier and not a path, we always use `/` /// to avoid invalid values on non-Posix machines. (String, String?) _packageNameAndSubpath(String specifier) { - var parts = p.posix.split(specifier); + var parts = p.url.split(specifier); var name = parts.removeAt(0); if (name.startsWith('.')) { throw "pkg: name $name must not start with a '.'."; - } - if (name.contains('\\')) { + } else if (name.contains('\\')) { throw "pkg: name $name must not contain a '\\'."; - } - if (name.contains('%')) { + } else if (name.contains('%')) { throw "pkg: name $name must not contain a '%'."; } @@ -114,10 +109,10 @@ class NodePackageImporter extends Importer { throw "pkg: name $name is an invalid package name." "Scoped packages, which start with '@', must have a second segment."; } - name = p.posix.join(name, parts.removeAt(0)); + name = p.url.join(name, parts.removeAt(0)); } - - return (name, parts.isNotEmpty ? p.posix.joinAll(parts) : null); + var subpath = parts.isNotEmpty ? p.fromUri(p.url.joinAll(parts)) : null; + return (name, subpath); } /// Returns an absolute path to the root directory for the most proximate @@ -356,19 +351,19 @@ class NodePackageImporter extends Importer { /// Returns a list of all possible variations of `subpath` with extensions and /// partials. /// - /// `subpath` is part of the [bare import specifier], so we use the `/` - /// separator. + /// If there is no subpath, returns a single `null` value, which is used in + /// `_nodePackageExportsResolve` to denote the main package export. List _exportsToCheck(String? subpath, {bool addIndex = false}) { var paths = []; if (subpath == null && addIndex) { subpath = 'index'; } else if (subpath != null && addIndex) { - subpath = p.posix.join(subpath, 'index'); + subpath = p.join(subpath, 'index'); } if (subpath == null) return [null]; - if (['scss', 'sass', 'css'].any((ext) => subpath!.endsWith(ext))) { + if (const {'.scss', '.sass', '.css'}.contains(p.extension(subpath))) { paths.add(subpath); } else { paths.addAll([ @@ -377,22 +372,18 @@ class NodePackageImporter extends Importer { '$subpath.css', ]); } - var subpathSegments = p.posix.split(subpath); - var basename = subpathSegments.last; - if (!basename.startsWith('_')) { - List prefixedPaths = []; - for (final path in paths) { - var dirPath = subpathSegments.sublist(0, subpathSegments.length - 1); - var pathBasename = p.posix.split(path).last; - if (dirPath.isEmpty) { - prefixedPaths.add('_$pathBasename'); - } else { - dirPath.add('_$pathBasename'); - prefixedPaths.add(p.posix.joinAll(dirPath)); - } - } - paths.addAll(prefixedPaths); - } - return paths; + var basename = p.basename(subpath); + var dirname = p.dirname(subpath); + + if (basename.startsWith('_')) return paths; + + return [ + ...paths, + for (var path in paths) + if (dirname == '.') + '_${p.basename(path)}' + else + p.join(dirname, '_${p.basename(path)}') + ]; } } From aaadfc7df8e94eda3293ded26d559ed2b594708e Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Fri, 5 Jan 2024 11:26:22 -0500 Subject: [PATCH 089/113] More review updates --- lib/src/importer/node_package.dart | 47 +++++++++++++++--------------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/lib/src/importer/node_package.dart b/lib/src/importer/node_package.dart index 88205759f..e4ef4a580 100644 --- a/lib/src/importer/node_package.dart +++ b/lib/src/importer/node_package.dart @@ -55,14 +55,14 @@ class NodePackageImporter extends Importer { try { packageManifest = json.decode(jsonString) as Map; } catch (e) { - throw "'package.json' in 'pkg:$packageName' cannot be parsed."; + 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(resolved); + return p.toUri(p.canonicalize(resolved)); } else { throw "The export for '${subpath ?? "root"}' in " "'$packageName' resolved to '${resolved.toString()}', " @@ -74,13 +74,13 @@ class NodePackageImporter extends Importer { // partials. if (subpath == null) { var rootPath = _resolvePackageRootValues(packageRoot, packageManifest); - return rootPath != null ? p.toUri(rootPath) : null; + 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 relativeSubpath = p.join(packageRoot, subpath); - return FilesystemImporter.cwd.canonicalize(p.toUri(relativeSubpath)); + var subpathInRoot = p.join(packageRoot, subpath); + return FilesystemImporter.cwd.canonicalize(p.toUri(subpathInRoot)); } @override @@ -169,12 +169,12 @@ class NodePackageImporter extends Importer { /// `packageName` is used for error reporting. String? _resolvePackageExports(String packageRoot, String? subpath, Map packageManifest, String packageName) { - if (packageManifest['exports'] == null) return null; - var exports = packageManifest['exports'] as Object; + var exports = packageManifest['exports'] as Object?; + if (exports == null) return null; var subpathVariants = _exportsToCheck(subpath); if (_nodePackageExportsResolve( packageRoot, subpathVariants, exports, subpath, packageName) - case String path) { + case var path?) { return path; } @@ -183,7 +183,7 @@ class NodePackageImporter extends Importer { var subpathIndexVariants = _exportsToCheck(subpath, addIndex: true); if (_nodePackageExportsResolve( packageRoot, subpathIndexVariants, exports, subpath, packageName) - case String path) { + case var path?) { return path; } @@ -204,22 +204,24 @@ class NodePackageImporter extends Importer { Object exports, String? subpath, String packageName) { - if (exports is Map) { - if (exports.keys.any((key) => key.startsWith('.')) && - exports.keys.any((key) => !key.startsWith('.'))) { - throw 'Invalid Package Configuration'; - } + if (exports is Map && + exports.keys.any((key) => key.startsWith('.')) && + exports.keys.any((key) => !key.startsWith('.'))) { + throw '`exports` in $packageName can not have both conditions and paths ' + 'at the same level.\n' + 'Found ${exports.keys.map((key) => '"$key"').join(',')} in ' + '${p.join(packageRoot, 'package.json')}'; } - String? processVariant(String? subpath) { - if (subpath == null) { + String? processVariant(String? variant) { + if (variant == null) { return _getMainExport(exports).andThen((mainExport) => - _packageTargetResolve(subpath, mainExport, packageRoot)); + _packageTargetResolve(variant, mainExport, packageRoot)); } if (exports is! Map || exports.keys.every((key) => !key.startsWith('.'))) { return null; } - var matchKey = "./$subpath"; + var matchKey = "./$variant"; if (exports.containsKey(matchKey) && !matchKey.contains('*')) { return _packageTargetResolve( matchKey, exports[matchKey] as Object, packageRoot); @@ -227,7 +229,7 @@ class NodePackageImporter extends Importer { var expansionKeys = [ for (var key in exports.keys) - if (key.split('').where((char) => char == '*').length == 1) key + if ('*'.allMatches(key).length == 1) key ]..sort(_compareExpansionKeys); for (var expansionKey in expansionKeys) { @@ -241,7 +243,7 @@ class NodePackageImporter extends Importer { var patternMatch = matchKey.substring( patternBase.length, matchKey.length - patternTrailer.length); return _packageTargetResolve( - subpath, target, packageRoot, patternMatch); + variant, target, packageRoot, patternMatch); } } @@ -292,15 +294,14 @@ class NodePackageImporter extends Importer { throw "Export '$string' must be a path relative to the package root at '$packageRoot'."; } if (patternMatch != null) { - var replaced = string.replaceAll(RegExp(r'\*'), patternMatch); + var replaced = string.replaceFirst('*', patternMatch); var path = p.normalize(p.join(packageRoot, replaced)); return fileExists(path) ? path : null; } return p.join(packageRoot, string); case Map map: - var conditions = ['sass', 'style', 'default']; for (var (key, value) in map.pairs) { - if (!conditions.contains(key)) continue; + if (!const {'sass', 'style', 'default'}.contains(key)) continue; if (_packageTargetResolve( subpath, value as Object, packageRoot, patternMatch) case var result?) { From f0985f349566f9d6583e3a4e8e186a9f32684d3e Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Fri, 5 Jan 2024 12:18:33 -0500 Subject: [PATCH 090/113] Handle paths in _nodePackageExportsResolve --- lib/src/importer/node_package.dart | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/src/importer/node_package.dart b/lib/src/importer/node_package.dart index e4ef4a580..711d417b2 100644 --- a/lib/src/importer/node_package.dart +++ b/lib/src/importer/node_package.dart @@ -190,7 +190,7 @@ class NodePackageImporter extends Importer { return null; } - /// Returns the path to one subpath variants, resolved in the `exports` of a + /// Returns the path to one subpath variant, resolved in the `exports` of a /// package manifest. /// /// Throws an error if multiple `subpathVariants` match, and null if none @@ -210,7 +210,7 @@ class NodePackageImporter extends Importer { throw '`exports` in $packageName can not have both conditions and paths ' 'at the same level.\n' 'Found ${exports.keys.map((key) => '"$key"').join(',')} in ' - '${p.join(packageRoot, 'package.json')}'; + '${p.join(packageRoot, 'package.json')}.'; } String? processVariant(String? variant) { if (variant == null) { @@ -221,7 +221,7 @@ class NodePackageImporter extends Importer { exports.keys.every((key) => !key.startsWith('.'))) { return null; } - var matchKey = "./$variant"; + var matchKey = "./${p.toUri(variant)}"; if (exports.containsKey(matchKey) && !matchKey.contains('*')) { return _packageTargetResolve( matchKey, exports[matchKey] as Object, packageRoot); @@ -254,7 +254,7 @@ class NodePackageImporter extends Importer { switch (matches) { case [var path]: - return path; + return p.fromUri(path); case [_, _, ...] && var paths: throw "Unable to determine which of multiple potential resolutions " "found for ${subpath ?? 'root'} in $packageName should be used. " From e1329cc98838f2544fdf02ae348439eb2a019f02 Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Fri, 5 Jan 2024 12:27:21 -0500 Subject: [PATCH 091/113] Remove unneeded fromUri --- lib/src/importer/node_package.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/importer/node_package.dart b/lib/src/importer/node_package.dart index 711d417b2..9c19df6fa 100644 --- a/lib/src/importer/node_package.dart +++ b/lib/src/importer/node_package.dart @@ -254,7 +254,7 @@ class NodePackageImporter extends Importer { switch (matches) { case [var path]: - return p.fromUri(path); + return path; case [_, _, ...] && var paths: throw "Unable to determine which of multiple potential resolutions " "found for ${subpath ?? 'root'} in $packageName should be used. " From c3b1c8b0ea952119418f3df93b1a068149333c38 Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Fri, 5 Jan 2024 14:42:47 -0500 Subject: [PATCH 092/113] Review updates --- lib/src/importer/node_package.dart | 56 +++++++++++++----------------- 1 file changed, 25 insertions(+), 31 deletions(-) diff --git a/lib/src/importer/node_package.dart b/lib/src/importer/node_package.dart index 9c19df6fa..61fde0dd1 100644 --- a/lib/src/importer/node_package.dart +++ b/lib/src/importer/node_package.dart @@ -148,17 +148,17 @@ class NodePackageImporter extends Importer { String packageRoot, Map packageManifest) { if (packageManifest['sass'] case String sassValue) { if (validExtensions.contains(p.extension(sassValue))) { - return p.url.join(packageRoot, sassValue); + return p.join(packageRoot, sassValue); } } if (packageManifest['style'] case String styleValue) { if (validExtensions.contains(p.extension(styleValue))) { - return p.url.join(packageRoot, styleValue); + return p.join(packageRoot, styleValue); } } - var result = resolveImportPath(p.url.join(packageRoot, 'index')); + var result = resolveImportPath(p.join(packageRoot, 'index')); if (result != null) return result; return null; } @@ -222,7 +222,9 @@ class NodePackageImporter extends Importer { return null; } var matchKey = "./${p.toUri(variant)}"; - if (exports.containsKey(matchKey) && !matchKey.contains('*')) { + if (exports.containsKey(matchKey) && + exports[matchKey] != null && + !matchKey.contains('*')) { return _packageTargetResolve( matchKey, exports[matchKey] as Object, packageRoot); } @@ -239,7 +241,8 @@ class NodePackageImporter extends Importer { if (patternTrailer.isEmpty || (matchKey.endsWith(patternTrailer) && matchKey.length >= expansionKey.length)) { - var target = exports[expansionKey] as Object; + var target = exports[expansionKey] as Object?; + if (target == null) continue; var patternMatch = matchKey.substring( patternBase.length, matchKey.length - patternTrailer.length); return _packageTargetResolve( @@ -252,16 +255,15 @@ class NodePackageImporter extends Importer { var matches = subpathVariants.map(processVariant).whereNotNull().toList(); - switch (matches) { - case [var path]: - return path; - case [_, _, ...] && var paths: + return switch (matches) { + [var path] => path, + [_, _, ...] && var paths => throw "Unable to determine which of multiple potential resolutions " "found for ${subpath ?? 'root'} in $packageName should be used. " "\n\nFound:\n" - "${paths.join('\n')}"; - } - return null; + "${paths.join('\n')}", + _ => null + }; } /// Implementation of the `PATTERN_KEY_COMPARE` comparator from @@ -289,15 +291,13 @@ class NodePackageImporter extends Importer { String? subpath, Object exports, String packageRoot, [String? patternMatch]) { switch (exports) { + case String string when !string.startsWith('./'): + throw "Export '$string' must be a path relative to the package root at '$packageRoot'."; + case String string when patternMatch != null: + var replaced = string.replaceFirst('*', patternMatch); + var path = p.normalize(p.join(packageRoot, replaced)); + return fileExists(path) ? path : null; case String string: - if (!string.startsWith('./')) { - throw "Export '$string' must be a path relative to the package root at '$packageRoot'."; - } - if (patternMatch != null) { - var replaced = string.replaceFirst('*', patternMatch); - var path = p.normalize(p.join(packageRoot, replaced)); - return fileExists(path) ? path : null; - } return p.join(packageRoot, string); case Map map: for (var (key, value) in map.pairs) { @@ -331,20 +331,14 @@ class NodePackageImporter extends Importer { /// Returns a path to a package's export without a subpath. Object? _getMainExport(Object exports) { - Object? parseMap(Map map) { - if (!map.keys.any((key) => key.startsWith('.'))) { - return map; - } - if (map.containsKey('.')) { - return map['.'] as Object; - } - return null; - } - return switch (exports) { String string => string, List list => list, - Map map => parseMap(map), + Map map + when !map.keys.any((key) => key.startsWith('.')) => + map, + Map map when map.containsKey('.') && map['.'] != null => + map['.'] as Object, _ => null }; } From 15e33b9b2fc0a9848076f2356a1c99680f8198bc Mon Sep 17 00:00:00 2001 From: Jonny Gerig Meyer Date: Fri, 5 Jan 2024 14:46:58 -0500 Subject: [PATCH 093/113] Address review --- lib/src/importer/node_package.dart | 2 +- lib/src/js/compile.dart | 34 +++++++-------------------- lib/src/js/legacy.dart | 22 +++++++---------- lib/src/js/legacy/render_options.dart | 12 +++------- lib/src/js/utils.dart | 11 --------- lib/src/visitor/async_evaluate.dart | 1 + lib/src/visitor/evaluate.dart | 3 ++- 7 files changed, 23 insertions(+), 62 deletions(-) diff --git a/lib/src/importer/node_package.dart b/lib/src/importer/node_package.dart index 61fde0dd1..6e56f3fe1 100644 --- a/lib/src/importer/node_package.dart +++ b/lib/src/importer/node_package.dart @@ -17,7 +17,7 @@ class NodePackageImporter extends Importer { /// The starting path for canonicalizations without a containing URL. final String _entryPointPath; - /// Creates a Node Package Importer with the associated entry point. + /// Creates a Node package importer with the associated entry point. NodePackageImporter(this._entryPointPath); static const validExtensions = {'.scss', '.sass', '.css'}; diff --git a/lib/src/js/compile.dart b/lib/src/js/compile.dart index 9e37d462e..961c33da4 100644 --- a/lib/src/js/compile.dart +++ b/lib/src/js/compile.dart @@ -14,6 +14,7 @@ import '../importer/js_to_dart/async.dart'; import '../importer/js_to_dart/async_file.dart'; import '../importer/js_to_dart/file.dart'; import '../importer/js_to_dart/sync.dart'; +import '../importer/js_node_package.dart'; import '../importer/node_package.dart'; import '../io.dart'; import '../logger/js_to_dart.dart'; @@ -185,20 +186,13 @@ OutputStyle _parseOutputStyle(String? style) => switch (style) { /// Converts [importer] into an [AsyncImporter] that can be used with /// [compileAsync] or [compileStringAsync]. AsyncImporter _parseAsyncImporter(Object? importer) { - if (importer is NodePackageImporterClass) { - if (isBrowser) { - jsThrow(JsError( - "The Node Package Importer cannot be used without a filesystem.")); - } + if (importer is JSNodePackageImporter) { + if (isBrowser) throwMissingFileSystem(); var entryPointPath = importer.entryPointPath != null ? p.join(p.current, importer.entryPointPath) : requireMainFilename; - if (entryPointPath == null) { - jsThrow(JsError( - "The Node Package Importer cannot determine an entry point." - "Please provide an `entryPointPath` to the Node Package Importer.")); - } + if (entryPointPath == null) throwMissingEntryPointPath(); return NodePackageImporter(entryPointPath); } @@ -227,20 +221,13 @@ AsyncImporter _parseAsyncImporter(Object? importer) { /// Converts [importer] into a synchronous [Importer]. Importer _parseImporter(Object? importer) { - if (importer is NodePackageImporterClass) { - if (isBrowser) { - jsThrow(JsError( - "The Node Package Importer cannot be used without a filesystem.")); - } + if (importer is JSNodePackageImporter) { + if (isBrowser) throwMissingFileSystem(); var entryPointPath = importer.entryPointPath != null ? p.join(p.current, importer.entryPointPath) : requireMainFilename; - if (entryPointPath == null) { - jsThrow(JsError( - "The Node Package Importer cannot determine an entry point." - "Please provide an `entryPointPath` to the Node Package Importer.")); - } + if (entryPointPath == null) throwMissingEntryPointPath(); return NodePackageImporter(entryPointPath); } @@ -360,17 +347,12 @@ List _parseFunctions(Object? functions, {bool asynch = false}) { return result; } -class NodePackageImporterClass { - final String? entryPointPath; - NodePackageImporterClass(this.entryPointPath); -} - /// The exported `NodePackageImporter` class that can be added to the /// `importers` option to enable loading `pkg:` URLs from `node_modules`. final JSClass nodePackageImporterClass = () { var jsClass = createJSClass( 'sass.NodePackageImporter', (Object self, [String? entryPointPath]) => - NodePackageImporterClass(entryPointPath)); + JSNodePackageImporter(entryPointPath)); return jsClass; }(); diff --git a/lib/src/js/legacy.dart b/lib/src/js/legacy.dart index 38ea0a664..fa750a00c 100644 --- a/lib/src/js/legacy.dart +++ b/lib/src/js/legacy.dart @@ -18,6 +18,7 @@ import '../callable.dart'; import '../compile.dart'; import '../compile_result.dart'; import '../exception.dart'; +import '../importer/js_node_package.dart'; import '../importer/legacy_node.dart'; import '../io.dart'; import '../logger.dart'; @@ -299,16 +300,12 @@ NodeImporter _parseImporter(RenderOptions options, DateTime start) { /// Creates an [AsyncImportCache] for Package Importers. AsyncImportCache? _parsePackageImportersAsync( RenderOptions options, DateTime start) { - if (options.pkgImporter?.type case 'node') { + if (options.pkgImporter is JSNodePackageImporter) { var entryPointPath = options.pkgImporter?.entryPointPath != null - ? p.join(p.current, options.pkgImporter?.entryPointPath) + ? p.join(p.current, options.pkgImporter!.entryPointPath) : requireMainFilename; - if (entryPointPath == null) { - jsThrow(JsError( - "The Node Package Importer cannot determine an entry point." - "Please provide an `entryPointPath` to the Node Package Importer.")); - } + if (entryPointPath == null) throwMissingEntryPointPath(); return AsyncImportCache.only([NodePackageImporter(entryPointPath)]); } @@ -317,16 +314,13 @@ AsyncImportCache? _parsePackageImportersAsync( /// Creates an [ImportCache] for Package Importers. ImportCache? _parsePackageImporters(RenderOptions options, DateTime start) { - if (options.pkgImporter?.type case 'node') { + if (options.pkgImporter is JSNodePackageImporter) { var entryPointPath = options.pkgImporter?.entryPointPath != null - ? p.join(p.current, options.pkgImporter?.entryPointPath) + ? p.join(p.current, options.pkgImporter!.entryPointPath) : requireMainFilename; - if (entryPointPath == null) { - jsThrow(JsError( - "The Node Package Importer cannot determine an entry point." - "Please provide an `entryPointPath` to the Node Package Importer.")); - } + if (entryPointPath == null) throwMissingEntryPointPath(); + return ImportCache.only([NodePackageImporter(entryPointPath)]); } return null; diff --git a/lib/src/js/legacy/render_options.dart b/lib/src/js/legacy/render_options.dart index 0ca3b4533..eb02d0fe4 100644 --- a/lib/src/js/legacy/render_options.dart +++ b/lib/src/js/legacy/render_options.dart @@ -4,23 +4,17 @@ import 'package:js/js.dart'; +import '../../importer/js_node_package.dart'; import '../logger.dart'; import 'fiber.dart'; -@JS() -@anonymous -class PackageImporterOptions { - external String get type; - external String? get entryPointPath; -} - @JS() @anonymous class RenderOptions { external String? get file; external String? get data; external Object? get importer; - external PackageImporterOptions? get pkgImporter; + external JSNodePackageImporter? get pkgImporter; external Object? get functions; external List? get includePaths; external bool? get indentedSyntax; @@ -44,7 +38,7 @@ class RenderOptions { {String? file, String? data, Object? importer, - PackageImporterOptions? pkgImporter, + JSNodePackageImporter? pkgImporter, Object? functions, List? includePaths, bool? indentedSyntax, diff --git a/lib/src/js/utils.dart b/lib/src/js/utils.dart index 71927ac81..08fdd8f6b 100644 --- a/lib/src/js/utils.dart +++ b/lib/src/js/utils.dart @@ -33,17 +33,6 @@ external JSClass get jsErrorClass; /// Returns whether [value] is a JS Error object. bool isJSError(Object value) => instanceof(value, jsErrorClass); -final _jsEquals = JSFunction('a', 'b', 'return a===b'); - -/// Returns where [a] and [b] are considered equal in JS. -bool jsEquals(Object? a, Object? b) => _jsEquals.call(a, b) == true; - -@JS("Symbol") -class JSSymbol {} - -@JS("Symbol") -external JSSymbol createJSSymbol(); - /// Attaches [trace] to [error] as its stack trace. void attachJsStack(JsError error, StackTrace trace) { // Stack traces in v8 contain the error message itself as well as the stack diff --git a/lib/src/visitor/async_evaluate.dart b/lib/src/visitor/async_evaluate.dart index 110543356..26679cd8e 100644 --- a/lib/src/visitor/async_evaluate.dart +++ b/lib/src/visitor/async_evaluate.dart @@ -1708,6 +1708,7 @@ final class _EvaluateVisitor } } } + if (_nodeImporter != null) { if (await _importLikeNode( url, baseUrl ?? _stylesheet.span.sourceUrl, forImport) diff --git a/lib/src/visitor/evaluate.dart b/lib/src/visitor/evaluate.dart index 27af2f3cf..096ae21fe 100644 --- a/lib/src/visitor/evaluate.dart +++ b/lib/src/visitor/evaluate.dart @@ -5,7 +5,7 @@ // DO NOT EDIT. This file was generated from async_evaluate.dart. // See tool/grind/synchronize.dart for details. // -// Checksum: 3bc9913c25a05f7babfa094fea6426ddd2f00899 +// Checksum: 7351193aa9229e1434c09a2cbc9fa596cd924901 // // ignore_for_file: unused_import @@ -1702,6 +1702,7 @@ final class _EvaluateVisitor } } } + if (_nodeImporter != null) { if (_importLikeNode( url, baseUrl ?? _stylesheet.span.sourceUrl, forImport) From 397b78910dd43f244a7bb28b0fbd35c956a68b3b Mon Sep 17 00:00:00 2001 From: Jonny Gerig Meyer Date: Fri, 5 Jan 2024 14:50:38 -0500 Subject: [PATCH 094/113] Add missing file. --- lib/src/importer/js_node_package.dart | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 lib/src/importer/js_node_package.dart diff --git a/lib/src/importer/js_node_package.dart b/lib/src/importer/js_node_package.dart new file mode 100644 index 000000000..ad237c32c --- /dev/null +++ b/lib/src/importer/js_node_package.dart @@ -0,0 +1,26 @@ +// Copyright 2023 Google Inc. Use of this source code is governed by an +// MIT-style license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +import 'package:node_interop/js.dart'; + +import '../js/utils.dart'; + +/// The JS [NodePackageImporter] class that can be added to the +/// `importers` option to enable loading `pkg:` URLs from `node_modules`. +class JSNodePackageImporter { + final String? entryPointPath; + + JSNodePackageImporter(this.entryPointPath); +} + +Never throwMissingFileSystem() { + jsThrow(JsError( + "The Node package importer cannot be used without a filesystem.")); +} + +Never throwMissingEntryPointPath() { + jsThrow(JsError("The Node package importer cannot determine an entry point " + "because `require.main.filename` is not defined. " + "Please provide an `entryPointPath` to the `NodePackageImporter`.")); +} From 069b2721fd427a495ad2dd695998c22b7ef8bc27 Mon Sep 17 00:00:00 2001 From: Jonny Gerig Meyer Date: Fri, 5 Jan 2024 15:52:21 -0500 Subject: [PATCH 095/113] update copyrights --- lib/src/importer/js_node_package.dart | 2 +- lib/src/importer/node_package.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/importer/js_node_package.dart b/lib/src/importer/js_node_package.dart index ad237c32c..62390e71e 100644 --- a/lib/src/importer/js_node_package.dart +++ b/lib/src/importer/js_node_package.dart @@ -1,4 +1,4 @@ -// Copyright 2023 Google Inc. Use of this source code is governed by an +// Copyright 2024 Google Inc. Use of this source code is governed by an // MIT-style license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT. diff --git a/lib/src/importer/node_package.dart b/lib/src/importer/node_package.dart index 6e56f3fe1..3e2720e79 100644 --- a/lib/src/importer/node_package.dart +++ b/lib/src/importer/node_package.dart @@ -1,4 +1,4 @@ -// Copyright 2023 Google Inc. Use of this source code is governed by an +// Copyright 2024 Google Inc. Use of this source code is governed by an // MIT-style license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT. From aa56d07ccbaf52a0e9be54a4e1fccf4e208fcb79 Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Fri, 5 Jan 2024 17:00:18 -0500 Subject: [PATCH 096/113] Address review --- lib/src/importer/node_package.dart | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/lib/src/importer/node_package.dart b/lib/src/importer/node_package.dart index 3e2720e79..3eb7d4af0 100644 --- a/lib/src/importer/node_package.dart +++ b/lib/src/importer/node_package.dart @@ -280,10 +280,13 @@ class NodePackageImporter extends Importer { return 0; } - /// Returns a file path to `subpath`, resolved in the `exports` object. + /// Returns a file path for `subpath`, as resolved in the `exports` object. /// - /// Verifies the file exists relative to `packageRoot`. Instances of `*` - /// will be replaced with `patternMatch`. + /// Verifies the file exists relative to `packageRoot`. Instances of `*` will + /// be replaced with `patternMatch`. + /// + /// `subpath` and `packageRoot` are native paths, and `patternMatch` is a URL + /// path. /// /// Implementation of `PACKAGE_TARGET_RESOLVE` from the [Resolution Algorithm /// Specification](https://nodejs.org/api/esm.html#resolution-algorithm-specification). @@ -315,9 +318,9 @@ class NodePackageImporter extends Importer { case List array: for (var value in array) { - var result = _packageTargetResolve( - subpath, value as Object, packageRoot, patternMatch); - if (result != null) { + if (_packageTargetResolve( + subpath, value as Object, packageRoot, patternMatch) + case var result?) { return result; } } @@ -325,7 +328,8 @@ class NodePackageImporter extends Importer { return null; default: - throw "Invalid 'exports' value in ${p.join(packageRoot, 'package.json')}"; + throw "Invalid 'exports' value $exports in " + "${p.join(packageRoot, 'package.json')}."; } } From e0c712e52685b1af2d4a90807b6b4e5bd37aa495 Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Sun, 7 Jan 2024 21:01:42 -0500 Subject: [PATCH 097/113] Switch recursive resolvePackateRoot to while --- lib/src/importer/node_package.dart | 24 ++++++------------------ 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/lib/src/importer/node_package.dart b/lib/src/importer/node_package.dart index 3eb7d4af0..ee3f1dd5c 100644 --- a/lib/src/importer/node_package.dart +++ b/lib/src/importer/node_package.dart @@ -120,26 +120,14 @@ class NodePackageImporter extends Importer { /// /// Implementation of `PACKAGE_RESOLVE` from the [Resolution Algorithm /// Specification](https://nodejs.org/api/esm.html#resolution-algorithm-specification). - String? _resolvePackageRoot(String packageName, String basePath) { - var baseDirectory = p.dirname(basePath); - - String? recurseUpFrom(String entry) { - var potentialPackage = p.join(entry, 'node_modules', packageName); - + String? _resolvePackageRoot(String packageName, String baseDirectory) { + while (true) { + baseDirectory = p.dirname(baseDirectory); + var potentialPackage = p.join(baseDirectory, 'node_modules', packageName); if (dirExists(potentialPackage)) return potentialPackage; - var parent = p.dirname(entry); - - // prevent infinite recursion - if (entry == parent) return null; - - var rootLength = isWindows ? 1 : 0; - - if (Uri.directory(parent).pathSegments.length == rootLength) return null; - - return recurseUpFrom(parent); + // baseDirectory has now reached root without finding a match. + if (p.split(baseDirectory).length == 1) return null; } - - return recurseUpFrom(baseDirectory); } /// Returns a file path specified by the `sass` or `style` values in a package From 915d6d936ebc2bdb34e4b3abb34d0b84e5d2aa07 Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Sun, 7 Jan 2024 21:11:38 -0500 Subject: [PATCH 098/113] Differentiate path and directory --- lib/src/importer/node_package.dart | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/src/importer/node_package.dart b/lib/src/importer/node_package.dart index ee3f1dd5c..e795868f9 100644 --- a/lib/src/importer/node_package.dart +++ b/lib/src/importer/node_package.dart @@ -120,9 +120,10 @@ class NodePackageImporter extends Importer { /// /// Implementation of `PACKAGE_RESOLVE` from the [Resolution Algorithm /// Specification](https://nodejs.org/api/esm.html#resolution-algorithm-specification). - String? _resolvePackageRoot(String packageName, String baseDirectory) { + String? _resolvePackageRoot(String packageName, String basePath) { + String? baseDirectory; while (true) { - baseDirectory = p.dirname(baseDirectory); + baseDirectory = p.dirname(baseDirectory ?? basePath); var potentialPackage = p.join(baseDirectory, 'node_modules', packageName); if (dirExists(potentialPackage)) return potentialPackage; // baseDirectory has now reached root without finding a match. From a3ddb064ee023cd5db072981392b73025287dd91 Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Sun, 7 Jan 2024 21:30:16 -0500 Subject: [PATCH 099/113] Update package name handling --- lib/src/importer/node_package.dart | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/lib/src/importer/node_package.dart b/lib/src/importer/node_package.dart index e795868f9..b74f11d7f 100644 --- a/lib/src/importer/node_package.dart +++ b/lib/src/importer/node_package.dart @@ -45,6 +45,13 @@ class NodePackageImporter extends Importer { : _entryPointPath; 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('\\')) { + return null; + } + var packageRoot = _resolvePackageRoot(packageName, basePath); if (packageRoot == null) return null; @@ -94,13 +101,9 @@ class NodePackageImporter extends Importer { /// to avoid invalid values on non-Posix machines. (String, String?) _packageNameAndSubpath(String specifier) { var parts = p.url.split(specifier); - var name = parts.removeAt(0); + var name = p.fromUri(parts.removeAt(0)); - if (name.startsWith('.')) { - throw "pkg: name $name must not start with a '.'."; - } else if (name.contains('\\')) { - throw "pkg: name $name must not contain a '\\'."; - } else if (name.contains('%')) { + if (name.contains('%')) { throw "pkg: name $name must not contain a '%'."; } From 89e4dbaeb0395a007a11ee7823e38b20121c1bbf Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Sun, 7 Jan 2024 21:37:50 -0500 Subject: [PATCH 100/113] Convert URL targets to native paths --- lib/src/importer/node_package.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/importer/node_package.dart b/lib/src/importer/node_package.dart index b74f11d7f..35c76a49c 100644 --- a/lib/src/importer/node_package.dart +++ b/lib/src/importer/node_package.dart @@ -289,11 +289,11 @@ class NodePackageImporter extends Importer { case String string when !string.startsWith('./'): throw "Export '$string' must be a path relative to the package root at '$packageRoot'."; case String string when patternMatch != null: - var replaced = string.replaceFirst('*', patternMatch); + var replaced = p.fromUri(string.replaceFirst('*', patternMatch)); var path = p.normalize(p.join(packageRoot, replaced)); return fileExists(path) ? path : null; case String string: - return p.join(packageRoot, string); + return p.join(packageRoot, p.fromUri(string)); case Map map: for (var (key, value) in map.pairs) { if (!const {'sass', 'style', 'default'}.contains(key)) continue; From e5c177336048b8b97d704005a438e7dd22429066 Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Mon, 8 Jan 2024 12:08:06 -0500 Subject: [PATCH 101/113] Ensure 'as Object' is not null --- lib/src/importer/node_package.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/src/importer/node_package.dart b/lib/src/importer/node_package.dart index 35c76a49c..b625d65c9 100644 --- a/lib/src/importer/node_package.dart +++ b/lib/src/importer/node_package.dart @@ -297,6 +297,7 @@ class NodePackageImporter extends Importer { case Map map: for (var (key, value) in map.pairs) { if (!const {'sass', 'style', 'default'}.contains(key)) continue; + if (value == null) continue; if (_packageTargetResolve( subpath, value as Object, packageRoot, patternMatch) case var result?) { @@ -310,6 +311,7 @@ class NodePackageImporter extends Importer { case List array: for (var value in array) { + if (value == null) continue; if (_packageTargetResolve( subpath, value as Object, packageRoot, patternMatch) case var result?) { From 4820ecc34e996dce578804a8f1acaffceaf686d3 Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Thu, 18 Jan 2024 23:00:23 -0500 Subject: [PATCH 102/113] Update errors and paths --- lib/src/importer/node_package.dart | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/lib/src/importer/node_package.dart b/lib/src/importer/node_package.dart index b625d65c9..91a93787f 100644 --- a/lib/src/importer/node_package.dart +++ b/lib/src/importer/node_package.dart @@ -31,13 +31,13 @@ class NodePackageImporter extends Importer { if (url.scheme != 'pkg') return null; if (url.hasAuthority) { - throw "pkg: URL $url must not have a host, port, username or password."; - } else if (p.isAbsolute(url.path)) { - throw "pkg: URL $url must not be an absolute path."; + 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 "pkg: URL $url must not have an empty path."; + throw "A pkg: URL must not have an empty path."; } else if (url.hasQuery || url.hasFragment) { - throw "pkg: URL $url must not have a query or fragment."; + throw "A pkg: URL must not have a query or fragment."; } var basePath = containingUrl?.scheme == 'file' @@ -48,9 +48,7 @@ class NodePackageImporter extends Importer { // If the package name is not a valid Node package name, return null in case // another importer can handle. - if (packageName.startsWith('.') || packageName.contains('\\')) { - return null; - } + if (packageName.startsWith('.') || packageName.contains('\\')) return null; var packageRoot = _resolvePackageRoot(packageName, basePath); @@ -139,13 +137,13 @@ class NodePackageImporter extends Importer { String? _resolvePackageRootValues( String packageRoot, Map packageManifest) { if (packageManifest['sass'] case String sassValue) { - if (validExtensions.contains(p.extension(sassValue))) { + if (validExtensions.contains(p.url.extension(sassValue))) { return p.join(packageRoot, sassValue); } } if (packageManifest['style'] case String styleValue) { - if (validExtensions.contains(p.extension(styleValue))) { + if (validExtensions.contains(p.url.extension(styleValue))) { return p.join(packageRoot, styleValue); } } @@ -170,7 +168,7 @@ class NodePackageImporter extends Importer { return path; } - if (subpath != null && p.extension(subpath).isNotEmpty) return null; + if (subpath != null && p.url.extension(subpath).isNotEmpty) return null; var subpathIndexVariants = _exportsToCheck(subpath, addIndex: true); if (_nodePackageExportsResolve( @@ -356,7 +354,7 @@ class NodePackageImporter extends Importer { } if (subpath == null) return [null]; - if (const {'.scss', '.sass', '.css'}.contains(p.extension(subpath))) { + if (validExtensions.contains(p.url.extension(subpath))) { paths.add(subpath); } else { paths.addAll([ From 521b1cf70df8a4e67058edfe52e3487d6b86fdd8 Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Thu, 18 Jan 2024 23:26:57 -0500 Subject: [PATCH 103/113] Review cleanup --- lib/src/importer/node_package.dart | 110 ++++++++++++++--------------- 1 file changed, 54 insertions(+), 56 deletions(-) diff --git a/lib/src/importer/node_package.dart b/lib/src/importer/node_package.dart index 91a93787f..a6c1af6f6 100644 --- a/lib/src/importer/node_package.dart +++ b/lib/src/importer/node_package.dart @@ -122,9 +122,9 @@ class NodePackageImporter extends Importer { /// Implementation of `PACKAGE_RESOLVE` from the [Resolution Algorithm /// Specification](https://nodejs.org/api/esm.html#resolution-algorithm-specification). String? _resolvePackageRoot(String packageName, String basePath) { - String? baseDirectory; + String baseDirectory = basePath; while (true) { - baseDirectory = p.dirname(baseDirectory ?? basePath); + baseDirectory = p.dirname(baseDirectory); var potentialPackage = p.join(baseDirectory, 'node_modules', packageName); if (dirExists(potentialPackage)) return potentialPackage; // baseDirectory has now reached root without finding a match. @@ -136,21 +136,18 @@ class NodePackageImporter extends Importer { /// manifest, or an `index` file relative to the package root. String? _resolvePackageRootValues( String packageRoot, Map packageManifest) { - if (packageManifest['sass'] case String sassValue) { - if (validExtensions.contains(p.url.extension(sassValue))) { - return p.join(packageRoot, sassValue); - } + if (packageManifest['sass'] case String sassValue + when validExtensions.contains(p.url.extension(sassValue))) { + return p.join(packageRoot, sassValue); } - if (packageManifest['style'] case String styleValue) { - if (validExtensions.contains(p.url.extension(styleValue))) { - return p.join(packageRoot, styleValue); - } + if (packageManifest['style'] case String styleValue + when validExtensions.contains(p.url.extension(styleValue))) { + return p.join(packageRoot, styleValue); } var result = resolveImportPath(p.join(packageRoot, 'index')); - if (result != null) return result; - return null; + return result; } /// Returns a file path specified by a `subpath` in the `exports` section of @@ -202,57 +199,59 @@ class NodePackageImporter extends Importer { 'Found ${exports.keys.map((key) => '"$key"').join(',')} in ' '${p.join(packageRoot, 'package.json')}.'; } - String? processVariant(String? variant) { - if (variant == null) { - return _getMainExport(exports).andThen((mainExport) => - _packageTargetResolve(variant, mainExport, packageRoot)); - } - if (exports is! Map || - exports.keys.every((key) => !key.startsWith('.'))) { - return null; - } - var matchKey = "./${p.toUri(variant)}"; - if (exports.containsKey(matchKey) && - exports[matchKey] != null && - !matchKey.contains('*')) { - return _packageTargetResolve( - matchKey, exports[matchKey] as Object, packageRoot); - } - var expansionKeys = [ - for (var key in exports.keys) - if ('*'.allMatches(key).length == 1) key - ]..sort(_compareExpansionKeys); - - for (var expansionKey in expansionKeys) { - var [patternBase, patternTrailer] = expansionKey.split('*'); - if (!matchKey.startsWith(patternBase)) continue; - if (matchKey == patternBase) continue; - if (patternTrailer.isEmpty || - (matchKey.endsWith(patternTrailer) && - matchKey.length >= expansionKey.length)) { - var target = exports[expansionKey] as Object?; - if (target == null) continue; - var patternMatch = matchKey.substring( - patternBase.length, matchKey.length - patternTrailer.length); - return _packageTargetResolve( - variant, target, packageRoot, patternMatch); - } - } + var matches = subpathVariants + .map((String? variant) { + if (variant == null) { + return _getMainExport(exports).andThen((mainExport) => + _packageTargetResolve(variant, mainExport, packageRoot)); + } + if (exports is! Map || + exports.keys.every((key) => !key.startsWith('.'))) { + return null; + } + var matchKey = "./${p.toUri(variant)}"; + if (exports.containsKey(matchKey) && + exports[matchKey] != null && + !matchKey.contains('*')) { + return _packageTargetResolve( + matchKey, exports[matchKey] as Object, packageRoot); + } - return null; - } + var expansionKeys = [ + for (var key in exports.keys) + if ('*'.allMatches(key).length == 1) key + ]..sort(_compareExpansionKeys); + + for (var expansionKey in expansionKeys) { + var [patternBase, patternTrailer] = expansionKey.split('*'); + if (!matchKey.startsWith(patternBase)) continue; + if (matchKey == patternBase) continue; + if (patternTrailer.isEmpty || + (matchKey.endsWith(patternTrailer) && + matchKey.length >= expansionKey.length)) { + var target = exports[expansionKey] as Object?; + if (target == null) continue; + var patternMatch = matchKey.substring( + patternBase.length, matchKey.length - patternTrailer.length); + return _packageTargetResolve( + variant, target, packageRoot, patternMatch); + } + } - var matches = subpathVariants.map(processVariant).whereNotNull().toList(); + return null; + }) + .whereNotNull() + .toList(); return switch (matches) { [var path] => path, - [_, _, ...] && var paths => + [] => null, + var paths => throw "Unable to determine which of multiple potential resolutions " "found for ${subpath ?? 'root'} in $packageName should be used. " "\n\nFound:\n" - "${paths.join('\n')}", - _ => null + "${paths.join('\n')}" }; } @@ -333,8 +332,7 @@ class NodePackageImporter extends Importer { Map map when !map.keys.any((key) => key.startsWith('.')) => map, - Map map when map.containsKey('.') && map['.'] != null => - map['.'] as Object, + {'.': var export?} => export, _ => null }; } From 0d4f2c6cd76f27cd5295ad403cdf1217c18c7f39 Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Fri, 19 Jan 2024 09:46:15 -0500 Subject: [PATCH 104/113] Don't reject invalid node package names --- lib/src/importer/node_package.dart | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/lib/src/importer/node_package.dart b/lib/src/importer/node_package.dart index a6c1af6f6..d908c5be6 100644 --- a/lib/src/importer/node_package.dart +++ b/lib/src/importer/node_package.dart @@ -48,7 +48,12 @@ class NodePackageImporter extends Importer { // If the package name is not a valid Node package name, return null in case // another importer can handle. - if (packageName.startsWith('.') || packageName.contains('\\')) return null; + if (packageName.startsWith('.') || + packageName.contains('\\') || + packageName.contains('%') || + packageName.startsWith('@') && !packageName.contains(p.url.separator)) { + return null; + } var packageRoot = _resolvePackageRoot(packageName, basePath); @@ -101,16 +106,8 @@ class NodePackageImporter extends Importer { var parts = p.url.split(specifier); var name = p.fromUri(parts.removeAt(0)); - if (name.contains('%')) { - throw "pkg: name $name must not contain a '%'."; - } - if (name.startsWith('@')) { - if (parts.isEmpty) { - throw "pkg: name $name is an invalid package name." - "Scoped packages, which start with '@', must have a second segment."; - } - name = p.url.join(name, parts.removeAt(0)); + if (parts.isNotEmpty) name = p.url.join(name, parts.removeAt(0)); } var subpath = parts.isNotEmpty ? p.fromUri(p.url.joinAll(parts)) : null; return (name, subpath); From 0434343d417baafb330e91cc2a09ad9501bd0d91 Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Fri, 19 Jan 2024 10:04:32 -0500 Subject: [PATCH 105/113] Check for no entryPointPath or file system in constructor --- lib/src/importer/js_node_package.dart | 15 --------------- lib/src/importer/node_package.dart | 15 +++++++++++++-- lib/src/js/compile.dart | 6 ------ lib/src/js/legacy.dart | 4 ---- 4 files changed, 13 insertions(+), 27 deletions(-) diff --git a/lib/src/importer/js_node_package.dart b/lib/src/importer/js_node_package.dart index 62390e71e..8a56745b5 100644 --- a/lib/src/importer/js_node_package.dart +++ b/lib/src/importer/js_node_package.dart @@ -2,10 +2,6 @@ // MIT-style license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT. -import 'package:node_interop/js.dart'; - -import '../js/utils.dart'; - /// The JS [NodePackageImporter] class that can be added to the /// `importers` option to enable loading `pkg:` URLs from `node_modules`. class JSNodePackageImporter { @@ -13,14 +9,3 @@ class JSNodePackageImporter { JSNodePackageImporter(this.entryPointPath); } - -Never throwMissingFileSystem() { - jsThrow(JsError( - "The Node package importer cannot be used without a filesystem.")); -} - -Never throwMissingEntryPointPath() { - jsThrow(JsError("The Node package importer cannot determine an entry point " - "because `require.main.filename` is not defined. " - "Please provide an `entryPointPath` to the `NodePackageImporter`.")); -} diff --git a/lib/src/importer/node_package.dart b/lib/src/importer/node_package.dart index d908c5be6..d91939cb7 100644 --- a/lib/src/importer/node_package.dart +++ b/lib/src/importer/node_package.dart @@ -2,6 +2,7 @@ // MIT-style license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT. +import 'package:cli_pkg/js.dart'; import 'package:collection/collection.dart'; import 'package:sass/src/util/map.dart'; import 'package:sass/src/util/nullable.dart'; @@ -15,10 +16,20 @@ import 'package:path/path.dart' as p; /// An [Importer] that resolves `pkg:` URLs using the Node resolution algorithm. class NodePackageImporter extends Importer { /// The starting path for canonicalizations without a containing URL. - final String _entryPointPath; + late final String _entryPointPath; /// Creates a Node package importer with the associated entry point. - NodePackageImporter(this._entryPointPath); + NodePackageImporter(String? entryPointPath) { + if (entryPointPath == null) { + throw "The Node package importer cannot determine an entry point " + "because `require.main.filename` is not defined. " + "Please provide an `entryPointPath` to the `NodePackageImporter`."; + } + if (isBrowser) { + throw "The Node package importer cannot be used without a filesystem."; + } + _entryPointPath = entryPointPath; + } static const validExtensions = {'.scss', '.sass', '.css'}; diff --git a/lib/src/js/compile.dart b/lib/src/js/compile.dart index 961c33da4..bb4be5736 100644 --- a/lib/src/js/compile.dart +++ b/lib/src/js/compile.dart @@ -187,13 +187,10 @@ OutputStyle _parseOutputStyle(String? style) => switch (style) { /// [compileAsync] or [compileStringAsync]. AsyncImporter _parseAsyncImporter(Object? importer) { if (importer is JSNodePackageImporter) { - if (isBrowser) throwMissingFileSystem(); var entryPointPath = importer.entryPointPath != null ? p.join(p.current, importer.entryPointPath) : requireMainFilename; - if (entryPointPath == null) throwMissingEntryPointPath(); - return NodePackageImporter(entryPointPath); } if (importer == null) jsThrow(JsError("Importers may not be null.")); @@ -222,13 +219,10 @@ AsyncImporter _parseAsyncImporter(Object? importer) { /// Converts [importer] into a synchronous [Importer]. Importer _parseImporter(Object? importer) { if (importer is JSNodePackageImporter) { - if (isBrowser) throwMissingFileSystem(); var entryPointPath = importer.entryPointPath != null ? p.join(p.current, importer.entryPointPath) : requireMainFilename; - if (entryPointPath == null) throwMissingEntryPointPath(); - return NodePackageImporter(entryPointPath); } diff --git a/lib/src/js/legacy.dart b/lib/src/js/legacy.dart index fa750a00c..95b042908 100644 --- a/lib/src/js/legacy.dart +++ b/lib/src/js/legacy.dart @@ -305,8 +305,6 @@ AsyncImportCache? _parsePackageImportersAsync( ? p.join(p.current, options.pkgImporter!.entryPointPath) : requireMainFilename; - if (entryPointPath == null) throwMissingEntryPointPath(); - return AsyncImportCache.only([NodePackageImporter(entryPointPath)]); } return null; @@ -319,8 +317,6 @@ ImportCache? _parsePackageImporters(RenderOptions options, DateTime start) { ? p.join(p.current, options.pkgImporter!.entryPointPath) : requireMainFilename; - if (entryPointPath == null) throwMissingEntryPointPath(); - return ImportCache.only([NodePackageImporter(entryPointPath)]); } return null; From 1cb845c031cf86703a91214766a856dd194e746f Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Fri, 19 Jan 2024 10:28:35 -0500 Subject: [PATCH 106/113] Private and document valid extensions --- lib/src/importer/node_package.dart | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/lib/src/importer/node_package.dart b/lib/src/importer/node_package.dart index d91939cb7..0de9055be 100644 --- a/lib/src/importer/node_package.dart +++ b/lib/src/importer/node_package.dart @@ -31,7 +31,10 @@ class NodePackageImporter extends Importer { _entryPointPath = entryPointPath; } - static const validExtensions = {'.scss', '.sass', '.css'}; + /// The set of file extensions that Sass can parse. NodePackageImporter will + /// only resolve files with these extenstions, and uses these extensions to + /// check for matches if no extension is provided in the Url to canonicalize. + static const _validExtensions = {'.scss', '.sass', '.css'}; @override bool isNonCanonicalScheme(String scheme) => scheme == 'pkg'; @@ -82,7 +85,7 @@ class NodePackageImporter extends Importer { if (_resolvePackageExports( packageRoot, subpath, packageManifest, packageName) case var resolved?) { - if (validExtensions.contains(p.extension(resolved))) { + if (_validExtensions.contains(p.extension(resolved))) { return p.toUri(p.canonicalize(resolved)); } else { throw "The export for '${subpath ?? "root"}' in " @@ -145,12 +148,12 @@ class NodePackageImporter extends Importer { String? _resolvePackageRootValues( String packageRoot, Map packageManifest) { if (packageManifest['sass'] case String sassValue - when validExtensions.contains(p.url.extension(sassValue))) { + when _validExtensions.contains(p.url.extension(sassValue))) { return p.join(packageRoot, sassValue); } if (packageManifest['style'] case String styleValue - when validExtensions.contains(p.url.extension(styleValue))) { + when _validExtensions.contains(p.url.extension(styleValue))) { return p.join(packageRoot, styleValue); } @@ -360,7 +363,7 @@ class NodePackageImporter extends Importer { } if (subpath == null) return [null]; - if (validExtensions.contains(p.url.extension(subpath))) { + if (_validExtensions.contains(p.url.extension(subpath))) { paths.add(subpath); } else { paths.addAll([ From 125acf351141814789ab7ae3797a48226dd7137f Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Fri, 19 Jan 2024 10:41:14 -0500 Subject: [PATCH 107/113] Move absolute path logic into constructor --- lib/src/importer/node_package.dart | 2 +- lib/src/js/compile.dart | 8 ++------ lib/src/js/legacy.dart | 4 ++-- 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/lib/src/importer/node_package.dart b/lib/src/importer/node_package.dart index 0de9055be..60de7b3f8 100644 --- a/lib/src/importer/node_package.dart +++ b/lib/src/importer/node_package.dart @@ -28,7 +28,7 @@ class NodePackageImporter extends Importer { if (isBrowser) { throw "The Node package importer cannot be used without a filesystem."; } - _entryPointPath = entryPointPath; + _entryPointPath = p.absolute(entryPointPath); } /// The set of file extensions that Sass can parse. NodePackageImporter will diff --git a/lib/src/js/compile.dart b/lib/src/js/compile.dart index bb4be5736..6d9b5feb4 100644 --- a/lib/src/js/compile.dart +++ b/lib/src/js/compile.dart @@ -187,9 +187,7 @@ OutputStyle _parseOutputStyle(String? style) => switch (style) { /// [compileAsync] or [compileStringAsync]. AsyncImporter _parseAsyncImporter(Object? importer) { if (importer is JSNodePackageImporter) { - var entryPointPath = importer.entryPointPath != null - ? p.join(p.current, importer.entryPointPath) - : requireMainFilename; + var entryPointPath = importer.entryPointPath ?? requireMainFilename; return NodePackageImporter(entryPointPath); } @@ -219,9 +217,7 @@ AsyncImporter _parseAsyncImporter(Object? importer) { /// Converts [importer] into a synchronous [Importer]. Importer _parseImporter(Object? importer) { if (importer is JSNodePackageImporter) { - var entryPointPath = importer.entryPointPath != null - ? p.join(p.current, importer.entryPointPath) - : requireMainFilename; + var entryPointPath = importer.entryPointPath ?? requireMainFilename; return NodePackageImporter(entryPointPath); } diff --git a/lib/src/js/legacy.dart b/lib/src/js/legacy.dart index 95b042908..ba5d7c630 100644 --- a/lib/src/js/legacy.dart +++ b/lib/src/js/legacy.dart @@ -302,7 +302,7 @@ AsyncImportCache? _parsePackageImportersAsync( RenderOptions options, DateTime start) { if (options.pkgImporter is JSNodePackageImporter) { var entryPointPath = options.pkgImporter?.entryPointPath != null - ? p.join(p.current, options.pkgImporter!.entryPointPath) + ? options.pkgImporter!.entryPointPath : requireMainFilename; return AsyncImportCache.only([NodePackageImporter(entryPointPath)]); @@ -314,7 +314,7 @@ AsyncImportCache? _parsePackageImportersAsync( ImportCache? _parsePackageImporters(RenderOptions options, DateTime start) { if (options.pkgImporter is JSNodePackageImporter) { var entryPointPath = options.pkgImporter?.entryPointPath != null - ? p.join(p.current, options.pkgImporter!.entryPointPath) + ? options.pkgImporter!.entryPointPath : requireMainFilename; return ImportCache.only([NodePackageImporter(entryPointPath)]); From 0a4aa2fc96c45e4d87d1ec5c533eab029c923bbd Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Fri, 19 Jan 2024 10:44:46 -0500 Subject: [PATCH 108/113] Remove unused import --- lib/src/js/compile.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/src/js/compile.dart b/lib/src/js/compile.dart index 6d9b5feb4..9b027f608 100644 --- a/lib/src/js/compile.dart +++ b/lib/src/js/compile.dart @@ -5,7 +5,6 @@ import 'package:cli_pkg/js.dart'; import 'package:node_interop/js.dart'; import 'package:node_interop/util.dart' hide futureToPromise; -import 'package:path/path.dart' as p; import 'package:term_glyph/term_glyph.dart' as glyph; import '../../sass.dart'; From 8e60938a09515e9909edf9f0dc26ec3b78d77060 Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Fri, 19 Jan 2024 19:04:22 -0500 Subject: [PATCH 109/113] Address review --- lib/src/importer/node_package.dart | 27 +++++++++++++-------------- lib/src/js/compile.dart | 8 ++------ lib/src/js/legacy.dart | 18 ++++++++---------- 3 files changed, 23 insertions(+), 30 deletions(-) diff --git a/lib/src/importer/node_package.dart b/lib/src/importer/node_package.dart index 60de7b3f8..5a16fe746 100644 --- a/lib/src/importer/node_package.dart +++ b/lib/src/importer/node_package.dart @@ -24,18 +24,12 @@ class NodePackageImporter extends Importer { throw "The Node package importer cannot determine an entry point " "because `require.main.filename` is not defined. " "Please provide an `entryPointPath` to the `NodePackageImporter`."; - } - if (isBrowser) { + } else if (isBrowser) { throw "The Node package importer cannot be used without a filesystem."; } _entryPointPath = p.absolute(entryPointPath); } - /// The set of file extensions that Sass can parse. NodePackageImporter will - /// only resolve files with these extenstions, and uses these extensions to - /// check for matches if no extension is provided in the Url to canonicalize. - static const _validExtensions = {'.scss', '.sass', '.css'}; - @override bool isNonCanonicalScheme(String scheme) => scheme == 'pkg'; @@ -65,7 +59,8 @@ class NodePackageImporter extends Importer { if (packageName.startsWith('.') || packageName.contains('\\') || packageName.contains('%') || - packageName.startsWith('@') && !packageName.contains(p.url.separator)) { + (packageName.startsWith('@') && + !packageName.contains(p.url.separator))) { return null; } @@ -133,7 +128,7 @@ class NodePackageImporter extends Importer { /// Implementation of `PACKAGE_RESOLVE` from the [Resolution Algorithm /// Specification](https://nodejs.org/api/esm.html#resolution-algorithm-specification). String? _resolvePackageRoot(String packageName, String basePath) { - String baseDirectory = basePath; + var baseDirectory = basePath; while (true) { baseDirectory = p.dirname(baseDirectory); var potentialPackage = p.join(baseDirectory, 'node_modules', packageName); @@ -150,9 +145,7 @@ class NodePackageImporter extends Importer { if (packageManifest['sass'] case String sassValue when _validExtensions.contains(p.url.extension(sassValue))) { return p.join(packageRoot, sassValue); - } - - if (packageManifest['style'] case String styleValue + } else if (packageManifest['style'] case String styleValue when _validExtensions.contains(p.url.extension(styleValue))) { return p.join(packageRoot, styleValue); } @@ -216,8 +209,7 @@ class NodePackageImporter extends Importer { if (variant == null) { return _getMainExport(exports).andThen((mainExport) => _packageTargetResolve(variant, mainExport, packageRoot)); - } - if (exports is! Map || + } else if (exports is! Map || exports.keys.every((key) => !key.startsWith('.'))) { return null; } @@ -387,3 +379,10 @@ class NodePackageImporter extends Importer { ]; } } + +/// The set of file extensions that Sass can parse. +/// +/// NodePackageImporter will only resolve files with these extenstions, and +/// uses these extensions to check for matches if no extension is provided in +/// the Url to canonicalize. +const _validExtensions = {'.scss', '.sass', '.css'}; diff --git a/lib/src/js/compile.dart b/lib/src/js/compile.dart index 9b027f608..e514944c2 100644 --- a/lib/src/js/compile.dart +++ b/lib/src/js/compile.dart @@ -186,9 +186,7 @@ OutputStyle _parseOutputStyle(String? style) => switch (style) { /// [compileAsync] or [compileStringAsync]. AsyncImporter _parseAsyncImporter(Object? importer) { if (importer is JSNodePackageImporter) { - var entryPointPath = importer.entryPointPath ?? requireMainFilename; - - return NodePackageImporter(entryPointPath); + return NodePackageImporter(importer.entryPointPath ?? requireMainFilename); } if (importer == null) jsThrow(JsError("Importers may not be null.")); @@ -216,9 +214,7 @@ AsyncImporter _parseAsyncImporter(Object? importer) { /// Converts [importer] into a synchronous [Importer]. Importer _parseImporter(Object? importer) { if (importer is JSNodePackageImporter) { - var entryPointPath = importer.entryPointPath ?? requireMainFilename; - - return NodePackageImporter(entryPointPath); + return NodePackageImporter(importer.entryPointPath ?? requireMainFilename); } if (importer == null) jsThrow(JsError("Importers may not be null.")); diff --git a/lib/src/js/legacy.dart b/lib/src/js/legacy.dart index ba5d7c630..bf922bac7 100644 --- a/lib/src/js/legacy.dart +++ b/lib/src/js/legacy.dart @@ -301,11 +301,10 @@ NodeImporter _parseImporter(RenderOptions options, DateTime start) { AsyncImportCache? _parsePackageImportersAsync( RenderOptions options, DateTime start) { if (options.pkgImporter is JSNodePackageImporter) { - var entryPointPath = options.pkgImporter?.entryPointPath != null - ? options.pkgImporter!.entryPointPath - : requireMainFilename; - - return AsyncImportCache.only([NodePackageImporter(entryPointPath)]); + return AsyncImportCache.only([ + NodePackageImporter( + options.pkgImporter?.entryPointPath ?? requireMainFilename) + ]); } return null; } @@ -313,11 +312,10 @@ AsyncImportCache? _parsePackageImportersAsync( /// Creates an [ImportCache] for Package Importers. ImportCache? _parsePackageImporters(RenderOptions options, DateTime start) { if (options.pkgImporter is JSNodePackageImporter) { - var entryPointPath = options.pkgImporter?.entryPointPath != null - ? options.pkgImporter!.entryPointPath - : requireMainFilename; - - return ImportCache.only([NodePackageImporter(entryPointPath)]); + return ImportCache.only([ + NodePackageImporter( + options.pkgImporter?.entryPointPath ?? requireMainFilename) + ]); } return null; } From 7f2132ad741547d18cf2ea265e0c25addd39fd7f Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Fri, 19 Jan 2024 21:48:27 -0500 Subject: [PATCH 110/113] Expose Node Package Importer directly to JS --- lib/src/importer/js_node_package.dart | 11 ----------- lib/src/js/compile.dart | 12 ++++-------- lib/src/js/legacy.dart | 15 ++++----------- lib/src/js/legacy/render_options.dart | 6 +++--- 4 files changed, 11 insertions(+), 33 deletions(-) delete mode 100644 lib/src/importer/js_node_package.dart diff --git a/lib/src/importer/js_node_package.dart b/lib/src/importer/js_node_package.dart deleted file mode 100644 index 8a56745b5..000000000 --- a/lib/src/importer/js_node_package.dart +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright 2024 Google Inc. Use of this source code is governed by an -// MIT-style license that can be found in the LICENSE file or at -// https://opensource.org/licenses/MIT. - -/// The JS [NodePackageImporter] class that can be added to the -/// `importers` option to enable loading `pkg:` URLs from `node_modules`. -class JSNodePackageImporter { - final String? entryPointPath; - - JSNodePackageImporter(this.entryPointPath); -} diff --git a/lib/src/js/compile.dart b/lib/src/js/compile.dart index e514944c2..6f12f9bb7 100644 --- a/lib/src/js/compile.dart +++ b/lib/src/js/compile.dart @@ -13,7 +13,6 @@ import '../importer/js_to_dart/async.dart'; import '../importer/js_to_dart/async_file.dart'; import '../importer/js_to_dart/file.dart'; import '../importer/js_to_dart/sync.dart'; -import '../importer/js_node_package.dart'; import '../importer/node_package.dart'; import '../io.dart'; import '../logger/js_to_dart.dart'; @@ -185,9 +184,8 @@ OutputStyle _parseOutputStyle(String? style) => switch (style) { /// Converts [importer] into an [AsyncImporter] that can be used with /// [compileAsync] or [compileStringAsync]. AsyncImporter _parseAsyncImporter(Object? importer) { - if (importer is JSNodePackageImporter) { - return NodePackageImporter(importer.entryPointPath ?? requireMainFilename); - } + if (importer is NodePackageImporter) return importer; + if (importer == null) jsThrow(JsError("Importers may not be null.")); importer as JSImporter; @@ -213,9 +211,7 @@ AsyncImporter _parseAsyncImporter(Object? importer) { /// Converts [importer] into a synchronous [Importer]. Importer _parseImporter(Object? importer) { - if (importer is JSNodePackageImporter) { - return NodePackageImporter(importer.entryPointPath ?? requireMainFilename); - } + if (importer is NodePackageImporter) return importer; if (importer == null) jsThrow(JsError("Importers may not be null.")); @@ -338,6 +334,6 @@ final JSClass nodePackageImporterClass = () { var jsClass = createJSClass( 'sass.NodePackageImporter', (Object self, [String? entryPointPath]) => - JSNodePackageImporter(entryPointPath)); + NodePackageImporter(entryPointPath ?? requireMainFilename)); return jsClass; }(); diff --git a/lib/src/js/legacy.dart b/lib/src/js/legacy.dart index bf922bac7..ed4ba7584 100644 --- a/lib/src/js/legacy.dart +++ b/lib/src/js/legacy.dart @@ -18,7 +18,6 @@ import '../callable.dart'; import '../compile.dart'; import '../compile_result.dart'; import '../exception.dart'; -import '../importer/js_node_package.dart'; import '../importer/legacy_node.dart'; import '../io.dart'; import '../logger.dart'; @@ -300,22 +299,16 @@ NodeImporter _parseImporter(RenderOptions options, DateTime start) { /// Creates an [AsyncImportCache] for Package Importers. AsyncImportCache? _parsePackageImportersAsync( RenderOptions options, DateTime start) { - if (options.pkgImporter is JSNodePackageImporter) { - return AsyncImportCache.only([ - NodePackageImporter( - options.pkgImporter?.entryPointPath ?? requireMainFilename) - ]); + if (options.pkgImporter is NodePackageImporter) { + return AsyncImportCache.only([options.pkgImporter!]); } return null; } /// Creates an [ImportCache] for Package Importers. ImportCache? _parsePackageImporters(RenderOptions options, DateTime start) { - if (options.pkgImporter is JSNodePackageImporter) { - return ImportCache.only([ - NodePackageImporter( - options.pkgImporter?.entryPointPath ?? requireMainFilename) - ]); + if (options.pkgImporter is NodePackageImporter) { + return ImportCache.only([options.pkgImporter!]); } return null; } diff --git a/lib/src/js/legacy/render_options.dart b/lib/src/js/legacy/render_options.dart index eb02d0fe4..ac8cc61b8 100644 --- a/lib/src/js/legacy/render_options.dart +++ b/lib/src/js/legacy/render_options.dart @@ -4,7 +4,7 @@ import 'package:js/js.dart'; -import '../../importer/js_node_package.dart'; +import '../../importer/node_package.dart'; import '../logger.dart'; import 'fiber.dart'; @@ -14,7 +14,7 @@ class RenderOptions { external String? get file; external String? get data; external Object? get importer; - external JSNodePackageImporter? get pkgImporter; + external NodePackageImporter? get pkgImporter; external Object? get functions; external List? get includePaths; external bool? get indentedSyntax; @@ -38,7 +38,7 @@ class RenderOptions { {String? file, String? data, Object? importer, - JSNodePackageImporter? pkgImporter, + NodePackageImporter? pkgImporter, Object? functions, List? includePaths, bool? indentedSyntax, From c9f9bc99db3aeea5c4b20f0ddcaaff97eb19cb72 Mon Sep 17 00:00:00 2001 From: Jonny Gerig Meyer Date: Wed, 24 Jan 2024 16:54:44 -0500 Subject: [PATCH 111/113] typo --- lib/src/importer/node_package.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/importer/node_package.dart b/lib/src/importer/node_package.dart index 5a16fe746..1a6fdd148 100644 --- a/lib/src/importer/node_package.dart +++ b/lib/src/importer/node_package.dart @@ -382,7 +382,7 @@ class NodePackageImporter extends Importer { /// The set of file extensions that Sass can parse. /// -/// NodePackageImporter will only resolve files with these extenstions, and +/// `NodePackageImporter` will only resolve files with these extensions, and /// uses these extensions to check for matches if no extension is provided in /// the Url to canonicalize. const _validExtensions = {'.scss', '.sass', '.css'}; From 2f878b03b7844440aeb44da192997155f339316d Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Thu, 1 Feb 2024 12:01:50 -0500 Subject: [PATCH 112/113] Update entry point to directory --- lib/src/embedded/compilation_dispatcher.dart | 2 +- lib/src/importer/node_package.dart | 14 +++++++------- lib/src/js/compile.dart | 8 ++++++-- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/lib/src/embedded/compilation_dispatcher.dart b/lib/src/embedded/compilation_dispatcher.dart index 36cb76a6e..356130d84 100644 --- a/lib/src/embedded/compilation_dispatcher.dart +++ b/lib/src/embedded/compilation_dispatcher.dart @@ -230,7 +230,7 @@ final class CompilationDispatcher { case InboundMessage_CompileRequest_Importer_Importer.nodePackageImporter: return npi.NodePackageImporter( - importer.nodePackageImporter.entryPointPath); + importer.nodePackageImporter.entryPointDirectory); } } diff --git a/lib/src/importer/node_package.dart b/lib/src/importer/node_package.dart index 1a6fdd148..685a5de74 100644 --- a/lib/src/importer/node_package.dart +++ b/lib/src/importer/node_package.dart @@ -16,18 +16,18 @@ import 'package:path/path.dart' as p; /// An [Importer] that resolves `pkg:` URLs using the Node resolution algorithm. class NodePackageImporter extends Importer { /// The starting path for canonicalizations without a containing URL. - late final String _entryPointPath; + late final String _entryPointDirectory; /// Creates a Node package importer with the associated entry point. - NodePackageImporter(String? entryPointPath) { - if (entryPointPath == null) { + NodePackageImporter(String? entryPointDirectory) { + if (entryPointDirectory == null) { throw "The Node package importer cannot determine an entry point " "because `require.main.filename` is not defined. " - "Please provide an `entryPointPath` to the `NodePackageImporter`."; + "Please provide an `entryPointDirectory` to the `NodePackageImporter`."; } else if (isBrowser) { throw "The Node package importer cannot be used without a filesystem."; } - _entryPointPath = p.absolute(entryPointPath); + _entryPointDirectory = p.absolute(entryPointDirectory); } @override @@ -50,7 +50,7 @@ class NodePackageImporter extends Importer { var basePath = containingUrl?.scheme == 'file' ? p.fromUri(containingUrl!) - : _entryPointPath; + : _entryPointDirectory; var (packageName, subpath) = _packageNameAndSubpath(url.path); @@ -130,11 +130,11 @@ class NodePackageImporter extends Importer { String? _resolvePackageRoot(String packageName, String basePath) { var baseDirectory = basePath; while (true) { - baseDirectory = p.dirname(baseDirectory); var potentialPackage = p.join(baseDirectory, 'node_modules', packageName); if (dirExists(potentialPackage)) return potentialPackage; // baseDirectory has now reached root without finding a match. if (p.split(baseDirectory).length == 1) return null; + baseDirectory = p.dirname(baseDirectory); } } diff --git a/lib/src/js/compile.dart b/lib/src/js/compile.dart index 6f12f9bb7..0a2c0c9b0 100644 --- a/lib/src/js/compile.dart +++ b/lib/src/js/compile.dart @@ -6,6 +6,7 @@ import 'package:cli_pkg/js.dart'; import 'package:node_interop/js.dart'; import 'package:node_interop/util.dart' hide futureToPromise; import 'package:term_glyph/term_glyph.dart' as glyph; +import 'package:path/path.dart' as p; import '../../sass.dart'; import '../importer/no_op.dart'; @@ -333,7 +334,10 @@ List _parseFunctions(Object? functions, {bool asynch = false}) { final JSClass nodePackageImporterClass = () { var jsClass = createJSClass( 'sass.NodePackageImporter', - (Object self, [String? entryPointPath]) => - NodePackageImporter(entryPointPath ?? requireMainFilename)); + (Object self, [String? entryPointDirectory]) => NodePackageImporter( + entryPointDirectory ?? + (requireMainFilename != null + ? p.dirname(requireMainFilename!) + : null))); return jsClass; }(); From 53472fdb880494924f06d5b0974cfac94b36b433 Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Fri, 2 Feb 2024 11:04:08 -0500 Subject: [PATCH 113/113] Use parent directory of containing URL --- lib/src/importer/node_package.dart | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/lib/src/importer/node_package.dart b/lib/src/importer/node_package.dart index 685a5de74..ff7b51ca7 100644 --- a/lib/src/importer/node_package.dart +++ b/lib/src/importer/node_package.dart @@ -48,8 +48,8 @@ class NodePackageImporter extends Importer { throw "A pkg: URL must not have a query or fragment."; } - var basePath = containingUrl?.scheme == 'file' - ? p.fromUri(containingUrl!) + var baseDirectory = containingUrl?.scheme == 'file' + ? p.dirname(p.fromUri(containingUrl!)) : _entryPointDirectory; var (packageName, subpath) = _packageNameAndSubpath(url.path); @@ -64,7 +64,7 @@ class NodePackageImporter extends Importer { return null; } - var packageRoot = _resolvePackageRoot(packageName, basePath); + var packageRoot = _resolvePackageRoot(packageName, baseDirectory); if (packageRoot == null) return null; var jsonPath = p.join(packageRoot, 'package.json'); @@ -127,8 +127,7 @@ class NodePackageImporter extends Importer { /// /// Implementation of `PACKAGE_RESOLVE` from the [Resolution Algorithm /// Specification](https://nodejs.org/api/esm.html#resolution-algorithm-specification). - String? _resolvePackageRoot(String packageName, String basePath) { - var baseDirectory = basePath; + String? _resolvePackageRoot(String packageName, String baseDirectory) { while (true) { var potentialPackage = p.join(baseDirectory, 'node_modules', packageName); if (dirExists(potentialPackage)) return potentialPackage;