diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a269cf7..9b122cb1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -68,6 +68,8 @@ * Error out if a function is passed an unknown named parameter. +* Improve the speed of loading large files on Node. + * Don't consider browser-prefixed selector pseudos to be superselectors of differently- or non-prefixed selector pseudos with the same base name. diff --git a/lib/sass.dart b/lib/sass.dart index 134163b5..3886c394 100644 --- a/lib/sass.dart +++ b/lib/sass.dart @@ -5,8 +5,9 @@ import 'package:path/path.dart' as p; import 'src/ast/sass.dart'; +import 'src/exception.dart'; +import 'src/io.dart'; import 'src/sync_package_resolver.dart'; -import 'src/utils.dart'; import 'src/visitor/perform.dart'; import 'src/visitor/serialize.dart'; @@ -23,7 +24,7 @@ import 'src/visitor/serialize.dart'; /// Finally throws a [SassException] if conversion fails. String render(String path, {bool color: false, SyncPackageResolver packageResolver}) { - var contents = readSassFile(path); + var contents = readFile(path); var url = p.toUri(path); var sassTree = p.extension(path) == '.sass' ? new Stylesheet.parseSass(contents, url: url, color: color) diff --git a/lib/src/executable.dart b/lib/src/executable.dart index 127c3c99..d4ed470e 100644 --- a/lib/src/executable.dart +++ b/lib/src/executable.dart @@ -106,7 +106,7 @@ Future _loadVersion() async { var libDir = p.fromUri(await Isolate.resolvePackageUri(Uri.parse('package:sass/'))); - var pubspec = readFileAsString(p.join(libDir, '..', 'pubspec.yaml')); + var pubspec = readFile(p.join(libDir, '..', 'pubspec.yaml')); return pubspec .split("\n") .firstWhere((line) => line.startsWith('version: ')) diff --git a/lib/src/io/interface.dart b/lib/src/io/interface.dart index 86b942a3..b15ca6d5 100644 --- a/lib/src/io/interface.dart +++ b/lib/src/io/interface.dart @@ -17,7 +17,7 @@ class Stderr { void flush() {} } -/// An error thrown by [readFileAsBytes] and [readFileAsString]. +/// An error thrown by [readFile]. class FileSystemException { String get message => null; } @@ -28,11 +28,11 @@ Stderr get stderr => null; /// Returns whether or not stdout is connected to an interactive terminal. bool get hasTerminal => false; -/// Reads the file at [path] as a list of bytes. -List readFileAsBytes(String path) => null; - /// Reads the file at [path] as a UTF-8 encoded string. -String readFileAsString(String path) => null; +/// +/// Throws a [FileSystemException] if reading fails, and a [SassException] if +/// the file isn't valid UTF-8. +String readFile(String path) => null; /// Returns whether a file at [path] exists. bool fileExists(String path) => null; diff --git a/lib/src/io/node.dart b/lib/src/io/node.dart index fc36e64d..6eb6635f 100644 --- a/lib/src/io/node.dart +++ b/lib/src/io/node.dart @@ -2,9 +2,11 @@ // MIT-style license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT. -import 'dart:typed_data'; - import 'package:js/js.dart'; +import 'package:path/path.dart' as p; +import 'package:source_span/source_span.dart'; + +import '../exception.dart'; @JS() class _FS { @@ -51,14 +53,28 @@ external _FS _require(String name); final _fs = _require("fs"); -List readFileAsBytes(String path) => _readFile(path) as Uint8List; +String readFile(String path) { + // TODO(nweiz): explicitly decode the bytes as UTF-8 like we do in the VM when + // it doesn't cause a substantial performance degradation for large files. See + // also dart-lang/sdk#25377. + var contents = _readFile(path, 'utf8') as String; + if (!contents.contains("�")) return contents; -String readFileAsString(String path) => _readFile(path, 'utf8') as String; + var sourceFile = new SourceFile(contents, url: p.toUri(path)); + for (var i = 0; i < contents.length; i++) { + if (contents.codeUnitAt(i) != 0xFFFD) continue; + throw new SassException( + "Invalid UTF-8.", sourceFile.location(i).pointSpan()); + } + + // This should be unreachable. + return contents; +} /// Wraps `fs.readFileSync` to throw a [FileSystemException]. _readFile(String path, [String encoding]) { try { - return _fs.readFileSync(path); + return _fs.readFileSync(path, encoding); } catch (error) { throw new FileSystemException._(_cleanErrorMessage(error as _SystemError)); } diff --git a/lib/src/io/vm.dart b/lib/src/io/vm.dart index 1dcaef0b..c76a8773 100644 --- a/lib/src/io/vm.dart +++ b/lib/src/io/vm.dart @@ -2,17 +2,42 @@ // MIT-style license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT. +import 'dart:convert'; import 'dart:io' as io; +import 'package:path/path.dart' as p; +import 'package:source_span/source_span.dart'; + +import '../exception.dart'; + export 'dart:io' show exitCode, FileSystemException; io.Stdout get stderr => io.stderr; bool get hasTerminal => io.stdout.hasTerminal; -List readFileAsBytes(String path) => new io.File(path).readAsBytesSync(); +String readFile(String path) { + var bytes = new io.File(path).readAsBytesSync(); -String readFileAsString(String path) => new io.File(path).readAsStringSync(); + try { + return UTF8.decode(bytes); + } on FormatException { + var decoded = UTF8.decode(bytes, allowMalformed: true); + var sourceFile = new SourceFile(decoded, url: p.toUri(path)); + + // TODO(nweiz): Use [FormatException.offset] instead when + // dart-lang/sdk#28293 is fixed. + for (var i = 0; i < bytes.length; i++) { + if (decoded.codeUnitAt(i) != 0xFFFD) continue; + throw new SassException( + "Invalid UTF-8.", sourceFile.location(i).pointSpan()); + } + + // This should be unreachable, but we'll rethrow the original exception just + // in case. + rethrow; + } +} bool fileExists(String path) => new io.File(path).existsSync(); diff --git a/lib/src/utils.dart b/lib/src/utils.dart index 8c0cb696..e66ac875 100644 --- a/lib/src/utils.dart +++ b/lib/src/utils.dart @@ -3,16 +3,13 @@ // https://opensource.org/licenses/MIT. import 'dart:collection'; -import 'dart:convert'; import 'dart:math' as math; import 'package:charcode/charcode.dart'; import 'package:collection/collection.dart'; -import 'package:path/path.dart' as p; import 'package:source_span/source_span.dart'; import 'ast/node.dart'; -import 'exception.dart'; import 'io.dart'; import 'util/character.dart'; @@ -257,31 +254,6 @@ List/**/ longestCommonSubsequence/**/( } } -/// Reads a Sass source file from diskx, and throws a [SassException] if UTF-8 -/// decoding fails. -String readSassFile(String path) { - var bytes = readFileAsBytes(path); - - try { - return UTF8.decode(bytes); - } on FormatException { - var decoded = UTF8.decode(bytes, allowMalformed: true); - var sourceFile = new SourceFile(decoded, url: p.toUri(path)); - - // TODO(nweiz): Use [FormatException.offset] instead when - // dart-lang/sdk#28293 is fixed. - for (var i = 0; i < bytes.length; i++) { - if (decoded.codeUnitAt(i) != 0xFFFD) continue; - throw new SassException( - "Invalid UTF-8.", sourceFile.location(i).pointSpan()); - } - - // This should be unreachable, but we'll rethrow the original exception just - // in case. - rethrow; - } -} - /// Prints a warning to standard error, associated with [span]. /// /// If [color] is `true`, this uses terminal colors. diff --git a/lib/src/visitor/perform.dart b/lib/src/visitor/perform.dart index 030691d1..c8839482 100644 --- a/lib/src/visitor/perform.dart +++ b/lib/src/visitor/perform.dart @@ -636,7 +636,7 @@ class _PerformVisitor return _importedFiles.putIfAbsent(path, () { String contents; try { - contents = readSassFile(path); + contents = readFile(path); } on SassException catch (error) { var frames = _stack.toList()..add(_stackFrame(import.span)); throw new SassRuntimeException(