Skip to content

Commit

Permalink
Implement sass --embedded in pure JS mode
Browse files Browse the repository at this point in the history
  • Loading branch information
ntkme committed Oct 26, 2024
1 parent 7129352 commit d53fcc5
Show file tree
Hide file tree
Showing 13 changed files with 104 additions and 25 deletions.
3 changes: 1 addition & 2 deletions bin/sass.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,7 @@ import 'package:sass/src/io.dart';
import 'package:sass/src/stylesheet_graph.dart';
import 'package:sass/src/utils.dart';
import 'package:sass/src/embedded/executable.dart'
// Never load the embedded protocol when compiling to JS.
if (dart.library.js) 'package:sass/src/embedded/unavailable.dart'
if (dart.library.js) 'package:sass/src/embedded/js/executable.dart'
as embedded;

Future<void> main(List<String> args) async {
Expand Down
8 changes: 6 additions & 2 deletions lib/src/embedded/compilation_dispatcher.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@

import 'dart:convert';
import 'dart:io';
import 'dart:isolate';
import 'dart:isolate' if (dart.library.js) './js/isolate.dart';
import 'dart:typed_data';

import 'package:native_synchronization/mailbox.dart';
import 'package:native_synchronization/mailbox.dart'
if (dart.library.js) './js/mailbox.dart';
import 'package:path/path.dart' as p;
import 'package:protobuf/protobuf.dart';
import 'package:pub_semver/pub_semver.dart';
Expand Down Expand Up @@ -392,3 +393,6 @@ final class CompilationDispatcher {
}
}
}

void spawnCompilationDispatcher(Mailbox mailbox, SendPort sendPort) =>
CompilationDispatcher(mailbox, sendPort).listen();
20 changes: 20 additions & 0 deletions lib/src/embedded/js/executable.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// 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 'dart:js_interop';

import '../compilation_dispatcher.dart';

@JS('sass_embedded')
extension type SassEmbedded._(JSObject o) implements JSObject {
external static void main(JSExportedDartFunction createCompilationDispatcher);
}

void main(List<String> args) {
try {
SassEmbedded.main(spawnCompilationDispatcher.toJS);
} catch (error) {
print(error);
}
}
24 changes: 24 additions & 0 deletions lib/src/embedded/js/isolate.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// 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 'dart:js_interop';
import 'dart:typed_data';

/// MessagePort from JS wrapped as SendPort in Dart
@JS()
extension type SendPort._(JSObject o) implements JSObject {
@JS('postMessage')
external void _postMessage(JSAny message);
void send(Uint8List message) => _postMessage(message.toJS);
}

@JS()
extension type Isolate._(JSObject o) implements JSObject {
static Never exit([SendPort? finalMessagePort, Uint8List? message]) {
if (message != null) {
finalMessagePort?.send(message);
}
throw Error();
}
}
14 changes: 14 additions & 0 deletions lib/src/embedded/js/mailbox.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// 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 'dart:js_interop';
import 'dart:typed_data';

/// SyncMessagePort from JS wrapped as Mailbox in Dart
@JS()
extension type Mailbox._(JSObject o) implements JSObject {
@JS('receiveMessage')
external JSAny _receiveMessage();
Uint8List take() => (_receiveMessage() as JSUint8Array).toDart;
}
10 changes: 0 additions & 10 deletions lib/src/embedded/unavailable.dart

This file was deleted.

12 changes: 7 additions & 5 deletions lib/src/embedded/utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@
// MIT-style license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.

import 'dart:io';
import 'dart:typed_data';

import 'package:protobuf/protobuf.dart';
import 'package:source_span/source_span.dart';
import 'package:stack_trace/stack_trace.dart';
import 'package:term_glyph/term_glyph.dart' as term_glyph;

import '../io.dart';
import '../syntax.dart';
import 'embedded_sass.pb.dart' as proto;
import 'embedded_sass.pb.dart' hide SourceSpan, Syntax;
Expand Down Expand Up @@ -136,15 +136,17 @@ ProtocolError handleError(Object error, StackTrace stackTrace,
{int? messageId}) {
if (error is ProtocolError) {
error.id = messageId ?? errorId;
stderr.write("Host caused ${error.type.name.toLowerCase()} error");
if (error.id != errorId) stderr.write(" with request ${error.id}");
stderr.writeln(": ${error.message}");
var buffer = StringBuffer();
buffer.write("Host caused ${error.type.name.toLowerCase()} error");
if (error.id != errorId) buffer.write(" with request ${error.id}");
buffer.write(": ${error.message}");
printError(buffer.toString());
// PROTOCOL error from https://bit.ly/2poTt90
exitCode = 76; // EX_PROTOCOL
return error;
} else {
var errorMessage = "$error\n${Chain.forTrace(stackTrace)}";
stderr.write("Internal compiler error: $errorMessage");
printError("Internal compiler error: $errorMessage");
exitCode = 70; // EX_SOFTWARE
return ProtocolError()
..type = ProtocolErrorType.INTERNAL
Expand Down
2 changes: 1 addition & 1 deletion lib/src/parse/parser.dart
Original file line number Diff line number Diff line change
Expand Up @@ -651,7 +651,7 @@ class Parser {
var span = scanner.spanFrom(state);
return _interpolationMap == null
? span
: LazyFileSpan(() => _interpolationMap!.mapSpan(span));
: LazyFileSpan(() => _interpolationMap.mapSpan(span));
}

/// Throws an error associated with [span].
Expand Down
2 changes: 1 addition & 1 deletion lib/src/visitor/async_evaluate.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1812,7 +1812,7 @@ final class _EvaluateVisitor
if (result != null) {
isDependency = _inDependency;
} else {
result = await _nodeImporter!.loadAsync(originalUrl, previous, forImport);
result = await _nodeImporter.loadAsync(originalUrl, previous, forImport);
if (result == null) return null;
isDependency = true;
}
Expand Down
4 changes: 2 additions & 2 deletions lib/src/visitor/evaluate.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
// DO NOT EDIT. This file was generated from async_evaluate.dart.
// See tool/grind/synchronize.dart for details.
//
// Checksum: 396c8f169d95c601598b8c3be1f4b948ca22effa
// Checksum: 3986f5db33dd220dcd971a39e8587ca4e52d9a3f
//
// ignore_for_file: unused_import

Expand Down Expand Up @@ -1808,7 +1808,7 @@ final class _EvaluateVisitor
if (result != null) {
isDependency = _inDependency;
} else {
result = _nodeImporter!.load(originalUrl, previous, forImport);
result = _nodeImporter.load(originalUrl, previous, forImport);
if (result == null) return null;
isDependency = true;
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/sass_api/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ description: Additional APIs for Dart Sass.
homepage: https://github.com/sass/dart-sass

environment:
sdk: ">=3.0.0 <4.0.0"
sdk: ">=3.3.0 <4.0.0"

dependencies:
sass: 1.80.5
Expand Down
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ executables:
sass: sass

environment:
sdk: ">=3.0.0 <4.0.0"
sdk: ">=3.3.0 <4.0.0"

dependencies:
args: ^2.0.0
Expand Down
26 changes: 26 additions & 0 deletions tool/grind.dart
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ void main(List<String> args) {
target: pkg.JSRequireTarget.node, identifier: 'nodeModule'),
pkg.JSRequire("stream", target: pkg.JSRequireTarget.node),
pkg.JSRequire("util", target: pkg.JSRequireTarget.node),
pkg.JSRequire("./sass-embedded",
target: pkg.JSRequireTarget.cli, identifier: 'sass_embedded'),
];
pkg.jsModuleMainLibrary.value = "lib/src/js.dart";
pkg.npmPackageJson.fn = () =>
Expand Down Expand Up @@ -130,6 +132,8 @@ void main(List<String> args) {

afterTask("pkg-npm-dev", _addDefaultExport);
afterTask("pkg-npm-release", _addDefaultExport);
afterTask("pkg-npm-dev", _addOptionalSassEmbedded);
afterTask("pkg-npm-release", _addOptionalSassEmbedded);

grind(args);
}
Expand Down Expand Up @@ -302,6 +306,28 @@ function defaultExportDeprecation() {
File("build/npm/sass.node.mjs").writeAsStringSync(buffer.toString());
}

/// After building the NPM package, write a wrapper script to lazily
/// require "sass-embedded/embedded".
void _addOptionalSassEmbedded() {
var buffer = """
const path = require('path');
module.exports = (function () {
try {
return require(path.join(path.dirname(require.resolve('sass-embedded')), 'src', 'embedded', 'index.js'));
} catch (_) {
return {
main: function () {
console.error('`sass --embedded` requires "sass-embedded" package in pure JS mode.');
process.exitCode = 1;
}
}
}
})();
""";
File("build/npm/sass-embedded.js").writeAsStringSync(buffer);
}

/// A regular expression to locate the language repo revision in the Dart Sass
/// Homebrew formula.
final _homebrewLanguageRegExp = RegExp(
Expand Down

0 comments on commit d53fcc5

Please sign in to comment.