diff --git a/CHANGELOG.md b/CHANGELOG.md index f45e9f6d3..ddf9cbc85 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,14 @@ * Export the `NodePackageImporter` class in ESM mode. +* Allow `NodePackageImporter` to locate a default directory even when the + entrypoint is an ESM module. + +### Dart API + +* Make passing a null argument to `NodePackageImporter()` a static error rather + than just a runtime error. + ### Embedded Sass * In the JS Embedded Host, properly install the musl Linux embedded compiler diff --git a/lib/src/importer/node_package.dart b/lib/src/importer/node_package.dart index ff7b51ca7..5a81baf3b 100644 --- a/lib/src/importer/node_package.dart +++ b/lib/src/importer/node_package.dart @@ -19,12 +19,8 @@ class NodePackageImporter extends Importer { late final String _entryPointDirectory; /// Creates a Node package importer with the associated entry point. - 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 `entryPointDirectory` to the `NodePackageImporter`."; - } else if (isBrowser) { + NodePackageImporter(String entryPointDirectory) { + if (isBrowser) { throw "The Node package importer cannot be used without a filesystem."; } _entryPointDirectory = p.absolute(entryPointDirectory); diff --git a/lib/src/js/compile.dart b/lib/src/js/compile.dart index 086123846..a08806975 100644 --- a/lib/src/js/compile.dart +++ b/lib/src/js/compile.dart @@ -334,10 +334,13 @@ List _parseFunctions(Object? functions, {bool asynch = false}) { final JSClass nodePackageImporterClass = () { var jsClass = createJSClass( 'sass.NodePackageImporter', - (Object self, [String? entryPointDirectory]) => NodePackageImporter( - entryPointDirectory ?? - (requireMainFilename != null - ? p.dirname(requireMainFilename!) - : null))); + (Object self, [String? entrypointDirectory]) => NodePackageImporter( + switch ((entrypointDirectory, entrypointFilename)) { + ((var directory?, _)) => directory, + (_, var filename?) => p.dirname(filename), + _ => throw "The Node package importer cannot determine an entry " + "point because `require.main.filename` is not defined. Please " + "provide an `entryPointDirectory` to the `NodePackageImporter`." + })); return jsClass; }(); diff --git a/lib/src/js/module.dart b/lib/src/js/module.dart new file mode 100644 index 000000000..0723b994e --- /dev/null +++ b/lib/src/js/module.dart @@ -0,0 +1,26 @@ +// 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. + +import 'package:js/js.dart'; + +@JS('nodeModule') +external JSModule get module; + +/// A Dart API for the [`node:module`] module. +/// +/// [`node:module`]: https://nodejs.org/api/module.html#modules-nodemodule-api +@JS() +@anonymous +class JSModule { + /// See https://nodejs.org/api/module.html#modulecreaterequirefilename. + external JSModuleRequire createRequire(String filename); +} + +/// A `require` function returned by `module.createRequire()`. +@JS() +@anonymous +class JSModuleRequire { + /// See https://nodejs.org/api/modules.html#requireresolverequest-options. + external String resolve(String filename); +} diff --git a/lib/src/js/utils.dart b/lib/src/js/utils.dart index 08fdd8f6b..844157676 100644 --- a/lib/src/js/utils.dart +++ b/lib/src/js/utils.dart @@ -5,7 +5,7 @@ import 'dart:js_util'; import 'dart:typed_data'; -import 'package:node_interop/js.dart'; +import 'package:node_interop/node.dart' hide module; import 'package:js/js.dart'; import 'package:js/js_util.dart'; @@ -14,6 +14,7 @@ import '../utils.dart'; import '../value.dart'; import 'array.dart'; import 'function.dart'; +import 'module.dart'; import 'reflection.dart'; import 'url.dart'; @@ -234,6 +235,22 @@ Syntax parseSyntax(String? syntax) => switch (syntax) { _ => jsThrow(JsError('Unknown syntax "$syntax".')) }; -/// The value of require.main.filename -@JS("require.main.filename") -external String? get requireMainFilename; +/// The path to the Node.js entrypoint, if one can be located. +String? get entrypointFilename { + if (_requireMain?.filename case var filename?) { + return filename; + } else if (process.argv case [_, String path]) { + return module.createRequire(path).resolve(path); + } else { + return null; + } +} + +@JS("require.main") +external _RequireMain? get _requireMain; + +@JS() +@anonymous +class _RequireMain { + external String? get filename; +} diff --git a/tool/grind.dart b/tool/grind.dart index c7ab27bf8..95d9b3cc8 100644 --- a/tool/grind.dart +++ b/tool/grind.dart @@ -35,6 +35,8 @@ void main(List args) { pkg.JSRequire("chokidar", target: pkg.JSRequireTarget.cli), pkg.JSRequire("readline", target: pkg.JSRequireTarget.cli), pkg.JSRequire("fs", target: pkg.JSRequireTarget.node), + pkg.JSRequire("module", + target: pkg.JSRequireTarget.node, identifier: 'nodeModule'), pkg.JSRequire("stream", target: pkg.JSRequireTarget.node), pkg.JSRequire("util", target: pkg.JSRequireTarget.node), ];