refactor(transformer): precompile stylesheets

Part of #3605
This commit is contained in:
Yegor Jbanov
2015-10-01 18:48:27 -07:00
committed by Tobias Bosch
parent 52236bd765
commit 841f8789fd
10 changed files with 771 additions and 17 deletions
+21 -11
View File
@@ -744,24 +744,31 @@ gulp.task('test.unit.cjs', ['build/clean.js', 'build.tools'], function (neverDon
watch('modules/**', buildAndTest);
});
// Use this target to continuously run dartvm unit-tests (such as transformer
// tests) while coding. Note: these tests do not use Karma.
gulp.task('test.unit.dartvm', function (done) {
runSequence(
'build/tree.dart',
'build/pure-packages.dart',
'build/pubspec.dart',
'!build/pubget.angular2.dart',
'!build/change_detect.dart',
'!test.unit.dartvm/run',
function(error) {
// if initial build failed (likely due to build or formatting step) then exit
// otherwise karma server doesn't start and we can't continue running properly
if (error) {
done(error);
return;
}
// Watch for changes made in the TS and Dart code under "modules" and
// run ts2dart and test change detector generator prior to rerunning the
// tests.
watch('modules/angular2/**', { ignoreInitial: true }, [
'!build/tree.dart',
'!build/change_detect.dart',
'!test.unit.dartvm/run'
]);
// Watch for changes made in Dart code under "modules_dart", then copy it
// to dist and run test change detector generator prior to retunning the
// tests.
watch('modules_dart/**', { ignoreInitial: true }, [
'build/pure-packages.dart',
'!build/change_detect.dart',
'!test.unit.dartvm/run'
]);
}
@@ -863,14 +870,17 @@ gulp.task('build/pure-packages.dart', function() {
var transformStream = gulp
.src([
'modules_dart/transform/**/*',
'!modules_dart/transform/**/*.proto'
'!modules_dart/transform/**/*.proto',
'!modules_dart/transform/pubspec.yaml',
'!modules_dart/transform/**/packages{,/**}',
])
.pipe(gulp.dest(path.join(CONFIG.dest.dart, 'angular2')));
var moveStream = gulp.src([
'modules_dart/**/*.dart',
'modules_dart/**/pubspec.yaml',
'!modules_dart/transform/**'
'!modules_dart/transform/**',
'!modules_dart/**/packages{,/**}'
])
.pipe(through2.obj(function(file, enc, done) {
if (/pubspec.yaml$/.test(file.path)) {
+1
View File
@@ -11,6 +11,7 @@ environment:
dependencies:
analyzer: '>=0.24.4 <0.27.0'
barback: '^0.15.2+2'
csslib: '>=0.12.0 <1.0.0'
code_transformers: '^0.2.8'
dart_style: '>=0.1.8 <0.3.0'
glob: '^1.0.0'
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,102 @@
/**
* Emulates browser CSS API.
*
* WARNING: this is a very incomplete emulation; it only has enough to support
* Angular's CSS scoping (a.k.a. shimming).
*/
library angular2.dom.emulated_css;
import 'package:csslib/parser.dart' as cssp;
import 'package:csslib/visitor.dart' as cssv;
/// Parses [css] string and emits the list of top-level CSS rules in it via
/// data structures that mimick browser CSS APIs.
List<EmulatedCssRule> parseAndEmulateCssRules(String css) {
var stylesheet = cssp.parse(css);
return emulateRules(stylesheet.topLevels);
}
/// Converts `csslib` [rules] to their emulated counterparts.
List<EmulatedCssRule> emulateRules(Iterable<cssv.TreeNode> rules) {
return rules
.map((cssv.TreeNode node) {
if (node is cssv.RuleSet) {
if (node.declarationGroup.span.text.isEmpty) {
// Skip CSS matchers with no bodies
return null;
}
return new EmulatedCssStyleRule(node);
} else if (node is cssv.MediaDirective) {
return new EmulatedCssMedialRule(node);
}
})
.where((r) => r != null)
.toList();
}
/// Emulates [CSSRule](https://developer.mozilla.org/en-US/docs/Web/API/CSSRule)
abstract class EmulatedCssRule {
int type;
String cssText;
}
/// Emulates [CSSStyleRule](https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleRule)
class EmulatedCssStyleRule extends EmulatedCssRule {
String selectorText;
EmulatedCssStyleDeclaration style;
EmulatedCssStyleRule(cssv.RuleSet ruleSet) {
final declarationText = new StringBuffer();
ruleSet.declarationGroup.declarations.forEach((d) {
if (d is! cssv.Declaration) {
// Nested selectors not supported
return;
}
// TODO: expression spans are currently broken in csslib; see:
// https://github.com/dart-lang/csslib/pull/14
var declarationSpan = d.span.text;
var colonIdx = declarationSpan.indexOf(':');
var expression = declarationSpan.substring(colonIdx + 1);
declarationText.write('${d.property}: ${expression};');
});
final style = new EmulatedCssStyleDeclaration()
..cssText = declarationText.toString();
this
..type = 1
..cssText = ruleSet.span.text
..selectorText = ruleSet.selectorGroup.span.text
..style = style;
}
}
/// Emulates [CSSStyleDeclaration](https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleDeclaration)
class EmulatedCssStyleDeclaration {
final String content = '';
String cssText;
}
/// Emulates [CSSMediaRule](https://developer.mozilla.org/en-US/docs/Web/API/CSSMediaRule)
class EmulatedCssMedialRule extends EmulatedCssRule {
List<EmulatedCssStyleRule> cssRules;
EmulatedMediaList media;
EmulatedCssMedialRule(cssv.MediaDirective directive) {
this
..type = 4
..media = new EmulatedMediaList(directive)
..cssText = directive.span.text
..cssRules = emulateRules(directive.rulesets);
}
}
/// Emulates [MediaList](https://developer.mozilla.org/en-US/docs/Web/API/MediaList)
class EmulatedMediaList {
String mediaText;
EmulatedMediaList(cssv.MediaDirective directive) {
this.mediaText = directive.mediaQueries
.map((q) => q.span.text).join(' and ');
}
}
@@ -0,0 +1,11 @@
library angular2.compiler.shadow_css_html5lib.test;
import 'package:angular2/src/core/dom/html_adapter.dart';
import 'package:angular2/src/test_lib/test_lib.dart' show testSetup;
import 'shadow_css_spec.dart' as shadow_css_spec_test;
void main() {
Html5LibDomAdapter.makeCurrent();
testSetup();
shadow_css_spec_test.main();
}
@@ -33,7 +33,7 @@ export function main() {
expect(s(css, 'a')).toEqual(expected);
});
it('should hanlde invalid css', () => {
it('should handle invalid css', () => {
var css = 'one {color: red;}garbage';
var expected = 'one[a] {color:red;}';
expect(s(css, 'a')).toEqual(expected);
@@ -58,8 +58,7 @@ export function main() {
});
// Check that the browser supports unprefixed CSS animation
if (isPresent(DOM.defaultDoc().body.style) &&
isPresent(DOM.defaultDoc().body.style.animationName)) {
if (DOM.supportsUnprefixedCssAnimation()) {
it('should handle keyframes rules', () => {
var css = '@keyframes foo {0% {transform: translate(-50%) scaleX(0);}}';
var passRe =
@@ -80,9 +79,9 @@ export function main() {
it('should handle complicated selectors', () => {
expect(s('one::before {}', 'a')).toEqual('one[a]::before {}');
expect(s('one two {}', 'a')).toEqual('one[a] two[a] {}');
expect(s('one>two {}', 'a')).toEqual('one[a] > two[a] {}');
expect(s('one+two {}', 'a')).toEqual('one[a] + two[a] {}');
expect(s('one~two {}', 'a')).toEqual('one[a] ~ two[a] {}');
expect(s('one > two {}', 'a')).toEqual('one[a] > two[a] {}');
expect(s('one + two {}', 'a')).toEqual('one[a] + two[a] {}');
expect(s('one ~ two {}', 'a')).toEqual('one[a] ~ two[a] {}');
var res = s('.one.two > three {}', 'a'); // IE swap classes
expect(res == '.one.two[a] > three[a] {}' || res == '.two.one[a] > three[a] {}')
.toEqual(true);
@@ -0,0 +1,37 @@
library angular2.transform.stylesheet_compiler.processor;
import 'dart:async';
import 'package:angular2/src/transform/common/asset_reader.dart';
import 'package:angular2/src/transform/common/code/source_module.dart';
import 'package:angular2/src/transform/common/names.dart';
import 'package:angular2/src/transform/common/ng_compiler.dart';
import 'package:angular2/src/compiler/source_module.dart';
import 'package:barback/barback.dart';
AssetId shimmedStylesheetAssetId(AssetId cssAssetId) => new AssetId(
cssAssetId.package, toShimmedStylesheetExtension(cssAssetId.path));
AssetId nonShimmedStylesheetAssetId(AssetId cssAssetId) => new AssetId(
cssAssetId.package, toNonShimmedStylesheetExtension(cssAssetId.path));
Future<Iterable<Asset>> processStylesheet(
AssetReader reader, AssetId stylesheetId) async {
final stylesheetUrl = '${stylesheetId.package}|${stylesheetId.path}';
final templateCompiler = createTemplateCompiler(reader);
final cssText = await reader.readAsString(stylesheetId);
final sourceModules =
templateCompiler.compileStylesheetCodeGen(stylesheetUrl, cssText);
var libraryIdx = 0;
return sourceModules.map((SourceModule module) => new Asset.fromString(
new AssetId.parse('${module.moduleUrl}'),
writeSourceModule(module,
libraryName: '${_getLibBase(module.moduleUrl)}${libraryIdx++}')));
}
final _unsafeCharsPattern = new RegExp(r'[^a-zA-Z0-9_]');
String _getLibBase(String libraryName) {
return libraryName.replaceAll('/', '.').replaceAll(_unsafeCharsPattern, '_');
}
@@ -0,0 +1,40 @@
library angular2.transform.stylesheet_compiler.transformer;
import 'dart:async';
import 'package:angular2/src/core/dom/html_adapter.dart';
import 'package:angular2/src/transform/common/asset_reader.dart';
import 'package:angular2/src/transform/common/logging.dart' as log;
import 'package:angular2/src/transform/common/names.dart';
import 'package:barback/barback.dart';
import 'processor.dart';
/// Pre-compiles CSS stylesheet files to Dart code for Angular 2.
class StylesheetCompiler extends Transformer implements DeclaringTransformer {
StylesheetCompiler();
@override
bool isPrimary(AssetId id) {
return id.path.endsWith(CSS_EXTENSION);
}
@override
declareOutputs(DeclaringTransform transform) {
transform.declareOutput(nonShimmedStylesheetAssetId(transform.primaryId));
transform.declareOutput(shimmedStylesheetAssetId(transform.primaryId));
}
@override
Future apply(Transform transform) async {
await log.initZoned(transform, () async {
Html5LibDomAdapter.makeCurrent();
var reader = new AssetReader.fromTransform(transform);
var outputs = await processStylesheet(reader, transform.primaryInput.id);
outputs.forEach((Asset compiledStylesheet) {
transform.addOutput(compiledStylesheet);
});
});
}
}
+23
View File
@@ -0,0 +1,23 @@
name: ng2_transform
version: 0.0.0
environment:
sdk: '>=1.8.0 <2.0.0'
dependencies:
angular2:
path: ../../dist/dart/angular2
analyzer: '>=0.24.4 <0.27.0'
barback: '^0.15.2+2'
code_transformers: '^0.2.8'
dart_style: '>=0.1.8 <0.3.0'
glob: '^1.0.0'
guinness: any
html: '^0.12.0'
intl: '^0.12.4'
logging: '>=0.9.0 <0.12.0'
observe: '^0.13.1'
protobuf: '^0.4.2'
quiver: '^0.21.4'
source_span: '^1.0.0'
stack_trace: '^1.1.1'
dev_dependencies:
test: '>=0.12.0 <0.13.0'
@@ -0,0 +1,96 @@
library angular2.test.transform.stylesheet_compiler.all_tests;
import 'dart:async';
import 'dart:convert';
import 'package:angular2/src/transform/stylesheet_compiler/transformer.dart';
import 'package:barback/barback.dart';
import 'package:guinness/guinness.dart';
const SIMPLE_CSS = '''
.foo {
width: 10px;
}
''';
main() {
Html5LibDomAdapter.makeCurrent();
allTests();
}
allTests() {
StylesheetCompiler subject;
beforeEach(() {
subject = new StylesheetCompiler();
});
it('should accept CSS assets', () {
expect(subject.isPrimary(new AssetId('somepackage', 'lib/style.css')))
.toBe(true);
});
it('should reject non-CSS assets', () {
expect(subject.isPrimary(new AssetId('somepackage', 'lib/style.scss')))
.toBe(false);
});
it('should declare outputs', () {
var transform = new FakeDeclaringTransform()
..primaryId = new AssetId('somepackage', 'lib/style.css');
subject.declareOutputs(transform);
expect(transform.outputs.length).toBe(2);
expect(transform.outputs[0].toString())
.toEqual('somepackage|lib/style.css.dart');
expect(transform.outputs[1].toString())
.toEqual('somepackage|lib/style.css.shim.dart');
});
it('should compile stylesheets', () async {
var cssFile = new Asset.fromString(
new AssetId('somepackage', 'lib/style.css'), SIMPLE_CSS);
var transform = new FakeTransform()..primaryInput = cssFile;
await subject.apply(transform);
expect(transform.outputs.length).toBe(2);
expect(transform.outputs[0].id.toString())
.toEqual('somepackage|lib/style.css.dart');
expect(transform.outputs[1].id.toString())
.toEqual('somepackage|lib/style.css.shim.dart');
});
}
@proxy
class FakeTransform implements Transform {
final outputs = <Asset>[];
Asset primaryInput;
addOutput(Asset output) {
this.outputs.add(output);
}
readInputAsString(AssetId id, {Encoding encoding}) {
if (id == primaryInput.id) {
return primaryInput.readAsString(encoding: encoding);
}
throw 'Could not read input $id';
}
noSuchMethod(Invocation i) {
throw '${i.memberName} not implemented';
}
}
@proxy
class FakeDeclaringTransform implements DeclaringTransform {
final outputs = <AssetId>[];
AssetId primaryId;
declareOutput(AssetId output) {
this.outputs.add(output);
}
noSuchMethod(Invocation i) {
throw '${i.memberName} not implemented';
}
}