Skip to content

Commit

Permalink
fix(js): add watchIgnore and runBuildTargetDependencies options to sp…
Browse files Browse the repository at this point in the history
…eed up build
  • Loading branch information
jaysoo committed Jul 5, 2023
1 parent 47d23c8 commit 7033d78
Show file tree
Hide file tree
Showing 8 changed files with 316 additions and 58 deletions.
34 changes: 27 additions & 7 deletions docs/generated/packages/js/executors/node.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,46 +28,66 @@
"host": {
"type": "string",
"default": "localhost",
"description": "The host to inspect the process on."
"description": "The host to inspect the process on.",
"x-priority": "important"
},
"port": {
"type": "number",
"default": 9229,
"description": "The port to inspect the process on. Setting port to 0 will assign random free ports to all forked processes."
"description": "The port to inspect the process on. Setting port to 0 will assign random free ports to all forked processes.",
"x-priority": "important"
},
"inspect": {
"oneOf": [
{ "type": "string", "enum": ["inspect", "inspect-brk"] },
{ "type": "boolean" }
],
"description": "Ensures the app is starting with debugging.",
"default": "inspect"
"default": "inspect",
"x-priority": "important"
},
"runtimeArgs": {
"type": "array",
"description": "Extra args passed to the node process.",
"default": [],
"items": { "type": "string" }
"items": { "type": "string" },
"x-priority": "important"
},
"args": {
"type": "array",
"description": "Extra args when starting the app.",
"default": [],
"items": { "type": "string" }
"items": { "type": "string" },
"x-priority": "important"
},
"watch": {
"type": "boolean",
"description": "Enable re-building when files change.",
"default": true
"default": true,
"x-priority": "important"
},
"watchIgnore": {
"type": "array",
"description": "List of glob patterns to ignore for file watching.",
"items": { "type": "string" },
"default": [],
"x-priority": "important"
},
"debounce": {
"type": "number",
"description": "Delay in milliseconds to wait before restarting. Useful to batch multiple file changes events together. Set to zero (0) to disable.",
"default": 500
"default": 500,
"x-priority": "important"
},
"watchRunBuildTargetDependencies": {
"type": "boolean",
"description": "Whether to run dependencies before running the build. Set this to true if the project does not build libraries from source (e.g. 'buildLibsFromSource: false').",
"default": false
}
},
"additionalProperties": false,
"required": ["buildTarget"],
"examplesFile": "---\ntitle: JS Node executor examples\ndescription: This page contains examples for the @nx/js:node executor.\n---\n\nThe `@nx/js:node` executor runs the output of a build target. For example, an application uses esbuild ([`@nx/esbuild:esbuild`](/packages/esbuild/executors/esbuild)) to output the bundle to `dist/my-app` folder, which can then be executed by `@nx/js:node`.\n\n`project.json`:\n\n```json\n\"my-app\": {\n \"targets\": {\n \"serve\": {\n \"executor\": \"@nx/js:node\",\n \"options\": {\n \"buildTarget\": \"my-app:build\"\n }\n },\n \"build\": {\n \"executor\": \"@nx/esbuild:esbuild\",\n \"options\": {\n \"main\": \"my-app/src/main.ts\",\n \"output\": [\"dist/my-app\"],\n //...\n }\n },\n }\n}\n```\n\n```bash\nnpx nx serve my-app\n```\n\n## Examples\n\n{% tabs %}\n{% tab label=\"Pass extra Node CLI arguments\" %}\n\nUsing `runtimeArgs`, you can pass arguments to the underlying `node` command. For example, if you want to set [`--no-warnings`](https://nodejs.org/api/cli.html#--no-warnings) to silence all Node warnings, then add the following to the `project.json` file.\n\n```json\n\"my-app\": {\n \"targets\": {\n \"serve\": {\n \"executor\": \"@nx/js:node\",\n \"options\": {\n \"runtimeArgs\": [\"--no-warnings\"],\n //...\n },\n },\n }\n}\n```\n\n{% /tab %}\n\n{% tab label=\"Ignore files during watch\" %}\n\nIf you have project files that do not affect the application, you can set `watchIgnore` to avoid kicking off a rebuild and restart when the only file changes are the ones you ignore. For example, if you have a `README.md` files, you can safely ignore them with `**/README.md`.\n\nNote that the glob patterns are matched relative to the workspace root.\n\n```json\n\"my-app\": {\n \"targets\": {\n \"serve\": {\n \"executor\": \"@nx/js:node\",\n \"options\": {\n \"watchIgnore\": [\"**/README.md\"]\n //...\n },\n },\n }\n}\n```\n\n{% /tab %}\n\n{% tab label=\"Run all task dependencies\" %}\n\nIf your application build depends on other tasks, and you want those tasks to also be executed, then set the `watchRunBuildTargetDependencies` to `true`. For example, a library may have a task to generate GraphQL schemas, which is consume by the application. In this case, you want to run the generate task before building and running the application.\n\nThis option is also useful when the build consumes a library from its output, not its source. For example, if an executor that supports `buildLibsFromSource` option has it set to `false` (e.g. [`@nx/webpack:webpack`](/packages/webpack/executors/webpack)).\n\nNote that this option will increase the build time, so use it only when necessary.\n\n```json\n\"my-app\": {\n \"targets\": {\n \"serve\": {\n \"executor\": \"@nx/js:node\",\n \"options\": {\n \"watchRunBuildTargetDependencies\": true,\n //...\n },\n },\n }\n}\n```\n\n{% /tab %}\n\n{% /tabs %}\n",
"presets": []
},
"description": "Execute a Node application.",
Expand Down
63 changes: 63 additions & 0 deletions e2e/node/src/node-webpack.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@ import {
readFile,
runCLI,
runCLIAsync,
runCommandUntil,
waitUntil,
tmpProjPath,
uniq,
updateFile,
updateProjectConfig,
} from '@nx/e2e/utils';
import { execSync } from 'child_process';

Expand Down Expand Up @@ -47,5 +50,65 @@ describe('Node Applications + webpack', () => {
await runCLIAsync(`build ${app} --optimization`);
const optimizedContent = readFile(`dist/apps/${app}/main.js`);
expect(optimizedContent).toContain('console.log("foo "+"bar")');

// Test that serve can re-run dependency builds.
const lib = uniq('nodelib');
runCLI(`generate @nx/js:lib ${lib} --bundler=esbuild --no-interactive`);

updateProjectConfig(app, (config) => {
// Since we read from lib from dist, we should re-build it when lib changes.
config.targets.build.options.buildLibsFromSource = false;
config.targets.serve.options.watchIgnore = ['libs/**/*.md'];
config.targets.serve.options.watchRunBuildTargetDependencies = true;
return config;
});

updateFile(
`apps/${app}/src/main.ts`,
`
import { ${lib} } from '@proj/${lib}';
console.log('Hello ' + ${lib}());
`
);

const serveProcess = await runCommandUntil(
`serve ${app} --watch --watchRunBuildTargetDependencies`,
(output) => {
return output.includes(`Hello`);
}
);

// Update library source and check that it triggers rebuild.
const terminalOutputs: string[] = [];
serveProcess.stdout.on('data', (chunk) => {
const data = chunk.toString();
terminalOutputs.push(data);
});

updateFile(
`libs/${lib}/README.md`, // This file is in `watchIgnore` so should not trigger rebuild.
`This is a readme file`
);
updateFile(
`libs/${lib}/src/index.ts`,
`export function ${lib}() { return 'should rebuild lib'; }`
);

await waitUntil(
() => {
return terminalOutputs.some((output) =>
output.includes(`should rebuild lib`)
);
},
{ timeout: 30_000, ms: 200 }
);

// Only one rebuild triggered sine README.md is ignored.
const fileChangedDetectedOutputs = terminalOutputs.filter((output) =>
output.includes(`File change detected`)
);
expect(fileChangedDetectedOutputs.length).toBe(1);

serveProcess.kill();
}, 300_000);
});
104 changes: 104 additions & 0 deletions packages/js/docs/node-examples.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
---
title: JS Node executor examples
description: This page contains examples for the @nx/js:node executor.
---

The `@nx/js:node` executor runs the output of a build target. For example, an application uses esbuild ([`@nx/esbuild:esbuild`](/packages/esbuild/executors/esbuild)) to output the bundle to `dist/my-app` folder, which can then be executed by `@nx/js:node`.

`project.json`:

```json
"my-app": {
"targets": {
"serve": {
"executor": "@nx/js:node",
"options": {
"buildTarget": "my-app:build"
}
},
"build": {
"executor": "@nx/esbuild:esbuild",
"options": {
"main": "my-app/src/main.ts",
"output": ["dist/my-app"],
//...
}
},
}
}
```

```bash
npx nx serve my-app
```

## Examples

{% tabs %}
{% tab label="Pass extra Node CLI arguments" %}

Using `runtimeArgs`, you can pass arguments to the underlying `node` command. For example, if you want to set [`--no-warnings`](https://nodejs.org/api/cli.html#--no-warnings) to silence all Node warnings, then add the following to the `project.json` file.

```json
"my-app": {
"targets": {
"serve": {
"executor": "@nx/js:node",
"options": {
"runtimeArgs": ["--no-warnings"],
//...
},
},
}
}
```

{% /tab %}

{% tab label="Ignore files during watch" %}

If you have project files that do not affect the application, you can set `watchIgnore` to avoid kicking off a rebuild and restart when the only file changes are the ones you ignore. For example, if you have a `README.md` files, you can safely ignore them with `**/README.md`.

Note that the glob patterns are matched relative to the workspace root.

```json
"my-app": {
"targets": {
"serve": {
"executor": "@nx/js:node",
"options": {
"watchIgnore": ["**/README.md"]
//...
},
},
}
}
```

{% /tab %}

{% tab label="Run all task dependencies" %}

If your application build depends on other tasks, and you want those tasks to also be executed, then set the `watchRunBuildTargetDependencies` to `true`. For example, a library may have a task to generate GraphQL schemas, which is consume by the application. In this case, you want to run the generate task before building and running the application.

This option is also useful when the build consumes a library from its output, not its source. For example, if an executor that supports `buildLibsFromSource` option has it set to `false` (e.g. [`@nx/webpack:webpack`](/packages/webpack/executors/webpack)).

Note that this option will increase the build time, so use it only when necessary.

```json
"my-app": {
"targets": {
"serve": {
"executor": "@nx/js:node",
"options": {
"watchRunBuildTargetDependencies": true,
//...
},
},
}
}
```

{% /tab %}

{% /tabs %}
10 changes: 10 additions & 0 deletions packages/js/src/executors/node/lib/any-matching-files.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { anyMatchingFiles } from './any-matching-files';

describe('anyMatchingFiles', () => {
it('should return true if a file matches any of the patterns', () => {
const fn = anyMatchingFiles(['**/*.txt']);

expect(fn([{ path: 'a.ts' }, { path: 'b.ts' }])).toBe(false);
expect(fn([{ path: 'a.txt' }, { path: 'b.ts' }])).toBe(true);
});
});
14 changes: 14 additions & 0 deletions packages/js/src/executors/node/lib/any-matching-files.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import * as minimatch from 'minimatch';

export function anyMatchingFiles(patterns: string[]) {
const filters = patterns.map((p) => minimatch.filter(p));
return (files: { path: string }[]) => {
// Worst-case is nested loop through both files and patterns, but neither should be large.
for (const filter of filters) {
for (const file of files) {
if (filter(file.path)) return true;
}
}
return false;
};
}
Loading

0 comments on commit 7033d78

Please sign in to comment.