-
Notifications
You must be signed in to change notification settings - Fork 12k
/
Copy pathbuilder.ts
245 lines (217 loc) · 8.54 KB
/
builder.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
import type { DevServerBuilderOutput } from '@angular/build';
import { type IndexHtmlTransform, checkPort, purgeStaleBuildCache } from '@angular/build/private';
import type { BuilderContext } from '@angular-devkit/architect';
import type { Plugin } from 'esbuild';
import type http from 'node:http';
import { EMPTY, Observable, defer, switchMap } from 'rxjs';
import type { ExecutionTransformer } from '../../transforms';
import { normalizeOptions } from './options';
import type { Schema as DevServerBuilderOptions } from './schema';
/**
* A Builder that executes a development server based on the provided browser target option.
*
* Usage of the `transforms` and/or `extensions` parameters is NOT supported and may cause
* unexpected build output or build failures.
*
* @param options Dev Server options.
* @param context The build context.
* @param transforms A map of transforms that can be used to hook into some logic (such as
* transforming webpack configuration before passing it to webpack).
* @param extensions An optional object containing an array of build plugins (esbuild-based)
* and/or HTTP request middleware.
*
* @experimental Direct usage of this function is considered experimental.
*/
export function execute(
options: DevServerBuilderOptions,
context: BuilderContext,
transforms: {
webpackConfiguration?: ExecutionTransformer<import('webpack').Configuration>;
logging?: import('@angular-devkit/build-webpack').WebpackLoggingCallback;
indexHtml?: IndexHtmlTransform;
} = {},
extensions?: {
buildPlugins?: Plugin[];
middleware?: ((
req: http.IncomingMessage,
res: http.ServerResponse,
next: (err?: unknown) => void,
) => void)[];
builderSelector?: (info: BuilderSelectorInfo, logger: BuilderContext['logger']) => string;
},
): Observable<DevServerBuilderOutput> {
// Determine project name from builder context target
const projectName = context.target?.project;
if (!projectName) {
context.logger.error(`The "dev-server" builder requires a target to be specified.`);
return EMPTY;
}
return defer(() => initialize(options, projectName, context, extensions?.builderSelector)).pipe(
switchMap(({ builderName, normalizedOptions }) => {
// Use vite-based development server for esbuild-based builds
if (isEsbuildBased(builderName)) {
if (transforms?.logging || transforms?.webpackConfiguration) {
throw new Error(
`The "application" and "browser-esbuild" builders do not support Webpack transforms.`,
);
}
if (options.allowedHosts?.length) {
context.logger.warn(
`The "allowedHosts" option will not be used because it is not supported by the "${builderName}" builder.`,
);
}
if (options.publicHost) {
context.logger.warn(
`The "publicHost" option will not be used because it is not supported by the "${builderName}" builder.`,
);
}
if (options.disableHostCheck) {
context.logger.warn(
`The "disableHostCheck" option will not be used because it is not supported by the "${builderName}" builder.`,
);
}
// New build system defaults hmr option to the value of liveReload
normalizedOptions.hmr ??= normalizedOptions.liveReload;
// New build system uses Vite's allowedHost option convention of true for disabling host checks
if (normalizedOptions.disableHostCheck) {
(normalizedOptions as unknown as { allowedHosts: true }).allowedHosts = true;
} else {
normalizedOptions.allowedHosts ??= [];
}
return defer(() =>
Promise.all([import('@angular/build/private'), import('../browser-esbuild')]),
).pipe(
switchMap(([{ serveWithVite, buildApplicationInternal }, { convertBrowserOptions }]) =>
serveWithVite(
normalizedOptions as typeof normalizedOptions & {
hmr: boolean;
allowedHosts: true | string[];
},
builderName,
(options, context, codePlugins) => {
return builderName === '@angular-devkit/build-angular:browser-esbuild'
? // eslint-disable-next-line @typescript-eslint/no-explicit-any
buildApplicationInternal(convertBrowserOptions(options as any), context, {
codePlugins,
})
: buildApplicationInternal(options, context, { codePlugins });
},
context,
transforms,
extensions,
),
),
);
}
// Warn if the initial options provided by the user enable prebundling with Webpack-based builders
if (options.prebundle) {
context.logger.warn(
`Prebundling has been configured but will not be used because it is not supported by the "${builderName}" builder.`,
);
}
if (extensions?.buildPlugins?.length) {
throw new Error('Only the "application" and "browser-esbuild" builders support plugins.');
}
if (extensions?.middleware?.length) {
throw new Error(
'Only the "application" and "browser-esbuild" builders support middleware.',
);
}
// Webpack based build systems default to false for hmr option
normalizedOptions.hmr ??= false;
// Use Webpack for all other browser targets
return defer(() => import('./webpack-server')).pipe(
switchMap(({ serveWebpackBrowser }) =>
serveWebpackBrowser(normalizedOptions, builderName, context, transforms),
),
);
}),
);
}
async function initialize(
initialOptions: DevServerBuilderOptions,
projectName: string,
context: BuilderContext,
builderSelector = defaultBuilderSelector,
) {
// Purge old build disk cache.
await purgeStaleBuildCache(context);
const normalizedOptions = await normalizeOptions(context, projectName, initialOptions);
const builderName = builderSelector(
{
builderName: await context.getBuilderNameForTarget(normalizedOptions.buildTarget),
forceEsbuild: !!normalizedOptions.forceEsbuild,
},
context.logger,
);
if (
!normalizedOptions.disableHostCheck &&
!/^127\.\d+\.\d+\.\d+/g.test(normalizedOptions.host) &&
normalizedOptions.host !== 'localhost'
) {
context.logger.warn(`
Warning: This is a simple server for use in testing or debugging Angular applications
locally. It hasn't been reviewed for security issues.
Binding this server to an open connection can result in compromising your application or
computer. Using a different host than the one passed to the "--host" flag might result in
websocket connection issues. You might need to use "--disable-host-check" if that's the
case.
`);
}
if (normalizedOptions.disableHostCheck) {
context.logger.warn(
'Warning: Running a server with --disable-host-check is a security risk. ' +
'See https://medium.com/webpack/webpack-dev-server-middleware-security-issues-1489d950874a for more information.',
);
}
normalizedOptions.port = await checkPort(normalizedOptions.port, normalizedOptions.host);
return {
builderName,
normalizedOptions,
};
}
export function isEsbuildBased(
builderName: string,
): builderName is
| '@angular/build:application'
| '@angular-devkit/build-angular:application'
| '@angular-devkit/build-angular:browser-esbuild' {
if (
builderName === '@angular/build:application' ||
builderName === '@angular-devkit/build-angular:application' ||
builderName === '@angular-devkit/build-angular:browser-esbuild'
) {
return true;
}
return false;
}
interface BuilderSelectorInfo {
builderName: string;
forceEsbuild: boolean;
}
function defaultBuilderSelector(
info: BuilderSelectorInfo,
logger: BuilderContext['logger'],
): string {
if (isEsbuildBased(info.builderName)) {
return info.builderName;
}
if (info.forceEsbuild) {
if (!info.builderName.startsWith('@angular-devkit/build-angular:')) {
logger.warn(
'Warning: Forcing the use of the esbuild-based build system with third-party builders' +
' may cause unexpected behavior and/or build failures.',
);
}
// The compatibility builder should be used if esbuild is force enabled.
return '@angular-devkit/build-angular:browser-esbuild';
}
return info.builderName;
}