Skip to content

Commit d53fcc5

Browse files
committed
Implement sass --embedded in pure JS mode
1 parent 7129352 commit d53fcc5

13 files changed

+104
-25
lines changed

bin/sass.dart

+1-2
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,7 @@ import 'package:sass/src/io.dart';
1818
import 'package:sass/src/stylesheet_graph.dart';
1919
import 'package:sass/src/utils.dart';
2020
import 'package:sass/src/embedded/executable.dart'
21-
// Never load the embedded protocol when compiling to JS.
22-
if (dart.library.js) 'package:sass/src/embedded/unavailable.dart'
21+
if (dart.library.js) 'package:sass/src/embedded/js/executable.dart'
2322
as embedded;
2423

2524
Future<void> main(List<String> args) async {

lib/src/embedded/compilation_dispatcher.dart

+6-2
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@
44

55
import 'dart:convert';
66
import 'dart:io';
7-
import 'dart:isolate';
7+
import 'dart:isolate' if (dart.library.js) './js/isolate.dart';
88
import 'dart:typed_data';
99

10-
import 'package:native_synchronization/mailbox.dart';
10+
import 'package:native_synchronization/mailbox.dart'
11+
if (dart.library.js) './js/mailbox.dart';
1112
import 'package:path/path.dart' as p;
1213
import 'package:protobuf/protobuf.dart';
1314
import 'package:pub_semver/pub_semver.dart';
@@ -392,3 +393,6 @@ final class CompilationDispatcher {
392393
}
393394
}
394395
}
396+
397+
void spawnCompilationDispatcher(Mailbox mailbox, SendPort sendPort) =>
398+
CompilationDispatcher(mailbox, sendPort).listen();

lib/src/embedded/js/executable.dart

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Copyright 2024 Google Inc. Use of this source code is governed by an
2+
// MIT-style license that can be found in the LICENSE file or at
3+
// https://opensource.org/licenses/MIT.
4+
5+
import 'dart:js_interop';
6+
7+
import '../compilation_dispatcher.dart';
8+
9+
@JS('sass_embedded')
10+
extension type SassEmbedded._(JSObject o) implements JSObject {
11+
external static void main(JSExportedDartFunction createCompilationDispatcher);
12+
}
13+
14+
void main(List<String> args) {
15+
try {
16+
SassEmbedded.main(spawnCompilationDispatcher.toJS);
17+
} catch (error) {
18+
print(error);
19+
}
20+
}

lib/src/embedded/js/isolate.dart

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// Copyright 2024 Google Inc. Use of this source code is governed by an
2+
// MIT-style license that can be found in the LICENSE file or at
3+
// https://opensource.org/licenses/MIT.
4+
5+
import 'dart:js_interop';
6+
import 'dart:typed_data';
7+
8+
/// MessagePort from JS wrapped as SendPort in Dart
9+
@JS()
10+
extension type SendPort._(JSObject o) implements JSObject {
11+
@JS('postMessage')
12+
external void _postMessage(JSAny message);
13+
void send(Uint8List message) => _postMessage(message.toJS);
14+
}
15+
16+
@JS()
17+
extension type Isolate._(JSObject o) implements JSObject {
18+
static Never exit([SendPort? finalMessagePort, Uint8List? message]) {
19+
if (message != null) {
20+
finalMessagePort?.send(message);
21+
}
22+
throw Error();
23+
}
24+
}

lib/src/embedded/js/mailbox.dart

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// Copyright 2024 Google Inc. Use of this source code is governed by an
2+
// MIT-style license that can be found in the LICENSE file or at
3+
// https://opensource.org/licenses/MIT.
4+
5+
import 'dart:js_interop';
6+
import 'dart:typed_data';
7+
8+
/// SyncMessagePort from JS wrapped as Mailbox in Dart
9+
@JS()
10+
extension type Mailbox._(JSObject o) implements JSObject {
11+
@JS('receiveMessage')
12+
external JSAny _receiveMessage();
13+
Uint8List take() => (_receiveMessage() as JSUint8Array).toDart;
14+
}

lib/src/embedded/unavailable.dart

-10
This file was deleted.

lib/src/embedded/utils.dart

+7-5
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,14 @@
22
// MIT-style license that can be found in the LICENSE file or at
33
// https://opensource.org/licenses/MIT.
44

5-
import 'dart:io';
65
import 'dart:typed_data';
76

87
import 'package:protobuf/protobuf.dart';
98
import 'package:source_span/source_span.dart';
109
import 'package:stack_trace/stack_trace.dart';
1110
import 'package:term_glyph/term_glyph.dart' as term_glyph;
1211

12+
import '../io.dart';
1313
import '../syntax.dart';
1414
import 'embedded_sass.pb.dart' as proto;
1515
import 'embedded_sass.pb.dart' hide SourceSpan, Syntax;
@@ -136,15 +136,17 @@ ProtocolError handleError(Object error, StackTrace stackTrace,
136136
{int? messageId}) {
137137
if (error is ProtocolError) {
138138
error.id = messageId ?? errorId;
139-
stderr.write("Host caused ${error.type.name.toLowerCase()} error");
140-
if (error.id != errorId) stderr.write(" with request ${error.id}");
141-
stderr.writeln(": ${error.message}");
139+
var buffer = StringBuffer();
140+
buffer.write("Host caused ${error.type.name.toLowerCase()} error");
141+
if (error.id != errorId) buffer.write(" with request ${error.id}");
142+
buffer.write(": ${error.message}");
143+
printError(buffer.toString());
142144
// PROTOCOL error from https://bit.ly/2poTt90
143145
exitCode = 76; // EX_PROTOCOL
144146
return error;
145147
} else {
146148
var errorMessage = "$error\n${Chain.forTrace(stackTrace)}";
147-
stderr.write("Internal compiler error: $errorMessage");
149+
printError("Internal compiler error: $errorMessage");
148150
exitCode = 70; // EX_SOFTWARE
149151
return ProtocolError()
150152
..type = ProtocolErrorType.INTERNAL

lib/src/parse/parser.dart

+1-1
Original file line numberDiff line numberDiff line change
@@ -651,7 +651,7 @@ class Parser {
651651
var span = scanner.spanFrom(state);
652652
return _interpolationMap == null
653653
? span
654-
: LazyFileSpan(() => _interpolationMap!.mapSpan(span));
654+
: LazyFileSpan(() => _interpolationMap.mapSpan(span));
655655
}
656656

657657
/// Throws an error associated with [span].

lib/src/visitor/async_evaluate.dart

+1-1
Original file line numberDiff line numberDiff line change
@@ -1812,7 +1812,7 @@ final class _EvaluateVisitor
18121812
if (result != null) {
18131813
isDependency = _inDependency;
18141814
} else {
1815-
result = await _nodeImporter!.loadAsync(originalUrl, previous, forImport);
1815+
result = await _nodeImporter.loadAsync(originalUrl, previous, forImport);
18161816
if (result == null) return null;
18171817
isDependency = true;
18181818
}

lib/src/visitor/evaluate.dart

+2-2
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
// DO NOT EDIT. This file was generated from async_evaluate.dart.
66
// See tool/grind/synchronize.dart for details.
77
//
8-
// Checksum: 396c8f169d95c601598b8c3be1f4b948ca22effa
8+
// Checksum: 3986f5db33dd220dcd971a39e8587ca4e52d9a3f
99
//
1010
// ignore_for_file: unused_import
1111

@@ -1808,7 +1808,7 @@ final class _EvaluateVisitor
18081808
if (result != null) {
18091809
isDependency = _inDependency;
18101810
} else {
1811-
result = _nodeImporter!.load(originalUrl, previous, forImport);
1811+
result = _nodeImporter.load(originalUrl, previous, forImport);
18121812
if (result == null) return null;
18131813
isDependency = true;
18141814
}

pkg/sass_api/pubspec.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ description: Additional APIs for Dart Sass.
77
homepage: https://github.com/sass/dart-sass
88

99
environment:
10-
sdk: ">=3.0.0 <4.0.0"
10+
sdk: ">=3.3.0 <4.0.0"
1111

1212
dependencies:
1313
sass: 1.80.5

pubspec.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ executables:
88
sass: sass
99

1010
environment:
11-
sdk: ">=3.0.0 <4.0.0"
11+
sdk: ">=3.3.0 <4.0.0"
1212

1313
dependencies:
1414
args: ^2.0.0

tool/grind.dart

+26
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ void main(List<String> args) {
4444
target: pkg.JSRequireTarget.node, identifier: 'nodeModule'),
4545
pkg.JSRequire("stream", target: pkg.JSRequireTarget.node),
4646
pkg.JSRequire("util", target: pkg.JSRequireTarget.node),
47+
pkg.JSRequire("./sass-embedded",
48+
target: pkg.JSRequireTarget.cli, identifier: 'sass_embedded'),
4749
];
4850
pkg.jsModuleMainLibrary.value = "lib/src/js.dart";
4951
pkg.npmPackageJson.fn = () =>
@@ -130,6 +132,8 @@ void main(List<String> args) {
130132

131133
afterTask("pkg-npm-dev", _addDefaultExport);
132134
afterTask("pkg-npm-release", _addDefaultExport);
135+
afterTask("pkg-npm-dev", _addOptionalSassEmbedded);
136+
afterTask("pkg-npm-release", _addOptionalSassEmbedded);
133137

134138
grind(args);
135139
}
@@ -302,6 +306,28 @@ function defaultExportDeprecation() {
302306
File("build/npm/sass.node.mjs").writeAsStringSync(buffer.toString());
303307
}
304308

309+
/// After building the NPM package, write a wrapper script to lazily
310+
/// require "sass-embedded/embedded".
311+
void _addOptionalSassEmbedded() {
312+
var buffer = """
313+
const path = require('path');
314+
315+
module.exports = (function () {
316+
try {
317+
return require(path.join(path.dirname(require.resolve('sass-embedded')), 'src', 'embedded', 'index.js'));
318+
} catch (_) {
319+
return {
320+
main: function () {
321+
console.error('`sass --embedded` requires "sass-embedded" package in pure JS mode.');
322+
process.exitCode = 1;
323+
}
324+
}
325+
}
326+
})();
327+
""";
328+
File("build/npm/sass-embedded.js").writeAsStringSync(buffer);
329+
}
330+
305331
/// A regular expression to locate the language repo revision in the Dart Sass
306332
/// Homebrew formula.
307333
final _homebrewLanguageRegExp = RegExp(

0 commit comments

Comments
 (0)