Skip to content

Commit

Permalink
feat(vite): support incremental builds with nxViteTsPaths (#23908)
Browse files Browse the repository at this point in the history
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->

<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->

## Current Behavior
<!-- This is the behavior we have today -->
We do not support incremental builds with vite when using inference
plugin


## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
Support incremental builds with vite when using inference plugin

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #
  • Loading branch information
Coly010 authored May 22, 2024
1 parent 8cfc0a0 commit 64c6287
Show file tree
Hide file tree
Showing 3 changed files with 221 additions and 7 deletions.
75 changes: 75 additions & 0 deletions e2e/vite/src/vite-crystal.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ import {
runCLI,
runCommandUntil,
uniq,
updateFile,
} from '@nx/e2e/utils';
import { ChildProcess } from 'child_process';
import { names } from '@nx/devkit';

const myApp = uniq('my-app');
const myVueApp = uniq('my-vue-app');
Expand Down Expand Up @@ -63,6 +65,79 @@ describe('@nx/vite/plugin', () => {
}, 200_000);
});

describe('should support buildable libraries', () => {
it('should build the library and application successfully', () => {
const myApp = uniq('myapp');
runCLI(
`generate @nx/react:app ${myApp} --bundler=vite --unitTestRunner=vitest`
);

const myBuildableLib = uniq('mybuildablelib');
runCLI(
`generate @nx/react:library ${myBuildableLib} --bundler=vite --unitTestRunner=vitest --buildable`
);

const exportedLibraryComponent = names(myBuildableLib).className;

updateFile(
`apps/${myApp}/src/app/App.tsx`,
`import NxWelcome from './nx-welcome';
import { ${exportedLibraryComponent} } from '@proj/${myBuildableLib}';
export function App() {
return (
<div>
<${exportedLibraryComponent} />
<NxWelcome title="viteib" />
</div>
);
}
export default App;`
);

updateFile(
`apps/${myApp}/vite.config.ts`,
`/// <reference types='vitest' />
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
export default defineConfig({
root: __dirname,
cacheDir: '../../node_modules/.vite/${myApp}',
server: {
port: 4200,
host: 'localhost',
},
preview: {
port: 4300,
host: 'localhost',
},
plugins: [react(), nxViteTsPaths({buildLibsFromSource: false})],
build: {
outDir: '../../dist/${myApp}',
emptyOutDir: true,
reportCompressedSize: true,
commonjsOptions: {
transformMixedEsModules: true,
},
},
});`
);

const result = runCLI(`build ${myApp}`);
expect(result).toContain(
`Running target build for project ${myApp} and 1 task it depends on`
);
expect(result).toContain(
`Successfully ran target build for project ${myApp} and 1 task it depends on`
);
});
});

it('should run serve-static', async () => {
let process: ChildProcess;
const port = 8081;
Expand Down
82 changes: 75 additions & 7 deletions packages/vite/plugins/nx-tsconfig-paths.plugin.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,23 @@
import { joinPathFragments, stripIndents, workspaceRoot } from '@nx/devkit';
import { existsSync } from 'node:fs';
import {
createProjectGraphAsync,
joinPathFragments,
stripIndents,
workspaceRoot,
} from '@nx/devkit';
import { copyFileSync, existsSync } from 'node:fs';
import { relative, join, resolve } from 'node:path';
import {
loadConfig,
createMatchPath,
MatchPath,
ConfigLoaderSuccessResult,
} from 'tsconfig-paths';
import {
calculateProjectBuildableDependencies,
createTmpTsConfig,
} from '@nx/js/src/utils/buildable-libs-utils';
import { Plugin } from 'vite';
import { nxViteBuildCoordinationPlugin } from './nx-vite-build-coordination.plugin';

export interface nxViteTsPathsOptions {
/**
Expand All @@ -26,6 +37,12 @@ export interface nxViteTsPathsOptions {
* @default ['.ts', '.tsx', '.js', '.jsx', '.json', '.mjs', '.cjs']
**/
extensions?: string[];
/**
* Inform Nx whether to use the raw source or to use the built output for buildable dependencies.
* Set to `false` to use incremental builds.
* @default true
*/
buildLibsFromSource?: boolean;
}

export function nxViteTsPaths(options: nxViteTsPathsOptions = {}) {
Expand All @@ -44,14 +61,15 @@ export function nxViteTsPaths(options: nxViteTsPathsOptions = {}) {
'.cjs',
];
options.mainFields ??= [['exports', '.', 'import'], 'module', 'main'];
options.buildLibsFromSource ??= true;
let projectRoot = '';

return {
name: 'nx-vite-ts-paths',
configResolved(config: any) {
const projectRoot = config.root;
async configResolved(config: any) {
projectRoot = config.root;
const projectRootFromWorkspaceRoot = relative(workspaceRoot, projectRoot);

const foundTsConfigPath = getTsConfig(
let foundTsConfigPath = getTsConfig(
join(
workspaceRoot,
'tmp',
Expand All @@ -64,6 +82,43 @@ export function nxViteTsPaths(options: nxViteTsPathsOptions = {}) {
throw new Error(stripIndents`Unable to find a tsconfig in the workspace!
There should at least be a tsconfig.base.json or tsconfig.json in the root of the workspace ${workspaceRoot}`);
}

if (!options.buildLibsFromSource && !global.NX_GRAPH_CREATION) {
const projectGraph = await createProjectGraphAsync({
exitOnError: false,
resetDaemonClient: true,
});
const { dependencies } = calculateProjectBuildableDependencies(
undefined,
projectGraph,
workspaceRoot,
process.env.NX_TASK_TARGET_PROJECT,
// When using incremental building and the serve target is called
// we need to get the deps for the 'build' target instead.
process.env.NX_TASK_TARGET_TARGET === 'serve'
? 'build'
: process.env.NX_TASK_TARGET_TARGET,
process.env.NX_TASK_TARGET_CONFIGURATION
);
// This tsconfig is used via the Vite ts paths plugin.
// It can be also used by other user-defined Vite plugins (e.g. for creating type declaration files).
foundTsConfigPath = createTmpTsConfig(
foundTsConfigPath,
workspaceRoot,
relative(workspaceRoot, projectRoot),
dependencies
);

if (config.command === 'serve') {
const buildableLibraryDependencies = dependencies
.filter((dep) => dep.node.type === 'lib')
.map((dep) => dep.node.name)
.join(',');
const buildCommand = `npx nx run-many --target=${process.env.NX_TASK_TARGET_TARGET} --projects=${buildableLibraryDependencies}`;
config.plugins.push(nxViteBuildCoordinationPlugin({ buildCommand }));
}
}

const parsed = loadConfig(foundTsConfigPath);

logIt('first parsed tsconfig: ', parsed);
Expand Down Expand Up @@ -119,7 +174,20 @@ There should at least be a tsconfig.base.json or tsconfig.json in the root of th
// https://rollupjs.org/plugin-development/#resolveid
return resolvedFile || null;
},
};
async writeBundle(options) {
const outDir = options.dir || 'dist';
const src = resolve(projectRoot, 'package.json');
if (existsSync(src)) {
const dest = join(outDir, 'package.json');

try {
copyFileSync(src, dest);
} catch (err) {
console.error('Error copying package.json:', err);
}
}
},
} as Plugin;

function getTsConfig(preferredTsConfigPath: string): string {
return [
Expand Down
71 changes: 71 additions & 0 deletions packages/vite/plugins/nx-vite-build-coordination.plugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { type Plugin } from 'vite';
import { BatchFunctionRunner } from 'nx/src/command-line/watch/watch';
import { exec, type ChildProcess } from 'child_process';
import {
daemonClient,
type UnregisterCallback,
} from 'nx/src/daemon/client/client';
import { output } from 'nx/src/utils/output';

export interface NxViteBuildCoordinationPluginOptions {
buildCommand: string;
}
export function nxViteBuildCoordinationPlugin(
options: NxViteBuildCoordinationPluginOptions
): Plugin {
let activeBuildProcess: ChildProcess | undefined;
let unregisterFileWatcher: UnregisterCallback | undefined;

async function buildChangedProjects() {
await new Promise<void>((res) => {
activeBuildProcess = exec(options.buildCommand);
activeBuildProcess.stdout.pipe(process.stdout);
activeBuildProcess.stderr.pipe(process.stderr);
activeBuildProcess.on('exit', () => {
res();
});
activeBuildProcess.on('error', () => {
res();
});
});
activeBuildProcess = undefined;
}

function createFileWatcher() {
const runner = new BatchFunctionRunner(() => buildChangedProjects());
return daemonClient.registerFileWatcher(
{ watchProjects: 'all' },
(err, { changedProjects, changedFiles }) => {
if (err === 'closed') {
output.error({
title: 'Watch connection closed',
bodyLines: [
'The daemon had closed the connection to this watch process.',
'Please restart your watch command.',
],
});
process.exit(1);
}

if (activeBuildProcess) {
activeBuildProcess.kill(2);
activeBuildProcess = undefined;
}

runner.enqueue(changedProjects, changedFiles);
}
);
}

return {
name: 'nx-vite-build-coordination-plugin',
async buildStart() {
if (!unregisterFileWatcher) {
await buildChangedProjects();
unregisterFileWatcher = await createFileWatcher();
process.on('exit', () => unregisterFileWatcher());
process.on('SIGINT', () => process.exit());
}
},
};
}

0 comments on commit 64c6287

Please sign in to comment.