From 6ad80a4d60c2ad995489c1aaef9fdb81006a99fd Mon Sep 17 00:00:00 2001 From: vakrilov Date: Tue, 22 Aug 2017 19:49:35 +0300 Subject: [PATCH 1/3] feat(styling): Allow loading .css files as a fallback if no .scss file is found(#954) --- nativescript-angular/resource-loader.ts | 50 +++++++++++++++++++------ 1 file changed, 38 insertions(+), 12 deletions(-) diff --git a/nativescript-angular/resource-loader.ts b/nativescript-angular/resource-loader.ts index e3c2ba4db..6e1da9206 100644 --- a/nativescript-angular/resource-loader.ts +++ b/nativescript-angular/resource-loader.ts @@ -1,25 +1,51 @@ -import { path, knownFolders, File } from "tns-core-modules/file-system"; import { ResourceLoader } from "@angular/compiler"; +import { File, knownFolders, path } from "tns-core-modules/file-system"; + +const extensionsFallbacks = [ + [".scss", ".css"], + [".sass", ".css"], + [".less", ".css"] +]; export class FileSystemResourceLoader extends ResourceLoader { - resolve(url: string, baseUrl: string): string { - // Angular assembles absolute URL's and prefixes them with // + get(url: string): Promise { + const resolvedPath = this.resolve(url); + + const templateFile = File.fromPath(resolvedPath); + + return templateFile.readText(); + } + + private handleAbsoluteUrls(url: string): string { + // Angular assembles absolute URLs and prefixes them with // if (url.indexOf("/") !== 0) { - // Resolve relative URL's based on the app root. - return path.join(baseUrl, url); + // Resolve relative URLs based on the app root. + return path.join(knownFolders.currentApp().path, url); } else { return url; } } - get(url: string): Promise { - const appDir = knownFolders.currentApp().path; - const templatePath = this.resolve(url, appDir); + private resolve(url: string) { + const normalizedUrl = this.handleAbsoluteUrls(url); - if (!File.exists(templatePath)) { - throw new Error(`File ${templatePath} does not exist. Resolved from: ${url}.`); + if (File.exists(normalizedUrl)) { + return normalizedUrl; } - let templateFile = File.fromPath(templatePath); - return templateFile.readText(); + + const fallbackCandidates = []; + extensionsFallbacks.forEach(([extension, fallback]) => { + if (normalizedUrl.endsWith(extension)) { + fallbackCandidates.push(normalizedUrl.substr(0, normalizedUrl.length - extension.length) + fallback); + } + }); + + for (let i = 0; i < fallbackCandidates.length; i++) { + if (File.exists(fallbackCandidates[i])) { + return fallbackCandidates[i]; + } + } + + throw new Error(`Could not resolve ${url}. Looked for: ${normalizedUrl}, ${fallbackCandidates}`); } } From 9dea49a9c4fdc52f722faeddb0ea45d179ea2cd6 Mon Sep 17 00:00:00 2001 From: vakrilov Date: Wed, 23 Aug 2017 18:50:48 +0300 Subject: [PATCH 2/3] Add tests --- .../file-system/ns-file-system.ts | 10 ++- nativescript-angular/platform.ts | 2 + nativescript-angular/resource-loader.ts | 24 ++++--- tests/app/tests/xhr-paths.ts | 62 ++++++++++++++++--- 4 files changed, 81 insertions(+), 17 deletions(-) diff --git a/nativescript-angular/file-system/ns-file-system.ts b/nativescript-angular/file-system/ns-file-system.ts index f8cf590d5..1cdc8c9e5 100644 --- a/nativescript-angular/file-system/ns-file-system.ts +++ b/nativescript-angular/file-system/ns-file-system.ts @@ -1,5 +1,5 @@ import { Injectable } from "@angular/core"; -import { knownFolders, Folder } from "tns-core-modules/file-system"; +import { knownFolders, Folder, File } from "tns-core-modules/file-system"; // Allows greater flexibility with `file-system` and Angular // Also provides a way for `file-system` to be mocked for testing @@ -9,4 +9,12 @@ export class NSFileSystem { public currentApp(): Folder { return knownFolders.currentApp(); } + + public fileFromPath(path: string): File { + return File.fromPath(path); + } + + public fileExists(path: string): boolean { + return File.exists(path); + } } diff --git a/nativescript-angular/platform.ts b/nativescript-angular/platform.ts index 1c418e794..f1a6ad80b 100644 --- a/nativescript-angular/platform.ts +++ b/nativescript-angular/platform.ts @@ -35,6 +35,7 @@ if ((global).___TS_UNUSED) { import "./dom-adapter"; import { NativeScriptElementSchemaRegistry } from "./schema-registry"; +import { NSFileSystem } from "./file-system/ns-file-system"; import { FileSystemResourceLoader } from "./resource-loader"; export const NS_COMPILER_PROVIDERS = [ @@ -43,6 +44,7 @@ export const NS_COMPILER_PROVIDERS = [ provide: COMPILER_OPTIONS, useValue: { providers: [ + NSFileSystem, { provide: ResourceLoader, useClass: FileSystemResourceLoader }, { provide: ElementSchemaRegistry, useClass: NativeScriptElementSchemaRegistry }, ] diff --git a/nativescript-angular/resource-loader.ts b/nativescript-angular/resource-loader.ts index 6e1da9206..10e6ffc47 100644 --- a/nativescript-angular/resource-loader.ts +++ b/nativescript-angular/resource-loader.ts @@ -1,5 +1,8 @@ +import { Injectable } from "@angular/core"; import { ResourceLoader } from "@angular/compiler"; -import { File, knownFolders, path } from "tns-core-modules/file-system"; +import { path } from "tns-core-modules/file-system"; + +import { NSFileSystem } from "./file-system/ns-file-system"; const extensionsFallbacks = [ [".scss", ".css"], @@ -7,29 +10,34 @@ const extensionsFallbacks = [ [".less", ".css"] ]; +@Injectable() export class FileSystemResourceLoader extends ResourceLoader { + constructor(private fs: NSFileSystem) { + super(); + } + get(url: string): Promise { const resolvedPath = this.resolve(url); - const templateFile = File.fromPath(resolvedPath); + const templateFile = this.fs.fileFromPath(resolvedPath); return templateFile.readText(); } - private handleAbsoluteUrls(url: string): string { + resolveRelativeUrls(url: string): string { // Angular assembles absolute URLs and prefixes them with // if (url.indexOf("/") !== 0) { // Resolve relative URLs based on the app root. - return path.join(knownFolders.currentApp().path, url); + return path.join(this.fs.currentApp().path, url); } else { return url; } } - private resolve(url: string) { - const normalizedUrl = this.handleAbsoluteUrls(url); + resolve(url: string) { + const normalizedUrl = this.resolveRelativeUrls(url); - if (File.exists(normalizedUrl)) { + if (this.fs.fileExists(normalizedUrl)) { return normalizedUrl; } @@ -41,7 +49,7 @@ export class FileSystemResourceLoader extends ResourceLoader { }); for (let i = 0; i < fallbackCandidates.length; i++) { - if (File.exists(fallbackCandidates[i])) { + if (this.fs.fileExists(fallbackCandidates[i])) { return fallbackCandidates[i]; } } diff --git a/tests/app/tests/xhr-paths.ts b/tests/app/tests/xhr-paths.ts index 9a4927515..46545808d 100644 --- a/tests/app/tests/xhr-paths.ts +++ b/tests/app/tests/xhr-paths.ts @@ -1,22 +1,68 @@ // make sure you import mocha-config before @angular/core -import {assert} from "./test-config"; -import {FileSystemResourceLoader} from "nativescript-angular/resource-loader"; +import { assert } from "./test-config"; +import { FileSystemResourceLoader } from "nativescript-angular/resource-loader"; + +import { File } from "tns-core-modules/file-system"; +import { NSFileSystem } from "nativescript-angular/file-system/ns-file-system"; + +class NSFileSystemMock { + public currentApp(): any { + return { path: "/app/dir" }; + } + + public fileFromPath(path: string): File { + return null; + } + + public fileExists(path: string): boolean { + // mycomponent.html aways exists + // mycomponent.css is the other file + return path.indexOf("mycomponent.html") >= 0 || path === "/app/dir/mycomponent.css"; + } +} +const fsMock = new NSFileSystemMock(); describe("XHR name resolution", () => { + let resourceLoader: FileSystemResourceLoader; + before(() => { + resourceLoader = new FileSystemResourceLoader(new NSFileSystemMock()); + }); + it("resolves relative paths from app root", () => { - const xhr = new FileSystemResourceLoader(); - assert.strictEqual("/app/dir/mydir/mycomponent.html", xhr.resolve("mydir/mycomponent.html", "/app/dir")); + assert.strictEqual("/app/dir/mydir/mycomponent.html", resourceLoader.resolve("mydir/mycomponent.html")); }); it("resolves double-slashed absolute paths as is", () => { - const xhr = new FileSystemResourceLoader(); - assert.strictEqual("//app/mydir/mycomponent.html", xhr.resolve("//app/mydir/mycomponent.html", "/app/dir")); + assert.strictEqual("//app/mydir/mycomponent.html", resourceLoader.resolve("//app/mydir/mycomponent.html")); }); it("resolves single-slashed absolute paths as is", () => { - const xhr = new FileSystemResourceLoader(); assert.strictEqual( "/data/data/app/mydir/mycomponent.html", - xhr.resolve("/data/data/app/mydir/mycomponent.html", "/app/dir")); + resourceLoader.resolve("/data/data/app/mydir/mycomponent.html")); + }); + + it("resolves existing CSS file", () => { + assert.strictEqual( + "/app/dir/mycomponent.css", + resourceLoader.resolve("mycomponent.css")); + }); + + it("resolves non-existing .scss file to existing .css file", () => { + assert.strictEqual( + "/app/dir/mycomponent.css", + resourceLoader.resolve("mycomponent.scss")); + }); + + it("resolves non-existing .sass file to existing .css file", () => { + assert.strictEqual( + "/app/dir/mycomponent.css", + resourceLoader.resolve("mycomponent.sass")); + }); + + it("resolves non-existing .less file to existing .css file", () => { + assert.strictEqual( + "/app/dir/mycomponent.css", + resourceLoader.resolve("mycomponent.less")); }); }); From 409d189af9a376695fbb67af47a5716a1f3a2e66 Mon Sep 17 00:00:00 2001 From: Stanimira Vlaeva Date: Wed, 30 Aug 2017 15:27:04 +0300 Subject: [PATCH 3/3] refactor: resource loader (#968) * chore(lint): use 4 spaces instead of 2 in ns-file-system * refactor: resource loader * test(xhr-paths): assert resource loader throws when resolving non-existing files --- .../file-system/ns-file-system.ts | 18 +++---- nativescript-angular/resource-loader.ts | 51 ++++++++++++------- tests/app/tests/xhr-paths.ts | 6 ++- 3 files changed, 46 insertions(+), 29 deletions(-) diff --git a/nativescript-angular/file-system/ns-file-system.ts b/nativescript-angular/file-system/ns-file-system.ts index 1cdc8c9e5..222a54c12 100644 --- a/nativescript-angular/file-system/ns-file-system.ts +++ b/nativescript-angular/file-system/ns-file-system.ts @@ -6,15 +6,15 @@ import { knownFolders, Folder, File } from "tns-core-modules/file-system"; @Injectable() export class NSFileSystem { - public currentApp(): Folder { - return knownFolders.currentApp(); - } + public currentApp(): Folder { + return knownFolders.currentApp(); + } - public fileFromPath(path: string): File { - return File.fromPath(path); - } + public fileFromPath(path: string): File { + return File.fromPath(path); + } - public fileExists(path: string): boolean { - return File.exists(path); - } + public fileExists(path: string): boolean { + return File.exists(path); + } } diff --git a/nativescript-angular/resource-loader.ts b/nativescript-angular/resource-loader.ts index 10e6ffc47..43a5091bc 100644 --- a/nativescript-angular/resource-loader.ts +++ b/nativescript-angular/resource-loader.ts @@ -24,7 +24,24 @@ export class FileSystemResourceLoader extends ResourceLoader { return templateFile.readText(); } - resolveRelativeUrls(url: string): string { + resolve(url: string): string { + const normalizedUrl = this.resolveRelativeUrls(url); + + if (this.fs.fileExists(normalizedUrl)) { + return normalizedUrl; + } + + const { candidates: fallbackCandidates, resource: fallbackResource } = + this.fallbackResolve(normalizedUrl); + + if (fallbackResource) { + return fallbackResource; + } + + throw new Error(`Could not resolve ${url}. Looked for: ${normalizedUrl}, ${fallbackCandidates}`); + } + + private resolveRelativeUrls(url: string): string { // Angular assembles absolute URLs and prefixes them with // if (url.indexOf("/") !== 0) { // Resolve relative URLs based on the app root. @@ -34,26 +51,22 @@ export class FileSystemResourceLoader extends ResourceLoader { } } - resolve(url: string) { - const normalizedUrl = this.resolveRelativeUrls(url); + private fallbackResolve(url: string): + ({ resource: string, candidates: string[] }) { - if (this.fs.fileExists(normalizedUrl)) { - return normalizedUrl; - } + const candidates = extensionsFallbacks + .filter(([extension]) => url.endsWith(extension)) + .map(([extension, fallback]) => + this.replaceExtension(url, extension, fallback)); - const fallbackCandidates = []; - extensionsFallbacks.forEach(([extension, fallback]) => { - if (normalizedUrl.endsWith(extension)) { - fallbackCandidates.push(normalizedUrl.substr(0, normalizedUrl.length - extension.length) + fallback); - } - }); - - for (let i = 0; i < fallbackCandidates.length; i++) { - if (this.fs.fileExists(fallbackCandidates[i])) { - return fallbackCandidates[i]; - } - } + const resource = candidates.find(candidate => this.fs.fileExists(candidate)); - throw new Error(`Could not resolve ${url}. Looked for: ${normalizedUrl}, ${fallbackCandidates}`); + return { candidates, resource }; + } + + private replaceExtension(fileName: string, oldExtension: string, newExtension: string): string { + const baseName = fileName.substr(0, fileName.length - oldExtension.length); + return baseName + newExtension; } } + diff --git a/tests/app/tests/xhr-paths.ts b/tests/app/tests/xhr-paths.ts index 46545808d..c3de6a6dc 100644 --- a/tests/app/tests/xhr-paths.ts +++ b/tests/app/tests/xhr-paths.ts @@ -15,7 +15,7 @@ class NSFileSystemMock { } public fileExists(path: string): boolean { - // mycomponent.html aways exists + // mycomponent.html always exists // mycomponent.css is the other file return path.indexOf("mycomponent.html") >= 0 || path === "/app/dir/mycomponent.css"; } @@ -65,4 +65,8 @@ describe("XHR name resolution", () => { "/app/dir/mycomponent.css", resourceLoader.resolve("mycomponent.less")); }); + + it("throws for non-existing file that has no fallbacks", () => { + assert.throws(() => resourceLoader.resolve("does-not-exist.css")); + }); });