Assets defined for `templateUrl` and `styleUrls` can now be loaded in relative to where the component file is placed so long as the `moduleId` is set within the component annotation. Closes #5634 Closes #5634
		
			
				
	
	
		
			332 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			332 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| import {Injectable, Inject} from 'angular2/src/core/di';
 | |
| import {
 | |
|   StringWrapper,
 | |
|   isPresent,
 | |
|   isBlank,
 | |
|   RegExpWrapper,
 | |
|   normalizeBlank
 | |
| } from 'angular2/src/facade/lang';
 | |
| import {BaseException, WrappedException} from 'angular2/src/facade/exceptions';
 | |
| import {ListWrapper} from 'angular2/src/facade/collection';
 | |
| import {PACKAGE_ROOT_URL} from 'angular2/src/core/application_tokens';
 | |
| import {Provider} from 'angular2/src/core/di';
 | |
| 
 | |
| export function createWithoutPackagePrefix(): UrlResolver {
 | |
|   return new UrlResolver();
 | |
| }
 | |
| 
 | |
| export var DEFAULT_PACKAGE_URL_PROVIDER = new Provider(PACKAGE_ROOT_URL, {useValue: "/"});
 | |
| 
 | |
| /**
 | |
|  * Used by the {@link Compiler} when resolving HTML and CSS template URLs.
 | |
|  *
 | |
|  * This interface can be overridden by the application developer to create custom behavior.
 | |
|  *
 | |
|  * See {@link Compiler}
 | |
|  */
 | |
| @Injectable()
 | |
| export class UrlResolver {
 | |
|   private _packagePrefix: string;
 | |
| 
 | |
|   constructor(@Inject(PACKAGE_ROOT_URL) packagePrefix: string = null) {
 | |
|     if (isPresent(packagePrefix)) {
 | |
|       this._packagePrefix = StringWrapper.stripRight(packagePrefix, "/") + "/";
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Resolves the `url` given the `baseUrl`:
 | |
|    * - when the `url` is null, the `baseUrl` is returned,
 | |
|    * - if `url` is relative ('path/to/here', './path/to/here'), the resolved url is a combination of
 | |
|    * `baseUrl` and `url`,
 | |
|    * - if `url` is absolute (it has a scheme: 'http://', 'https://' or start with '/'), the `url` is
 | |
|    * returned as is (ignoring the `baseUrl`)
 | |
|    *
 | |
|    * @param {string} baseUrl
 | |
|    * @param {string} url
 | |
|    * @returns {string} the resolved URL
 | |
|    */
 | |
|   resolve(baseUrl: string, url: string): string {
 | |
|     var resolvedUrl = url;
 | |
|     if (isPresent(baseUrl) && baseUrl.length > 0) {
 | |
|       resolvedUrl = _resolveUrl(baseUrl, resolvedUrl);
 | |
|     }
 | |
|     if (isPresent(this._packagePrefix) && getUrlScheme(resolvedUrl) == "package") {
 | |
|       resolvedUrl = resolvedUrl.replace("package:", this._packagePrefix);
 | |
|     }
 | |
|     return resolvedUrl;
 | |
|   }
 | |
| }
 | |
| 
 | |
| export function getUrlScheme(url: string): string {
 | |
|   var match = _split(url);
 | |
|   return (match && match[_ComponentIndex.Scheme]) || "";
 | |
| }
 | |
| 
 | |
| // The code below is adapted from Traceur:
 | |
| // https://github.com/google/traceur-compiler/blob/9511c1dafa972bf0de1202a8a863bad02f0f95a8/src/runtime/url.js
 | |
| 
 | |
| /**
 | |
|  * Builds a URI string from already-encoded parts.
 | |
|  *
 | |
|  * No encoding is performed.  Any component may be omitted as either null or
 | |
|  * undefined.
 | |
|  *
 | |
|  * @param {?string=} opt_scheme The scheme such as 'http'.
 | |
|  * @param {?string=} opt_userInfo The user name before the '@'.
 | |
|  * @param {?string=} opt_domain The domain such as 'www.google.com', already
 | |
|  *     URI-encoded.
 | |
|  * @param {(string|null)=} opt_port The port number.
 | |
|  * @param {?string=} opt_path The path, already URI-encoded.  If it is not
 | |
|  *     empty, it must begin with a slash.
 | |
|  * @param {?string=} opt_queryData The URI-encoded query data.
 | |
|  * @param {?string=} opt_fragment The URI-encoded fragment identifier.
 | |
|  * @return {string} The fully combined URI.
 | |
|  */
 | |
| function _buildFromEncodedParts(opt_scheme?: string, opt_userInfo?: string, opt_domain?: string,
 | |
|                                 opt_port?: string, opt_path?: string, opt_queryData?: string,
 | |
|                                 opt_fragment?: string): string {
 | |
|   var out = [];
 | |
| 
 | |
|   if (isPresent(opt_scheme)) {
 | |
|     out.push(opt_scheme + ':');
 | |
|   }
 | |
| 
 | |
|   if (isPresent(opt_domain)) {
 | |
|     out.push('//');
 | |
| 
 | |
|     if (isPresent(opt_userInfo)) {
 | |
|       out.push(opt_userInfo + '@');
 | |
|     }
 | |
| 
 | |
|     out.push(opt_domain);
 | |
| 
 | |
|     if (isPresent(opt_port)) {
 | |
|       out.push(':' + opt_port);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (isPresent(opt_path)) {
 | |
|     out.push(opt_path);
 | |
|   }
 | |
| 
 | |
|   if (isPresent(opt_queryData)) {
 | |
|     out.push('?' + opt_queryData);
 | |
|   }
 | |
| 
 | |
|   if (isPresent(opt_fragment)) {
 | |
|     out.push('#' + opt_fragment);
 | |
|   }
 | |
| 
 | |
|   return out.join('');
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * A regular expression for breaking a URI into its component parts.
 | |
|  *
 | |
|  * {@link http://www.gbiv.com/protocols/uri/rfc/rfc3986.html#RFC2234} says
 | |
|  * As the "first-match-wins" algorithm is identical to the "greedy"
 | |
|  * disambiguation method used by POSIX regular expressions, it is natural and
 | |
|  * commonplace to use a regular expression for parsing the potential five
 | |
|  * components of a URI reference.
 | |
|  *
 | |
|  * The following line is the regular expression for breaking-down a
 | |
|  * well-formed URI reference into its components.
 | |
|  *
 | |
|  * <pre>
 | |
|  * ^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?
 | |
|  *  12            3  4          5       6  7        8 9
 | |
|  * </pre>
 | |
|  *
 | |
|  * The numbers in the second line above are only to assist readability; they
 | |
|  * indicate the reference points for each subexpression (i.e., each paired
 | |
|  * parenthesis). We refer to the value matched for subexpression <n> as $<n>.
 | |
|  * For example, matching the above expression to
 | |
|  * <pre>
 | |
|  *     http://www.ics.uci.edu/pub/ietf/uri/#Related
 | |
|  * </pre>
 | |
|  * results in the following subexpression matches:
 | |
|  * <pre>
 | |
|  *    $1 = http:
 | |
|  *    $2 = http
 | |
|  *    $3 = //www.ics.uci.edu
 | |
|  *    $4 = www.ics.uci.edu
 | |
|  *    $5 = /pub/ietf/uri/
 | |
|  *    $6 = <undefined>
 | |
|  *    $7 = <undefined>
 | |
|  *    $8 = #Related
 | |
|  *    $9 = Related
 | |
|  * </pre>
 | |
|  * where <undefined> indicates that the component is not present, as is the
 | |
|  * case for the query component in the above example. Therefore, we can
 | |
|  * determine the value of the five components as
 | |
|  * <pre>
 | |
|  *    scheme    = $2
 | |
|  *    authority = $4
 | |
|  *    path      = $5
 | |
|  *    query     = $7
 | |
|  *    fragment  = $9
 | |
|  * </pre>
 | |
|  *
 | |
|  * The regular expression has been modified slightly to expose the
 | |
|  * userInfo, domain, and port separately from the authority.
 | |
|  * The modified version yields
 | |
|  * <pre>
 | |
|  *    $1 = http              scheme
 | |
|  *    $2 = <undefined>       userInfo -\
 | |
|  *    $3 = www.ics.uci.edu   domain     | authority
 | |
|  *    $4 = <undefined>       port     -/
 | |
|  *    $5 = /pub/ietf/uri/    path
 | |
|  *    $6 = <undefined>       query without ?
 | |
|  *    $7 = Related           fragment without #
 | |
|  * </pre>
 | |
|  * @type {!RegExp}
 | |
|  * @internal
 | |
|  */
 | |
| var _splitRe =
 | |
|     RegExpWrapper.create('^' +
 | |
|                          '(?:' +
 | |
|                          '([^:/?#.]+)' +  // scheme - ignore special characters
 | |
|                                           // used by other URL parts such as :,
 | |
|                                           // ?, /, #, and .
 | |
|                          ':)?' +
 | |
|                          '(?://' +
 | |
|                          '(?:([^/?#]*)@)?' +                  // userInfo
 | |
|                          '([\\w\\d\\-\\u0100-\\uffff.%]*)' +  // domain - restrict to letters,
 | |
|                                                               // digits, dashes, dots, percent
 | |
|                                                               // escapes, and unicode characters.
 | |
|                          '(?::([0-9]+))?' +                   // port
 | |
|                          ')?' +
 | |
|                          '([^?#]+)?' +        // path
 | |
|                          '(?:\\?([^#]*))?' +  // query
 | |
|                          '(?:#(.*))?' +       // fragment
 | |
|                          '$');
 | |
| 
 | |
| /**
 | |
|  * The index of each URI component in the return value of goog.uri.utils.split.
 | |
|  * @enum {number}
 | |
|  */
 | |
| enum _ComponentIndex {
 | |
|   Scheme = 1,
 | |
|   UserInfo,
 | |
|   Domain,
 | |
|   Port,
 | |
|   Path,
 | |
|   QueryData,
 | |
|   Fragment
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Splits a URI into its component parts.
 | |
|  *
 | |
|  * Each component can be accessed via the component indices; for example:
 | |
|  * <pre>
 | |
|  * goog.uri.utils.split(someStr)[goog.uri.utils.CompontentIndex.QUERY_DATA];
 | |
|  * </pre>
 | |
|  *
 | |
|  * @param {string} uri The URI string to examine.
 | |
|  * @return {!Array.<string|undefined>} Each component still URI-encoded.
 | |
|  *     Each component that is present will contain the encoded value, whereas
 | |
|  *     components that are not present will be undefined or empty, depending
 | |
|  *     on the browser's regular expression implementation.  Never null, since
 | |
|  *     arbitrary strings may still look like path names.
 | |
|  */
 | |
| function _split(uri: string): Array<string | any> {
 | |
|   return RegExpWrapper.firstMatch(_splitRe, uri);
 | |
| }
 | |
| 
 | |
| /**
 | |
|   * Removes dot segments in given path component, as described in
 | |
|   * RFC 3986, section 5.2.4.
 | |
|   *
 | |
|   * @param {string} path A non-empty path component.
 | |
|   * @return {string} Path component with removed dot segments.
 | |
|   */
 | |
| function _removeDotSegments(path: string): string {
 | |
|   if (path == '/') return '/';
 | |
| 
 | |
|   var leadingSlash = path[0] == '/' ? '/' : '';
 | |
|   var trailingSlash = path[path.length - 1] === '/' ? '/' : '';
 | |
|   var segments = path.split('/');
 | |
| 
 | |
|   var out: string[] = [];
 | |
|   var up = 0;
 | |
|   for (var pos = 0; pos < segments.length; pos++) {
 | |
|     var segment = segments[pos];
 | |
|     switch (segment) {
 | |
|       case '':
 | |
|       case '.':
 | |
|         break;
 | |
|       case '..':
 | |
|         if (out.length > 0) {
 | |
|           out.pop();
 | |
|         } else {
 | |
|           up++;
 | |
|         }
 | |
|         break;
 | |
|       default:
 | |
|         out.push(segment);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (leadingSlash == '') {
 | |
|     while (up-- > 0) {
 | |
|       out.unshift('..');
 | |
|     }
 | |
| 
 | |
|     if (out.length === 0) out.push('.');
 | |
|   }
 | |
| 
 | |
|   return leadingSlash + out.join('/') + trailingSlash;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Takes an array of the parts from split and canonicalizes the path part
 | |
|  * and then joins all the parts.
 | |
|  * @param {Array.<string?>} parts
 | |
|  * @return {string}
 | |
|  */
 | |
| function _joinAndCanonicalizePath(parts: any[]): string {
 | |
|   var path = parts[_ComponentIndex.Path];
 | |
|   path = isBlank(path) ? '' : _removeDotSegments(path);
 | |
|   parts[_ComponentIndex.Path] = path;
 | |
| 
 | |
|   return _buildFromEncodedParts(parts[_ComponentIndex.Scheme], parts[_ComponentIndex.UserInfo],
 | |
|                                 parts[_ComponentIndex.Domain], parts[_ComponentIndex.Port], path,
 | |
|                                 parts[_ComponentIndex.QueryData], parts[_ComponentIndex.Fragment]);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Resolves a URL.
 | |
|  * @param {string} base The URL acting as the base URL.
 | |
|  * @param {string} to The URL to resolve.
 | |
|  * @return {string}
 | |
|  */
 | |
| function _resolveUrl(base: string, url: string): string {
 | |
|   var parts = _split(encodeURI(url));
 | |
|   var baseParts = _split(base);
 | |
| 
 | |
|   if (isPresent(parts[_ComponentIndex.Scheme])) {
 | |
|     return _joinAndCanonicalizePath(parts);
 | |
|   } else {
 | |
|     parts[_ComponentIndex.Scheme] = baseParts[_ComponentIndex.Scheme];
 | |
|   }
 | |
| 
 | |
|   for (var i = _ComponentIndex.Scheme; i <= _ComponentIndex.Port; i++) {
 | |
|     if (isBlank(parts[i])) {
 | |
|       parts[i] = baseParts[i];
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (parts[_ComponentIndex.Path][0] == '/') {
 | |
|     return _joinAndCanonicalizePath(parts);
 | |
|   }
 | |
| 
 | |
|   var path = baseParts[_ComponentIndex.Path];
 | |
|   if (isBlank(path)) path = '/';
 | |
|   var index = path.lastIndexOf('/');
 | |
|   path = path.substring(0, index + 1) + parts[_ComponentIndex.Path];
 | |
|   parts[_ComponentIndex.Path] = path;
 | |
|   return _joinAndCanonicalizePath(parts);
 | |
| }
 |