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

View File

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

View File

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

View File

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

View File

@ -242,7 +242,7 @@ class _EvaluateVisitor
var module = await _execute(importer, node); var module = await _execute(importer, node);
_extender.finalize(); _extender.finalize();
return EvaluateResult(module.css, _includedFiles); return EvaluateResult(_combineCss(module), _includedFiles);
} }
Future<Value> runExpression(Expression expression, Future<Value> runExpression(Expression expression,
@ -406,6 +406,47 @@ class _EvaluateVisitor
return CssStylesheet(statements.build(), _root.span); 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 // ## Statements
Future<Value> visitStylesheet(Stylesheet node) async { Future<Value> visitStylesheet(Stylesheet node) async {

View File

@ -5,7 +5,7 @@
// DO NOT EDIT. This file was generated from async_evaluate.dart. // DO NOT EDIT. This file was generated from async_evaluate.dart.
// See tool/synchronize.dart for details. // See tool/synchronize.dart for details.
// //
// Checksum: ae0454752ee85a9094d0f0d0e6e8a252364b70a1 // Checksum: da6068544fd58007ee2d44c2688063cbe4543e73
// //
// ignore_for_file: unused_import // ignore_for_file: unused_import
@ -249,7 +249,7 @@ class _EvaluateVisitor
var module = _execute(importer, node); var module = _execute(importer, node);
_extender.finalize(); _extender.finalize();
return EvaluateResult(module.css, _includedFiles); return EvaluateResult(_combineCss(module), _includedFiles);
} }
Value runExpression(Expression expression, {Map<String, Value> variables}) { Value runExpression(Expression expression, {Map<String, Value> variables}) {
@ -412,6 +412,47 @@ class _EvaluateVisitor
return CssStylesheet(statements.build(), _root.span); 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 // ## Statements
Value visitStylesheet(Stylesheet node) { Value visitStylesheet(Stylesheet node) {