feat: env based replacements (#9286)

* feat: file replacement handling for TS and pure file copy replacements

* test: add tests for replacements & refactor a bit

Co-authored-by: Igor Randjelovic <rigor789@gmail.com>
This commit is contained in:
Nathan Walker
2021-03-28 16:13:51 -07:00
committed by GitHub
parent 0b32d5a88d
commit 7594d00ed9
11 changed files with 211 additions and 322 deletions

View File

@@ -1,12 +1,11 @@
import { resolve, dirname } from 'path';
import Config from 'webpack-chain';
import { existsSync } from 'fs';
import get from 'lodash.get'
import get from 'lodash.get';
import { getProjectFilePath, getProjectRootPath } from '../helpers/project';
import { getEntryPath, getPlatformName } from '../helpers/platform';
import { env as _env, IWebpackEnv } from '../index';
import { addCopyRule } from "../helpers/copyRules";
import base from './base';
export default function (config: Config, env: IWebpackEnv = _env): Config {
@@ -14,10 +13,8 @@ export default function (config: Config, env: IWebpackEnv = _env): Config {
const tsConfigPath = [
getProjectFilePath('tsconfig.app.json'),
getProjectFilePath('tsconfig.json')
].find(path => existsSync(path))
applyFileReplacements(config)
getProjectFilePath('tsconfig.json'),
].find((path) => existsSync(path));
// remove default ts rule
config.module.rules.delete('ts');
@@ -63,32 +60,6 @@ function getAngularCompilerPlugin() {
return AngularCompilerPlugin;
}
/**
* @internal exported for tests
*/
export function applyFileReplacements(
config,
fileReplacements = getFileReplacementsFromWorkspaceConfig()
) {
if (!fileReplacements) {
return
}
Object.entries(fileReplacements).forEach(([_replace, _with]) => {
// in case we are replacing source files - we'll use aliases
if (_replace.match(/\.ts$/)) {
return config.resolve.alias.set(_replace, _with)
}
// otherwise we will override the replaced file with the replacement
addCopyRule({
from: _with, // copy the replacement file
to: _replace, // to the original "to-be-replaced" file
force: true,
})
})
}
// todo: move into project helper if used elsewhere
// todo: write tests
function findFile(fileName, currentDir): string | null {
@@ -96,88 +67,29 @@ function findFile(fileName, currentDir): string | null {
const path = resolve(currentDir, fileName);
if (existsSync(path)) {
return path
return path;
}
// bail if we reached the root dir
if (currentDir === resolve('/')) {
return null
return null;
}
// traverse to the parent folder
return findFile(fileName, resolve(currentDir, '..'))
return findFile(fileName, resolve(currentDir, '..'));
}
function findWorkspaceConfig(): string {
const possibleConfigNames = [
'angular.json',
'workspace.json'
]
const possibleConfigNames = ['angular.json', 'workspace.json'];
for (const name of possibleConfigNames) {
const path = findFile(name, getProjectRootPath());
if (path) {
return path
return path;
}
}
// not found
return null;
}
interface IWorkspaceConfigFileReplacement {
replace: string,
with: string
}
interface IReplacementMap {
[from: string]: string
}
/**
* @internal exported for tests
*/
export function getFileReplacementsFromWorkspaceConfig(
configPath: string = findWorkspaceConfig()
): IReplacementMap | null {
const platform = getPlatformName();
if (!_env.configuration || !_env.projectName) {
return null;
}
const configurations = _env.configuration.split(',').map(c => c.trim())
if (!configPath || configPath === '') {
return null;
}
const config = require(configPath);
const project = get(config, `projects.${_env.projectName}`)
const targetProp = project?.architect ? 'architect' : 'targets';
if (!project) {
return null;
}
const replacements = {};
const setReplacement = (entry: IWorkspaceConfigFileReplacement) => {
const relativeReplace = resolve(dirname(configPath), entry.replace)
const relativeWith = resolve(dirname(configPath), entry.with)
replacements[relativeReplace] = relativeWith
}
configurations.forEach(configuration => {
const defaultReplacements = get(project, `${targetProp}.default.configurations.${configuration}.fileReplacements`)
const platformReplacements = get(project, `${targetProp}.${platform}.configurations.${configuration}.fileReplacements`)
// add default replacements
defaultReplacements?.map(setReplacement)
// add platform replacements (always override defaults!)
platformReplacements?.map(setReplacement)
})
return replacements;
}

View File

@@ -15,6 +15,7 @@ import { hasDependency } from '../helpers/dependencies';
import { applyDotEnvPlugin } from '../helpers/dotEnv';
import { env as _env, IWebpackEnv } from '../index';
import { getIPS } from '../helpers/host';
import { applyFileReplacements } from '../helpers/fileReplacements';
import {
getPlatformName,
getAbsoluteDistPath,
@@ -81,9 +82,9 @@ export default function (config: Config, env: IWebpackEnv = _env): Config {
config.watchOptions({
ignored: [
`${getProjectFilePath('platforms')}/**`,
`${env.appResourcesPath ?? getProjectFilePath('App_Resources')}/**`
]
})
`${env.appResourcesPath ?? getProjectFilePath('App_Resources')}/**`,
],
});
// Set up Terser options
config.optimization.minimizer('TerserPlugin').use(TerserPlugin, [
@@ -136,14 +137,15 @@ export default function (config: Config, env: IWebpackEnv = _env): Config {
// resolve symlinks
config.resolve.symlinks(true);
config.module.rule('bundle')
config.module
.rule('bundle')
.enforce('post')
.test(entryPath)
.use('nativescript-hot-loader')
.loader('nativescript-hot-loader')
.options({
injectHMRRuntime: true
})
injectHMRRuntime: true,
});
// set up ts support
config.module
@@ -202,7 +204,7 @@ export default function (config: Config, env: IWebpackEnv = _env): Config {
.exclude.add(/node_modules/)
.end()
.use('nativescript-worker-loader')
.loader('nativescript-worker-loader')
.loader('nativescript-worker-loader');
// default PostCSS options to use
// projects can change settings
@@ -301,6 +303,9 @@ export default function (config: Config, env: IWebpackEnv = _env): Config {
// enable DotEnv
applyDotEnvPlugin(config);
// replacements
applyFileReplacements(config);
// set up default copy rules
addCopyRule('assets/**');
addCopyRule('fonts/**');

View File

@@ -0,0 +1,57 @@
import { env as _env, IWebpackEnv } from '../index';
import { addCopyRule } from './copyRules';
interface IReplacementMap {
[_replace: string]: /* _with */ string;
}
/**
* @internal
*/
export function getFileReplacementsFromEnv(env: IWebpackEnv = _env): IReplacementMap {
const fileReplacements: IReplacementMap = {};
const entries: string[] = (() => {
if (Array.isArray(env.replace)) {
return env.replace;
}
if (typeof env.replace === 'string') {
return [env.replace]
}
return []
})();
entries.forEach((replaceEntry) => {
replaceEntry.split(/,\s*/).forEach((r: string) => {
const [_replace, _with] = r.split(':');
if (!_replace || !_with) {
return;
}
fileReplacements[_replace] = _with;
});
});
return fileReplacements;
}
export function applyFileReplacements(
config,
fileReplacements: IReplacementMap = getFileReplacementsFromEnv()
) {
Object.entries(fileReplacements).forEach(([_replace, _with]) => {
// in case we are replacing source files - we'll use aliases
if (_replace.match(/\.(ts|js)$/)) {
return config.resolve.alias.set(_replace, _with);
}
// otherwise we will override the replaced file with the replacement
addCopyRule({
from: _with, // copy the replacement file
to: _replace, // to the original "to-be-replaced" file
force: true,
});
});
}

View File

@@ -1,12 +1,13 @@
import { merge } from 'webpack-merge';
import { addVirtualEntry, addVirtualModule } from './virtualModules'
import { addVirtualEntry, addVirtualModule } from './virtualModules';
import { getPackageJson, getProjectRootPath } from './project';
import { applyFileReplacements } from './fileReplacements';
import { addCopyRule, removeCopyRule } from './copyRules';
import { determineProjectFlavor } from './flavor';
import { error, info, warn } from './log';
import { getValue } from './config';
import { getIPS } from './host'
import { getIPS } from './host';
import {
getAllDependencies,
hasDependency,
@@ -22,7 +23,6 @@ import {
getPlatformName,
} from './platform';
// intentionally populated manually
// as this generates nicer typings
// that show all the utils inline
@@ -32,6 +32,7 @@ export default {
merge,
addCopyRule,
removeCopyRule,
applyFileReplacements,
config: {
getValue,
},
@@ -66,6 +67,6 @@ export default {
},
virtualModules: {
addVirtualEntry,
addVirtualModule
}
addVirtualModule,
},
};

View File

@@ -35,7 +35,9 @@ export interface IWebpackEnv {
// enable verbose output
verbose?: boolean;
// todo: add others
// misc
replace?: string[] | string
}
interface IChainEntry {