UNPKG

nativescript

Version:

Command-line interface for building NativeScript projects

340 lines • 17.8 kB
"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