nativescript
Version:
Command-line interface for building NativeScript projects
340 lines • 17.8 kB
JavaScript
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.PrepareController = void 0;
const chokidar_1 = require("chokidar");
const events_1 = require("events");
const _ = require("lodash");
const path = require("path");
const decorators_1 = require("../common/decorators");
const helpers_1 = require("../common/helpers");
const yok_1 = require("../common/yok");
const constants_1 = require("../constants");
class PrepareController extends events_1.EventEmitter {
constructor($platformController, $hooksService, $fs, $logger, $options, $mobileHelper, $nodeModulesDependenciesBuilder, $platformsDataService, $pluginsService, $prepareNativePlatformService, $projectChangesService, $projectDataService, $webpackCompilerService, $watchIgnoreListService, $analyticsService, $markingModeService, $projectConfigService, $projectService) {
super();
this.$platformController = $platformController;
this.$hooksService = $hooksService;
this.$fs = $fs;
this.$logger = $logger;
this.$options = $options;
this.$mobileHelper = $mobileHelper;
this.$nodeModulesDependenciesBuilder = $nodeModulesDependenciesBuilder;
this.$platformsDataService = $platformsDataService;
this.$pluginsService = $pluginsService;
this.$prepareNativePlatformService = $prepareNativePlatformService;
this.$projectChangesService = $projectChangesService;
this.$projectDataService = $projectDataService;
this.$webpackCompilerService = $webpackCompilerService;
this.$watchIgnoreListService = $watchIgnoreListService;
this.$analyticsService = $analyticsService;
this.$markingModeService = $markingModeService;
this.$projectConfigService = $projectConfigService;
this.$projectService = $projectService;
this.watchersData = {};
this.isInitialPrepareReady = false;
this.persistedData = [];
this.webpackCompilerHandler = null;
this.pausedFileWatch = false;
}
async prepare(prepareData) {
const projectData = this.$projectDataService.getProjectData(prepareData.projectDir);
if (this.$mobileHelper.isAndroidPlatform(prepareData.platform)) {
await this.$markingModeService.handleMarkingModeFullDeprecation({
projectDir: projectData.projectDir,
});
}
await this.$pluginsService.ensureAllDependenciesAreInstalled(projectData);
return this.prepareCore(prepareData, projectData);
}
async stopWatchers(projectDir, platform) {
const platformLowerCase = platform.toLowerCase();
if (this.watchersData &&
this.watchersData[projectDir] &&
this.watchersData[projectDir][platformLowerCase] &&
this.watchersData[projectDir][platformLowerCase].nativeFilesWatcher) {
await this.watchersData[projectDir][platformLowerCase].nativeFilesWatcher.close();
this.watchersData[projectDir][platformLowerCase].nativeFilesWatcher =
null;
}
if (this.watchersData &&
this.watchersData[projectDir] &&
this.watchersData[projectDir][platformLowerCase] &&
this.watchersData[projectDir][platformLowerCase].hasWebpackCompilerProcess) {
await this.$webpackCompilerService.stopWebpackCompiler(platformLowerCase);
this.$webpackCompilerService.removeListener(constants_1.WEBPACK_COMPILATION_COMPLETE, this.webpackCompilerHandler);
this.watchersData[projectDir][platformLowerCase].hasWebpackCompilerProcess = false;
}
}
async prepareCore(prepareData, projectData) {
await this.$projectService.ensureAppResourcesExist(projectData.projectDir);
await this.$platformController.addPlatformIfNeeded(prepareData, projectData);
await this.trackRuntimeVersion(prepareData.platform, projectData);
this.$logger.info("Preparing project...");
// we need to mark the ~/package.json (used by core modules)
// as external for us to be able to write the config to it
// in writeRuntimePackageJson() below, because otherwise
// webpack will inline it into the bundle/vendor chunks
prepareData.env = prepareData.env || {};
prepareData.env.externals = prepareData.env.externals || [];
prepareData.env.externals.push("~/package.json");
prepareData.env.externals.push("package.json");
if (this.$mobileHelper.isAndroidPlatform(prepareData.platform)) {
await this.$projectConfigService.writeLegacyNSConfigIfNeeded(projectData.projectDir, this.$projectDataService.getRuntimePackage(projectData.projectDir, prepareData.platform));
}
let result = null;
const platformData = this.$platformsDataService.getPlatformData(prepareData.platform, projectData);
if (prepareData.watch) {
result = await this.startWatchersWithPrepare(platformData, projectData, prepareData);
}
else {
await this.$webpackCompilerService.compileWithoutWatch(platformData, projectData, prepareData);
const hasNativeChanges = await this.$prepareNativePlatformService.prepareNativePlatform(platformData, projectData, prepareData);
result = {
hasNativeChanges,
platform: prepareData.platform.toLowerCase(),
};
}
await this.writeRuntimePackageJson(projectData, platformData, prepareData);
await this.$projectChangesService.savePrepareInfo(platformData, projectData, prepareData);
this.$logger.info(`Project successfully prepared (${prepareData.platform.toLowerCase()})`);
return result;
}
async startWatchersWithPrepare(platformData, projectData, prepareData) {
if (!this.watchersData[projectData.projectDir]) {
this.watchersData[projectData.projectDir] = {};
}
if (!this.watchersData[projectData.projectDir][platformData.platformNameLowerCase]) {
this.watchersData[projectData.projectDir][platformData.platformNameLowerCase] = {
nativeFilesWatcher: null,
hasWebpackCompilerProcess: false,
prepareArguments: {
platformData,
projectData,
prepareData,
},
};
}
await this.startJSWatcherWithPrepare(platformData, projectData, prepareData); // -> start watcher + initial compilation
const hasNativeChanges = await this.startNativeWatcherWithPrepare(platformData, projectData, prepareData); // -> start watcher + initial prepare
const result = {
platform: platformData.platformNameLowerCase,
hasNativeChanges,
};
const hasPersistedDataWithNativeChanges = this.persistedData.find((data) => data.platform === result.platform && data.hasNativeChanges);
if (hasPersistedDataWithNativeChanges) {
result.hasNativeChanges = true;
}
// TODO: Do not persist this in `this` context. Also it should be per platform.
this.isInitialPrepareReady = true;
if (this.persistedData && this.persistedData.length) {
this.emitPrepareEvent({
files: [],
staleFiles: [],
hasOnlyHotUpdateFiles: false,
hasNativeChanges: result.hasNativeChanges,
hmrData: null,
platform: platformData.platformNameLowerCase,
});
}
return result;
}
async startJSWatcherWithPrepare(platformData, projectData, prepareData) {
if (!this.watchersData[projectData.projectDir][platformData.platformNameLowerCase].hasWebpackCompilerProcess) {
const handler = (data) => {
if (data.platform.toLowerCase() === platformData.platformNameLowerCase) {
if (this.isFileWatcherPaused())
return;
this.emitPrepareEvent({ ...data, hasNativeChanges: false });
}
};
this.webpackCompilerHandler = handler.bind(this);
this.$webpackCompilerService.on(constants_1.WEBPACK_COMPILATION_COMPLETE, this.webpackCompilerHandler);
this.watchersData[projectData.projectDir][platformData.platformNameLowerCase].hasWebpackCompilerProcess = true;
await this.$webpackCompilerService.compileWithWatch(platformData, projectData, prepareData);
}
}
async startNativeWatcherWithPrepare(platformData, projectData, prepareData) {
let newNativeWatchStarted = false;
let hasNativeChanges = false;
if (prepareData.watchNative) {
newNativeWatchStarted = await this.startNativeWatcher(platformData, projectData);
}
if (newNativeWatchStarted) {
hasNativeChanges =
await this.$prepareNativePlatformService.prepareNativePlatform(platformData, projectData, prepareData);
}
return hasNativeChanges;
}
async startNativeWatcher(platformData, projectData) {
if (this.watchersData[projectData.projectDir][platformData.platformNameLowerCase].nativeFilesWatcher) {
return false;
}
const patterns = await this.getWatcherPatterns(platformData, projectData);
const watcherOptions = {
ignoreInitial: true,
cwd: projectData.projectDir,
awaitWriteFinish: {
pollInterval: 100,
stabilityThreshold: 500,
},
ignored: ["**/.*", ".*"], // hidden files
};
const watcher = (0, chokidar_1.watch)(patterns, watcherOptions).on("all", async (event, filePath) => {
if (this.isFileWatcherPaused())
return;
filePath = path.join(projectData.projectDir, filePath);
if (this.$watchIgnoreListService.isFileInIgnoreList(filePath)) {
this.$watchIgnoreListService.removeFileFromIgnoreList(filePath);
}
else {
this.$logger.info(`Chokidar raised event ${event} for ${filePath}.`);
await this.writeRuntimePackageJson(projectData, platformData);
this.emitPrepareEvent({
files: [],
staleFiles: [],
hasOnlyHotUpdateFiles: false,
hmrData: null,
hasNativeChanges: true,
platform: platformData.platformNameLowerCase,
});
}
});
this.watchersData[projectData.projectDir][platformData.platformNameLowerCase].nativeFilesWatcher = watcher;
return true;
}
async getWatcherPatterns(platformData, projectData) {
const dependencies = this.$nodeModulesDependenciesBuilder
.getProductionDependencies(projectData.projectDir, projectData.ignoredDependencies)
.filter((dep) => dep.nativescript);
const pluginsNativeDirectories = dependencies.map((dep) => path.join(dep.directory, constants_1.PLATFORMS_DIR_NAME, platformData.platformNameLowerCase));
const pluginsPackageJsonFiles = dependencies.map((dep) => path.join(dep.directory, constants_1.PACKAGE_JSON_FILE_NAME));
const patterns = [
path.join(projectData.projectDir, constants_1.PACKAGE_JSON_FILE_NAME),
path.join(projectData.projectDir, constants_1.CONFIG_FILE_NAME_JS),
path.join(projectData.projectDir, constants_1.CONFIG_FILE_NAME_TS),
path.join(projectData.getAppDirectoryPath(), constants_1.PACKAGE_JSON_FILE_NAME),
path.join(projectData.getAppResourcesRelativeDirectoryPath(), platformData.normalizedPlatformName),
]
.concat(pluginsNativeDirectories)
.concat(pluginsPackageJsonFiles);
return patterns;
}
/**
* TODO: move this logic to the webpack side of things - WIP and deprecate here with a webpack version check...
*/
async writeRuntimePackageJson(projectData, platformData, prepareData = null) {
const configInfo = this.$projectConfigService.detectProjectConfigs(projectData.projectDir);
if (configInfo.usingNSConfig) {
return;
}
this.$logger.info("Updating runtime package.json with configuration values...");
const nsConfig = this.$projectConfigService.readConfig(projectData.projectDir);
const packageData = {
..._.pick(projectData.packageJsonData, ["name"]),
...nsConfig,
main: "bundle",
};
if (platformData.platformNameLowerCase === "ios" &&
packageData.ios &&
packageData.ios.discardUncaughtJsExceptions) {
packageData.discardUncaughtJsExceptions =
packageData.ios.discardUncaughtJsExceptions;
}
if (platformData.platformNameLowerCase === "android" &&
packageData.android &&
packageData.android.discardUncaughtJsExceptions) {
packageData.discardUncaughtJsExceptions =
packageData.android.discardUncaughtJsExceptions;
}
let packagePath;
if (this.$mobileHelper.isApplePlatform(platformData.platformNameLowerCase)) {
packagePath = path.join(platformData.projectRoot, projectData.projectName, "app", "package.json");
}
else {
packagePath = path.join(platformData.projectRoot, this.$options.hostProjectModuleName, "src", this.$options.hostProjectPath ? "nativescript" : "main", "assets", "app", "package.json");
}
try {
// this will read the package.json that is already emitted by
// the GenerateNativeScriptEntryPointsPlugin webpack plugin
const emittedPackageData = this.$fs.readJson(packagePath);
// since ns7 we only care about the main key from the emitted
// package.json, the rest is coming from the new config.
if (emittedPackageData === null || emittedPackageData === void 0 ? void 0 : emittedPackageData.main) {
packageData.main = emittedPackageData.main;
}
}
catch (error) {
this.$logger.trace("Failed to read emitted package.json. Error is: ", error);
}
if (prepareData === null || prepareData === void 0 ? void 0 : prepareData.uniqueBundle) {
packageData.main = `${packageData.main}.${prepareData.uniqueBundle}`;
}
this.$fs.writeJson(packagePath, packageData);
}
emitPrepareEvent(filesChangeEventData) {
if (this.isInitialPrepareReady) {
this.emit(constants_1.PREPARE_READY_EVENT_NAME, filesChangeEventData);
}
else {
this.persistedData.push(filesChangeEventData);
}
}
async trackRuntimeVersion(platform, projectData) {
const { version } = this.$projectDataService.getRuntimePackage(projectData.projectDir, platform);
if (!version) {
this.$logger.trace(`Unable to get runtime version for project directory: ${projectData.projectDir} and platform ${platform}.`);
return;
}
await this.$analyticsService.trackEventActionInGoogleAnalytics({
action: "Using Runtime Version" /* TrackActionNames.UsingRuntimeVersion */,
additionalData: `${platform.toLowerCase()}${constants_1.AnalyticsEventLabelDelimiter}${version}`,
});
}
isFileWatcherPaused() {
return this.pausedFileWatch;
}
async toggleFileWatcher() {
this.pausedFileWatch = !this.pausedFileWatch;
const watchers = Object.values(this.watchersData);
if (this.pausedFileWatch) {
for (const watcher of watchers) {
for (const platform in watcher) {
await this.$webpackCompilerService.stopWebpackCompiler(platform);
watcher[platform].hasWebpackCompilerProcess = false;
}
}
}
else {
for (const watcher of watchers) {
for (const platform in watcher) {
const args = watcher[platform].prepareArguments;
watcher[platform].hasWebpackCompilerProcess = true;
await this.$webpackCompilerService.compileWithWatch(args.platformData, args.projectData, args.prepareData);
}
}
}
return this.pausedFileWatch;
}
}
exports.PrepareController = PrepareController;
__decorate([
(0, decorators_1.performanceLog)(),
(0, helpers_1.hook)("prepare")
], PrepareController.prototype, "prepareCore", null);
__decorate([
(0, helpers_1.hook)("watch")
], PrepareController.prototype, "startWatchersWithPrepare", null);
__decorate([
(0, helpers_1.hook)("watchPatterns")
], PrepareController.prototype, "getWatcherPatterns", null);
__decorate([
(0, decorators_1.cache)()
], PrepareController.prototype, "trackRuntimeVersion", null);
yok_1.injector.register("prepareController", PrepareController);
//# sourceMappingURL=prepare-controller.js.map