diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e674fd96..411bfe56c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ ### JavaScript API +* Re-enable support for `import sass from 'sass'` when loading the package from + an ESM module in Node.js. However, this syntax is now deprecated; ESM users + should use `import * as sass from 'sass'` instead. + + On the browser and other ESM-only platforms, only `import * as sass from + 'sass'` is supported. + * Properly export the legacy API values `TRUE`, `FALSE`, `NULL`, and `types` from the ECMAScript module API. diff --git a/tool/grind.dart b/tool/grind.dart index 464bbeb52..117e38946 100644 --- a/tool/grind.dart +++ b/tool/grind.dart @@ -105,6 +105,10 @@ void main(List args) { }; pkg.addAllTasks(); + + afterTask("pkg-npm-dev", _addDefaultExport); + afterTask("pkg-npm-release", _addDefaultExport); + grind(args); } @@ -247,3 +251,37 @@ dart run protoc_plugin "\$@" Platform.environment["PATH"]! })); } + +/// After building the NPM package, add default exports to +/// `build/npm/sass.node.mjs`. +/// +/// See sass/dart-sass#2008. +void _addDefaultExport() { + var buffer = StringBuffer(); + buffer.writeln(File("build/npm/sass.node.mjs").readAsStringSync()); + + buffer.writeln(""" +let printedDefaultExportDeprecation = false; +function defaultExportDeprecation() { + if (printedDefaultExportDeprecation) return; + printedDefaultExportDeprecation = true; + console.error( + "`import sass from 'sass'` is deprecated.\\n" + + "Please use `import * as sass from 'sass'` instead."); +} +"""); + + buffer.writeln("export default {"); + for (var export in pkg.jsEsmExports.value!) { + buffer.write(""" + get $export() { + defaultExportDeprecation(); + return _cliPkgExports.$export; + }, +"""); + } + + buffer.writeln("};"); + + File("build/npm/sass.node.mjs").writeAsStringSync(buffer.toString()); +} diff --git a/tool/grind/utils.dart b/tool/grind/utils.dart index 1135865ee..56b9b7458 100644 --- a/tool/grind/utils.dart +++ b/tool/grind/utils.dart @@ -2,6 +2,7 @@ // MIT-style license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT. +import 'dart:async'; import 'dart:convert'; import 'dart:io'; @@ -9,6 +10,9 @@ import 'package:cli_pkg/cli_pkg.dart' as pkg; import 'package:grinder/grinder.dart'; import 'package:path/path.dart' as p; +// Work around the lack of google/grinder.dart#402. +import 'package:grinder/src/singleton.dart'; + /// Options for [run] that tell Git to commit using SassBot's name and email. final sassBotEnvironment = RunOptions(environment: { "GIT_AUTHOR_NAME": pkg.botName.value, @@ -75,3 +79,21 @@ String cloneOrCheckout(String url, String ref, {String? name}) { return path; } + +/// Registers [callback] to run after the task named [taskName]. +/// +/// This must be called after the base [taskName] is registered. +void afterTask(String taskName, FutureOr callback()) { + // This takes advantage of the fact that Grinder's task list is mutable to + // override the existing task with our new one. + var index = grinder.tasks.indexWhere((task) => task.name == taskName); + if (index == -1) fail("There is no task named $taskName."); + + var oldTask = grinder.tasks[index]; + grinder.tasks[index] = GrinderTask(taskName, + description: oldTask.description, + depends: oldTask.depends, taskFunction: (TaskArgs args) async { + await oldTask.execute(context, args); + await callback(); + }); +}