fix(compiler): support css stylesheets in offline compiler

This commit is contained in:
Tobias Bosch 2016-05-02 09:38:46 -07:00
parent c386fc8379
commit 00d3b6083c
38 changed files with 436 additions and 388 deletions

2
.gitignore vendored
View File

@ -23,6 +23,8 @@ tmp
# Files created by the template compiler # Files created by the template compiler
**/*.ngfactory.ts **/*.ngfactory.ts
**/*.css.ts
**/*.css.shim.ts
# Or type definitions we mirror from github # Or type definitions we mirror from github
# (NB: these lines are removed in publish-build-artifacts.sh) # (NB: these lines are removed in publish-build-artifacts.sh)

View File

@ -1,4 +1,5 @@
import * as selector from './src/selector'; import * as selector from './src/selector';
import * as pathUtil from './src/output/path_util';
export namespace __compiler_private__ { export namespace __compiler_private__ {
export type SelectorMatcher = selector.SelectorMatcher; export type SelectorMatcher = selector.SelectorMatcher;
@ -6,4 +7,10 @@ export namespace __compiler_private__ {
export type CssSelector = selector.CssSelector; export type CssSelector = selector.CssSelector;
export var CssSelector = selector.CssSelector; export var CssSelector = selector.CssSelector;
export type AssetUrl = pathUtil.AssetUrl;
export var AssetUrl = pathUtil.AssetUrl;
export type ImportGenerator = pathUtil.ImportGenerator;
export var ImportGenerator = pathUtil.ImportGenerator;
} }

View File

@ -466,16 +466,13 @@ export class CompileTemplateMetadata {
styles: string[]; styles: string[];
styleUrls: string[]; styleUrls: string[];
ngContentSelectors: string[]; ngContentSelectors: string[];
baseUrl: string; constructor({encapsulation, template, templateUrl, styles, styleUrls, ngContentSelectors}: {
constructor({encapsulation, template, templateUrl, styles, styleUrls, ngContentSelectors,
baseUrl}: {
encapsulation?: ViewEncapsulation, encapsulation?: ViewEncapsulation,
template?: string, template?: string,
templateUrl?: string, templateUrl?: string,
styles?: string[], styles?: string[],
styleUrls?: string[], styleUrls?: string[],
ngContentSelectors?: string[], ngContentSelectors?: string[]
baseUrl?: string
} = {}) { } = {}) {
this.encapsulation = isPresent(encapsulation) ? encapsulation : ViewEncapsulation.Emulated; this.encapsulation = isPresent(encapsulation) ? encapsulation : ViewEncapsulation.Emulated;
this.template = template; this.template = template;
@ -483,7 +480,6 @@ export class CompileTemplateMetadata {
this.styles = isPresent(styles) ? styles : []; this.styles = isPresent(styles) ? styles : [];
this.styleUrls = isPresent(styleUrls) ? styleUrls : []; this.styleUrls = isPresent(styleUrls) ? styleUrls : [];
this.ngContentSelectors = isPresent(ngContentSelectors) ? ngContentSelectors : []; this.ngContentSelectors = isPresent(ngContentSelectors) ? ngContentSelectors : [];
this.baseUrl = baseUrl;
} }
static fromJson(data: {[key: string]: any}): CompileTemplateMetadata { static fromJson(data: {[key: string]: any}): CompileTemplateMetadata {
@ -495,8 +491,7 @@ export class CompileTemplateMetadata {
templateUrl: data['templateUrl'], templateUrl: data['templateUrl'],
styles: data['styles'], styles: data['styles'],
styleUrls: data['styleUrls'], styleUrls: data['styleUrls'],
ngContentSelectors: data['ngContentSelectors'], ngContentSelectors: data['ngContentSelectors']
baseUrl: data['baseUrl']
}); });
} }
@ -508,8 +503,7 @@ export class CompileTemplateMetadata {
'templateUrl': this.templateUrl, 'templateUrl': this.templateUrl,
'styles': this.styles, 'styles': this.styles,
'styleUrls': this.styleUrls, 'styleUrls': this.styleUrls,
'ngContentSelectors': this.ngContentSelectors, 'ngContentSelectors': this.ngContentSelectors
'baseUrl': this.baseUrl
}; };
} }
} }

View File

@ -63,9 +63,9 @@ export class DirectiveNormalizer {
template: CompileTemplateMetadata): Promise<CompileTemplateMetadata> { template: CompileTemplateMetadata): Promise<CompileTemplateMetadata> {
if (isPresent(template.template)) { if (isPresent(template.template)) {
return PromiseWrapper.resolve(this.normalizeLoadedTemplate( return PromiseWrapper.resolve(this.normalizeLoadedTemplate(
directiveType, template, template.template, template.baseUrl)); directiveType, template, template.template, directiveType.moduleUrl));
} else if (isPresent(template.templateUrl)) { } else if (isPresent(template.templateUrl)) {
var sourceAbsUrl = this._urlResolver.resolve(template.baseUrl, template.templateUrl); var sourceAbsUrl = this._urlResolver.resolve(directiveType.moduleUrl, template.templateUrl);
return this._xhr.get(sourceAbsUrl) return this._xhr.get(sourceAbsUrl)
.then(templateContent => this.normalizeLoadedTemplate(directiveType, template, .then(templateContent => this.normalizeLoadedTemplate(directiveType, template,
templateContent, sourceAbsUrl)); templateContent, sourceAbsUrl));
@ -90,7 +90,7 @@ export class DirectiveNormalizer {
visitor.styleUrls.filter(isStyleUrlResolvable) visitor.styleUrls.filter(isStyleUrlResolvable)
.map(url => this._urlResolver.resolve(templateAbsUrl, url)) .map(url => this._urlResolver.resolve(templateAbsUrl, url))
.concat(templateMeta.styleUrls.filter(isStyleUrlResolvable) .concat(templateMeta.styleUrls.filter(isStyleUrlResolvable)
.map(url => this._urlResolver.resolve(templateMeta.baseUrl, url))); .map(url => this._urlResolver.resolve(directiveType.moduleUrl, url)));
var allResolvedStyles = allStyles.map(style => { var allResolvedStyles = allStyles.map(style => {
var styleWithImports = extractStyleUrls(this._urlResolver, templateAbsUrl, style); var styleWithImports = extractStyleUrls(this._urlResolver, templateAbsUrl, style);

View File

@ -95,8 +95,7 @@ export class CompileMetadataResolver {
template: viewMeta.template, template: viewMeta.template,
templateUrl: viewMeta.templateUrl, templateUrl: viewMeta.templateUrl,
styles: viewMeta.styles, styles: viewMeta.styles,
styleUrls: viewMeta.styleUrls, styleUrls: viewMeta.styleUrls
baseUrl: calcTemplateBaseUrl(this._reflector, directiveType, cmpMeta)
}); });
changeDetectionStrategy = cmpMeta.changeDetection; changeDetectionStrategy = cmpMeta.changeDetection;
if (isPresent(dirMeta.viewProviders)) { if (isPresent(dirMeta.viewProviders)) {
@ -118,7 +117,10 @@ export class CompileMetadataResolver {
selector: dirMeta.selector, selector: dirMeta.selector,
exportAs: dirMeta.exportAs, exportAs: dirMeta.exportAs,
isComponent: isPresent(templateMeta), isComponent: isPresent(templateMeta),
type: this.getTypeMetadata(directiveType, staticTypeModuleUrl(directiveType)), type: this.getTypeMetadata(directiveType,
isPresent(cmpMeta) ?
componentModuleUrl(this._reflector, directiveType, cmpMeta) :
staticTypeModuleUrl(dirMeta)),
template: templateMeta, template: templateMeta,
changeDetection: changeDetectionStrategy, changeDetection: changeDetectionStrategy,
inputs: dirMeta.inputs, inputs: dirMeta.inputs,
@ -392,7 +394,7 @@ function flattenArray(tree: any[], out: Array<Type | any[]>): void {
} }
function isStaticType(value: any): boolean { function isStaticType(value: any): boolean {
return isStringMap(value) && isPresent(value['name']) && isPresent(value['moduleId']); return isStringMap(value) && isPresent(value['name']) && isPresent(value['filePath']);
} }
function isValidType(value: any): boolean { function isValidType(value: any): boolean {
@ -400,13 +402,13 @@ function isValidType(value: any): boolean {
} }
function staticTypeModuleUrl(value: any): string { function staticTypeModuleUrl(value: any): string {
return isStaticType(value) ? value['moduleId'] : null; return isStaticType(value) ? value['filePath'] : null;
} }
function calcTemplateBaseUrl(reflector: ReflectorReader, type: any, function componentModuleUrl(reflector: ReflectorReader, type: any,
cmpMetadata: ComponentMetadata): string { cmpMetadata: ComponentMetadata): string {
if (isStaticType(type)) { if (isStaticType(type)) {
return type['filePath']; return staticTypeModuleUrl(type);
} }
if (isPresent(cmpMetadata.moduleId)) { if (isPresent(cmpMetadata.moduleId)) {

View File

@ -15,6 +15,7 @@ import {TemplateParser} from './template_parser';
import {DirectiveNormalizer} from './directive_normalizer'; import {DirectiveNormalizer} from './directive_normalizer';
import {OutputEmitter} from './output/abstract_emitter'; import {OutputEmitter} from './output/abstract_emitter';
import * as o from './output/output_ast'; import * as o from './output/output_ast';
import {XHR} from './xhr';
import { import {
MODULE_SUFFIX, MODULE_SUFFIX,
@ -31,6 +32,10 @@ export class SourceModule {
constructor(public moduleUrl: string, public source: string) {} constructor(public moduleUrl: string, public source: string) {}
} }
export class StyleSheetSourceWithImports {
constructor(public source: SourceModule, public importedUrls: string[]) {}
}
export class NormalizedComponentWithViewDirectives { export class NormalizedComponentWithViewDirectives {
constructor(public component: CompileDirectiveMetadata, constructor(public component: CompileDirectiveMetadata,
public directives: CompileDirectiveMetadata[], public pipes: CompilePipeMetadata[]) {} public directives: CompileDirectiveMetadata[], public pipes: CompilePipeMetadata[]) {}
@ -39,7 +44,8 @@ export class NormalizedComponentWithViewDirectives {
export class OfflineCompiler { export class OfflineCompiler {
constructor(private _directiveNormalizer: DirectiveNormalizer, constructor(private _directiveNormalizer: DirectiveNormalizer,
private _templateParser: TemplateParser, private _styleCompiler: StyleCompiler, private _templateParser: TemplateParser, private _styleCompiler: StyleCompiler,
private _viewCompiler: ViewCompiler, private _outputEmitter: OutputEmitter) {} private _viewCompiler: ViewCompiler, private _outputEmitter: OutputEmitter,
private _xhr: XHR) {}
normalizeDirectiveMetadata(directive: CompileDirectiveMetadata): normalizeDirectiveMetadata(directive: CompileDirectiveMetadata):
Promise<CompileDirectiveMetadata> { Promise<CompileDirectiveMetadata> {
@ -80,15 +86,19 @@ export class OfflineCompiler {
return this._codegenSourceModule(moduleUrl, statements, exportedVars); return this._codegenSourceModule(moduleUrl, statements, exportedVars);
} }
compileStylesheet(stylesheetUrl: string, cssText: string): SourceModule[] { loadAndCompileStylesheet(stylesheetUrl: string, shim: boolean,
var plainStyles = this._styleCompiler.compileStylesheet(stylesheetUrl, cssText, false); suffix: string): Promise<StyleSheetSourceWithImports> {
var shimStyles = this._styleCompiler.compileStylesheet(stylesheetUrl, cssText, true); return this._xhr.get(stylesheetUrl)
return [ .then((cssText) => {
this._codegenSourceModule(_stylesModuleUrl(stylesheetUrl, false), var compileResult = this._styleCompiler.compileStylesheet(stylesheetUrl, cssText, shim);
_resolveStyleStatements(plainStyles), [plainStyles.stylesVar]), var importedUrls = [];
this._codegenSourceModule(_stylesModuleUrl(stylesheetUrl, true), compileResult.dependencies.forEach((dep) => {
_resolveStyleStatements(shimStyles), [shimStyles.stylesVar]) importedUrls.push(dep.moduleUrl);
]; dep.valuePlaceholder.moduleUrl = _stylesModuleUrl(dep.moduleUrl, dep.isShimmed, suffix);
});
return new StyleSheetSourceWithImports(
this._codgenStyles(stylesheetUrl, shim, suffix, compileResult), importedUrls);
});
} }
private _compileComponent(compMeta: CompileDirectiveMetadata, private _compileComponent(compMeta: CompileDirectiveMetadata,
@ -99,11 +109,18 @@ export class OfflineCompiler {
directives, pipes, compMeta.type.name); directives, pipes, compMeta.type.name);
var viewResult = this._viewCompiler.compileComponent(compMeta, parsedTemplate, var viewResult = this._viewCompiler.compileComponent(compMeta, parsedTemplate,
o.variable(styleResult.stylesVar), pipes); o.variable(styleResult.stylesVar), pipes);
ListWrapper.addAll(targetStatements, _resolveStyleStatements(styleResult)); ListWrapper.addAll(targetStatements,
_resolveStyleStatements(compMeta.type.moduleUrl, styleResult));
ListWrapper.addAll(targetStatements, _resolveViewStatements(viewResult)); ListWrapper.addAll(targetStatements, _resolveViewStatements(viewResult));
return viewResult.viewFactoryVar; return viewResult.viewFactoryVar;
} }
private _codgenStyles(inputUrl: string, shim: boolean, suffix: string,
stylesCompileResult: StylesCompileResult): SourceModule {
return this._codegenSourceModule(_stylesModuleUrl(inputUrl, shim, suffix),
stylesCompileResult.statements,
[stylesCompileResult.stylesVar]);
}
private _codegenSourceModule(moduleUrl: string, statements: o.Statement[], private _codegenSourceModule(moduleUrl: string, statements: o.Statement[],
exportedVars: string[]): SourceModule { exportedVars: string[]): SourceModule {
@ -119,21 +136,23 @@ function _resolveViewStatements(compileResult: ViewCompileResult): o.Statement[]
} }
function _resolveStyleStatements(compileResult: StylesCompileResult): o.Statement[] { function _resolveStyleStatements(containingModuleUrl: string,
compileResult: StylesCompileResult): o.Statement[] {
var containingSuffix = _splitSuffix(containingModuleUrl)[1];
compileResult.dependencies.forEach((dep) => { compileResult.dependencies.forEach((dep) => {
dep.valuePlaceholder.moduleUrl = _stylesModuleUrl(dep.sourceUrl, dep.isShimmed); dep.valuePlaceholder.moduleUrl =
_stylesModuleUrl(dep.moduleUrl, dep.isShimmed, containingSuffix);
}); });
return compileResult.statements; return compileResult.statements;
} }
function _templateModuleUrl(comp: CompileDirectiveMetadata): string { function _templateModuleUrl(comp: CompileDirectiveMetadata): string {
var moduleUrl = comp.type.moduleUrl; var urlWithSuffix = _splitSuffix(comp.type.moduleUrl);
var urlWithoutSuffix = moduleUrl.substring(0, moduleUrl.length - MODULE_SUFFIX.length); return `${urlWithSuffix[0]}.ngfactory${urlWithSuffix[1]}`;
return `${urlWithoutSuffix}.ngfactory${MODULE_SUFFIX}`;
} }
function _stylesModuleUrl(stylesheetUrl: string, shim: boolean): string { function _stylesModuleUrl(stylesheetUrl: string, shim: boolean, suffix: string): string {
return shim ? `${stylesheetUrl}.shim${MODULE_SUFFIX}` : `${stylesheetUrl}${MODULE_SUFFIX}`; return shim ? `${stylesheetUrl}.shim${suffix}` : `${stylesheetUrl}${suffix}`;
} }
function _assertComponent(meta: CompileDirectiveMetadata) { function _assertComponent(meta: CompileDirectiveMetadata) {
@ -141,3 +160,12 @@ function _assertComponent(meta: CompileDirectiveMetadata) {
throw new BaseException(`Could not compile '${meta.type.name}' because it is not a component.`); throw new BaseException(`Could not compile '${meta.type.name}' because it is not a component.`);
} }
} }
function _splitSuffix(path: string): string[] {
let lastDot = path.lastIndexOf('.');
if (lastDot !== -1) {
return [path.substring(0, lastDot), path.substring(lastDot)];
} else {
return [path, ''];
}
}

View File

@ -9,7 +9,7 @@ import {
CATCH_ERROR_VAR, CATCH_ERROR_VAR,
CATCH_STACK_VAR, CATCH_STACK_VAR,
} from './abstract_emitter'; } from './abstract_emitter';
import {getImportModulePath, ImportEnv} from './path_util'; import {ImportGenerator} from './path_util';
var _debugModuleUrl = 'asset://debug/lib'; var _debugModuleUrl = 'asset://debug/lib';
@ -37,7 +37,7 @@ export function debugOutputAstAsDart(ast: o.Statement | o.Expression | o.Type |
} }
export class DartEmitter implements OutputEmitter { export class DartEmitter implements OutputEmitter {
constructor() {} constructor(private _importGenerator: ImportGenerator) {}
emitStatements(moduleUrl: string, stmts: o.Statement[], exportedVars: string[]): string { emitStatements(moduleUrl: string, stmts: o.Statement[], exportedVars: string[]): string {
var srcParts = []; var srcParts = [];
// Note: We are not creating a library here as Dart does not need it. // Note: We are not creating a library here as Dart does not need it.
@ -49,7 +49,7 @@ export class DartEmitter implements OutputEmitter {
converter.importsWithPrefixes.forEach((prefix, importedModuleUrl) => { converter.importsWithPrefixes.forEach((prefix, importedModuleUrl) => {
srcParts.push( srcParts.push(
`import '${getImportModulePath(moduleUrl, importedModuleUrl, ImportEnv.Dart)}' as ${prefix};`); `import '${this._importGenerator.getImportPath(moduleUrl, importedModuleUrl)}' as ${prefix};`);
}); });
srcParts.push(ctx.toSource()); srcParts.push(ctx.toSource());
return srcParts.join('\n'); return srcParts.join('\n');

View File

@ -0,0 +1,52 @@
import {BaseException} from '../../src/facade/exceptions';
import {isPresent, isBlank, RegExpWrapper, Math} from '../../src/facade/lang';
import {Injectable} from '@angular/core';
import {AssetUrl, ImportGenerator} from './path_util';
var _PATH_SEP = '/';
var _PATH_SEP_RE = /\//g;
@Injectable()
export class DartImportGenerator implements ImportGenerator {
getImportPath(moduleUrlStr: string, importedUrlStr: string): string {
var moduleUrl = AssetUrl.parse(moduleUrlStr, false);
var importedUrl = AssetUrl.parse(importedUrlStr, true);
if (isBlank(importedUrl)) {
return importedUrlStr;
}
// Try to create a relative path first
if (moduleUrl.firstLevelDir == importedUrl.firstLevelDir &&
moduleUrl.packageName == importedUrl.packageName) {
return getRelativePath(moduleUrl.modulePath, importedUrl.modulePath);
} else if (importedUrl.firstLevelDir == 'lib') {
return `package:${importedUrl.packageName}/${importedUrl.modulePath}`;
}
throw new BaseException(`Can't import url ${importedUrlStr} from ${moduleUrlStr}`);
}
}
export function getRelativePath(modulePath: string, importedPath: string): string {
var moduleParts = modulePath.split(_PATH_SEP_RE);
var importedParts = importedPath.split(_PATH_SEP_RE);
var longestPrefix = getLongestPathSegmentPrefix(moduleParts, importedParts);
var resultParts = [];
var goParentCount = moduleParts.length - 1 - longestPrefix;
for (var i = 0; i < goParentCount; i++) {
resultParts.push('..');
}
for (var i = longestPrefix; i < importedParts.length; i++) {
resultParts.push(importedParts[i]);
}
return resultParts.join(_PATH_SEP);
}
export function getLongestPathSegmentPrefix(arr1: string[], arr2: string[]): number {
var prefixSize = 0;
var minLen = Math.min(arr1.length, arr2.length);
while (prefixSize < minLen && arr1[prefixSize] == arr2[prefixSize]) {
prefixSize++;
}
return prefixSize;
}

View File

@ -10,10 +10,10 @@ import {
import {BaseException} from '@angular/core'; import {BaseException} from '@angular/core';
import {OutputEmitter, EmitterVisitorContext} from './abstract_emitter'; import {OutputEmitter, EmitterVisitorContext} from './abstract_emitter';
import {AbstractJsEmitterVisitor} from './abstract_js_emitter'; import {AbstractJsEmitterVisitor} from './abstract_js_emitter';
import {getImportModulePath, ImportEnv} from './path_util'; import {ImportGenerator} from './path_util';
export class JavaScriptEmitter implements OutputEmitter { export class JavaScriptEmitter implements OutputEmitter {
constructor() {} constructor(private _importGenerator: ImportGenerator) {}
emitStatements(moduleUrl: string, stmts: o.Statement[], exportedVars: string[]): string { emitStatements(moduleUrl: string, stmts: o.Statement[], exportedVars: string[]): string {
var converter = new JsEmitterVisitor(moduleUrl); var converter = new JsEmitterVisitor(moduleUrl);
var ctx = EmitterVisitorContext.createRoot(exportedVars); var ctx = EmitterVisitorContext.createRoot(exportedVars);
@ -21,8 +21,9 @@ export class JavaScriptEmitter implements OutputEmitter {
var srcParts = []; var srcParts = [];
converter.importsWithPrefixes.forEach((prefix, importedModuleUrl) => { converter.importsWithPrefixes.forEach((prefix, importedModuleUrl) => {
// Note: can't write the real word for import as it screws up system.js auto detection... // Note: can't write the real word for import as it screws up system.js auto detection...
srcParts.push(`var ${prefix} = req` + srcParts.push(
`uire('${getImportModulePath(moduleUrl, importedModuleUrl, ImportEnv.JS)}');`); `var ${prefix} = req` +
`uire('${this._importGenerator.getImportPath(moduleUrl, importedModuleUrl)}');`);
}); });
srcParts.push(ctx.toSource()); srcParts.push(ctx.toSource());
return srcParts.join('\n'); return srcParts.join('\n');

View File

@ -1,44 +1,24 @@
import {BaseException} from '../../src/facade/exceptions'; import {BaseException} from '../../src/facade/exceptions';
import {isPresent, isBlank, RegExpWrapper, Math} from '../../src/facade/lang'; import {isPresent, isBlank, RegExpWrapper, Math} from '../../src/facade/lang';
import {Injectable} from '@angular/core';
// asset:<package-name>/<realm>/<path-to-module> // asset:<package-name>/<realm>/<path-to-module>
var _ASSET_URL_RE = /asset:([^\/]+)\/([^\/]+)\/(.+)/g; var _ASSET_URL_RE = /asset:([^\/]+)\/([^\/]+)\/(.+)/g;
var _PATH_SEP = '/';
var _PATH_SEP_RE = /\//g;
export enum ImportEnv {
Dart,
JS
}
/** /**
* Returns the module path to use for an import. * Interface that defines how import statements should be generated.
*/ */
export function getImportModulePath(moduleUrlStr: string, importedUrlStr: string, export abstract class ImportGenerator {
importEnv: ImportEnv): string { static parseAssetUrl(url: string): AssetUrl { return AssetUrl.parse(url); }
var absolutePathPrefix: string = importEnv === ImportEnv.Dart ? `package:` : '';
var moduleUrl = _AssetUrl.parse(moduleUrlStr, false);
var importedUrl = _AssetUrl.parse(importedUrlStr, true);
if (isBlank(importedUrl)) {
return importedUrlStr;
}
// Try to create a relative path first abstract getImportPath(moduleUrlStr: string, importedUrlStr: string): string;
if (moduleUrl.firstLevelDir == importedUrl.firstLevelDir &&
moduleUrl.packageName == importedUrl.packageName) {
return getRelativePath(moduleUrl.modulePath, importedUrl.modulePath, importEnv);
} else if (importedUrl.firstLevelDir == 'lib') {
return `${absolutePathPrefix}${importedUrl.packageName}/${importedUrl.modulePath}`;
}
throw new BaseException(`Can't import url ${importedUrlStr} from ${moduleUrlStr}`);
} }
class _AssetUrl { export class AssetUrl {
static parse(url: string, allowNonMatching: boolean): _AssetUrl { static parse(url: string, allowNonMatching: boolean = true): AssetUrl {
var match = RegExpWrapper.firstMatch(_ASSET_URL_RE, url); var match = RegExpWrapper.firstMatch(_ASSET_URL_RE, url);
if (isPresent(match)) { if (isPresent(match)) {
return new _AssetUrl(match[1], match[2], match[3]); return new AssetUrl(match[1], match[2], match[3]);
} }
if (allowNonMatching) { if (allowNonMatching) {
return null; return null;
@ -49,32 +29,3 @@ class _AssetUrl {
constructor(public packageName: string, public firstLevelDir: string, public modulePath: string) { constructor(public packageName: string, public firstLevelDir: string, public modulePath: string) {
} }
} }
export function getRelativePath(modulePath: string, importedPath: string,
importEnv: ImportEnv): string {
var moduleParts = modulePath.split(_PATH_SEP_RE);
var importedParts = importedPath.split(_PATH_SEP_RE);
var longestPrefix = getLongestPathSegmentPrefix(moduleParts, importedParts);
var resultParts = [];
var goParentCount = moduleParts.length - 1 - longestPrefix;
for (var i = 0; i < goParentCount; i++) {
resultParts.push('..');
}
if (goParentCount <= 0 && importEnv === ImportEnv.JS) {
resultParts.push('.');
}
for (var i = longestPrefix; i < importedParts.length; i++) {
resultParts.push(importedParts[i]);
}
return resultParts.join(_PATH_SEP);
}
export function getLongestPathSegmentPrefix(arr1: string[], arr2: string[]): number {
var prefixSize = 0;
var minLen = Math.min(arr1.length, arr2.length);
while (prefixSize < minLen && arr1[prefixSize] == arr2[prefixSize]) {
prefixSize++;
}
return prefixSize;
}

View File

@ -9,7 +9,7 @@ import {
CATCH_ERROR_VAR, CATCH_ERROR_VAR,
CATCH_STACK_VAR CATCH_STACK_VAR
} from './abstract_emitter'; } from './abstract_emitter';
import {getImportModulePath, ImportEnv} from './path_util'; import {ImportGenerator} from './path_util';
var _debugModuleUrl = 'asset://debug/lib'; var _debugModuleUrl = 'asset://debug/lib';
@ -38,7 +38,7 @@ export function debugOutputAstAsTypeScript(ast: o.Statement | o.Expression | o.T
} }
export class TypeScriptEmitter implements OutputEmitter { export class TypeScriptEmitter implements OutputEmitter {
constructor() {} constructor(private _importGenerator: ImportGenerator) {}
emitStatements(moduleUrl: string, stmts: o.Statement[], exportedVars: string[]): string { emitStatements(moduleUrl: string, stmts: o.Statement[], exportedVars: string[]): string {
var converter = new _TsEmitterVisitor(moduleUrl); var converter = new _TsEmitterVisitor(moduleUrl);
var ctx = EmitterVisitorContext.createRoot(exportedVars); var ctx = EmitterVisitorContext.createRoot(exportedVars);
@ -48,7 +48,7 @@ export class TypeScriptEmitter implements OutputEmitter {
// Note: can't write the real word for import as it screws up system.js auto detection... // Note: can't write the real word for import as it screws up system.js auto detection...
srcParts.push( srcParts.push(
`imp` + `imp` +
`ort * as ${prefix} from '${getImportModulePath(moduleUrl, importedModuleUrl, ImportEnv.JS)}';`); `ort * as ${prefix} from '${this._importGenerator.getImportPath(moduleUrl, importedModuleUrl)}';`);
}); });
srcParts.push(ctx.toSource()); srcParts.push(ctx.toSource());
return srcParts.join('\n'); return srcParts.join('\n');

View File

@ -160,9 +160,9 @@ export class RuntimeCompiler implements ComponentResolver {
var dep = result.dependencies[i]; var dep = result.dependencies[i];
var cssText = cssTexts[i]; var cssText = cssTexts[i];
var nestedCompileResult = var nestedCompileResult =
this._styleCompiler.compileStylesheet(dep.sourceUrl, cssText, dep.isShimmed); this._styleCompiler.compileStylesheet(dep.moduleUrl, cssText, dep.isShimmed);
nestedCompileResultPromises.push( nestedCompileResultPromises.push(
this._resolveStylesCompileResult(dep.sourceUrl, nestedCompileResult)); this._resolveStylesCompileResult(dep.moduleUrl, nestedCompileResult));
} }
return PromiseWrapper.all(nestedCompileResultPromises); return PromiseWrapper.all(nestedCompileResultPromises);
}) })
@ -182,10 +182,10 @@ export class RuntimeCompiler implements ComponentResolver {
} }
private _loadStylesheetDep(dep: StylesCompileDependency): Promise<string> { private _loadStylesheetDep(dep: StylesCompileDependency): Promise<string> {
var cacheKey = `${dep.sourceUrl}${dep.isShimmed ? '.shim' : ''}`; var cacheKey = `${dep.moduleUrl}${dep.isShimmed ? '.shim' : ''}`;
var cssTextPromise = this._styleCache.get(cacheKey); var cssTextPromise = this._styleCache.get(cacheKey);
if (isBlank(cssTextPromise)) { if (isBlank(cssTextPromise)) {
cssTextPromise = this._xhr.get(dep.sourceUrl); cssTextPromise = this._xhr.get(dep.moduleUrl);
this._styleCache.set(cacheKey, cssTextPromise); this._styleCache.set(cacheKey, cssTextPromise);
} }
return cssTextPromise; return cssTextPromise;

View File

@ -11,7 +11,7 @@ const HOST_ATTR = /*@ts2dart_const*/ `_nghost-${COMPONENT_VARIABLE}`;
const CONTENT_ATTR = /*@ts2dart_const*/ `_ngcontent-${COMPONENT_VARIABLE}`; const CONTENT_ATTR = /*@ts2dart_const*/ `_ngcontent-${COMPONENT_VARIABLE}`;
export class StylesCompileDependency { export class StylesCompileDependency {
constructor(public sourceUrl: string, public isShimmed: boolean, constructor(public moduleUrl: string, public isShimmed: boolean,
public valuePlaceholder: CompileIdentifierMetadata) {} public valuePlaceholder: CompileIdentifierMetadata) {}
} }

View File

@ -60,8 +60,7 @@ export function main() {
templateUrl: 'someTemplateUrl', templateUrl: 'someTemplateUrl',
styles: ['someStyle'], styles: ['someStyle'],
styleUrls: ['someStyleUrl'], styleUrls: ['someStyleUrl'],
ngContentSelectors: ['*'], ngContentSelectors: ['*']
baseUrl: 'someBaseUrl'
}); });
fullDirectiveMeta = CompileDirectiveMetadata.create({ fullDirectiveMeta = CompileDirectiveMetadata.create({
selector: 'someSelector', selector: 'someSelector',

View File

@ -21,10 +21,15 @@ import {TEST_PROVIDERS} from './test_bindings';
export function main() { export function main() {
describe('DirectiveNormalizer', () => { describe('DirectiveNormalizer', () => {
var dirType: CompileTypeMetadata; var dirType: CompileTypeMetadata;
var dirTypeWithHttpUrl: CompileTypeMetadata;
beforeEachProviders(() => TEST_PROVIDERS); beforeEachProviders(() => TEST_PROVIDERS);
beforeEach(() => { dirType = new CompileTypeMetadata({name: 'SomeComp'}); }); beforeEach(() => {
dirType = new CompileTypeMetadata({moduleUrl: 'package:some/module/a.js', name: 'SomeComp'});
dirTypeWithHttpUrl =
new CompileTypeMetadata({moduleUrl: 'http://some/module/a.js', name: 'SomeComp'});
});
describe('loadTemplate', () => { describe('loadTemplate', () => {
describe('inline template', () => { describe('inline template', () => {
@ -36,8 +41,7 @@ export function main() {
template: 'a', template: 'a',
templateUrl: null, templateUrl: null,
styles: [], styles: [],
styleUrls: ['test.css'], styleUrls: ['test.css']
baseUrl: 'package:some/module/a.js'
})) }))
.then((template: CompileTemplateMetadata) => { .then((template: CompileTemplateMetadata) => {
expect(template.template).toEqual('a'); expect(template.template).toEqual('a');
@ -46,7 +50,7 @@ export function main() {
}); });
})); }));
it('should resolve styles on the annotation against the baseUrl', it('should resolve styles on the annotation against the moduleUrl',
inject([AsyncTestCompleter, DirectiveNormalizer], inject([AsyncTestCompleter, DirectiveNormalizer],
(async, normalizer: DirectiveNormalizer) => { (async, normalizer: DirectiveNormalizer) => {
normalizer.normalizeTemplate(dirType, new CompileTemplateMetadata({ normalizer.normalizeTemplate(dirType, new CompileTemplateMetadata({
@ -54,8 +58,7 @@ export function main() {
template: '', template: '',
templateUrl: null, templateUrl: null,
styles: [], styles: [],
styleUrls: ['test.css'], styleUrls: ['test.css']
baseUrl: 'package:some/module/a.js'
})) }))
.then((template: CompileTemplateMetadata) => { .then((template: CompileTemplateMetadata) => {
expect(template.styleUrls).toEqual(['package:some/module/test.css']); expect(template.styleUrls).toEqual(['package:some/module/test.css']);
@ -63,7 +66,7 @@ export function main() {
}); });
})); }));
it('should resolve styles in the template against the baseUrl', it('should resolve styles in the template against the moduleUrl',
inject([AsyncTestCompleter, DirectiveNormalizer], inject([AsyncTestCompleter, DirectiveNormalizer],
(async, normalizer: DirectiveNormalizer) => { (async, normalizer: DirectiveNormalizer) => {
normalizer.normalizeTemplate(dirType, new CompileTemplateMetadata({ normalizer.normalizeTemplate(dirType, new CompileTemplateMetadata({
@ -71,8 +74,7 @@ export function main() {
template: '<style>@import test.css</style>', template: '<style>@import test.css</style>',
templateUrl: null, templateUrl: null,
styles: [], styles: [],
styleUrls: [], styleUrls: []
baseUrl: 'package:some/module/a.js'
})) }))
.then((template: CompileTemplateMetadata) => { .then((template: CompileTemplateMetadata) => {
expect(template.styleUrls).toEqual(['package:some/module/test.css']); expect(template.styleUrls).toEqual(['package:some/module/test.css']);
@ -83,7 +85,7 @@ export function main() {
describe('templateUrl', () => { describe('templateUrl', () => {
it('should load a template from a url that is resolved against baseUrl', it('should load a template from a url that is resolved against moduleUrl',
inject([AsyncTestCompleter, DirectiveNormalizer, XHR], inject([AsyncTestCompleter, DirectiveNormalizer, XHR],
(async, normalizer: DirectiveNormalizer, xhr: MockXHR) => { (async, normalizer: DirectiveNormalizer, xhr: MockXHR) => {
xhr.expect('package:some/module/sometplurl.html', 'a'); xhr.expect('package:some/module/sometplurl.html', 'a');
@ -92,8 +94,7 @@ export function main() {
template: null, template: null,
templateUrl: 'sometplurl.html', templateUrl: 'sometplurl.html',
styles: [], styles: [],
styleUrls: ['test.css'], styleUrls: ['test.css']
baseUrl: 'package:some/module/a.js'
})) }))
.then((template: CompileTemplateMetadata) => { .then((template: CompileTemplateMetadata) => {
expect(template.template).toEqual('a'); expect(template.template).toEqual('a');
@ -104,7 +105,7 @@ export function main() {
xhr.flush(); xhr.flush();
})); }));
it('should resolve styles on the annotation against the baseUrl', it('should resolve styles on the annotation against the moduleUrl',
inject([AsyncTestCompleter, DirectiveNormalizer, XHR], inject([AsyncTestCompleter, DirectiveNormalizer, XHR],
(async, normalizer: DirectiveNormalizer, xhr: MockXHR) => { (async, normalizer: DirectiveNormalizer, xhr: MockXHR) => {
xhr.expect('package:some/module/tpl/sometplurl.html', ''); xhr.expect('package:some/module/tpl/sometplurl.html', '');
@ -113,8 +114,7 @@ export function main() {
template: null, template: null,
templateUrl: 'tpl/sometplurl.html', templateUrl: 'tpl/sometplurl.html',
styles: [], styles: [],
styleUrls: ['test.css'], styleUrls: ['test.css']
baseUrl: 'package:some/module/a.js'
})) }))
.then((template: CompileTemplateMetadata) => { .then((template: CompileTemplateMetadata) => {
expect(template.styleUrls).toEqual(['package:some/module/test.css']); expect(template.styleUrls).toEqual(['package:some/module/test.css']);
@ -133,8 +133,7 @@ export function main() {
template: null, template: null,
templateUrl: 'tpl/sometplurl.html', templateUrl: 'tpl/sometplurl.html',
styles: [], styles: [],
styleUrls: [], styleUrls: []
baseUrl: 'package:some/module/a.js'
})) }))
.then((template: CompileTemplateMetadata) => { .then((template: CompileTemplateMetadata) => {
expect(template.styleUrls).toEqual(['package:some/module/tpl/test.css']); expect(template.styleUrls).toEqual(['package:some/module/tpl/test.css']);
@ -160,50 +159,36 @@ export function main() {
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => { inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
var viewEncapsulation = ViewEncapsulation.Native; var viewEncapsulation = ViewEncapsulation.Native;
var template = normalizer.normalizeLoadedTemplate(dirType, new CompileTemplateMetadata({ var template = normalizer.normalizeLoadedTemplate(
encapsulation: viewEncapsulation, dirType, new CompileTemplateMetadata(
styles: [], {encapsulation: viewEncapsulation, styles: [], styleUrls: []}),
styleUrls: [], '', 'package:some/module/');
baseUrl: 'package:some/module/a.js'
}),
'', 'package:some/module/');
expect(template.encapsulation).toBe(viewEncapsulation); expect(template.encapsulation).toBe(viewEncapsulation);
})); }));
it('should keep the template as html', it('should keep the template as html',
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => { inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
var template = normalizer.normalizeLoadedTemplate(dirType, new CompileTemplateMetadata({ var template = normalizer.normalizeLoadedTemplate(
encapsulation: null, dirType,
styles: [], new CompileTemplateMetadata({encapsulation: null, styles: [], styleUrls: []}), 'a',
styleUrls: [], 'package:some/module/');
baseUrl: 'package:some/module/a.js'
}),
'a', 'package:some/module/');
expect(template.template).toEqual('a') expect(template.template).toEqual('a')
})); }));
it('should collect ngContent', it('should collect ngContent',
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => { inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
var template = normalizer.normalizeLoadedTemplate(dirType, new CompileTemplateMetadata({ var template = normalizer.normalizeLoadedTemplate(
encapsulation: null, dirType,
styles: [], new CompileTemplateMetadata({encapsulation: null, styles: [], styleUrls: []}),
styleUrls: [], '<ng-content select="a"></ng-content>', 'package:some/module/');
baseUrl: 'package:some/module/a.js'
}),
'<ng-content select="a"></ng-content>',
'package:some/module/');
expect(template.ngContentSelectors).toEqual(['a']); expect(template.ngContentSelectors).toEqual(['a']);
})); }));
it('should normalize ngContent wildcard selector', it('should normalize ngContent wildcard selector',
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => { inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
var template = normalizer.normalizeLoadedTemplate( var template = normalizer.normalizeLoadedTemplate(
dirType, new CompileTemplateMetadata({ dirType,
encapsulation: null, new CompileTemplateMetadata({encapsulation: null, styles: [], styleUrls: []}),
styles: [],
styleUrls: [],
baseUrl: 'package:some/module/a.js'
}),
'<ng-content></ng-content><ng-content select></ng-content><ng-content select="*"></ng-content>', '<ng-content></ng-content><ng-content select></ng-content><ng-content select="*"></ng-content>',
'package:some/module/'); 'package:some/module/');
expect(template.ngContentSelectors).toEqual(['*', '*', '*']); expect(template.ngContentSelectors).toEqual(['*', '*', '*']);
@ -211,91 +196,64 @@ export function main() {
it('should collect top level styles in the template', it('should collect top level styles in the template',
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => { inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
var template = var template = normalizer.normalizeLoadedTemplate(
normalizer.normalizeLoadedTemplate(dirType, new CompileTemplateMetadata({ dirType,
encapsulation: null, new CompileTemplateMetadata({encapsulation: null, styles: [], styleUrls: []}),
styles: [], '<style>a</style>', 'package:some/module/');
styleUrls: [],
baseUrl: 'package:some/module/a.js'
}),
'<style>a</style>', 'package:some/module/');
expect(template.styles).toEqual(['a']); expect(template.styles).toEqual(['a']);
})); }));
it('should collect styles inside in elements', it('should collect styles inside in elements',
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => { inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
var template = normalizer.normalizeLoadedTemplate(dirType, new CompileTemplateMetadata({ var template = normalizer.normalizeLoadedTemplate(
encapsulation: null, dirType,
styles: [], new CompileTemplateMetadata({encapsulation: null, styles: [], styleUrls: []}),
styleUrls: [], '<div><style>a</style></div>', 'package:some/module/');
baseUrl: 'package:some/module/a.js'
}),
'<div><style>a</style></div>',
'package:some/module/');
expect(template.styles).toEqual(['a']); expect(template.styles).toEqual(['a']);
})); }));
it('should collect styleUrls in the template', it('should collect styleUrls in the template',
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => { inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
var template = normalizer.normalizeLoadedTemplate(dirType, new CompileTemplateMetadata({ var template = normalizer.normalizeLoadedTemplate(
encapsulation: null, dirType,
styles: [], new CompileTemplateMetadata({encapsulation: null, styles: [], styleUrls: []}),
styleUrls: [], '<link rel="stylesheet" href="aUrl">', 'package:some/module/');
baseUrl: 'package:some/module/a.js'
}),
'<link rel="stylesheet" href="aUrl">',
'package:some/module/');
expect(template.styleUrls).toEqual(['package:some/module/aUrl']); expect(template.styleUrls).toEqual(['package:some/module/aUrl']);
})); }));
it('should collect styleUrls in elements', it('should collect styleUrls in elements',
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => { inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
var template = normalizer.normalizeLoadedTemplate( var template = normalizer.normalizeLoadedTemplate(
dirType, new CompileTemplateMetadata({ dirType,
encapsulation: null, new CompileTemplateMetadata({encapsulation: null, styles: [], styleUrls: []}),
styles: [],
styleUrls: [],
baseUrl: 'package:some/module/a.js'
}),
'<div><link rel="stylesheet" href="aUrl"></div>', 'package:some/module/'); '<div><link rel="stylesheet" href="aUrl"></div>', 'package:some/module/');
expect(template.styleUrls).toEqual(['package:some/module/aUrl']); expect(template.styleUrls).toEqual(['package:some/module/aUrl']);
})); }));
it('should ignore link elements with non stylesheet rel attribute', it('should ignore link elements with non stylesheet rel attribute',
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => { inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
var template = normalizer.normalizeLoadedTemplate(dirType, new CompileTemplateMetadata({ var template = normalizer.normalizeLoadedTemplate(
encapsulation: null, dirType,
styles: [], new CompileTemplateMetadata({encapsulation: null, styles: [], styleUrls: []}),
styleUrls: [], '<link href="b" rel="a">', 'package:some/module/');
baseUrl: 'package:some/module/a.js'
}),
'<link href="b" rel="a">',
'package:some/module/');
expect(template.styleUrls).toEqual([]); expect(template.styleUrls).toEqual([]);
})); }));
it('should ignore link elements with absolute urls but non package: scheme', it('should ignore link elements with absolute urls but non package: scheme',
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => { inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
var template = normalizer.normalizeLoadedTemplate( var template = normalizer.normalizeLoadedTemplate(
dirType, new CompileTemplateMetadata({ dirType,
encapsulation: null, new CompileTemplateMetadata({encapsulation: null, styles: [], styleUrls: []}),
styles: [],
styleUrls: [],
baseUrl: 'package:some/module/a.js'
}),
'<link href="http://some/external.css" rel="stylesheet">', 'package:some/module/'); '<link href="http://some/external.css" rel="stylesheet">', 'package:some/module/');
expect(template.styleUrls).toEqual([]); expect(template.styleUrls).toEqual([]);
})); }));
it('should extract @import style urls into styleAbsUrl', it('should extract @import style urls into styleAbsUrl',
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => { inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
var template = normalizer.normalizeLoadedTemplate(dirType, new CompileTemplateMetadata({ var template = normalizer.normalizeLoadedTemplate(
encapsulation: null, dirType, new CompileTemplateMetadata(
styles: ['@import "test.css";'], {encapsulation: null, styles: ['@import "test.css";'], styleUrls: []}),
styleUrls: [], '', 'package:some/module/id');
baseUrl: 'package:some/module/a.js'
}),
'', 'package:some/module/id');
expect(template.styles).toEqual(['']); expect(template.styles).toEqual(['']);
expect(template.styleUrls).toEqual(['package:some/module/test.css']); expect(template.styleUrls).toEqual(['package:some/module/test.css']);
})); }));
@ -306,8 +264,7 @@ export function main() {
dirType, new CompileTemplateMetadata({ dirType, new CompileTemplateMetadata({
encapsulation: null, encapsulation: null,
styles: ['.foo{background-image: url(\'double.jpg\');'], styles: ['.foo{background-image: url(\'double.jpg\');'],
styleUrls: [], styleUrls: []
baseUrl: 'package:some/module/a.js'
}), }),
'', 'package:some/module/id'); '', 'package:some/module/id');
expect(template.styles).toEqual(['.foo{background-image: url(\'double.jpg\');']); expect(template.styles).toEqual(['.foo{background-image: url(\'double.jpg\');']);
@ -315,52 +272,38 @@ export function main() {
it('should resolve relative style urls in styleUrls', it('should resolve relative style urls in styleUrls',
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => { inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
var template = normalizer.normalizeLoadedTemplate(dirType, new CompileTemplateMetadata({ var template = normalizer.normalizeLoadedTemplate(
encapsulation: null, dirType, new CompileTemplateMetadata(
styles: [], {encapsulation: null, styles: [], styleUrls: ['test.css']}),
styleUrls: ['test.css'], '', 'package:some/module/id');
baseUrl: 'package:some/module/a.js'
}),
'', 'package:some/module/id');
expect(template.styles).toEqual([]); expect(template.styles).toEqual([]);
expect(template.styleUrls).toEqual(['package:some/module/test.css']); expect(template.styleUrls).toEqual(['package:some/module/test.css']);
})); }));
it('should resolve relative style urls in styleUrls with http directive url', it('should resolve relative style urls in styleUrls with http directive url',
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => { inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
var template = normalizer.normalizeLoadedTemplate(dirType, new CompileTemplateMetadata({ var template = normalizer.normalizeLoadedTemplate(
encapsulation: null, dirTypeWithHttpUrl, new CompileTemplateMetadata(
styles: [], {encapsulation: null, styles: [], styleUrls: ['test.css']}),
styleUrls: ['test.css'], '', 'http://some/module/id');
baseUrl: 'http://some/module/a.js'
}),
'', 'http://some/module/id');
expect(template.styles).toEqual([]); expect(template.styles).toEqual([]);
expect(template.styleUrls).toEqual(['http://some/module/test.css']); expect(template.styleUrls).toEqual(['http://some/module/test.css']);
})); }));
it('should normalize ViewEncapsulation.Emulated to ViewEncapsulation.None if there are no styles nor stylesheets', it('should normalize ViewEncapsulation.Emulated to ViewEncapsulation.None if there are no styles nor stylesheets',
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => { inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
var template = var template = normalizer.normalizeLoadedTemplate(
normalizer.normalizeLoadedTemplate(dirType, new CompileTemplateMetadata({ dirType, new CompileTemplateMetadata(
encapsulation: ViewEncapsulation.Emulated, {encapsulation: ViewEncapsulation.Emulated, styles: [], styleUrls: []}),
styles: [], '', 'package:some/module/id');
styleUrls: [],
baseUrl: 'package:some/module/a.js'
}),
'', 'package:some/module/id');
expect(template.encapsulation).toEqual(ViewEncapsulation.None); expect(template.encapsulation).toEqual(ViewEncapsulation.None);
})); }));
it('should ignore ng-content in elements with ngNonBindable', it('should ignore ng-content in elements with ngNonBindable',
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => { inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
var template = normalizer.normalizeLoadedTemplate( var template = normalizer.normalizeLoadedTemplate(
dirType, new CompileTemplateMetadata({ dirType,
encapsulation: null, new CompileTemplateMetadata({encapsulation: null, styles: [], styleUrls: []}),
styles: [],
styleUrls: [],
baseUrl: 'package:some/module/a.js'
}),
'<div ngNonBindable><ng-content select="a"></ng-content></div>', '<div ngNonBindable><ng-content select="a"></ng-content></div>',
'package:some/module/'); 'package:some/module/');
expect(template.ngContentSelectors).toEqual([]); expect(template.ngContentSelectors).toEqual([]);
@ -369,12 +312,8 @@ export function main() {
it('should still collect <style> in elements with ngNonBindable', it('should still collect <style> in elements with ngNonBindable',
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => { inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
var template = normalizer.normalizeLoadedTemplate( var template = normalizer.normalizeLoadedTemplate(
dirType, new CompileTemplateMetadata({ dirType,
encapsulation: null, new CompileTemplateMetadata({encapsulation: null, styles: [], styleUrls: []}),
styles: [],
styleUrls: [],
baseUrl: 'package:some/module/a.js'
}),
'<div ngNonBindable><style>div {color:red}</style></div>', 'package:some/module/'); '<div ngNonBindable><style>div {color:red}</style></div>', 'package:some/module/');
expect(template.styles).toEqual(['div {color:red}']); expect(template.styles).toEqual(['div {color:red}']);
})); }));

View File

@ -62,13 +62,12 @@ export function main() {
expect(meta.template.styleUrls).toEqual(['someStyleUrl']); expect(meta.template.styleUrls).toEqual(['someStyleUrl']);
expect(meta.template.template).toEqual('someTemplate'); expect(meta.template.template).toEqual('someTemplate');
expect(meta.template.templateUrl).toEqual('someTemplateUrl'); expect(meta.template.templateUrl).toEqual('someTemplateUrl');
expect(meta.template.baseUrl).toEqual(`package:someModuleId${MODULE_SUFFIX}`);
})); }));
it('should use the moduleUrl from the reflector if none is given', it('should use the moduleUrl from the reflector if none is given',
inject([CompileMetadataResolver], (resolver: CompileMetadataResolver) => { inject([CompileMetadataResolver], (resolver: CompileMetadataResolver) => {
var value: string = var value: string =
resolver.getDirectiveMetadata(ComponentWithoutModuleId).template.baseUrl; resolver.getDirectiveMetadata(ComponentWithoutModuleId).type.moduleUrl;
var expectedEndValue = var expectedEndValue =
IS_DART ? 'test/compiler/metadata_resolver_spec.dart' : './ComponentWithoutModuleId'; IS_DART ? 'test/compiler/metadata_resolver_spec.dart' : './ComponentWithoutModuleId';
expect(value.endsWith(expectedEndValue)).toBe(true); expect(value.endsWith(expectedEndValue)).toBe(true);

View File

@ -2,15 +2,17 @@
import {print, IS_DART} from '../src/facade/lang'; import {print, IS_DART} from '../src/facade/lang';
import {TypeScriptEmitter} from '@angular/compiler/src/output/ts_emitter'; import {TypeScriptEmitter} from '@angular/compiler/src/output/ts_emitter';
import {DartEmitter} from '@angular/compiler/src/output/dart_emitter'; import {DartEmitter} from '@angular/compiler/src/output/dart_emitter';
import {DartImportGenerator} from '@angular/compiler/src/output/dart_imports';
import * as o from '@angular/compiler/src/output/output_ast'; import * as o from '@angular/compiler/src/output/output_ast';
import {compileComp, compAMetadata} from './offline_compiler_util'; import {compileComp, compAMetadata} from './offline_compiler_util';
import {ComponentFactory} from '@angular/core/src/linker/component_factory'; import {ComponentFactory} from '@angular/core/src/linker/component_factory';
import {CompA} from './offline_compiler_util'; import {CompA, SimpleJsImportGenerator} from './offline_compiler_util';
export const CompANgFactory: ComponentFactory<CompA> = null; export const CompANgFactory: ComponentFactory<CompA> = null;
export function emit(): Promise<string> { export function emit(): Promise<string> {
var emitter = IS_DART ? new DartEmitter() : new TypeScriptEmitter(); var emitter = IS_DART ? new DartEmitter(new DartImportGenerator()) :
new TypeScriptEmitter(new SimpleJsImportGenerator());
return compileComp(emitter, compAMetadata); return compileComp(emitter, compAMetadata);
} }

View File

@ -3,12 +3,12 @@ import {print} from '../src/facade/lang';
import {JavaScriptEmitter} from '@angular/compiler/src/output/js_emitter'; import {JavaScriptEmitter} from '@angular/compiler/src/output/js_emitter';
import {compileComp, compAMetadata} from './offline_compiler_util'; import {compileComp, compAMetadata} from './offline_compiler_util';
import {ComponentFactory} from '@angular/core/src/linker/component_factory'; import {ComponentFactory} from '@angular/core/src/linker/component_factory';
import {CompA} from './offline_compiler_util'; import {CompA, SimpleJsImportGenerator} from './offline_compiler_util';
export const CompANgFactory: ComponentFactory<CompA> = null; export const CompANgFactory: ComponentFactory<CompA> = null;
export function emit() { export function emit() {
var emitter = new JavaScriptEmitter(); var emitter = new JavaScriptEmitter(new SimpleJsImportGenerator());
return compileComp(emitter, compAMetadata); return compileComp(emitter, compAMetadata);
} }

View File

@ -1,4 +1,4 @@
import {print, IS_DART} from '../src/facade/lang'; import {print, isPresent, IS_DART} from '../src/facade/lang';
import {OutputEmitter} from '@angular/compiler/src/output/abstract_emitter'; import {OutputEmitter} from '@angular/compiler/src/output/abstract_emitter';
import {Console} from '../core_private'; import {Console} from '../core_private';
@ -19,6 +19,7 @@ import {createOfflineCompileUrlResolver} from '@angular/compiler/src/url_resolve
import {MockSchemaRegistry} from '../testing/schema_registry_mock'; import {MockSchemaRegistry} from '../testing/schema_registry_mock';
import {MODULE_SUFFIX} from '@angular/compiler/src/util'; import {MODULE_SUFFIX} from '@angular/compiler/src/util';
import {MockXHR} from '../testing/xhr_mock'; import {MockXHR} from '../testing/xhr_mock';
import {ImportGenerator} from '@angular/compiler/src/output/path_util';
import { import {
CompileDirectiveMetadata, CompileDirectiveMetadata,
@ -29,7 +30,7 @@ import {
export class CompA { user: string; } export class CompA { user: string; }
var THIS_MODULE_PATH = `asset:angular2/test/compiler`; var THIS_MODULE_PATH = `asset:@angular/lib/compiler/test`;
var THIS_MODULE_URL = `${THIS_MODULE_PATH}/offline_compiler_util${MODULE_SUFFIX}`; var THIS_MODULE_URL = `${THIS_MODULE_PATH}/offline_compiler_util${MODULE_SUFFIX}`;
export var compAMetadata = CompileDirectiveMetadata.create({ export var compAMetadata = CompileDirectiveMetadata.create({
@ -40,8 +41,7 @@ export var compAMetadata = CompileDirectiveMetadata.create({
template: new CompileTemplateMetadata({ template: new CompileTemplateMetadata({
templateUrl: './offline_compiler_compa.html', templateUrl: './offline_compiler_compa.html',
styles: ['.redStyle { color: red; }'], styles: ['.redStyle { color: red; }'],
styleUrls: ['./offline_compiler_compa.css'], styleUrls: ['./offline_compiler_compa.css']
baseUrl: THIS_MODULE_URL,
}) })
}); });
@ -54,7 +54,7 @@ function _createOfflineCompiler(xhr: MockXHR, emitter: OutputEmitter): OfflineCo
normalizer, new TemplateParser(new Parser(new Lexer()), new MockSchemaRegistry({}, {}), normalizer, new TemplateParser(new Parser(new Lexer()), new MockSchemaRegistry({}, {}),
htmlParser, new Console(), []), htmlParser, new Console(), []),
new StyleCompiler(urlResolver), new ViewCompiler(new CompilerConfig(true, true, true)), new StyleCompiler(urlResolver), new ViewCompiler(new CompilerConfig(true, true, true)),
emitter); emitter, xhr);
} }
export function compileComp(emitter: OutputEmitter, export function compileComp(emitter: OutputEmitter,
@ -68,3 +68,15 @@ export function compileComp(emitter: OutputEmitter,
xhr.flush(); xhr.flush();
return result; return result;
} }
export class SimpleJsImportGenerator implements ImportGenerator {
getImportPath(moduleUrlStr: string, importedUrlStr: string): string {
// var moduleAssetUrl = ImportGenerator.parseAssetUrl(moduleUrlStr);
var importedAssetUrl = ImportGenerator.parseAssetUrl(importedUrlStr);
if (isPresent(importedAssetUrl)) {
return `${importedAssetUrl.packageName}/${importedAssetUrl.modulePath}`;
} else {
return importedUrlStr;
}
}
}

View File

@ -13,6 +13,7 @@ import {isBlank} from '../../src/facade/lang';
import {DartEmitter} from '@angular/compiler/src/output/dart_emitter'; import {DartEmitter} from '@angular/compiler/src/output/dart_emitter';
import {CompileIdentifierMetadata} from '@angular/compiler/src/compile_metadata'; import {CompileIdentifierMetadata} from '@angular/compiler/src/compile_metadata';
import * as o from '@angular/compiler/src/output/output_ast'; import * as o from '@angular/compiler/src/output/output_ast';
import {DartImportGenerator} from '@angular/compiler/src/output/dart_imports';
var someModuleUrl = 'asset:somePackage/lib/somePath'; var someModuleUrl = 'asset:somePackage/lib/somePath';
var anotherModuleUrl = 'asset:somePackage/lib/someOtherPath'; var anotherModuleUrl = 'asset:somePackage/lib/someOtherPath';
@ -36,7 +37,7 @@ export function main() {
var someVar: o.ReadVarExpr; var someVar: o.ReadVarExpr;
beforeEach(() => { beforeEach(() => {
emitter = new DartEmitter(); emitter = new DartEmitter(new DartImportGenerator());
someVar = o.variable('someVar'); someVar = o.variable('someVar');
}); });

View File

@ -0,0 +1,64 @@
import {
beforeEach,
ddescribe,
describe,
expect,
iit,
inject,
it,
xit,
} from '@angular/core/testing/testing_internal';
import {DartImportGenerator} from '@angular/compiler/src/output/dart_imports';
export function main() {
describe('DartImportGenerator', () => {
describe('getImportPath', () => {
var generator: DartImportGenerator;
beforeEach(() => { generator = new DartImportGenerator(); });
it('should calculate relative paths Dart', () => {
expect(generator.getImportPath('asset:somePkg/lib/modPath', 'asset:somePkg/lib/impPath'))
.toEqual('impPath');
});
it('should calculate relative paths for different constellations', () => {
expect(generator.getImportPath('asset:somePkg/test/modPath', 'asset:somePkg/test/impPath'))
.toEqual('impPath');
expect(
generator.getImportPath('asset:somePkg/lib/modPath', 'asset:somePkg/lib/dir2/impPath'))
.toEqual('dir2/impPath');
expect(
generator.getImportPath('asset:somePkg/lib/dir1/modPath', 'asset:somePkg/lib/impPath'))
.toEqual('../impPath');
expect(generator.getImportPath('asset:somePkg/lib/dir1/modPath',
'asset:somePkg/lib/dir2/impPath'))
.toEqual('../dir2/impPath');
});
it('should calculate absolute paths', () => {
expect(
generator.getImportPath('asset:somePkg/lib/modPath', 'asset:someOtherPkg/lib/impPath'))
.toEqual('package:someOtherPkg/impPath');
});
it('should not allow absolute imports of non lib modules', () => {
expect(() => generator.getImportPath('asset:somePkg/lib/modPath',
'asset:somePkg/test/impPath'))
.toThrowError(
`Can't import url asset:somePkg/test/impPath from asset:somePkg/lib/modPath`);
});
it('should not allow non asset urls as base url', () => {
expect(
() => generator.getImportPath('http:somePkg/lib/modPath', 'asset:somePkg/test/impPath'))
.toThrowError(`Url http:somePkg/lib/modPath is not a valid asset: url`);
});
it('should allow non asset urls as import urls and pass them through', () => {
expect(generator.getImportPath('asset:somePkg/lib/modPath', 'dart:html'))
.toEqual('dart:html');
});
});
});
}

View File

@ -13,6 +13,7 @@ import {isBlank} from '../../src/facade/lang';
import {JavaScriptEmitter} from '@angular/compiler/src/output/js_emitter'; import {JavaScriptEmitter} from '@angular/compiler/src/output/js_emitter';
import {CompileIdentifierMetadata} from '@angular/compiler/src/compile_metadata'; import {CompileIdentifierMetadata} from '@angular/compiler/src/compile_metadata';
import * as o from '@angular/compiler/src/output/output_ast'; import * as o from '@angular/compiler/src/output/output_ast';
import {SimpleJsImportGenerator} from '../offline_compiler_util';
var someModuleUrl = 'asset:somePackage/lib/somePath'; var someModuleUrl = 'asset:somePackage/lib/somePath';
var anotherModuleUrl = 'asset:somePackage/lib/someOtherPath'; var anotherModuleUrl = 'asset:somePackage/lib/someOtherPath';
@ -33,7 +34,7 @@ export function main() {
var someVar: o.ReadVarExpr; var someVar: o.ReadVarExpr;
beforeEach(() => { beforeEach(() => {
emitter = new JavaScriptEmitter(); emitter = new JavaScriptEmitter(new SimpleJsImportGenerator());
someVar = o.variable('someVar'); someVar = o.variable('someVar');
}); });
@ -111,9 +112,10 @@ export function main() {
it('should support external identifiers', () => { it('should support external identifiers', () => {
expect(emitStmt(o.importExpr(sameModuleIdentifier).toStmt())).toEqual('someLocalId;'); expect(emitStmt(o.importExpr(sameModuleIdentifier).toStmt())).toEqual('someLocalId;');
expect(emitStmt(o.importExpr(externalModuleIdentifier).toStmt())) expect(emitStmt(o.importExpr(externalModuleIdentifier).toStmt()))
.toEqual( .toEqual([
[`var import0 = re` + `quire('./someOtherPath');`, `import0.someExternalId;`].join( `var import0 = re` + `quire('somePackage/someOtherPath');`,
'\n')); `import0.someExternalId;`
].join('\n'));
}); });
it('should support operators', () => { it('should support operators', () => {

View File

@ -4,8 +4,10 @@ import {unimplemented} from '../../src/facade/exceptions';
import {codegenExportsVars, codegenStmts} from './output_emitter_util'; import {codegenExportsVars, codegenStmts} from './output_emitter_util';
import {TypeScriptEmitter} from '@angular/compiler/src/output/ts_emitter'; import {TypeScriptEmitter} from '@angular/compiler/src/output/ts_emitter';
import {DartEmitter} from '@angular/compiler/src/output/dart_emitter'; import {DartEmitter} from '@angular/compiler/src/output/dart_emitter';
import {DartImportGenerator} from '@angular/compiler/src/output/dart_imports';
import * as o from '@angular/compiler/src/output/output_ast'; import * as o from '@angular/compiler/src/output/output_ast';
import {assetUrl} from '../../src/util'; import {assetUrl} from '../../src/util';
import {SimpleJsImportGenerator} from '../offline_compiler_util';
export function getExpressions(): any { export function getExpressions(): any {
return unimplemented(); return unimplemented();
@ -13,7 +15,8 @@ export function getExpressions(): any {
// Generator // Generator
export function emit() { export function emit() {
var emitter = IS_DART ? new DartEmitter() : new TypeScriptEmitter(); var emitter = IS_DART ? new DartEmitter(new DartImportGenerator()) :
new TypeScriptEmitter(new SimpleJsImportGenerator());
var emittedCode = var emittedCode =
emitter.emitStatements(assetUrl('compiler', 'output/output_emitter_codegen_typed', 'test'), emitter.emitStatements(assetUrl('compiler', 'output/output_emitter_codegen_typed', 'test'),
codegenStmts, codegenExportsVars); codegenStmts, codegenExportsVars);

View File

@ -4,6 +4,7 @@ import {unimplemented} from '../../src/facade/exceptions';
import {codegenExportsVars, codegenStmts} from './output_emitter_util'; import {codegenExportsVars, codegenStmts} from './output_emitter_util';
import {JavaScriptEmitter} from '@angular/compiler/src/output/js_emitter'; import {JavaScriptEmitter} from '@angular/compiler/src/output/js_emitter';
import {assetUrl} from '../../src/util'; import {assetUrl} from '../../src/util';
import {SimpleJsImportGenerator} from '../offline_compiler_util';
export function getExpressions(): any { export function getExpressions(): any {
return unimplemented(); return unimplemented();
@ -11,7 +12,7 @@ export function getExpressions(): any {
// Generator // Generator
export function emit() { export function emit() {
var emitter = new JavaScriptEmitter(); var emitter = new JavaScriptEmitter(new SimpleJsImportGenerator());
var emittedCode = var emittedCode =
emitter.emitStatements(assetUrl('compiler', 'output/output_emitter_codegen_untyped', 'test'), emitter.emitStatements(assetUrl('compiler', 'output/output_emitter_codegen_untyped', 'test'),
codegenStmts, codegenExportsVars); codegenStmts, codegenExportsVars);

View File

@ -15,7 +15,7 @@ export class ExternalClass {
var testDataIdentifier = new CompileIdentifierMetadata({ var testDataIdentifier = new CompileIdentifierMetadata({
name: 'ExternalClass', name: 'ExternalClass',
moduleUrl: assetUrl('compiler', 'output/output_emitter_util'), moduleUrl: `asset:@angular/lib/compiler/test/output/output_emitter_util`,
runtime: ExternalClass runtime: ExternalClass
}); });

View File

@ -1,69 +0,0 @@
import {
beforeEach,
ddescribe,
describe,
expect,
iit,
inject,
it,
xit,
} from '@angular/core/testing/testing_internal';
import {getImportModulePath, ImportEnv} from '@angular/compiler/src/output/path_util';
export function main() {
describe('PathUtils', () => {
describe('getImportModulePath', () => {
it('should calculate relative paths for JS and Dart', () => {
expect(getImportModulePath('asset:somePkg/lib/modPath', 'asset:somePkg/lib/impPath',
ImportEnv.JS))
.toEqual('./impPath');
expect(getImportModulePath('asset:somePkg/lib/modPath', 'asset:somePkg/lib/impPath',
ImportEnv.Dart))
.toEqual('impPath');
});
it('should calculate relative paths for different constellations', () => {
expect(getImportModulePath('asset:somePkg/test/modPath', 'asset:somePkg/test/impPath',
ImportEnv.JS))
.toEqual('./impPath');
expect(getImportModulePath('asset:somePkg/lib/modPath', 'asset:somePkg/lib/dir2/impPath',
ImportEnv.JS))
.toEqual('./dir2/impPath');
expect(getImportModulePath('asset:somePkg/lib/dir1/modPath', 'asset:somePkg/lib/impPath',
ImportEnv.JS))
.toEqual('../impPath');
expect(getImportModulePath('asset:somePkg/lib/dir1/modPath',
'asset:somePkg/lib/dir2/impPath', ImportEnv.JS))
.toEqual('../dir2/impPath');
});
it('should calculate absolute paths for JS and Dart', () => {
expect(getImportModulePath('asset:somePkg/lib/modPath', 'asset:someOtherPkg/lib/impPath',
ImportEnv.JS))
.toEqual('someOtherPkg/impPath');
expect(getImportModulePath('asset:somePkg/lib/modPath', 'asset:someOtherPkg/lib/impPath',
ImportEnv.Dart))
.toEqual('package:someOtherPkg/impPath');
});
it('should not allow absolute imports of non lib modules', () => {
expect(() => getImportModulePath('asset:somePkg/lib/modPath', 'asset:somePkg/test/impPath',
ImportEnv.Dart))
.toThrowError(
`Can't import url asset:somePkg/test/impPath from asset:somePkg/lib/modPath`);
});
it('should not allow non asset urls as base url', () => {
expect(() => getImportModulePath('http:somePkg/lib/modPath', 'asset:somePkg/test/impPath',
ImportEnv.Dart))
.toThrowError(`Url http:somePkg/lib/modPath is not a valid asset: url`);
});
it('should allow non asset urls as import urls and pass them through', () => {
expect(getImportModulePath('asset:somePkg/lib/modPath', 'dart:html', ImportEnv.Dart))
.toEqual('dart:html');
});
});
});
}

View File

@ -13,6 +13,7 @@ import {isBlank} from '../../src/facade/lang';
import {TypeScriptEmitter} from '@angular/compiler/src/output/ts_emitter'; import {TypeScriptEmitter} from '@angular/compiler/src/output/ts_emitter';
import {CompileIdentifierMetadata} from '@angular/compiler/src/compile_metadata'; import {CompileIdentifierMetadata} from '@angular/compiler/src/compile_metadata';
import * as o from '@angular/compiler/src/output/output_ast'; import * as o from '@angular/compiler/src/output/output_ast';
import {SimpleJsImportGenerator} from '../offline_compiler_util';
var someModuleUrl = 'asset:somePackage/lib/somePath'; var someModuleUrl = 'asset:somePackage/lib/somePath';
var anotherModuleUrl = 'asset:somePackage/lib/someOtherPath'; var anotherModuleUrl = 'asset:somePackage/lib/someOtherPath';
@ -33,7 +34,7 @@ export function main() {
var someVar: o.ReadVarExpr; var someVar: o.ReadVarExpr;
beforeEach(() => { beforeEach(() => {
emitter = new TypeScriptEmitter(); emitter = new TypeScriptEmitter(new SimpleJsImportGenerator());
someVar = o.variable('someVar'); someVar = o.variable('someVar');
}); });
@ -112,8 +113,10 @@ export function main() {
it('should support external identifiers', () => { it('should support external identifiers', () => {
expect(emitStmt(o.importExpr(sameModuleIdentifier).toStmt())).toEqual('someLocalId;'); expect(emitStmt(o.importExpr(sameModuleIdentifier).toStmt())).toEqual('someLocalId;');
expect(emitStmt(o.importExpr(externalModuleIdentifier).toStmt())) expect(emitStmt(o.importExpr(externalModuleIdentifier).toStmt()))
.toEqual([`import * as import0 from './someOtherPath';`, `import0.someExternalId;`].join( .toEqual([
'\n')); `import * as import0 from 'somePackage/someOtherPath';`,
`import0.someExternalId;`
].join('\n'));
}); });
it('should support operators', () => { it('should support operators', () => {
@ -302,7 +305,7 @@ export function main() {
.toEqual('var a:someLocalId = null;'); .toEqual('var a:someLocalId = null;');
expect(emitStmt(writeVarExpr.toDeclStmt(o.importType(externalModuleIdentifier)))) expect(emitStmt(writeVarExpr.toDeclStmt(o.importType(externalModuleIdentifier))))
.toEqual([ .toEqual([
`import * as import0 from './someOtherPath';`, `import * as import0 from 'somePackage/someOtherPath';`,
`var a:import0.someExternalId = null;` `var a:import0.someExternalId = null;`
].join('\n')); ].join('\n'));
}); });

View File

@ -6,6 +6,7 @@ import * as ts from 'typescript';
import * as path from 'path'; import * as path from 'path';
import * as compiler from '@angular/compiler'; import * as compiler from '@angular/compiler';
import {ViewEncapsulation} from '@angular/core';
import {StaticReflector} from './static_reflector'; import {StaticReflector} from './static_reflector';
import {CompileMetadataResolver} from '@angular/compiler/src/metadata_resolver'; import {CompileMetadataResolver} from '@angular/compiler/src/metadata_resolver';
import {HtmlParser} from '@angular/compiler/src/html_parser'; import {HtmlParser} from '@angular/compiler/src/html_parser';
@ -22,7 +23,8 @@ import {Parse5DomAdapter} from '@angular/platform-server';
import {MetadataCollector} from 'ts-metadata-collector'; import {MetadataCollector} from 'ts-metadata-collector';
import {NodeReflectorHost} from './reflector_host'; import {NodeReflectorHost} from './reflector_host';
const SOURCE_EXTENSION = /\.[jt]s$/; const GENERATED_FILES = /\.ngfactory\.ts$|\.css\.ts$|\.css\.shim\.ts$/;
const PREAMBLE = `/** const PREAMBLE = `/**
* This file is generated by the Angular 2 template compiler. * This file is generated by the Angular 2 template compiler.
* Do not edit. * Do not edit.
@ -89,31 +91,55 @@ export class CodeGenerator {
return result; return result;
} }
codegen(): Promise<void[]> { // TODO(tbosch): add a cache for shared css files
// TODO(tbosch): detect cycles!
private generateStylesheet(filepath: string, shim: boolean): Promise<any> {
return this.compiler.loadAndCompileStylesheet(filepath, shim, '.ts')
.then((sourceWithImports) => {
// Write codegen in a directory structure matching the sources.
// TODO(alexeagle): relativize paths by the rootDirs option
const emitPath =
path.join(this.ngOptions.genDir,
path.relative(this.basePath, sourceWithImports.source.moduleUrl));
this.host.writeFile(emitPath, PREAMBLE + sourceWithImports.source.source, false);
return Promise.all(
sourceWithImports.importedUrls.map(url => this.generateStylesheet(url, shim)));
});
}
codegen(): Promise<any> {
Parse5DomAdapter.makeCurrent(); Parse5DomAdapter.makeCurrent();
const generateOneFile = (absSourcePath: string) => const generateOneFile = (absSourcePath: string) =>
Promise.all(this.readComponents(absSourcePath)) Promise.all(this.readComponents(absSourcePath))
.then((metadatas: compiler.CompileDirectiveMetadata[]) => { .then((metadatas: compiler.CompileDirectiveMetadata[]) => {
if (!metadatas || !metadatas.length) { if (!metadatas || !metadatas.length) {
return; return;
} }
let stylesheetPromises: Promise<any>[] = [];
metadatas.forEach((metadata) => {
let stylesheetPaths = metadata && metadata.template && metadata.template.styleUrls;
if (stylesheetPaths) {
stylesheetPaths.forEach((path) => {
stylesheetPromises.push(this.generateStylesheet(
path, metadata.template.encapsulation === ViewEncapsulation.Emulated));
});
}
});
const generated = this.generateSource(metadatas); const generated = this.generateSource(metadatas);
const sourceFile = this.program.getSourceFile(absSourcePath); const sourceFile = this.program.getSourceFile(absSourcePath);
// Write codegen in a directory structure matching the sources. // Write codegen in a directory structure matching the sources.
// TODO(alexeagle): maybe use generated.moduleUrl instead of hardcoded ".ngfactory.ts"
// TODO(alexeagle): relativize paths by the rootDirs option // TODO(alexeagle): relativize paths by the rootDirs option
const emitPath = const emitPath = path.join(this.ngOptions.genDir,
path.join(this.ngOptions.genDir, path.relative(this.basePath, absSourcePath)) path.relative(this.basePath, generated.moduleUrl));
.replace(SOURCE_EXTENSION, '.ngfactory.ts');
this.host.writeFile(emitPath, PREAMBLE + generated.source, false, () => {}, this.host.writeFile(emitPath, PREAMBLE + generated.source, false, () => {},
[sourceFile]); [sourceFile]);
return Promise.all(stylesheetPromises);
}) })
.catch((e) => { console.error(e.stack); }); .catch((e) => { console.error(e.stack); });
return Promise.all(
return Promise.all(this.program.getRootFileNames() this.program.getRootFileNames().filter(f => !GENERATED_FILES.test(f)).map(generateOneFile));
.filter(f => !/\.ngfactory\.ts$/.test(f))
.map(generateOneFile));
} }
static create(ngOptions: AngularCompilerOptions, program: ts.Program, options: ts.CompilerOptions, static create(ngOptions: AngularCompilerOptions, program: ts.Program, options: ts.CompilerOptions,
@ -129,7 +155,8 @@ export class CodeGenerator {
/*console*/ null, []); /*console*/ null, []);
const offlineCompiler = new compiler.OfflineCompiler( const offlineCompiler = new compiler.OfflineCompiler(
normalizer, tmplParser, new StyleCompiler(urlResolver), normalizer, tmplParser, new StyleCompiler(urlResolver),
new ViewCompiler(new compiler.CompilerConfig(true, true, true)), new TypeScriptEmitter()); new ViewCompiler(new compiler.CompilerConfig(true, true, true)),
new TypeScriptEmitter(reflectorHost), xhr);
const resolver = new CompileMetadataResolver( const resolver = new CompileMetadataResolver(
new compiler.DirectiveResolver(staticReflector), new compiler.PipeResolver(staticReflector), new compiler.DirectiveResolver(staticReflector), new compiler.PipeResolver(staticReflector),
new compiler.ViewResolver(staticReflector), null, null, staticReflector); new compiler.ViewResolver(staticReflector), null, null, staticReflector);

View File

@ -0,0 +1,7 @@
import {__compiler_private__ as _} from '@angular/compiler';
export var AssetUrl: typeof _.AssetUrl = _.AssetUrl;
export type AssetUrl = _.AssetUrl;
export var ImportGenerator: typeof _.ImportGenerator = _.ImportGenerator;
export type ImportGenerator = _.ImportGenerator;

View File

@ -4,11 +4,12 @@ import {MetadataCollector, ModuleMetadata} from 'ts-metadata-collector';
import * as fs from 'fs'; import * as fs from 'fs';
import * as path from 'path'; import * as path from 'path';
import {AngularCompilerOptions} from './codegen'; import {AngularCompilerOptions} from './codegen';
import {ImportGenerator, AssetUrl} from './compiler_private';
const EXT = /(\.ts|\.d\.ts|\.js|\.jsx|\.tsx)$/; const EXT = /(\.ts|\.d\.ts|\.js|\.jsx|\.tsx)$/;
const DTS = /\.d\.ts$/; const DTS = /\.d\.ts$/;
export class NodeReflectorHost implements StaticReflectorHost { export class NodeReflectorHost implements StaticReflectorHost, ImportGenerator {
private metadataCollector = new MetadataCollector(); private metadataCollector = new MetadataCollector();
constructor(private program: ts.Program, private compilerHost: ts.CompilerHost, constructor(private program: ts.Program, private compilerHost: ts.CompilerHost,
private options: ts.CompilerOptions, private ngOptions: AngularCompilerOptions) {} private options: ts.CompilerOptions, private ngOptions: AngularCompilerOptions) {}
@ -33,34 +34,48 @@ export class NodeReflectorHost implements StaticReflectorHost {
private resolve(m: string, containingFile: string) { private resolve(m: string, containingFile: string) {
const resolved = const resolved =
ts.resolveModuleName(m, containingFile, this.options, this.compilerHost).resolvedModule; ts.resolveModuleName(m, containingFile, this.options, this.compilerHost).resolvedModule;
return resolved ? resolved.resolvedFileName : null return resolved ? resolved.resolvedFileName : null;
}; };
private resolveAssetUrl(url: string, containingFile: string): string {
let assetUrl = AssetUrl.parse(url);
if (assetUrl) {
return this.resolve(`${assetUrl.packageName}/${assetUrl.modulePath}`, containingFile);
}
return url;
}
/** /**
* We want a moduleId that will appear in import statements in the generated code. * We want a moduleId that will appear in import statements in the generated code.
* These need to be in a form that system.js can load, so absolute file paths don't work. * These need to be in a form that system.js can load, so absolute file paths don't work.
* Relativize the paths by checking candidate prefixes of the absolute path, to see if * Relativize the paths by checking candidate prefixes of the absolute path, to see if
* they are resolvable by the moduleResolution strategy from the CompilerHost. * they are resolvable by the moduleResolution strategy from the CompilerHost.
*/ */
private getModuleId(declarationFile: string, containingFile: string) { getImportPath(containingFile: string, importedFile: string) {
const parts = declarationFile.replace(EXT, '').split(path.sep).filter(p => !!p); importedFile = this.resolveAssetUrl(importedFile, '');
containingFile = this.resolveAssetUrl(containingFile, '');
// TODO(tbosch): if a file does not yet exist (because we compile it later),
// we still need to create it so that the `resolve` method works!
if (!this.compilerHost.fileExists(importedFile)) {
this.compilerHost.writeFile(importedFile, '', false);
fs.writeFileSync(importedFile, '');
}
const parts = importedFile.replace(EXT, '').split(path.sep).filter(p => !!p);
for (let index = parts.length - 1; index >= 0; index--) { for (let index = parts.length - 1; index >= 0; index--) {
let candidate = parts.slice(index, parts.length).join(path.sep); let candidate = parts.slice(index, parts.length).join(path.sep);
if (this.resolve(candidate, containingFile) === declarationFile) { if (this.resolve('.' + path.sep + candidate, containingFile) === importedFile) {
let pkg = parts[index]; return `./${candidate}`;
let pkgPath = parts.slice(index + 1, parts.length).join(path.sep);
return `asset:${pkg}/lib/${pkgPath}`;
} }
} if (this.resolve(candidate, containingFile) === importedFile) {
for (let index = parts.length - 1; index >= 0; index--) { return candidate;
let candidate = parts.slice(index, parts.length).join(path.sep);
if (this.resolve('.' + path.sep + candidate, containingFile) === declarationFile) {
return `asset:./lib/${candidate}`;
} }
} }
throw new Error( throw new Error(
`Unable to find any resolvable import for ${declarationFile} relative to ${containingFile}`); `Unable to find any resolvable import for ${importedFile} relative to ${containingFile}`);
} }
findDeclaration(module: string, symbolName: string, containingFile: string, findDeclaration(module: string, symbolName: string, containingFile: string,
@ -93,9 +108,8 @@ export class NodeReflectorHost implements StaticReflectorHost {
} }
const declaration = symbol.getDeclarations()[0]; const declaration = symbol.getDeclarations()[0];
const declarationFile = declaration.getSourceFile().fileName; const declarationFile = declaration.getSourceFile().fileName;
const moduleId = this.getModuleId(declarationFile, containingFile);
return this.getStaticSymbol(moduleId, declarationFile, symbol.getName()); return this.getStaticSymbol(declarationFile, symbol.getName());
} catch (e) { } catch (e) {
console.error(`can't resolve module ${module} from ${containingFile}`); console.error(`can't resolve module ${module} from ${containingFile}`);
throw e; throw e;
@ -108,15 +122,14 @@ export class NodeReflectorHost implements StaticReflectorHost {
* getStaticSymbol produces a Type whose metadata is known but whose implementation is not loaded. * getStaticSymbol produces a Type whose metadata is known but whose implementation is not loaded.
* All types passed to the StaticResolver should be pseudo-types returned by this method. * All types passed to the StaticResolver should be pseudo-types returned by this method.
* *
* @param moduleId the module identifier as an absolute path.
* @param declarationFile the absolute path of the file where the symbol is declared * @param declarationFile the absolute path of the file where the symbol is declared
* @param name the name of the type. * @param name the name of the type.
*/ */
getStaticSymbol(moduleId: string, declarationFile: string, name: string): StaticSymbol { getStaticSymbol(declarationFile: string, name: string): StaticSymbol {
let key = `"${declarationFile}".${name}`; let key = `"${declarationFile}".${name}`;
let result = this.typeCache.get(key); let result = this.typeCache.get(key);
if (!result) { if (!result) {
result = new StaticSymbol(moduleId, declarationFile, name); result = new StaticSymbol(declarationFile, name);
this.typeCache.set(key, result); this.typeCache.set(key, result);
} }
return result; return result;

View File

@ -43,9 +43,9 @@ import {
*/ */
export interface StaticReflectorHost { export interface StaticReflectorHost {
/** /**
* Return a ModuleMetadata for the given module. * Return a ModuleMetadata for the given module.
* *
* @param moduleId is a string identifier for a module as an absolute path. * @param modulePath is a string identifier for a module as an absolute path.
* @returns the metadata for the given module. * @returns the metadata for the given module.
*/ */
getMetadataFor(modulePath: string): {[key: string]: any}; getMetadataFor(modulePath: string): {[key: string]: any};
@ -57,7 +57,7 @@ export interface StaticReflectorHost {
*/ */
findDeclaration(modulePath: string, symbolName: string, containingFile?: string): StaticSymbol; findDeclaration(modulePath: string, symbolName: string, containingFile?: string): StaticSymbol;
getStaticSymbol(moduleId: string, declarationFile: string, name: string): StaticSymbol; getStaticSymbol(declarationFile: string, name: string): StaticSymbol;
angularImportLocations(): angularImportLocations():
{coreDecorators: string, diDecorators: string, diMetadata: string, provider: string}; {coreDecorators: string, diDecorators: string, diMetadata: string, provider: string};
@ -66,10 +66,10 @@ export interface StaticReflectorHost {
/** /**
* A token representing the a reference to a static type. * A token representing the a reference to a static type.
* *
* This token is unique for a moduleId and name and can be used as a hash table key. * This token is unique for a filePath and name and can be used as a hash table key.
*/ */
export class StaticSymbol { export class StaticSymbol {
constructor(public moduleId: string, public filePath: string, public name: string) {} constructor(public filePath: string, public name: string) {}
} }
/** /**
@ -324,8 +324,7 @@ export class StaticReflector implements ReflectorReader {
staticSymbol = _this.host.findDeclaration(expression['module'], expression['name'], staticSymbol = _this.host.findDeclaration(expression['module'], expression['name'],
context.filePath); context.filePath);
} else { } else {
staticSymbol = _this.host.getStaticSymbol(context.moduleId, context.filePath, staticSymbol = _this.host.getStaticSymbol(context.filePath, expression['name']);
expression['name']);
} }
let result = staticSymbol; let result = staticSymbol;
let moduleMetadata = _this.getModuleMetadata(staticSymbol.filePath); let moduleMetadata = _this.getModuleMetadata(staticSymbol.filePath);

View File

@ -14,7 +14,7 @@ import {ListWrapper} from '@angular/facade/src/collection';
import {StaticReflector, StaticReflectorHost, StaticSymbol} from './static_reflector'; import {StaticReflector, StaticReflectorHost, StaticSymbol} from './static_reflector';
describe('StaticReflector', () => { describe('StaticReflector', () => {
let noContext = new StaticSymbol('', '', ''); let noContext = new StaticSymbol('', '');
let host: StaticReflectorHost; let host: StaticReflectorHost;
let reflector: StaticReflector; let reflector: StaticReflector;
@ -34,7 +34,6 @@ describe('StaticReflector', () => {
let annotation = annotations[0]; let annotation = annotations[0];
expect(annotation.selector).toEqual('[ngFor][ngForOf]'); expect(annotation.selector).toEqual('[ngFor][ngForOf]');
expect(annotation.inputs).toEqual(['ngForTrackBy', 'ngForOf', 'ngForTemplate']); expect(annotation.inputs).toEqual(['ngForTrackBy', 'ngForOf', 'ngForTemplate']);
}); });
it('should get constructor for NgFor', () => { it('should get constructor for NgFor', () => {
@ -226,15 +225,15 @@ describe('StaticReflector', () => {
}); });
it('should simplify a module reference', () => { it('should simplify a module reference', () => {
expect(simplify(new StaticSymbol('', '/src/cases', ''), expect(simplify(new StaticSymbol('/src/cases', ''),
({__symbolic: "reference", module: "./extern", name: "s"}))) ({__symbolic: "reference", module: "./extern", name: "s"})))
.toEqual("s"); .toEqual("s");
}); });
it('should simplify a non existing reference as a static symbol', () => { it('should simplify a non existing reference as a static symbol', () => {
expect(simplify(new StaticSymbol('', '/src/cases', ''), expect(simplify(new StaticSymbol('/src/cases', ''),
({__symbolic: "reference", module: "./extern", name: "nonExisting"}))) ({__symbolic: "reference", module: "./extern", name: "nonExisting"})))
.toEqual(host.getStaticSymbol('', '/src/extern.d.ts', 'nonExisting')); .toEqual(host.getStaticSymbol('/src/extern.d.ts', 'nonExisting'));
}); });
}); });
@ -249,11 +248,11 @@ class MockReflectorHost implements StaticReflectorHost {
provider: 'angular2/src/core/di/provider' provider: 'angular2/src/core/di/provider'
}; };
} }
getStaticSymbol(moduleId: string, declarationFile: string, name: string): StaticSymbol { getStaticSymbol(declarationFile: string, name: string): StaticSymbol {
var cacheKey = `${declarationFile}:${name}`; var cacheKey = `${declarationFile}:${name}`;
var result = this.staticTypeCache.get(cacheKey); var result = this.staticTypeCache.get(cacheKey);
if (isBlank(result)) { if (isBlank(result)) {
result = new StaticSymbol(moduleId, declarationFile, name); result = new StaticSymbol(declarationFile, name);
this.staticTypeCache.set(cacheKey, result); this.staticTypeCache.set(cacheKey, result);
} }
return result; return result;
@ -292,10 +291,9 @@ class MockReflectorHost implements StaticReflectorHost {
} }
if (modulePath.indexOf('.') === 0) { if (modulePath.indexOf('.') === 0) {
return this.getStaticSymbol(`mod/${symbolName}`, pathTo(containingFile, modulePath) + '.d.ts', return this.getStaticSymbol(pathTo(containingFile, modulePath) + '.d.ts', symbolName);
symbolName);
} }
return this.getStaticSymbol(`mod/${symbolName}`, '/tmp/' + modulePath + '.d.ts', symbolName); return this.getStaticSymbol('/tmp/' + modulePath + '.d.ts', symbolName);
} }
getMetadataFor(moduleId: string): any { getMetadataFor(moduleId: string): any {

View File

@ -0,0 +1,3 @@
@import './shared.css';
.green { color: green }

View File

@ -1,2 +1,3 @@
<div>{{ctxProp}}</div> <div>{{ctxProp}}</div>
<form><input type="button" [(ngModel)]="ctxProp"/></form> <form><input type="button" [(ngModel)]="ctxProp"/></form>
<my-comp></my-comp>

View File

@ -2,7 +2,13 @@ import {Component, Inject} from '@angular/core';
import {FORM_DIRECTIVES} from '@angular/common'; import {FORM_DIRECTIVES} from '@angular/common';
import {MyComp} from './a/multiple_components'; import {MyComp} from './a/multiple_components';
@Component({selector: 'basic', templateUrl: './basic.html', directives: [MyComp, FORM_DIRECTIVES]}) @Component({
selector: 'basic',
templateUrl: './basic.html',
styles: ['.red { color: red }'],
styleUrls: ['./basic.css'],
directives: [MyComp, FORM_DIRECTIVES]
})
export class Basic { export class Basic {
ctxProp: string; ctxProp: string;
constructor() { this.ctxProp = 'initiaValue'; } constructor() { this.ctxProp = 'initiaValue'; }

View File

@ -1,8 +1,8 @@
import {coreBootstrap, ReflectiveInjector} from '@angular/core'; import {coreBootstrap, ReflectiveInjector} from '@angular/core';
import {browserPlatform, BROWSER_APP_PROVIDERS} from '@angular/platform-browser'; import {browserPlatform, BROWSER_APP_STATIC_PROVIDERS} from '@angular/platform-browser';
import {BasicNgFactory} from './basic.ngfactory'; import {BasicNgFactory} from './basic.ngfactory';
import {Basic} from './basic'; import {Basic} from './basic';
const appInjector = const appInjector =
ReflectiveInjector.resolveAndCreate(BROWSER_APP_PROVIDERS, browserPlatform().injector); ReflectiveInjector.resolveAndCreate(BROWSER_APP_STATIC_PROVIDERS, browserPlatform().injector);
coreBootstrap(appInjector, BasicNgFactory); coreBootstrap(appInjector, BasicNgFactory);

View File

@ -0,0 +1 @@
.blue { color: blue }

View File

@ -3,7 +3,7 @@
// For TypeScript 1.8, we have to lay out generated files // For TypeScript 1.8, we have to lay out generated files
// in the same source directory with your code. // in the same source directory with your code.
"genDir": ".", "genDir": ".",
"legacyPackageLayout": true "legacyPackageLayout": false
}, },
"compilerOptions": { "compilerOptions": {