Skip to content
This repository was archived by the owner on May 15, 2023. It is now read-only.

Commit 1cbb0c5

Browse files
authored
Implement FileImporter (#57)
1 parent 17f1e69 commit 1cbb0c5

File tree

9 files changed

+439
-44
lines changed

9 files changed

+439
-44
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 1.0.0-beta.14
2+
3+
* Support `FileImporter`s.
4+
15
## 1.0.0-beta.13
26

37
* Report a better error message for an empty `CompileRequest.Input.path`.

bin/dart_sass_embedded.dart

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ import 'package:sass_embedded/src/dispatcher.dart';
1212
import 'package:sass_embedded/src/embedded_sass.pb.dart';
1313
import 'package:sass_embedded/src/function_registry.dart';
1414
import 'package:sass_embedded/src/host_callable.dart';
15-
import 'package:sass_embedded/src/importer.dart';
15+
import 'package:sass_embedded/src/importer/file.dart';
16+
import 'package:sass_embedded/src/importer/host.dart';
1617
import 'package:sass_embedded/src/logger.dart';
1718
import 'package:sass_embedded/src/util/length_delimited_transformer.dart';
1819
import 'package:sass_embedded/src/utils.dart';
@@ -125,7 +126,7 @@ void main(List<String> args) {
125126
});
126127
}
127128

128-
/// Converts [importer] into an [Importer].
129+
/// Converts [importer] into a [sass.Importer].
129130
sass.Importer? _decodeImporter(
130131
Dispatcher dispatcher,
131132
InboundMessage_CompileRequest request,
@@ -135,10 +136,10 @@ sass.Importer? _decodeImporter(
135136
return sass.FilesystemImporter(importer.path);
136137

137138
case InboundMessage_CompileRequest_Importer_Importer.importerId:
138-
return Importer(dispatcher, request.id, importer.importerId);
139+
return HostImporter(dispatcher, request.id, importer.importerId);
139140

140141
case InboundMessage_CompileRequest_Importer_Importer.fileImporterId:
141-
throw "CompileRequest.Importer.fileImporterId is not yet supported";
142+
return FileImporter(dispatcher, request.id, importer.fileImporterId);
142143

143144
case InboundMessage_CompileRequest_Importer_Importer.notSet:
144145
return null;

lib/src/dispatcher.dart

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,11 @@ class Dispatcher {
8080
_dispatchResponse(response.id, response);
8181
break;
8282

83+
case InboundMessage_Message.fileImportResponse:
84+
var response = message.fileImportResponse;
85+
_dispatchResponse(response.id, response);
86+
break;
87+
8388
case InboundMessage_Message.functionCallResponse:
8489
var response = message.functionCallResponse;
8590
_dispatchResponse(response.id, response);
@@ -131,6 +136,11 @@ class Dispatcher {
131136
_sendRequest<InboundMessage_ImportResponse>(
132137
OutboundMessage()..importRequest = request);
133138

139+
Future<InboundMessage_FileImportResponse> sendFileImportRequest(
140+
OutboundMessage_FileImportRequest request) =>
141+
_sendRequest<InboundMessage_FileImportResponse>(
142+
OutboundMessage()..fileImportRequest = request);
143+
134144
Future<InboundMessage_FunctionCallResponse> sendFunctionCallRequest(
135145
OutboundMessage_FunctionCallRequest request) =>
136146
_sendRequest<InboundMessage_FunctionCallResponse>(
@@ -165,8 +175,12 @@ class Dispatcher {
165175
/// Throws an error if there's no outstanding request with the given [id] or
166176
/// if that request is expecting a different type of response.
167177
void _dispatchResponse<T extends GeneratedMessage>(int id, T response) {
168-
var completer =
169-
id < _outstandingRequests.length ? _outstandingRequests[id] : null;
178+
Completer<GeneratedMessage>? completer;
179+
if (id < _outstandingRequests.length) {
180+
completer = _outstandingRequests[id];
181+
_outstandingRequests[id] = null;
182+
}
183+
170184
if (completer == null) {
171185
throw paramsError(
172186
"Response ID $id doesn't match any outstanding requests.");

lib/src/importer/base.dart

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// Copyright 2021 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 'package:meta/meta.dart';
6+
import 'package:sass_api/sass_api.dart' as sass;
7+
8+
import '../dispatcher.dart';
9+
import '../embedded_sass.pb.dart' hide SourceSpan;
10+
import '../utils.dart';
11+
12+
/// An abstract base class for importers that communicate with the host in some
13+
/// way.
14+
abstract class ImporterBase extends sass.Importer {
15+
/// The [Dispatcher] to which to send requests.
16+
@protected
17+
final Dispatcher dispatcher;
18+
19+
ImporterBase(this.dispatcher);
20+
21+
/// Parses [url] as a [Uri] and throws an error if it's invalid or relative
22+
/// (including root-relative).
23+
///
24+
/// The [field] name is used in the error message if one is thrown.
25+
@protected
26+
Uri parseAbsoluteUrl(String field, String url) {
27+
Uri parsedUrl;
28+
try {
29+
parsedUrl = Uri.parse(url);
30+
} on FormatException catch (error) {
31+
sendAndThrow(paramsError("$field is invalid: $error"));
32+
}
33+
34+
if (parsedUrl.scheme.isNotEmpty) return parsedUrl;
35+
sendAndThrow(paramsError('$field must be absolute, was "$parsedUrl"'));
36+
}
37+
38+
/// Sends [error] to the remote endpoint, and also throws it so that the Sass
39+
/// compilation fails.
40+
@protected
41+
Never sendAndThrow(ProtocolError error) {
42+
dispatcher.sendError(error);
43+
throw "Protocol error: ${error.message}";
44+
}
45+
}

lib/src/importer/file.dart

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// Copyright 2021 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:cli';
6+
7+
import 'package:sass_api/sass_api.dart' as sass;
8+
9+
import '../dispatcher.dart';
10+
import '../embedded_sass.pb.dart' hide SourceSpan;
11+
import '../utils.dart';
12+
import 'base.dart';
13+
14+
/// A filesystem importer to use for most implementation details of
15+
/// [FileImporter].
16+
///
17+
/// This allows us to avoid duplicating logic between the two importers.
18+
final _filesystemImporter = sass.FilesystemImporter('.');
19+
20+
/// An importer that asks the host to resolve imports in a simplified,
21+
/// file-system-centric way.
22+
class FileImporter extends ImporterBase {
23+
/// The ID of the compilation in which this importer is used.
24+
final int _compilationId;
25+
26+
/// The host-provided ID of the importer to invoke.
27+
final int _importerId;
28+
29+
FileImporter(Dispatcher dispatcher, this._compilationId, this._importerId)
30+
: super(dispatcher);
31+
32+
Uri? canonicalize(Uri url) {
33+
if (url.scheme == 'file') return _filesystemImporter.canonicalize(url);
34+
35+
return waitFor(() async {
36+
var response = await dispatcher
37+
.sendFileImportRequest(OutboundMessage_FileImportRequest()
38+
..compilationId = _compilationId
39+
..importerId = _importerId
40+
..url = url.toString()
41+
..fromImport = fromImport);
42+
43+
switch (response.whichResult()) {
44+
case InboundMessage_FileImportResponse_Result.fileUrl:
45+
var url =
46+
parseAbsoluteUrl("FileImportResponse.file_url", response.fileUrl);
47+
if (url.scheme != 'file') {
48+
sendAndThrow(paramsError(
49+
'FileImportResponse.file_url must be a file: URL, was "$url"'));
50+
}
51+
52+
return _filesystemImporter.canonicalize(url);
53+
54+
case InboundMessage_FileImportResponse_Result.error:
55+
throw response.error;
56+
57+
case InboundMessage_FileImportResponse_Result.notSet:
58+
return null;
59+
}
60+
}());
61+
}
62+
63+
sass.ImporterResult? load(Uri url) => _filesystemImporter.load(url);
64+
65+
String toString() => "FileImporter";
66+
}

lib/src/importer.dart renamed to lib/src/importer/host.dart

Lines changed: 12 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -6,26 +6,25 @@ import 'dart:cli';
66

77
import 'package:sass_api/sass_api.dart' as sass;
88

9-
import 'dispatcher.dart';
10-
import 'embedded_sass.pb.dart' hide SourceSpan;
11-
import 'utils.dart';
9+
import '../dispatcher.dart';
10+
import '../embedded_sass.pb.dart' hide SourceSpan;
11+
import '../utils.dart';
12+
import 'base.dart';
1213

1314
/// An importer that asks the host to resolve imports.
14-
class Importer extends sass.Importer {
15-
/// The [Dispatcher] to which to send requests.
16-
final Dispatcher _dispatcher;
17-
15+
class HostImporter extends ImporterBase {
1816
/// The ID of the compilation in which this importer is used.
1917
final int _compilationId;
2018

2119
/// The host-provided ID of the importer to invoke.
2220
final int _importerId;
2321

24-
Importer(this._dispatcher, this._compilationId, this._importerId);
22+
HostImporter(Dispatcher dispatcher, this._compilationId, this._importerId)
23+
: super(dispatcher);
2524

2625
Uri? canonicalize(Uri url) {
2726
return waitFor(() async {
28-
var response = await _dispatcher
27+
var response = await dispatcher
2928
.sendCanonicalizeRequest(OutboundMessage_CanonicalizeRequest()
3029
..compilationId = _compilationId
3130
..importerId = _importerId
@@ -34,7 +33,7 @@ class Importer extends sass.Importer {
3433

3534
switch (response.whichResult()) {
3635
case InboundMessage_CanonicalizeResponse_Result.url:
37-
return _parseAbsoluteUrl("CanonicalizeResponse.url", response.url);
36+
return parseAbsoluteUrl("CanonicalizeResponse.url", response.url);
3837

3938
case InboundMessage_CanonicalizeResponse_Result.error:
4039
throw response.error;
@@ -48,7 +47,7 @@ class Importer extends sass.Importer {
4847
sass.ImporterResult load(Uri url) {
4948
return waitFor(() async {
5049
var response =
51-
await _dispatcher.sendImportRequest(OutboundMessage_ImportRequest()
50+
await dispatcher.sendImportRequest(OutboundMessage_ImportRequest()
5251
..compilationId = _compilationId
5352
..importerId = _importerId
5453
..url = url.toString());
@@ -58,41 +57,18 @@ class Importer extends sass.Importer {
5857
return sass.ImporterResult(response.success.contents,
5958
sourceMapUrl: response.success.sourceMapUrl.isEmpty
6059
? null
61-
: _parseAbsoluteUrl("ImportResponse.success.source_map_url",
60+
: parseAbsoluteUrl("ImportResponse.success.source_map_url",
6261
response.success.sourceMapUrl),
6362
syntax: syntaxToSyntax(response.success.syntax));
6463

6564
case InboundMessage_ImportResponse_Result.error:
6665
throw response.error;
6766

6867
case InboundMessage_ImportResponse_Result.notSet:
69-
_sendAndThrow(mandatoryError("ImportResponse.result"));
68+
sendAndThrow(mandatoryError("ImportResponse.result"));
7069
}
7170
}());
7271
}
7372

74-
/// Parses [url] as a [Uri] and throws an error if it's invalid or relative
75-
/// (including root-relative).
76-
///
77-
/// The [field] name is used in the error message if one is thrown.
78-
Uri _parseAbsoluteUrl(String field, String url) {
79-
Uri parsedUrl;
80-
try {
81-
parsedUrl = Uri.parse(url);
82-
} on FormatException catch (error) {
83-
_sendAndThrow(paramsError("$field is invalid: $error"));
84-
}
85-
86-
if (parsedUrl.scheme.isNotEmpty) return parsedUrl;
87-
_sendAndThrow(paramsError('$field must be absolute, was "$parsedUrl"'));
88-
}
89-
90-
/// Sends [error] to the remote endpoint, and also throws it so that the Sass
91-
/// compilation fails.
92-
Never _sendAndThrow(ProtocolError error) {
93-
_dispatcher.sendError(error);
94-
throw "Protocol error: ${error.message}";
95-
}
96-
9773
String toString() => "HostImporter";
9874
}

pubspec.yaml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
name: sass_embedded
2-
version: 1.0.0-beta.13
2+
version: 1.0.0-beta.14
33
description: An implementation of the Sass embedded protocol using Dart Sass.
4-
author: Sass Team
54
homepage: https://github.com/sass/dart-sass-embedded
65

76
environment:

0 commit comments

Comments
 (0)