Skip to content

feat(styling): Allow loading .css files as a fallback if no .scss file is found(#954) #955

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Aug 30, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 12 additions & 4 deletions nativescript-angular/file-system/ns-file-system.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
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

@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 fileExists(path: string): boolean {
return File.exists(path);
}
}
2 changes: 2 additions & 0 deletions nativescript-angular/platform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ if ((<any>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 = [
Expand All @@ -43,6 +44,7 @@ export const NS_COMPILER_PROVIDERS = [
provide: COMPILER_OPTIONS,
useValue: {
providers: [
NSFileSystem,
{ provide: ResourceLoader, useClass: FileSystemResourceLoader },
{ provide: ElementSchemaRegistry, useClass: NativeScriptElementSchemaRegistry },
]
Expand Down
73 changes: 60 additions & 13 deletions nativescript-angular/resource-loader.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,72 @@
import { path, knownFolders, File } from "tns-core-modules/file-system";
import { Injectable } from "@angular/core";
import { ResourceLoader } from "@angular/compiler";
import { path } from "tns-core-modules/file-system";

import { NSFileSystem } from "./file-system/ns-file-system";

const extensionsFallbacks = [
[".scss", ".css"],
[".sass", ".css"],
[".less", ".css"]
];

@Injectable()
export class FileSystemResourceLoader extends ResourceLoader {
resolve(url: string, baseUrl: string): string {
// Angular assembles absolute URL's and prefixes them with //
constructor(private fs: NSFileSystem) {
super();
}

get(url: string): Promise<string> {
const resolvedPath = this.resolve(url);

const templateFile = this.fs.fileFromPath(resolvedPath);

return templateFile.readText();
}

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 URL's based on the app root.
return path.join(baseUrl, url);
// Resolve relative URLs based on the app root.
return path.join(this.fs.currentApp().path, url);
} else {
return url;
}
}

get(url: string): Promise<string> {
const appDir = knownFolders.currentApp().path;
const templatePath = this.resolve(url, appDir);
private fallbackResolve(url: string):
({ resource: string, candidates: string[] }) {

if (!File.exists(templatePath)) {
throw new Error(`File ${templatePath} does not exist. Resolved from: ${url}.`);
}
let templateFile = File.fromPath(templatePath);
return templateFile.readText();
const candidates = extensionsFallbacks
.filter(([extension]) => url.endsWith(extension))
.map(([extension, fallback]) =>
this.replaceExtension(url, extension, fallback));

const resource = candidates.find(candidate => this.fs.fileExists(candidate));

return { candidates, resource };
}

private replaceExtension(fileName: string, oldExtension: string, newExtension: string): string {
const baseName = fileName.substr(0, fileName.length - oldExtension.length);
return baseName + newExtension;
}
}

66 changes: 58 additions & 8 deletions tests/app/tests/xhr-paths.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,72 @@
// 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 always 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"));
});

it("throws for non-existing file that has no fallbacks", () => {
assert.throws(() => resourceLoader.resolve("does-not-exist.css"));
});
});