From 3f7bf676ff4321d7c490099e3c72687ad68d46a2 Mon Sep 17 00:00:00 2001 From: Nathan Walker Date: Tue, 6 Oct 2020 08:19:15 -0700 Subject: [PATCH] feat(webpack): angular configuration support for environment handling (#8938) --- .../webpack/helpers/angular-config-parser.js | 104 ++++++++++++++++++ packages/webpack/templates/webpack.angular.js | 22 +++- .../webpack/templates/webpack.config.spec.ts | 11 ++ 3 files changed, 134 insertions(+), 3 deletions(-) create mode 100644 packages/webpack/helpers/angular-config-parser.js diff --git a/packages/webpack/helpers/angular-config-parser.js b/packages/webpack/helpers/angular-config-parser.js new file mode 100644 index 000000000..b664af14b --- /dev/null +++ b/packages/webpack/helpers/angular-config-parser.js @@ -0,0 +1,104 @@ +const { resolve } = require('path'); +const fs = require('fs'); + +const parseWorkspaceConfig = function(platform, envConfigs, projectName, debug) { + if (debug) { + console.log('-- config DEBUG ---'); + console.log('platform:', platform); + console.log('configuration:', envConfigs); + } + // configuration file replacements + const fileReplacements = {}; + // anything other than .ts files should be added as part of copy plugin + const copyReplacements = []; + if (hasConfigurations(envConfigs)) { + envConfigs = envConfigs.split(',').map(e => e.trim()); + + const configData = findConfig(__dirname); + const rootPath = configData.rootPath; + const workspaceConfig = configData.workspaceConfig; + + if (workspaceConfig && projectName) { + const projectSettings = workspaceConfig.projects[projectName]; + if (projectSettings) { + + // default project configurations + for (const envConfig of envConfigs) { + if (projectSettings.configurations && projectSettings.configurations[envConfig]) { + if (projectSettings.configurations[envConfig].fileReplacements) { + for (const fileReplace of projectSettings.configurations[envConfig].fileReplacements) { + if (debug) { + console.log('project fileReplacement:', fileReplace); + } + if (fileReplace.replace.indexOf('.ts') > -1) { + fileReplacements[resolve(__dirname, `${rootPath}${fileReplace.replace}`)] = resolve(__dirname, `${rootPath}${fileReplace.with}`); + } else { + copyReplacements.push({ from: resolve(__dirname, `${rootPath}${fileReplace.with}`), to: resolve(__dirname, `${rootPath}${fileReplace.replace}`), force: true }); + } + } + } + } + } + // platform specific configurations (always override top level project configurations) + for (const envConfig of envConfigs) { + if (projectSettings.architect && projectSettings.architect[platform]) { + const platformConfig = projectSettings.architect[platform].configurations; + if (platformConfig && platformConfig[envConfig] && platformConfig[envConfig].fileReplacements) { + for (const fileReplace of platformConfig[envConfig].fileReplacements) { + if (debug) { + console.log(`"${platform}" specific fileReplacement:`, fileReplace); + } + if (fileReplace.replace.indexOf('.ts') > -1) { + fileReplacements[resolve(__dirname, `${rootPath}${fileReplace.replace}`)] = resolve(__dirname, `${rootPath}${fileReplace.with}`); + } else { + copyReplacements.push({ from: resolve(__dirname, `${rootPath}${fileReplace.with}`), to: resolve(__dirname, `${rootPath}${fileReplace.replace}`), force: true }); + } + } + } + } + } + } + } + } + + if (debug && copyReplacements.length) { + console.log('Adding to CopyWebpackPlugin:', copyReplacements); + } + + return { + fileReplacements, + copyReplacements + }; +} + +const findConfig = function(projectDir, rootPath = '') { + // support workspace.json and angular.json configurations + const angularConfigName = 'angular.json'; + const angularConfig = resolve(projectDir, angularConfigName); + const workspaceConfigName = 'workspace.json'; + const workspaceConfig = resolve(projectDir, workspaceConfigName); + if (fs.existsSync(workspaceConfig)) { + return { + rootPath, + workspaceConfig: require(workspaceConfig) + }; + } else if (fs.existsSync(angularConfig)) { + return { + rootPath, + workspaceConfig: require(angularConfig) + }; + } else { + rootPath += '../'; + return findConfig(resolve(projectDir, '..'), rootPath); + } +} + +const hasConfigurations = function(envConfigs) { + return envConfigs && envConfigs !== 'undefined'; +} + +module.exports = { + parseWorkspaceConfig, + findConfig, + hasConfigurations +}; \ No newline at end of file diff --git a/packages/webpack/templates/webpack.angular.js b/packages/webpack/templates/webpack.angular.js index be7cba6ba..33088382a 100644 --- a/packages/webpack/templates/webpack.angular.js +++ b/packages/webpack/templates/webpack.angular.js @@ -8,6 +8,9 @@ const { nsSupportHmrNg } = require('@nativescript/webpack/transformers/ns-support-hmr-ng'); const { nsTransformNativeClassesNg } = require("@nativescript/webpack/transformers/ns-transform-native-classes-ng"); +const { + parseWorkspaceConfig, hasConfigurations +} = require('@nativescript/webpack/helpers/angular-config-parser'); const { getMainModulePath } = require('@nativescript/webpack/utils/ast-utils'); @@ -54,6 +57,8 @@ module.exports = env => { // You can provide the following flags when running 'tns run android|ios' snapshot, // --env.snapshot, production, // --env.production + configuration, // --env.configuration (consistent with angular cli usage) + projectName, // --env.projectName (drive configuration through angular projects) uglify, // --env.uglify report, // --env.report sourceMap, // --env.sourceMap @@ -68,20 +73,29 @@ module.exports = env => { compileSnapshot // --env.compileSnapshot } = env; + const { fileReplacements, copyReplacements } = parseWorkspaceConfig(platform, configuration, projectName); + const useLibs = compileSnapshot; const isAnySourceMapEnabled = !!sourceMap || !!hiddenSourceMap; const externals = nsWebpack.getConvertedExternals(env.externals); const appFullPath = resolve(projectRoot, appPath); const appResourcesFullPath = resolve(projectRoot, appResourcesPath); let tsConfigName = 'tsconfig.json'; - let tsConfigTnsName = 'tsconfig.tns.json'; let tsConfigPath = resolve(projectRoot, tsConfigName); + const tsConfigTnsName = 'tsconfig.tns.json'; const tsConfigTnsPath = resolve(projectRoot, tsConfigTnsName); if (fs.existsSync(tsConfigTnsPath)) { - // still support shared angular app configurations + // support shared angular app configurations tsConfigName = tsConfigTnsName; tsConfigPath = tsConfigTnsPath; } + const tsConfigEnvName = 'tsconfig.env.json'; + const tsConfigEnvPath = resolve(projectRoot, tsConfigEnvName); + if (hasConfigurations(configuration) && fs.existsSync(tsConfigEnvPath)) { + // when configurations are used, switch to environments supported config + tsConfigName = tsConfigEnvName; + tsConfigPath = tsConfigEnvPath; + } const entryModule = `${nsWebpack.getEntryModule(appFullPath, platform)}.ts`; const entryPath = `.${sep}${entryModule}`; const entries = { bundle: entryPath }; @@ -104,6 +118,7 @@ module.exports = env => { const copyTargets = [ { from: 'assets/**', noErrorOnMissing: true, globOptions: { dot: false, ...copyIgnore } }, { from: 'fonts/**', noErrorOnMissing: true, globOptions: { dot: false, ...copyIgnore } }, + ...copyReplacements ]; if (!production) { @@ -217,7 +232,8 @@ module.exports = env => { '~/package.json': resolve(projectRoot, 'package.json'), '~': appFullPath, "tns-core-modules": "@nativescript/core", - "nativescript-angular": "@nativescript/angular" + "nativescript-angular": "@nativescript/angular", + ...fileReplacements }, symlinks: true }, diff --git a/packages/webpack/templates/webpack.config.spec.ts b/packages/webpack/templates/webpack.config.spec.ts index 595957968..9e59dd2f3 100644 --- a/packages/webpack/templates/webpack.config.spec.ts +++ b/packages/webpack/templates/webpack.config.spec.ts @@ -57,6 +57,17 @@ const webpackConfigAngular = proxyquire('./webpack.angular', { return FakeNativeClassTransformerFlag; }, }, + '@nativescript/webpack/helpers/angular-config-parser': { + parseWorkspaceConfig: (platform, envConfigs, rootPath = '') => { + return { + fileReplacements: {}, + copyReplacements: [], + }; + }, + hasConfigurations: (envConfigs) => { + return false; + } + }, '@nativescript/webpack/utils/ast-utils': { getMainModulePath: () => { return 'fakePath';