Skip to content

Commit 14ce44a

Browse files
authored
Merge pull request #2147 from NativeScript/hdeshev/webpack-app-dir
Do not copy app files to platform dir if using --bundle
2 parents 61dc8c3 + b461ddd commit 14ce44a

File tree

7 files changed

+233
-35
lines changed

7 files changed

+233
-35
lines changed

lib/bootstrap.ts

+2
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ $injector.requireCommand("debug|ios", "./commands/debug");
4242
$injector.requireCommand("debug|android", "./commands/debug");
4343

4444
$injector.requireCommand("prepare", "./commands/prepare");
45+
$injector.requireCommand("clean-app|ios", "./commands/clean-app");
46+
$injector.requireCommand("clean-app|android", "./commands/clean-app");
4547
$injector.requireCommand("build|ios", "./commands/build");
4648
$injector.requireCommand("build|android", "./commands/build");
4749
$injector.requireCommand("deploy", "./commands/deploy");

lib/commands/clean-app.ts

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
export class CleanAppCommandBase {
2+
constructor(protected $options: IOptions,
3+
private $platformService: IPlatformService) { }
4+
5+
execute(args: string[]): IFuture<void> {
6+
let platform = args[0].toLowerCase();
7+
return this.$platformService.cleanDestinationApp(platform);
8+
}
9+
}
10+
11+
export class CleanAppIosCommand extends CleanAppCommandBase implements ICommand {
12+
constructor(protected $options: IOptions,
13+
private $platformsData: IPlatformsData,
14+
$platformService: IPlatformService) {
15+
super($options, $platformService);
16+
}
17+
18+
public allowedParameters: ICommandParameter[] = [];
19+
20+
public execute(args: string[]): IFuture<void> {
21+
return super.execute([this.$platformsData.availablePlatforms.iOS]);
22+
}
23+
}
24+
$injector.registerCommand("clean-app|ios", CleanAppIosCommand);
25+
26+
export class CleanAppAndroidCommand extends CleanAppCommandBase implements ICommand {
27+
constructor(protected $options: IOptions,
28+
private $platformsData: IPlatformsData,
29+
private $errors: IErrors,
30+
$platformService: IPlatformService) {
31+
super($options, $platformService);
32+
}
33+
34+
public allowedParameters: ICommandParameter[] = [];
35+
36+
public execute(args: string[]): IFuture<void> {
37+
return super.execute([this.$platformsData.availablePlatforms.Android]);
38+
}
39+
}
40+
$injector.registerCommand("clean-app|android", CleanAppAndroidCommand);

lib/definitions/platform.d.ts

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ interface IPlatformService {
77
updatePlatforms(platforms: string[]): IFuture<void>;
88
runPlatform(platform: string, buildConfig?: IBuildConfig): IFuture<void>;
99
preparePlatform(platform: string): IFuture<boolean>;
10+
cleanDestinationApp(platform: string): IFuture<void>;
1011
buildPlatform(platform: string, buildConfig?: IBuildConfig): IFuture<void>;
1112
buildForDeploy(platform: string, buildConfig?: IBuildConfig): IFuture<void>;
1213
installOnDevice(platform: string, buildConfig?: IBuildConfig): IFuture<void>;

lib/services/app-files-updater.ts

+86
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import * as path from "path";
2+
import * as minimatch from "minimatch";
3+
import * as constants from "../constants";
4+
import Future = require("fibers/future");
5+
6+
export class AppFilesUpdater {
7+
constructor(
8+
private appSourceDirectoryPath: string,
9+
private appDestinationDirectoryPath: string,
10+
public options: IOptions,
11+
public fs: IFileSystem
12+
) {
13+
}
14+
15+
public updateApp(beforeCopyAction: (sourceFiles: string[]) => void): void {
16+
this.cleanDestinationApp();
17+
const sourceFiles = this.resolveAppSourceFiles();
18+
19+
beforeCopyAction(sourceFiles);
20+
this.copyAppSourceFiles(sourceFiles);
21+
}
22+
23+
public cleanDestinationApp(): void {
24+
if (this.options.bundle) {
25+
//Assuming an the bundle has updated the dest folder already.
26+
//Skip cleaning up completely.
27+
return;
28+
}
29+
30+
// Delete the destination app in order to prevent EEXIST errors when symlinks are used.
31+
let destinationAppContents = this.readDestinationDir();
32+
destinationAppContents = destinationAppContents.filter(
33+
(directoryName: string) => directoryName !== constants.TNS_MODULES_FOLDER_NAME);
34+
35+
_(destinationAppContents).each((directoryItem: string) => {
36+
this.deleteDestinationItem(directoryItem);
37+
});
38+
}
39+
40+
protected readDestinationDir(): string[] {
41+
if (this.fs.exists(this.appDestinationDirectoryPath).wait()) {
42+
return this.fs.readDirectory(this.appDestinationDirectoryPath).wait();
43+
} else {
44+
return [];
45+
}
46+
}
47+
48+
protected deleteDestinationItem(directoryItem: string): void {
49+
this.fs.deleteDirectory(path.join(this.appDestinationDirectoryPath, directoryItem)).wait();
50+
}
51+
52+
protected readSourceDir(): string[] {
53+
return this.fs.enumerateFilesInDirectorySync(this.appSourceDirectoryPath, null, { includeEmptyDirectories: true });
54+
}
55+
56+
protected resolveAppSourceFiles(): string[] {
57+
// Copy all files from app dir, but make sure to exclude tns_modules
58+
let sourceFiles = this.readSourceDir();
59+
60+
if (this.options.release) {
61+
let testsFolderPath = path.join(this.appSourceDirectoryPath, 'tests');
62+
sourceFiles = sourceFiles.filter(source => source.indexOf(testsFolderPath) === -1);
63+
}
64+
65+
// Remove .ts and .js.map files in release
66+
if (this.options.release) {
67+
constants.LIVESYNC_EXCLUDED_FILE_PATTERNS.forEach(pattern => sourceFiles = sourceFiles.filter(file => !minimatch(file, pattern, { nocase: true })));
68+
}
69+
70+
if (this.options.bundle) {
71+
sourceFiles = sourceFiles.filter(file => minimatch(file, "**/App_Resources/**", {nocase: true}));
72+
}
73+
return sourceFiles;
74+
}
75+
76+
protected copyAppSourceFiles(sourceFiles: string[]): void {
77+
let copyFileFutures = sourceFiles.map(source => {
78+
let destinationPath = path.join(this.appDestinationDirectoryPath, path.relative(this.appSourceDirectoryPath, source));
79+
if (this.fs.getFsStats(source).wait().isDirectory()) {
80+
return this.fs.createDirectory(destinationPath);
81+
}
82+
return this.fs.copyFile(source, destinationPath);
83+
});
84+
Future.wait(copyFileFutures);
85+
}
86+
}

lib/services/platform-service.ts

+17-34
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@ import * as shell from "shelljs";
33
import * as constants from "../constants";
44
import * as helpers from "../common/helpers";
55
import * as semver from "semver";
6-
import * as minimatch from "minimatch";
7-
import Future = require("fibers/future");
6+
import {AppFilesUpdater} from "./app-files-updater";
87
import * as temp from "temp";
98
temp.track();
109
let clui = require("clui");
@@ -244,44 +243,18 @@ export class PlatformService implements IPlatformService {
244243
this.$fs.ensureDirectoryExists(appDestinationDirectoryPath).wait();
245244
let appSourceDirectoryPath = path.join(this.$projectData.projectDir, constants.APP_FOLDER_NAME);
246245

247-
// Delete the destination app in order to prevent EEXIST errors when symlinks are used.
248-
let contents = this.$fs.readDirectory(appDestinationDirectoryPath).wait();
249-
250-
_(contents)
251-
.filter(directoryName => directoryName !== constants.TNS_MODULES_FOLDER_NAME)
252-
.each(directoryName => this.$fs.deleteDirectory(path.join(appDestinationDirectoryPath, directoryName)).wait());
253-
254-
// Copy all files from app dir, but make sure to exclude tns_modules
255-
let sourceFiles = this.$fs.enumerateFilesInDirectorySync(appSourceDirectoryPath, null, { includeEmptyDirectories: true });
256-
257-
if (this.$options.release) {
258-
let testsFolderPath = path.join(appSourceDirectoryPath, 'tests');
259-
sourceFiles = sourceFiles.filter(source => source.indexOf(testsFolderPath) === -1);
260-
}
261-
262-
// verify .xml files are well-formed
263-
this.$xmlValidator.validateXmlFiles(sourceFiles).wait();
264-
265-
// Remove .ts and .js.map files in release
266-
if (this.$options.release) {
267-
constants.LIVESYNC_EXCLUDED_FILE_PATTERNS.forEach(pattern => sourceFiles = sourceFiles.filter(file => !minimatch(file, pattern, { nocase: true })));
268-
}
269-
270-
let copyFileFutures = sourceFiles.map(source => {
271-
let destinationPath = path.join(appDestinationDirectoryPath, path.relative(appSourceDirectoryPath, source));
272-
if (this.$fs.getFsStats(source).wait().isDirectory()) {
273-
return this.$fs.createDirectory(destinationPath);
274-
}
275-
return this.$fs.copyFile(source, destinationPath);
246+
const appUpdater = new AppFilesUpdater(appSourceDirectoryPath, appDestinationDirectoryPath, this.$options, this.$fs);
247+
appUpdater.updateApp(sourceFiles => {
248+
this.$xmlValidator.validateXmlFiles(sourceFiles).wait();
276249
});
277-
Future.wait(copyFileFutures);
278250

279251
// Copy App_Resources to project root folder
280-
this.$fs.ensureDirectoryExists(platformData.platformProjectService.getAppResourcesDestinationDirectoryPath().wait()).wait(); // Should be deleted
252+
const appResourcesDestination = platformData.platformProjectService.getAppResourcesDestinationDirectoryPath().wait();
253+
this.$fs.ensureDirectoryExists(appResourcesDestination).wait(); // Should be deleted
281254
let appResourcesDirectoryPath = path.join(appDestinationDirectoryPath, constants.APP_RESOURCES_FOLDER_NAME);
282255
if (this.$fs.exists(appResourcesDirectoryPath).wait()) {
283256
platformData.platformProjectService.prepareAppResources(appResourcesDirectoryPath).wait();
284-
shell.cp("-Rf", path.join(appResourcesDirectoryPath, platformData.normalizedPlatformName, "*"), platformData.platformProjectService.getAppResourcesDestinationDirectoryPath().wait());
257+
shell.cp("-Rf", path.join(appResourcesDirectoryPath, platformData.normalizedPlatformName, "*"), appResourcesDestination);
285258
this.$fs.deleteDirectory(appResourcesDirectoryPath).wait();
286259
}
287260

@@ -316,6 +289,16 @@ export class PlatformService implements IPlatformService {
316289
}).future<boolean>()();
317290
}
318291

292+
public cleanDestinationApp(platform: string): IFuture<void> {
293+
return (() => {
294+
const appSourceDirectoryPath = path.join(this.$projectData.projectDir, constants.APP_FOLDER_NAME);
295+
let platformData = this.$platformsData.getPlatformData(platform);
296+
let appDestinationDirectoryPath = path.join(platformData.appDestinationDirectoryPath, constants.APP_FOLDER_NAME);
297+
const appUpdater = new AppFilesUpdater(appSourceDirectoryPath, appDestinationDirectoryPath, this.$options, this.$fs);
298+
appUpdater.cleanDestinationApp();
299+
}).future<void>()();
300+
}
301+
319302
public buildPlatform(platform: string, buildConfig?: IBuildConfig): IFuture<void> {
320303
return (() => {
321304
platform = platform.toLowerCase();

test/app-files-updates.ts

+82
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import {assert} from "chai";
2+
import {AppFilesUpdater} from "../lib/services/app-files-updater";
3+
4+
require("should");
5+
6+
describe("App files cleanup", () => {
7+
class CleanUpAppFilesUpdater extends AppFilesUpdater {
8+
public deletedDestinationItems: string[] = [];
9+
10+
constructor(
11+
public destinationFiles: string[],
12+
options: any
13+
) {
14+
super("<source>", "<destination>", options, null);
15+
}
16+
17+
public clean() {
18+
this.cleanDestinationApp();
19+
}
20+
21+
protected readDestinationDir(): string[] {
22+
return this.destinationFiles;
23+
}
24+
25+
protected deleteDestinationItem(directoryItem: string): void {
26+
this.deletedDestinationItems.push(directoryItem);
27+
}
28+
}
29+
30+
it("cleans up entire app when not bundling", () => {
31+
const updater = new CleanUpAppFilesUpdater([
32+
"file1", "dir1/file2", "App_Resources/Android/blah.png"
33+
], {bundle: false});
34+
updater.clean();
35+
assert.deepEqual(["file1", "dir1/file2", "App_Resources/Android/blah.png"], updater.deletedDestinationItems);
36+
});
37+
38+
it("does not clean up destination when bundling", () => {
39+
const updater = new CleanUpAppFilesUpdater([
40+
"file1", "dir1/file2", "App_Resources/Android/blah.png"
41+
], {bundle: true});
42+
updater.clean();
43+
assert.deepEqual([], updater.deletedDestinationItems);
44+
});
45+
});
46+
47+
describe("App files copy", () => {
48+
class CopyAppFilesUpdater extends AppFilesUpdater {
49+
public copiedDestinationItems: string[] = [];
50+
51+
constructor(
52+
public sourceFiles: string[],
53+
options: any
54+
) {
55+
super("<source>", "<destination>", options, null);
56+
}
57+
58+
protected readSourceDir(): string[] {
59+
return this.sourceFiles;
60+
}
61+
62+
public copy(): void {
63+
this.copiedDestinationItems = this.resolveAppSourceFiles();
64+
}
65+
}
66+
67+
it("copies all app files when not bundling", () => {
68+
const updater = new CopyAppFilesUpdater([
69+
"file1", "dir1/file2", "App_Resources/Android/blah.png"
70+
], {bundle: false});
71+
updater.copy();
72+
assert.deepEqual(["file1", "dir1/file2", "App_Resources/Android/blah.png"], updater.copiedDestinationItems);
73+
});
74+
75+
it("skips copying non-App_Resource files when bundling", () => {
76+
const updater = new CopyAppFilesUpdater([
77+
"file1", "dir1/file2", "App_Resources/Android/blah.png"
78+
], {bundle: true});
79+
updater.copy();
80+
assert.deepEqual(["App_Resources/Android/blah.png"], updater.copiedDestinationItems);
81+
});
82+
});

test/test-bootstrap.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,11 @@ global._ = require("lodash");
44
global.$injector = require("../lib/common/yok").injector;
55

66
$injector.register("analyticsService", {
7-
trackException: (): IFuture<void> => undefined
7+
trackException: (): {wait(): void} => {
8+
return {
9+
wait: () => undefined
10+
};
11+
}
812
});
913

1014
// Converts the js callstack to typescript

0 commit comments

Comments
 (0)