Skip to content

Commit

Permalink
feat(js): infer build-deps and watch-deps targets for incremental bui…
Browse files Browse the repository at this point in the history
…lds (#29609)

This PR adds `build-deps` and `watch-deps` targets to buildable JS
projects to help with incremental builds.

A use-case for this is if an app (e.g. Vite React app) has buildable
dependencies that need to be rebuilt when they change.

Say, you create a React app and lib as follows:

```
nx g @nx/react:app apps/react-app --bundler vite 
nx g @nx/react:lib packages/react-lib --bundler vite
```

And import `react-lib` inside the app.

```jsx
import { ReactLib } from '@acme/react-lib';
//...
return <ReactLib />
```

The user can then run:

```
nx watch-deps react-app
```

And then serve the app in another terminal:
```
nx serve react-app
```

Then whenever code is updated for a buildable dependency, it'll be
rebuilt and then reloaded in the app.
  • Loading branch information
jaysoo authored Jan 14, 2025
1 parent 65e9a6b commit 0ae8665
Show file tree
Hide file tree
Showing 38 changed files with 634 additions and 6 deletions.
10 changes: 10 additions & 0 deletions packages/detox/src/generators/init/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,16 @@ export async function detoxInitGeneratorInternal(host: Tree, schema: Schema) {
buildTargetName: ['build', 'detox:build', 'detox-build'],
startTargetName: ['start', 'detox:start', 'detox-start'],
testTargetName: ['test', 'detox:test', 'detox-test'],
buildDepsTargetName: [
'build-deps',
'detox:build-deps',
'detox-build-deps',
],
watchDepsTargetName: [
'watch-deps',
'detox:watch-deps',
'detox-watch-deps',
],
},
schema.updatePackageScripts
);
Expand Down
14 changes: 14 additions & 0 deletions packages/detox/src/plugins/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
CreateNodesResult,
CreateNodesV2,
detectPackageManager,
getPackageManagerCommand,
NxJsonConfiguration,
readJsonFile,
TargetConfiguration,
Expand All @@ -17,13 +18,18 @@ import { existsSync } from 'fs';
import { calculateHashForCreateNodes } from '@nx/devkit/src/utils/calculate-hash-for-create-nodes';
import { workspaceDataDirectory } from 'nx/src/utils/cache-directory';
import { hashObject } from 'nx/src/devkit-internals';
import { addBuildAndWatchDepsTargets } from '@nx/js/src/plugins/typescript/util';

export interface DetoxPluginOptions {
buildTargetName?: string;
startTargetName?: string;
testTargetName?: string;
buildDepsTargetName?: string;
watchDepsTargetName?: string;
}

const pmc = getPackageManagerCommand();

function readTargetsCache(
cachePath: string
): Record<string, Record<string, TargetConfiguration<DetoxPluginOptions>>> {
Expand Down Expand Up @@ -141,6 +147,14 @@ function buildDetoxTargets(
},
};

addBuildAndWatchDepsTargets(
context.workspaceRoot,
projectRoot,
targets,
options,
pmc
);

return targets;
}

Expand Down
13 changes: 13 additions & 0 deletions packages/expo/plugins/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
CreateNodesResult,
CreateNodesV2,
detectPackageManager,
getPackageManagerCommand,
logger,
NxJsonConfiguration,
readJsonFile,
Expand All @@ -19,6 +20,7 @@ import { calculateHashForCreateNodes } from '@nx/devkit/src/utils/calculate-hash
import { workspaceDataDirectory } from 'nx/src/utils/cache-directory';
import { hashObject } from 'nx/src/devkit-internals';
import { loadConfigFile } from '@nx/devkit/src/utils/config-utils';
import { addBuildAndWatchDepsTargets } from '@nx/js/src/plugins/typescript/util';

export interface ExpoPluginOptions {
startTargetName?: string;
Expand All @@ -30,7 +32,10 @@ export interface ExpoPluginOptions {
installTargetName?: string;
buildTargetName?: string;
submitTargetName?: string;
buildDepsTargetName?: string;
watchDepsTargetName?: string;
}
const pmc = getPackageManagerCommand();

function readTargetsCache(
cachePath: string
Expand Down Expand Up @@ -186,6 +191,14 @@ function buildExpoTargets(
},
};

addBuildAndWatchDepsTargets(
context.workspaceRoot,
projectRoot,
targets,
options,
pmc
);

return targets;
}

Expand Down
10 changes: 10 additions & 0 deletions packages/expo/src/generators/init/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,16 @@ export async function expoInitGeneratorInternal(host: Tree, schema: Schema) {
'expo:run-android',
'expo-run-android',
],
buildDepsTargetName: [
'build-deps',
'expo:build-deps',
'expo-build-deps',
],
watchDepsTargetName: [
'watch-deps',
'expo:watch-deps',
'expo-watch-deps',
],
},

schema.updatePackageScripts
Expand Down
21 changes: 18 additions & 3 deletions packages/js/src/generators/init/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,9 +105,24 @@ export async function initGeneratorInternal(
{ targetName: 'tsc-typecheck' },
],
build: [
{ targetName: 'build', configName: 'tsconfig.lib.json' },
{ targetName: 'tsc:build', configName: 'tsconfig.lib.json' },
{ targetName: 'tsc-build', configName: 'tsconfig.lib.json' },
{
targetName: 'build',
configName: 'tsconfig.lib.json',
buildDepsName: 'build-deps',
watchDepsName: 'watch-deps',
},
{
targetName: 'tsc:build',
configName: 'tsconfig.lib.json',
buildDepsName: 'tsc:build-deps',
watchDepsName: 'tsc:watch-deps',
},
{
targetName: 'tsc-build',
configName: 'tsconfig.lib.json',
buildDepsName: 'tsc-build-deps',
watchDepsName: 'tsc-watch-deps',
},
],
},
schema.updatePackageScripts
Expand Down
12 changes: 12 additions & 0 deletions packages/js/src/plugins/typescript/plugin.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1920,6 +1920,7 @@ describe(`Plugin: ${PLUGIN_NAME}`, () => {
'libs/my-lib/tsconfig.lib.json': `{"compilerOptions": {"outDir": "dist"}}`,
'libs/my-lib/tsconfig.build.json': `{}`,
'libs/my-lib/package.json': JSON.stringify({
name: 'my-lib',
main: 'dist/index.js',
types: 'dist/index.d.ts',
exports: {
Expand Down Expand Up @@ -1983,6 +1984,17 @@ describe(`Plugin: ${PLUGIN_NAME}`, () => {
"@nx/js:typescript-sync",
],
},
"build-deps": {
"dependsOn": [
"^build",
],
},
"watch-deps": {
"command": "npx nx watch --projects my-lib --includeDependentProjects -- npx nx build-deps my-lib",
"dependsOn": [
"build-deps",
],
},
},
},
},
Expand Down
20 changes: 20 additions & 0 deletions packages/js/src/plugins/typescript/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import { getLockFileName } from 'nx/src/plugins/js/lock-file/lock-file';
import { workspaceDataDirectory } from 'nx/src/utils/cache-directory';
import type { ParsedCommandLine } from 'typescript';
import { readTsConfig } from '../../utils/typescript/ts-config';
import { addBuildAndWatchDepsTargets } from './util';

export interface TscPluginOptions {
typecheck?:
Expand All @@ -47,6 +48,8 @@ export interface TscPluginOptions {
| {
targetName?: string;
configName?: string;
buildDepsName?: string;
watchDepsName?: string;
};
}

Expand All @@ -61,6 +64,8 @@ interface NormalizedPluginOptions {
| {
targetName: string;
configName: string;
buildDepsName?: string;
watchDepsName?: string;
};
}

Expand Down Expand Up @@ -373,6 +378,17 @@ function buildTscTargets(
},
},
};

addBuildAndWatchDepsTargets(
context.workspaceRoot,
projectRoot,
targets,
{
buildDepsTargetName: options.build.buildDepsName,
watchDepsTargetName: options.build.watchDepsName,
},
pmc
);
}

return { targets };
Expand Down Expand Up @@ -975,6 +991,8 @@ function normalizePluginOptions(
let build: NormalizedPluginOptions['build'] = {
targetName: defaultBuildTargetName,
configName: defaultBuildConfigName,
buildDepsName: 'build-deps',
watchDepsName: 'watch-deps',
};
// Build target is not enabled by default
if (!pluginOptions.build) {
Expand All @@ -983,6 +1001,8 @@ function normalizePluginOptions(
build = {
targetName: pluginOptions.build.targetName ?? defaultBuildTargetName,
configName: pluginOptions.build.configName ?? defaultBuildConfigName,
buildDepsName: pluginOptions.build.buildDepsName ?? 'build-deps',
watchDepsName: pluginOptions.build.watchDepsName ?? 'watch-deps',
};
}

Expand Down
41 changes: 41 additions & 0 deletions packages/js/src/plugins/typescript/util.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { readJsonFile, type TargetConfiguration } from '@nx/devkit';
import { existsSync } from 'node:fs';
import { type PackageManagerCommands } from 'nx/src/utils/package-manager';
import { join } from 'path';

/**
* Allow uses that use incremental builds to run `nx watch-deps` to continuously build all dependencies.
*/
export function addBuildAndWatchDepsTargets(
workspaceRoot: string,
projectRoot: string,
targets: Record<string, TargetConfiguration>,
options: { buildDepsTargetName?: string; watchDepsTargetName?: string },
pmc: PackageManagerCommands
): void {
let projectName: string;

const projectJsonPath = join(workspaceRoot, projectRoot, 'project.json');
const packageJsonPath = join(workspaceRoot, projectRoot, 'package.json');

if (existsSync(projectJsonPath)) {
const projectJson = readJsonFile(projectJsonPath);
projectName = projectJson.name;
} else if (existsSync(packageJsonPath)) {
const packageJson = readJsonFile(packageJsonPath);
projectName = packageJson.nx?.name ?? packageJson.name;
}

if (!projectName) return;

if (projectName) {
const buildDepsTargetName = options.buildDepsTargetName ?? 'build-deps';
targets[buildDepsTargetName] = {
dependsOn: ['^build'],
};
targets[options.watchDepsTargetName ?? 'watch-deps'] = {
dependsOn: [buildDepsTargetName],
command: `${pmc.exec} nx watch --projects ${projectName} --includeDependentProjects -- ${pmc.exec} nx ${buildDepsTargetName} ${projectName}`,
};
}
}
10 changes: 10 additions & 0 deletions packages/next/src/generators/init/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,16 @@ export async function nextInitGeneratorInternal(
'next:serve-static',
'next-serve-static',
],
buildDepsTargetName: [
'build-deps',
'next:build-deps',
'next-build-deps',
],
watchDepsTargetName: [
'watch-deps',
'next:watch-deps',
'next-watch-deps',
],
},
schema.updatePackageScripts
);
Expand Down
22 changes: 22 additions & 0 deletions packages/next/src/plugins/__snapshots__/plugin.spec.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ exports[`@nx/next/plugin integrated projects should create nodes 1`] = `
"my-app": {
"root": "my-app",
"targets": {
"build-deps": {
"dependsOn": [
"^build",
],
},
"my-build": {
"cache": true,
"command": "next build",
Expand Down Expand Up @@ -57,6 +62,12 @@ exports[`@nx/next/plugin integrated projects should create nodes 1`] = `
"cwd": "my-app",
},
},
"watch-deps": {
"command": "npx nx watch --projects my-app --includeDependentProjects -- npx nx build-deps my-app",
"dependsOn": [
"build-deps",
],
},
},
},
},
Expand Down Expand Up @@ -98,6 +109,11 @@ exports[`@nx/next/plugin root projects should create nodes 1`] = `
"{projectRoot}/.next/!(cache)",
],
},
"build-deps": {
"dependsOn": [
"^build",
],
},
"dev": {
"command": "next dev",
"options": {
Expand All @@ -122,6 +138,12 @@ exports[`@nx/next/plugin root projects should create nodes 1`] = `
"cwd": ".",
},
},
"watch-deps": {
"command": "npx nx watch --projects next --includeDependentProjects -- npx nx build-deps next",
"dependsOn": [
"build-deps",
],
},
},
},
},
Expand Down
14 changes: 14 additions & 0 deletions packages/next/src/plugins/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
writeJsonFile,
createNodesFromFiles,
logger,
getPackageManagerCommand,
} from '@nx/devkit';
import { dirname, join } from 'path';

Expand All @@ -21,14 +22,19 @@ import { calculateHashForCreateNodes } from '@nx/devkit/src/utils/calculate-hash
import { getLockFileName } from '@nx/js';
import { loadConfigFile } from '@nx/devkit/src/utils/config-utils';
import { hashObject } from 'nx/src/devkit-internals';
import { addBuildAndWatchDepsTargets } from '@nx/js/src/plugins/typescript/util';

export interface NextPluginOptions {
buildTargetName?: string;
devTargetName?: string;
startTargetName?: string;
serveStaticTargetName?: string;
buildDepsTargetName?: string;
watchDepsTargetName?: string;
}

const pmc = getPackageManagerCommand();

const nextConfigBlob = '**/next.config.{ts,js,cjs,mjs}';

function readTargetsCache(
Expand Down Expand Up @@ -170,6 +176,14 @@ async function buildNextTargets(

targets[options.serveStaticTargetName] = getStaticServeTargetConfig(options);

addBuildAndWatchDepsTargets(
context.workspaceRoot,
projectRoot,
targets,
options,
pmc
);

return targets;
}

Expand Down
2 changes: 2 additions & 0 deletions packages/nuxt/src/generators/init/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ export async function nuxtInitGenerator(host: Tree, schema: InitSchema) {
{
buildTargetName: ['build', 'nuxt:build', 'nuxt-build'],
serveTargetName: ['serve', 'nuxt:serve', 'nuxt-serve'],
buildDepsTargetName: ['build-deps', 'nuxt:build-deps', 'nuxt-build-deps'],
watchDepsTargetName: ['watch-deps', 'nuxt:watch-deps', 'nuxt-watch-deps'],
},
schema.updatePackageScripts
);
Expand Down
Loading

0 comments on commit 0ae8665

Please sign in to comment.