Emit CSS for used modules (#620)

This commit is contained in:
Natalie Weizenbaum 2019-03-11 16:49:58 -07:00 committed by GitHub
parent b66e1bad28
commit c98cfd53b0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 139 additions and 13 deletions

View File

@ -29,6 +29,10 @@ class AsyncEnvironment {
/// This is `null` if there are no namespaceless modules.
Set<AsyncModule> _globalModules;
/// Modules from both [_modules] and [_global], in the order in which they
/// were `@use`d.
final List<AsyncModule> _allModules;
/// A list of variables defined at each lexical scope level.
///
/// Each scope maps the names of declared variables to their values. These
@ -122,6 +126,7 @@ class AsyncEnvironment {
AsyncEnvironment({bool sourceMap = false})
: _modules = {},
_globalModules = null,
_allModules = [],
_variables = [normalizedMap()],
_variableNodes = sourceMap ? [normalizedMap()] : null,
_variableIndices = normalizedMap(),
@ -132,8 +137,15 @@ class AsyncEnvironment {
coreFunctions.forEach(setFunction);
}
AsyncEnvironment._(this._modules, this._globalModules, this._variables,
this._variableNodes, this._functions, this._mixins, this._content)
AsyncEnvironment._(
this._modules,
this._globalModules,
this._allModules,
this._variables,
this._variableNodes,
this._functions,
this._mixins,
this._content)
// Lazily fill in the indices rather than eagerly copying them from the
// existing environment in closure() because the copying took a lot of
// time and was rarely helpful. This saves a bunch of time on Susy's
@ -150,6 +162,7 @@ class AsyncEnvironment {
AsyncEnvironment closure() => AsyncEnvironment._(
_modules,
_globalModules,
_allModules,
_variables.toList(),
_variableNodes?.toList(),
_functions.toList(),
@ -160,8 +173,10 @@ class AsyncEnvironment {
///
/// The returned environment shares this environment's global variables,
/// functions, and mixins, but not its modules.
AsyncEnvironment global() => AsyncEnvironment._({},
AsyncEnvironment global() => AsyncEnvironment._(
{},
null,
[],
_variables.toList(),
_variableNodes?.toList(),
_functions.toList(),
@ -180,6 +195,7 @@ class AsyncEnvironment {
if (namespace == null) {
_globalModules ??= Set();
_globalModules.add(module);
_allModules.add(module);
for (var name in _variables.first.keys) {
if (module.variables.containsKey(name)) {
@ -195,6 +211,7 @@ class AsyncEnvironment {
}
_modules[namespace] = module;
_allModules.add(module);
}
}
@ -600,6 +617,7 @@ class AsyncEnvironment {
/// A module that represents the top-level members defined in an [Environment].
class _EnvironmentModule implements AsyncModule {
final List<AsyncModule> upstream;
final Map<String, Value> variables;
final Map<String, AstNode> variableNodes;
final Map<String, AsyncCallable> functions;
@ -612,7 +630,8 @@ class _EnvironmentModule implements AsyncModule {
// TODO(nweiz): Use custom [UnmodifiableMapView]s that forbid access to
// private members.
_EnvironmentModule(this._environment, this.css)
: variables = PublicMemberMap(_environment._variables.first),
: upstream = _environment._allModules,
variables = PublicMemberMap(_environment._variables.first),
variableNodes = _environment._variableNodes == null
? null
: PublicMemberMap(_environment._variableNodes.first),

View File

@ -11,6 +11,9 @@ import 'value.dart';
/// The interface for a Sass module.
abstract class AsyncModule {
/// Modules that this module uses.
List<AsyncModule> get upstream;
/// The module's variables.
Map<String, Value> get variables;

View File

@ -5,7 +5,7 @@
// DO NOT EDIT. This file was generated from async_environment.dart.
// See tool/synchronize.dart for details.
//
// Checksum: 77b03257b1770270e2d1f269281bdaab3f9f1ae9
// Checksum: e1d3693a4ede6e31d259efb13148a669be01e98c
//
// ignore_for_file: unused_import
@ -34,6 +34,10 @@ class Environment {
/// This is `null` if there are no namespaceless modules.
Set<Module> _globalModules;
/// Modules from both [_modules] and [_global], in the order in which they
/// were `@use`d.
final List<Module> _allModules;
/// A list of variables defined at each lexical scope level.
///
/// Each scope maps the names of declared variables to their values. These
@ -127,6 +131,7 @@ class Environment {
Environment({bool sourceMap = false})
: _modules = {},
_globalModules = null,
_allModules = [],
_variables = [normalizedMap()],
_variableNodes = sourceMap ? [normalizedMap()] : null,
_variableIndices = normalizedMap(),
@ -137,8 +142,15 @@ class Environment {
coreFunctions.forEach(setFunction);
}
Environment._(this._modules, this._globalModules, this._variables,
this._variableNodes, this._functions, this._mixins, this._content)
Environment._(
this._modules,
this._globalModules,
this._allModules,
this._variables,
this._variableNodes,
this._functions,
this._mixins,
this._content)
// Lazily fill in the indices rather than eagerly copying them from the
// existing environment in closure() because the copying took a lot of
// time and was rarely helpful. This saves a bunch of time on Susy's
@ -155,6 +167,7 @@ class Environment {
Environment closure() => Environment._(
_modules,
_globalModules,
_allModules,
_variables.toList(),
_variableNodes?.toList(),
_functions.toList(),
@ -165,8 +178,10 @@ class Environment {
///
/// The returned environment shares this environment's global variables,
/// functions, and mixins, but not its modules.
Environment global() => Environment._({},
Environment global() => Environment._(
{},
null,
[],
_variables.toList(),
_variableNodes?.toList(),
_functions.toList(),
@ -185,6 +200,7 @@ class Environment {
if (namespace == null) {
_globalModules ??= Set();
_globalModules.add(module);
_allModules.add(module);
for (var name in _variables.first.keys) {
if (module.variables.containsKey(name)) {
@ -200,6 +216,7 @@ class Environment {
}
_modules[namespace] = module;
_allModules.add(module);
}
}
@ -602,6 +619,7 @@ class Environment {
/// A module that represents the top-level members defined in an [Environment].
class _EnvironmentModule implements Module {
final List<Module> upstream;
final Map<String, Value> variables;
final Map<String, AstNode> variableNodes;
final Map<String, Callable> functions;
@ -614,7 +632,8 @@ class _EnvironmentModule implements Module {
// TODO(nweiz): Use custom [UnmodifiableMapView]s that forbid access to
// private members.
_EnvironmentModule(this._environment, this.css)
: variables = PublicMemberMap(_environment._variables.first),
: upstream = _environment._allModules,
variables = PublicMemberMap(_environment._variables.first),
variableNodes = _environment._variableNodes == null
? null
: PublicMemberMap(_environment._variableNodes.first),

View File

@ -5,7 +5,7 @@
// DO NOT EDIT. This file was generated from async_module.dart.
// See tool/synchronize.dart for details.
//
// Checksum: 5608be0fdb1bff974611b75d1bbcb364a15d4df2
// Checksum: 759037174212e69cd0d9a7ebf55f6ee9f66072e8
//
// ignore_for_file: unused_import
@ -18,6 +18,9 @@ import 'value.dart';
/// The interface for a Sass module.
abstract class Module {
/// Modules that this module uses.
List<Module> get upstream;
/// The module's variables.
Map<String, Value> get variables;

View File

@ -242,7 +242,7 @@ class _EvaluateVisitor
var module = await _execute(importer, node);
_extender.finalize();
return EvaluateResult(module.css, _includedFiles);
return EvaluateResult(_combineCss(module), _includedFiles);
}
Future<Value> runExpression(Expression expression,
@ -406,6 +406,47 @@ class _EvaluateVisitor
return CssStylesheet(statements.build(), _root.span);
}
/// Returns a new stylesheet containing [root]'s CSS as well as the CSS of all
/// modules transitively used by [root].
CssStylesheet _combineCss(AsyncModule root) {
if (root.upstream.isEmpty) return root.css;
var seen = Set<AsyncModule>();
var imports = <CssNode>[];
var css = <CssNode>[];
void visitModule(AsyncModule module) {
if (!seen.add(module)) return;
for (var module in module.upstream) {
visitModule(module);
}
var statements = module.css.children;
var index = _indexAfterImports(statements);
imports.addAll(statements.getRange(0, index));
css.addAll(statements.getRange(index, statements.length));
}
visitModule(root);
return CssStylesheet(imports + css, root.css.span);
}
/// Returns the index of the first node in [statements] that comes after all
/// static imports.
int _indexAfterImports(List<CssNode> statements) {
var lastImport = -1;
for (var i = 0; i < statements.length; i++) {
var statement = statements[i];
if (statement is CssImport) {
lastImport = i;
} else if (statement is! CssComment) {
break;
}
}
return lastImport + 1;
}
// ## Statements
Future<Value> visitStylesheet(Stylesheet node) async {

View File

@ -5,7 +5,7 @@
// DO NOT EDIT. This file was generated from async_evaluate.dart.
// See tool/synchronize.dart for details.
//
// Checksum: ae0454752ee85a9094d0f0d0e6e8a252364b70a1
// Checksum: da6068544fd58007ee2d44c2688063cbe4543e73
//
// ignore_for_file: unused_import
@ -249,7 +249,7 @@ class _EvaluateVisitor
var module = _execute(importer, node);
_extender.finalize();
return EvaluateResult(module.css, _includedFiles);
return EvaluateResult(_combineCss(module), _includedFiles);
}
Value runExpression(Expression expression, {Map<String, Value> variables}) {
@ -412,6 +412,47 @@ class _EvaluateVisitor
return CssStylesheet(statements.build(), _root.span);
}
/// Returns a new stylesheet containing [root]'s CSS as well as the CSS of all
/// modules transitively used by [root].
CssStylesheet _combineCss(Module root) {
if (root.upstream.isEmpty) return root.css;
var seen = Set<Module>();
var imports = <CssNode>[];
var css = <CssNode>[];
void visitModule(Module module) {
if (!seen.add(module)) return;
for (var module in module.upstream) {
visitModule(module);
}
var statements = module.css.children;
var index = _indexAfterImports(statements);
imports.addAll(statements.getRange(0, index));
css.addAll(statements.getRange(index, statements.length));
}
visitModule(root);
return CssStylesheet(imports + css, root.css.span);
}
/// Returns the index of the first node in [statements] that comes after all
/// static imports.
int _indexAfterImports(List<CssNode> statements) {
var lastImport = -1;
for (var i = 0; i < statements.length; i++) {
var statement = statements[i];
if (statement is CssImport) {
lastImport = i;
} else if (statement is! CssComment) {
break;
}
}
return lastImport + 1;
}
// ## Statements
Value visitStylesheet(Stylesheet node) {