Skip to content

Commit

Permalink
Add support for running in the browser (#1895)
Browse files Browse the repository at this point in the history
Closes #25

Co-authored-by: Jonny Gerig Meyer <jonny@oddbird.net>
Co-authored-by: Natalie Weizenbaum <nweiz@google.com>
  • Loading branch information
3 people authored May 19, 2023
1 parent eb18526 commit cca9464
Show file tree
Hide file tree
Showing 62 changed files with 529 additions and 106 deletions.
2 changes: 1 addition & 1 deletion .github/util/initialize/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ runs:
steps:
- uses: dart-lang/setup-dart@v1
with:
sdk: "${{ inputs.sdk }}"
sdk: "${{ inputs.dart-sdk }}"
architecture: "${{ inputs.architecture }}"

- uses: actions/setup-node@v3
Expand Down
64 changes: 62 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,43 @@ jobs:
run: npm run js-api-spec -- --sassPackage ../embedded-host-node --sassSassRepo ../build/language
working-directory: sass-spec

sass_spec_js_browser:
name: "JS API Tests | Browser | Dart ${{ matrix.dart_channel }}"

strategy:
matrix:
dart_channel: [stable]
fail-fast: false

runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: browser-actions/setup-chrome@v1
- uses: ./.github/util/initialize
with:
dart-sdk: ${{ matrix.dart_channel }}
github-token: ${{ github.token }}
- uses: ./.github/util/sass-spec

- name: Build JS
run: dart run grinder pkg-npm-dev

- name: Install built dependencies
run: npm install
working-directory: build/npm

- name: Check out Sass specification
uses: sass/clone-linked-repo@v1
with:
repo: sass/sass
path: language

- name: Run tests
run: npm run js-api-spec -- --sassSassRepo ../language --sassPackage ../build/npm --browser
working-directory: sass-spec
env:
CHROME_EXECUTABLE: chrome

dart_tests:
name: "Dart tests | Dart ${{ matrix.dart_channel }} | ${{ matrix.os }}"
runs-on: "${{ matrix.os }}"
Expand All @@ -207,7 +244,7 @@ jobs:

- run: dart run grinder pkg-standalone-dev
- name: Run tests
run: dart run test -p vm -x node
run: dart run test -x node

# Unit tests that use Node.js, defined in test/.
#
Expand Down Expand Up @@ -246,7 +283,30 @@ jobs:

- run: dart run grinder before-test
- name: Run tests
run: dart run test -j 2 -t node
run: dart run test -t node -j 2

browser-test:
name: "Browser Tests | Dart ${{ matrix.dart_channel }}"

strategy:
matrix:
dart_channel: [stable]
fail-fast: false

runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: browser-actions/setup-chrome@v1
- uses: ./.github/util/initialize
with:
dart-sdk: ${{ matrix.dart_channel }}
github-token: ${{ github.token }}

- run: dart run grinder before-test
- name: Run tests
run: dart run test -p chrome -j 2
env:
CHROME_EXECUTABLE: chrome

double_check:
name: Double-check
Expand Down
13 changes: 9 additions & 4 deletions bin/sass.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import 'package:sass/src/executable/repl.dart';
import 'package:sass/src/executable/watch.dart';
import 'package:sass/src/import_cache.dart';
import 'package:sass/src/io.dart';
import 'package:sass/src/io.dart' as io;
import 'package:sass/src/logger/deprecation_handling.dart';
import 'package:sass/src/stylesheet_graph.dart';
import 'package:sass/src/utils.dart';
Expand All @@ -32,14 +33,18 @@ Future<void> main(List<String> args) async {
//
// If [trace] is passed, its terse representation is printed after the error.
void printError(String error, StackTrace? stackTrace) {
if (printedError) stderr.writeln();
var buffer = StringBuffer();
if (printedError) buffer.writeln();
printedError = true;
stderr.writeln(error);
buffer.write(error);

if (stackTrace != null) {
stderr.writeln();
stderr.writeln(Trace.from(stackTrace).terse.toString().trimRight());
buffer.writeln();
buffer.writeln();
buffer.write(Trace.from(stackTrace).terse.toString().trimRight());
}

io.printError(buffer);
}

if (args.firstOrNull == '--embedded') {
Expand Down
3 changes: 2 additions & 1 deletion lib/src/async_compile.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import 'compile_result.dart';
import 'deprecation.dart';
import 'importer.dart';
import 'importer/legacy_node.dart';
import 'importer/no_op.dart';
import 'io.dart';
import 'logger.dart';
import 'logger/deprecation_handling.dart';
Expand Down Expand Up @@ -119,7 +120,7 @@ Future<CompileResult> compileStringAsync(String source,
logger,
importCache,
nodeImporter,
importer ?? FilesystemImporter('.'),
importer ?? (isBrowser ? NoOpImporter() : FilesystemImporter('.')),
functions,
style,
useSpaces,
Expand Down
8 changes: 8 additions & 0 deletions lib/src/async_import_cache.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import 'package:tuple/tuple.dart';
import 'ast/sass.dart';
import 'deprecation.dart';
import 'importer.dart';
import 'importer/no_op.dart';
import 'importer/utils.dart';
import 'io.dart';
import 'logger.dart';
Expand Down Expand Up @@ -95,6 +96,7 @@ class AsyncImportCache {
static List<AsyncImporter> _toImporters(Iterable<AsyncImporter>? importers,
Iterable<String>? loadPaths, PackageConfig? packageConfig) {
var sassPath = getEnvironmentVariable('SASS_PATH');
if (isBrowser) return [...?importers];
return [
...?importers,
if (loadPaths != null)
Expand Down Expand Up @@ -122,6 +124,12 @@ class AsyncImportCache {
{AsyncImporter? baseImporter,
Uri? baseUrl,
bool forImport = false}) async {
if (isBrowser &&
(baseImporter == null || baseImporter is NoOpImporter) &&
_importers.isEmpty) {
throw "Custom importers are required to load stylesheets when compiling in the browser.";
}

if (baseImporter != null) {
var relativeResult = await putIfAbsentAsync(_relativeCanonicalizeCache,
Tuple4(url, forImport, baseImporter, baseUrl), () async {
Expand Down
5 changes: 3 additions & 2 deletions lib/src/compile.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_compile.dart.
// See tool/grind/synchronize.dart for details.
//
// Checksum: 628fbfe8a6717cca332dd646eeda2260dd3e30c6
// Checksum: bac7360553f772bbf8243cca78f4f63e4bdf2755
//
// ignore_for_file: unused_import

Expand All @@ -22,6 +22,7 @@ import 'compile_result.dart';
import 'deprecation.dart';
import 'importer.dart';
import 'importer/legacy_node.dart';
import 'importer/no_op.dart';
import 'io.dart';
import 'logger.dart';
import 'logger/deprecation_handling.dart';
Expand Down Expand Up @@ -128,7 +129,7 @@ CompileResult compileString(String source,
logger,
importCache,
nodeImporter,
importer ?? FilesystemImporter('.'),
importer ?? (isBrowser ? NoOpImporter() : FilesystemImporter('.')),
functions,
style,
useSpaces,
Expand Down
2 changes: 1 addition & 1 deletion lib/src/embedded/unavailable.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@
import '../io.dart';

void main(List<String> args) async {
stderr.writeln('sass --embedded is unavailable in pure JS mode.');
printError('sass --embedded is unavailable in pure JS mode.');
exitCode = 1;
}
10 changes: 6 additions & 4 deletions lib/src/executable/watch.dart
Original file line number Diff line number Diff line change
Expand Up @@ -112,14 +112,16 @@ class _Watcher {
/// Prints [message] to standard error, with [stackTrace] if [_options.trace]
/// is set.
void _printError(String message, StackTrace stackTrace) {
stderr.writeln(message);
var buffer = StringBuffer(message);

if (_options.trace) {
stderr.writeln();
stderr.writeln(Trace.from(stackTrace).terse.toString().trimRight());
buffer.writeln();
buffer.writeln();
buffer.write(Trace.from(stackTrace).terse.toString().trimRight());
}

if (!_options.stopOnError) stderr.writeln();
if (!_options.stopOnError) buffer.writeln();
printError(buffer);
}

/// Listens to `watcher.events` and updates the filesystem accordingly.
Expand Down
10 changes: 9 additions & 1 deletion lib/src/import_cache.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_import_cache.dart.
// See tool/grind/synchronize.dart for details.
//
// Checksum: 92d6816f673ecbabd993aea7b79e27553f896ff4
// Checksum: 96e085628560f348a79b8f99b96f7352f450868c
//
// ignore_for_file: unused_import

Expand All @@ -18,6 +18,7 @@ import 'package:tuple/tuple.dart';
import 'ast/sass.dart';
import 'deprecation.dart';
import 'importer.dart';
import 'importer/no_op.dart';
import 'importer/utils.dart';
import 'io.dart';
import 'logger.dart';
Expand Down Expand Up @@ -101,6 +102,7 @@ class ImportCache {
static List<Importer> _toImporters(Iterable<Importer>? importers,
Iterable<String>? loadPaths, PackageConfig? packageConfig) {
var sassPath = getEnvironmentVariable('SASS_PATH');
if (isBrowser) return [...?importers];
return [
...?importers,
if (loadPaths != null)
Expand All @@ -126,6 +128,12 @@ class ImportCache {
/// applicable). Otherwise, returns `null`.
Tuple3<Importer, Uri, Uri>? canonicalize(Uri url,
{Importer? baseImporter, Uri? baseUrl, bool forImport = false}) {
if (isBrowser &&
(baseImporter == null || baseImporter is NoOpImporter) &&
_importers.isEmpty) {
throw "Custom importers are required to load stylesheets when compiling in the browser.";
}

if (baseImporter != null) {
var relativeResult = _relativeCanonicalizeCache
.putIfAbsent(Tuple4(url, forImport, baseImporter, baseUrl), () {
Expand Down
31 changes: 10 additions & 21 deletions lib/src/io/interface.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,30 +4,12 @@

import 'package:watcher/watcher.dart';

/// An output sink that writes to this process's standard error.
class Stderr {
/// Writes the string representation of [object] to standard error.
void write(Object object) {}

/// Writes the string representation of [object] to standard error, followed
/// by a newline.
///
/// If [object] is `null`, just writes a newline.
void writeln([Object? object]) {}

/// Flushes any buffered text.
void flush() {}
}

/// An error thrown by [readFile].
class FileSystemException {
String get message => throw '';
String? get path => throw '';
}

/// The standard error for the current process.
Stderr get stderr => throw '';

/// Whether the current process is running on Windows.
bool get isWindows => throw '';

Expand All @@ -37,15 +19,22 @@ bool get isMacOS => throw '';
/// Returns whether or not stdout is connected to an interactive terminal.
bool get hasTerminal => throw '';

/// Whether we're running as Node.JS.
/// Whether we're running as JS (browser or Node.js).
bool get isJS => throw '';

/// Whether we're running as Node.js (not browser or Dart VM).
bool get isNode => throw '';

/// Whether we're running as browser (not Node.js or Dart VM).
bool get isBrowser => throw '';

/// Whether this process is connected to a terminal that supports ANSI escape
/// sequences.
bool get supportsAnsiEscapes => throw '';

/// The current working directory.
String get currentPath => throw '';
/// Prints [message] (followed by a newline) to standard error or the
/// equivalent.
void printError(Object? message) => throw '';

/// Reads the file at [path] as a UTF-8 encoded string.
///
Expand Down
Loading

0 comments on commit cca9464

Please sign in to comment.