Skip to content

Commit

Permalink
feat(angular): add support for incremental builds to the webpack-serv…
Browse files Browse the repository at this point in the history
…er executor (#10754)
  • Loading branch information
leosvelperez authored Jun 16, 2022
1 parent 9900ceb commit f3a9f55
Show file tree
Hide file tree
Showing 5 changed files with 116 additions and 50 deletions.
4 changes: 4 additions & 0 deletions docs/generated/packages/angular.json
Original file line number Diff line number Diff line change
Expand Up @@ -2999,6 +2999,10 @@
"poll": {
"type": "number",
"description": "Enable and define the file watching poll time period in milliseconds."
},
"buildLibsFromSource": {
"type": "boolean",
"description": "Read buildable libraries from source instead of building them separately. If not set, it will take the value specified in the `browserTarget` options, or it will default to `true` if it's also not set in the `browserTarget` options."
}
},
"additionalProperties": false,
Expand Down
1 change: 1 addition & 0 deletions packages/angular/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"@nrwl/jest": "file:../jest",
"@nrwl/linter": "file:../linter",
"@nrwl/storybook": "file:../storybook",
"@nrwl/web": "file:../web",
"@nrwl/workspace": "file:../workspace",
"@phenomnomnominal/tsquery": "4.1.1",
"@schematics/angular": "~14.0.0",
Expand Down
1 change: 1 addition & 0 deletions packages/angular/src/builders/webpack-server/schema.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,5 @@ export interface Schema {
hmr?: boolean;
watch?: boolean;
poll?: number;
buildLibsFromSource?: boolean;
}
4 changes: 4 additions & 0 deletions packages/angular/src/builders/webpack-server/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,10 @@
"poll": {
"type": "number",
"description": "Enable and define the file watching poll time period in milliseconds."
},
"buildLibsFromSource": {
"type": "boolean",
"description": "Read buildable libraries from source instead of building them separately. If not set, it will take the value specified in the `browserTarget` options, or it will default to `true` if it's also not set in the `browserTarget` options."
}
},
"additionalProperties": false,
Expand Down
156 changes: 106 additions & 50 deletions packages/angular/src/builders/webpack-server/webpack-server.impl.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,38 @@
import { BuilderContext, createBuilder } from '@angular-devkit/architect';
import {
BuilderContext,
BuilderOutput,
createBuilder,
} from '@angular-devkit/architect';
import {
DevServerBuilderOptions,
serveWebpackBrowser,
} from '@angular-devkit/build-angular/src/builders/dev-server';
executeDevServerBuilder,
} from '@angular-devkit/build-angular';
import { JsonObject } from '@angular-devkit/core';
import {
joinPathFragments,
parseTargetString,
readAllWorkspaceConfiguration,
Workspaces,
readCachedProjectGraph,
} from '@nrwl/devkit';
import { WebpackNxBuildCoordinationPlugin } from '@nrwl/web/src/plugins/webpack-nx-build-coordination-plugin';
import {
calculateProjectDependencies,
createTmpTsConfig,
DependentBuildableProjectNode,
} from '@nrwl/workspace/src/utilities/buildable-libs-utils';
import { existsSync } from 'fs';
import { isNpmProject } from 'nx/src/project-graph/operators';
import { Observable } from 'rxjs';
import { merge } from 'webpack-merge';
import { resolveCustomWebpackConfig } from '../utilities/webpack';
import { normalizeOptions } from './lib';
import type { Schema } from './schema';

export function executeWebpackServerBuilder(
schema: Schema,
rawOptions: Schema,
context: BuilderContext
) {
process.env.NX_TSCONFIG_PATH = joinPathFragments(
context.workspaceRoot,
'tsconfig.base.json'
);

const options = normalizeOptions(schema);
): Observable<BuilderOutput> {
const options = normalizeOptions(rawOptions);
const workspaceConfig = readAllWorkspaceConfiguration();

const parsedBrowserTarget = parseTargetString(options.browserTarget);
Expand All @@ -34,62 +41,111 @@ export function executeWebpackServerBuilder(
parsedBrowserTarget.target
];

const selectedConfiguration = parsedBrowserTarget.configuration
const buildTargetConfiguration = parsedBrowserTarget.configuration
? buildTarget.configurations[parsedBrowserTarget.configuration]
: buildTarget.defaultConfiguration
? buildTarget.configurations[buildTarget.defaultConfiguration]
: buildTarget.options;
: undefined;

const buildLibsFromSource =
options.buildLibsFromSource ??
buildTargetConfiguration?.buildLibsFromSource ??
buildTarget.options.buildLibsFromSource ??
true;

const customWebpackConfig: { path: string } =
selectedConfiguration.customWebpackConfig ??
buildTargetConfiguration?.customWebpackConfig ??
buildTarget.options.customWebpackConfig;

let pathToWebpackConfig: string;
if (customWebpackConfig && customWebpackConfig.path) {
const pathToWebpackConfig = joinPathFragments(
pathToWebpackConfig = joinPathFragments(
context.workspaceRoot,
customWebpackConfig.path
);

if (existsSync(pathToWebpackConfig)) {
return serveWebpackBrowser(
options as DevServerBuilderOptions,
context as any,
{
webpackConfiguration: async (baseWebpackConfig) => {
const customWebpackConfiguration = resolveCustomWebpackConfig(
pathToWebpackConfig,
buildTarget.options.tsConfig
);
// The extra Webpack configuration file can also export a Promise, for instance:
// `module.exports = new Promise(...)`. If it exports a single object, but not a Promise,
// then await will just resolve that object.
const config = await customWebpackConfiguration;

// The extra Webpack configuration file can export a synchronous or asynchronous function,
// for instance: `module.exports = async config => { ... }`.
if (typeof config === 'function') {
return config(
baseWebpackConfig,
selectedConfiguration,
context.target
);
} else {
return merge(baseWebpackConfig, config);
}
},
}
);
} else {
if (!existsSync(pathToWebpackConfig)) {
throw new Error(
`Custom Webpack Config File Not Found!\nTo use a custom webpack config, please ensure the path to the custom webpack file is correct: \n${pathToWebpackConfig}`
);
}
}

return serveWebpackBrowser(
options as DevServerBuilderOptions,
context as any
);
let dependencies: DependentBuildableProjectNode[];
if (!buildLibsFromSource) {
const buildTargetTsConfigPath =
buildTargetConfiguration?.tsConfig ?? buildTarget.options.tsConfig;
const result = calculateProjectDependencies(
readCachedProjectGraph(),
context.workspaceRoot,
context.target.project,
parsedBrowserTarget.target,
context.target.configuration
);
dependencies = result.dependencies;
const updatedTsConfig = createTmpTsConfig(
joinPathFragments(context.workspaceRoot, buildTargetTsConfigPath),
context.workspaceRoot,
result.target.data.root,
dependencies
);
process.env.NX_TSCONFIG_PATH = updatedTsConfig;

// We can't just pass the tsconfig path in memory to the angular builder
// function because we can't pass the build target options to it, the build
// targets options will be retrieved by the builder from the project
// configuration. Therefore, we patch the method in the context to retrieve
// the target options to overwrite the tsconfig path to use the generated
// one with the updated path mappings.
const originalGetTargetOptions = context.getTargetOptions;
context.getTargetOptions = async (target) => {
const options = await originalGetTargetOptions(target);
options.tsConfig = updatedTsConfig;
return options;
};
}

return executeDevServerBuilder(options as DevServerBuilderOptions, context, {
webpackConfiguration: async (baseWebpackConfig) => {
if (!buildLibsFromSource) {
const workspaceDependencies = dependencies
.filter((dep) => !isNpmProject(dep.node))
.map((dep) => dep.node.name);
baseWebpackConfig.plugins.push(
new WebpackNxBuildCoordinationPlugin(
`nx run-many --target=${
parsedBrowserTarget.target
} --projects=${workspaceDependencies.join(',')}`
)
);
}

if (!pathToWebpackConfig) {
return baseWebpackConfig;
}

const customWebpackConfiguration = resolveCustomWebpackConfig(
pathToWebpackConfig,
buildTarget.options.tsConfig
);
// The extra Webpack configuration file can also export a Promise, for instance:
// `module.exports = new Promise(...)`. If it exports a single object, but not a Promise,
// then await will just resolve that object.
const config = await customWebpackConfiguration;

// The extra Webpack configuration file can export a synchronous or asynchronous function,
// for instance: `module.exports = async config => { ... }`.
if (typeof config === 'function') {
return config(
baseWebpackConfig,
buildTargetConfiguration,
context.target
);
}

return merge(baseWebpackConfig, config);
},
});
}

export default createBuilder<JsonObject & Schema>(
Expand Down

1 comment on commit f3a9f55

@vercel
Copy link

@vercel vercel bot commented on f3a9f55 Jun 16, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

nx-dev – ./

nx-five.vercel.app
nx-dev-nrwl.vercel.app
nx-dev-git-master-nrwl.vercel.app
nx.dev

Please sign in to comment.