diff --git a/packages/angular/src/builders/webpack-browser/webpack-browser.impl.ts b/packages/angular/src/builders/webpack-browser/webpack-browser.impl.ts
index 17051b7aab7c2..2593475c2e871 100644
--- a/packages/angular/src/builders/webpack-browser/webpack-browser.impl.ts
+++ b/packages/angular/src/builders/webpack-browser/webpack-browser.impl.ts
@@ -25,10 +25,19 @@ function shouldSkipInitialTargetRun(
   project: string,
   target: string
 ): boolean {
+  const allTargetNames = new Set<string>();
+  for (const projectName in projectGraph.nodes) {
+    const project = projectGraph.nodes[projectName];
+    for (const targetName in project.data.targets ?? {}) {
+      allTargetNames.add(targetName);
+    }
+  }
+
   const projectDependencyConfigs = getDependencyConfigs(
     { project, target },
     {},
-    projectGraph
+    projectGraph,
+    Array.from(allTargetNames)
   );
 
   // if the task runner already ran the target, skip the initial run
diff --git a/packages/nx/src/tasks-runner/create-task-graph.ts b/packages/nx/src/tasks-runner/create-task-graph.ts
index 9035be7940b7f..8989476b4b584 100644
--- a/packages/nx/src/tasks-runner/create-task-graph.ts
+++ b/packages/nx/src/tasks-runner/create-task-graph.ts
@@ -7,18 +7,27 @@ import {
 import { Task, TaskGraph } from '../config/task-graph';
 import { TargetDefaults, TargetDependencies } from '../config/nx-json';
 import { TargetDependencyConfig } from '../devkit-exports';
-import { findMatchingProjects } from '../utils/find-matching-projects';
 import { output } from '../utils/output';
 
 export class ProcessTasks {
   private readonly seen = new Set<string>();
   readonly tasks: { [id: string]: Task } = {};
   readonly dependencies: { [k: string]: string[] } = {};
+  private readonly allTargetNames: string[];
 
   constructor(
     private readonly extraTargetDependencies: TargetDependencies,
     private readonly projectGraph: ProjectGraph
-  ) {}
+  ) {
+    const allTargetNames = new Set<string>();
+    for (const projectName in projectGraph.nodes) {
+      const project = projectGraph.nodes[projectName];
+      for (const targetName in project.data.targets ?? {}) {
+        allTargetNames.add(targetName);
+      }
+    }
+    this.allTargetNames = Array.from(allTargetNames);
+  }
 
   processTasks(
     projectNames: string[],
@@ -100,7 +109,8 @@ export class ProcessTasks {
     const dependencyConfigs = getDependencyConfigs(
       { project: task.target.project, target: task.target.target },
       this.extraTargetDependencies,
-      this.projectGraph
+      this.projectGraph,
+      this.allTargetNames
     );
     for (const dependencyConfig of dependencyConfigs) {
       const taskOverrides =
@@ -108,40 +118,7 @@ export class ProcessTasks {
           ? overrides
           : { __overrides_unparsed__: [] };
       if (dependencyConfig.projects) {
-        /** LERNA SUPPORT START - Remove in v20 */
-        // Lerna uses `dependencies` in `prepNxOptions`, so we need to maintain
-        // support for it until lerna can be updated to use the syntax.
-        //
-        // This should have been removed in v17, but the updates to lerna had not
-        // been made yet.
-        //
-        // TODO(@agentender): Remove this part in v20
-        if (typeof dependencyConfig.projects === 'string') {
-          if (dependencyConfig.projects === 'self') {
-            this.processTasksForSingleProject(
-              task,
-              task.target.project,
-              dependencyConfig,
-              configuration,
-              taskOverrides,
-              overrides
-            );
-            continue;
-          } else if (dependencyConfig.projects === 'dependencies') {
-            this.processTasksForDependencies(
-              projectUsedToDeriveDependencies,
-              dependencyConfig,
-              configuration,
-              task,
-              taskOverrides,
-              overrides
-            );
-            continue;
-          }
-        }
-        /** LERNA SUPPORT END - Remove in v17 */
-
-        this.processTasksForMatchingProjects(
+        this.processTasksForMultipleProjects(
           dependencyConfig,
           configuration,
           task,
@@ -170,31 +147,22 @@ export class ProcessTasks {
     }
   }
 
-  private processTasksForMatchingProjects(
+  private processTasksForMultipleProjects(
     dependencyConfig: TargetDependencyConfig,
     configuration: string,
     task: Task,
     taskOverrides: Object | { __overrides_unparsed__: any[] },
     overrides: Object
   ) {
-    const targetProjectSpecifiers =
-      typeof dependencyConfig.projects === 'string'
-        ? [dependencyConfig.projects]
-        : dependencyConfig.projects;
-    const matchingProjects = findMatchingProjects(
-      targetProjectSpecifiers,
-      this.projectGraph.nodes
-    );
-
-    if (matchingProjects.length === 0) {
+    if (dependencyConfig.projects.length === 0) {
       output.warn({
         title: `\`dependsOn\` is misconfigured for ${task.target.project}:${task.target.target}`,
         bodyLines: [
-          `Project patterns "${targetProjectSpecifiers}" does not match any projects.`,
+          `Project patterns "${dependencyConfig.projects}" does not match any projects.`,
         ],
       });
     }
-    for (const projectName of matchingProjects) {
+    for (const projectName of dependencyConfig.projects) {
       this.processTasksForSingleProject(
         task,
         projectName,
diff --git a/packages/nx/src/tasks-runner/utils.spec.ts b/packages/nx/src/tasks-runner/utils.spec.ts
index d2002f95fd836..9c69104c6667b 100644
--- a/packages/nx/src/tasks-runner/utils.spec.ts
+++ b/packages/nx/src/tasks-runner/utils.spec.ts
@@ -1,5 +1,6 @@
 import {
   expandDependencyConfigSyntaxSugar,
+  getDependencyConfigs,
   getOutputsForTargetAndConfiguration,
   interpolate,
   transformLegacyOutputs,
@@ -408,6 +409,7 @@ describe('utils', () => {
         const result = transformLegacyOutputs('myapp', e);
         expect(result).toEqual(['{workspaceRoot}/dist']);
       }
+      expect.assertions(1);
     });
 
     it('should prefix unix-absolute paths with {workspaceRoot}', () => {
@@ -418,6 +420,7 @@ describe('utils', () => {
         const result = transformLegacyOutputs('myapp', e);
         expect(result).toEqual(['{workspaceRoot}/dist']);
       }
+      expect.assertions(1);
     });
   });
 
@@ -429,6 +432,7 @@ describe('utils', () => {
       const result = transformLegacyOutputs('myapp', e);
       expect(result).toEqual(['{workspaceRoot}/dist']);
     }
+    expect.assertions(1);
   });
 
   it('should prefix paths within the project with {projectRoot}', () => {
@@ -439,6 +443,7 @@ describe('utils', () => {
       const result = transformLegacyOutputs('myapp', e);
       expect(result).toEqual(['{projectRoot}/dist']);
     }
+    expect.assertions(1);
   });
 
   describe('expandDependencyConfigSyntaxSugar', () => {
@@ -509,6 +514,111 @@ describe('utils', () => {
         target: 'target:with:colons',
       });
     });
+
+    it('supports wildcards in targets', () => {
+      const result = getDependencyConfigs(
+        { project: 'project', target: 'build' },
+        {},
+        {
+          dependencies: {},
+          nodes: {
+            project: {
+              name: 'project',
+              type: 'app',
+              data: {
+                root: 'libs/project',
+                targets: {
+                  build: {
+                    dependsOn: ['build-*'],
+                  },
+                  'build-css': {},
+                  'build-js': {},
+                  'then-build-something-else': {},
+                },
+              },
+            },
+          },
+        },
+        ['build', 'build-css', 'build-js', 'then-build-something-else']
+      );
+      expect(result).toEqual([
+        {
+          target: 'build-css',
+          projects: ['project'],
+        },
+        {
+          target: 'build-js',
+          projects: ['project'],
+        },
+      ]);
+    });
+
+    it('should support wildcards with dependencies', () => {
+      const result = getDependencyConfigs(
+        { project: 'project', target: 'build' },
+        {},
+        {
+          dependencies: {},
+          nodes: {
+            project: {
+              name: 'project',
+              type: 'app',
+              data: {
+                root: 'libs/project',
+                targets: {
+                  build: {
+                    dependsOn: ['^build-*'],
+                  },
+                  'then-build-something-else': {},
+                },
+              },
+            },
+            dep1: {
+              name: 'dep1',
+              type: 'lib',
+              data: {
+                root: 'libs/dep1',
+                targets: {
+                  'build-css': {},
+                  'build-js': {},
+                },
+              },
+            },
+            dep2: {
+              name: 'dep2',
+              type: 'lib',
+              data: {
+                root: 'libs/dep2',
+                targets: {
+                  'build-python': {},
+                },
+              },
+            },
+          },
+        },
+        [
+          'build',
+          'build-css',
+          'build-js',
+          'then-build-something-else',
+          'build-python',
+        ]
+      );
+      expect(result).toEqual([
+        {
+          target: 'build-css',
+          dependencies: true,
+        },
+        {
+          target: 'build-js',
+          dependencies: true,
+        },
+        {
+          target: 'build-python',
+          dependencies: true,
+        },
+      ]);
+    });
   });
 
   describe('validateOutputs', () => {
diff --git a/packages/nx/src/tasks-runner/utils.ts b/packages/nx/src/tasks-runner/utils.ts
index 8fcc77eb83f15..2d7534cdc1191 100644
--- a/packages/nx/src/tasks-runner/utils.ts
+++ b/packages/nx/src/tasks-runner/utils.ts
@@ -15,40 +15,81 @@ import { splitByColons } from '../utils/split-target';
 import { getExecutorInformation } from '../command-line/run/executor-utils';
 import { CustomHasher, ExecutorConfig } from '../config/misc-interfaces';
 import { readProjectsConfigurationFromProjectGraph } from '../project-graph/project-graph';
+import {
+  GLOB_CHARACTERS,
+  findMatchingProjects,
+} from '../utils/find-matching-projects';
+import { minimatch } from 'minimatch';
+
+export type NormalizedTargetDependencyConfig = TargetDependencyConfig & {
+  projects: string[];
+};
 
 export function getDependencyConfigs(
   { project, target }: { project: string; target: string },
   extraTargetDependencies: Record<string, (TargetDependencyConfig | string)[]>,
-  projectGraph: ProjectGraph
-): TargetDependencyConfig[] | undefined {
+  projectGraph: ProjectGraph,
+  allTargetNames: string[]
+): NormalizedTargetDependencyConfig[] | undefined {
   const dependencyConfigs = (
     projectGraph.nodes[project].data?.targets[target]?.dependsOn ??
     // This is passed into `run-command` from programmatic invocations
     extraTargetDependencies[target] ??
     []
-  ).map((config) =>
-    typeof config === 'string'
-      ? expandDependencyConfigSyntaxSugar(config, projectGraph)
-      : config
+  ).flatMap((config) =>
+    normalizeDependencyConfigDefinition(
+      config,
+      project,
+      projectGraph,
+      allTargetNames
+    )
   );
-  for (const dependencyConfig of dependencyConfigs) {
-    if (dependencyConfig.projects && dependencyConfig.dependencies) {
-      output.error({
-        title: `dependsOn is improperly configured for ${project}:${target}`,
-        bodyLines: [
-          `dependsOn.projects and dependsOn.dependencies cannot be used together.`,
-        ],
-      });
-      process.exit(1);
-    }
-  }
   return dependencyConfigs;
 }
 
+export function normalizeDependencyConfigDefinition(
+  definition: string | TargetDependencyConfig,
+  currentProject: string,
+  graph: ProjectGraph,
+  allTargetNames: string[]
+): NormalizedTargetDependencyConfig[] {
+  return expandWildcardTargetConfiguration(
+    normalizeDependencyConfigProjects(
+      expandDependencyConfigSyntaxSugar(definition, graph),
+      currentProject,
+      graph
+    ),
+    allTargetNames
+  );
+}
+
+export function normalizeDependencyConfigProjects(
+  dependencyConfig: TargetDependencyConfig,
+  currentProject: string,
+  graph: ProjectGraph
+): NormalizedTargetDependencyConfig {
+  const noStringConfig =
+    normalizeTargetDependencyWithStringProjects(dependencyConfig);
+
+  if (noStringConfig.projects) {
+    dependencyConfig.projects = findMatchingProjects(
+      noStringConfig.projects,
+      graph.nodes
+    );
+  } else if (!noStringConfig.dependencies) {
+    dependencyConfig.projects = [currentProject];
+  }
+  return dependencyConfig as NormalizedTargetDependencyConfig;
+}
+
 export function expandDependencyConfigSyntaxSugar(
-  dependencyConfigString: string,
+  dependencyConfigString: string | TargetDependencyConfig,
   graph: ProjectGraph
 ): TargetDependencyConfig {
+  if (typeof dependencyConfigString !== 'string') {
+    return dependencyConfigString;
+  }
+
   const [dependencies, targetString] = dependencyConfigString.startsWith('^')
     ? [true, dependencyConfigString.substring(1)]
     : [false, dependencyConfigString];
@@ -62,22 +103,69 @@ export function expandDependencyConfigSyntaxSugar(
     };
   }
 
+  const { projects, target } = readProjectAndTargetFromTargetString(
+    targetString,
+    graph.nodes
+  );
+
+  return projects ? { projects, target } : { target };
+}
+
+// Weakmap let's the cache get cleared by garbage collector if allTargetNames is no longer used
+const patternResultCache = new WeakMap<
+  string[],
+  // Map< Pattern, Dependency Configs >
+  Map<string, NormalizedTargetDependencyConfig[]>
+>();
+
+export function expandWildcardTargetConfiguration(
+  dependencyConfig: NormalizedTargetDependencyConfig,
+  allTargetNames: string[]
+): NormalizedTargetDependencyConfig[] {
+  if (!GLOB_CHARACTERS.some((char) => dependencyConfig.target.includes(char))) {
+    return [dependencyConfig];
+  }
+  let cache = patternResultCache.get(allTargetNames);
+  if (!cache) {
+    cache = new Map();
+    patternResultCache.set(allTargetNames, cache);
+  }
+  const cachedResult = cache.get(dependencyConfig.target);
+  if (cachedResult) {
+    return cachedResult;
+  }
+
+  const matcher = minimatch.filter(dependencyConfig.target);
+
+  const matchingTargets = allTargetNames.filter((t) => matcher(t));
+
+  const result = matchingTargets.map((t) => ({
+    ...dependencyConfig,
+    target: t,
+  }));
+  cache.set(dependencyConfig.target, result);
+  return result;
+}
+
+export function readProjectAndTargetFromTargetString(
+  targetString: string,
+  projects: Record<string, ProjectGraphProjectNode>
+): { projects?: string[]; target: string } {
   // Support for both `project:target` and `target:with:colons` syntax
   const [maybeProject, ...segments] = splitByColons(targetString);
 
-  // if no additional segments are provided, then the string references
-  // a target of the same project
   if (!segments.length) {
+    // if no additional segments are provided, then the string references
+    // a target of the same project
     return { target: maybeProject };
-  }
-
-  return {
+  } else if (maybeProject in projects) {
     // Only the first segment could be a project. If it is, the rest is a target.
     // If its not, then the whole targetString was a target with colons in its name.
-    target: maybeProject in graph.nodes ? segments.join(':') : targetString,
+    return { projects: [maybeProject], target: segments.join(':') };
+  } else {
     // If the first segment is a project, then we have a specific project. Otherwise, we don't.
-    projects: maybeProject in graph.nodes ? [maybeProject] : undefined,
-  };
+    return { target: targetString };
+  }
 }
 
 export function getOutputs(
@@ -92,6 +180,34 @@ export function getOutputs(
   );
 }
 
+export function normalizeTargetDependencyWithStringProjects(
+  dependencyConfig: TargetDependencyConfig
+): Omit<TargetDependencyConfig, 'projects'> & { projects: string[] } {
+  if (typeof dependencyConfig.projects === 'string') {
+    /** LERNA SUPPORT START - Remove in v20 */
+    // Lerna uses `dependencies` in `prepNxOptions`, so we need to maintain
+    // support for it until lerna can be updated to use the syntax.
+    //
+    // This should have been removed in v17, but the updates to lerna had not
+    // been made yet.
+    //
+    // TODO(@agentender): Remove this part in v20
+    if (dependencyConfig.projects === 'self') {
+      delete dependencyConfig.projects;
+    } else if (dependencyConfig.projects === 'dependencies') {
+      dependencyConfig.dependencies = true;
+      delete dependencyConfig.projects;
+      return;
+      /** LERNA SUPPORT END - Remove in v20 */
+    } else {
+      dependencyConfig.projects = [dependencyConfig.projects];
+    }
+  }
+  return dependencyConfig as Omit<TargetDependencyConfig, 'projects'> & {
+    projects: string[];
+  };
+}
+
 class InvalidOutputsError extends Error {
   constructor(public outputs: string[], public invalidOutputs: Set<string>) {
     super(InvalidOutputsError.createMessage(invalidOutputs));
diff --git a/packages/nx/src/utils/find-matching-projects.ts b/packages/nx/src/utils/find-matching-projects.ts
index 497e2ab13ee1a..de76ec1e39294 100644
--- a/packages/nx/src/utils/find-matching-projects.ts
+++ b/packages/nx/src/utils/find-matching-projects.ts
@@ -18,7 +18,10 @@ interface ProjectPattern {
   value: string;
 }
 
-const globCharacters = ['*', '|', '{', '}', '(', ')'];
+/**
+ * The presence of these characters in a string indicates that it might be a glob pattern.
+ */
+export const GLOB_CHARACTERS = ['*', '|', '{', '}', '(', ')'];
 
 /**
  * Find matching project names given a list of potential project names or globs.
@@ -166,7 +169,7 @@ function addMatchingProjectsByName(
     return;
   }
 
-  if (!globCharacters.some((c) => pattern.value.includes(c))) {
+  if (!GLOB_CHARACTERS.some((c) => pattern.value.includes(c))) {
     return;
   }
 
@@ -201,7 +204,7 @@ function addMatchingProjectsByTag(
       continue;
     }
 
-    if (!globCharacters.some((c) => pattern.value.includes(c))) {
+    if (!GLOB_CHARACTERS.some((c) => pattern.value.includes(c))) {
       continue;
     }