[cli] Use Builder importing logic from vc build in vc dev (#8485)

This makes `vc dev` utilize the same Builder installing and importing logic that `vc build` uses. So now the Builders will be installed locally on a per-project basis, rather than being installed to a shared global installation directory.
This commit is contained in:
Nathan Rajlich
2022-09-13 14:35:01 -07:00
committed by GitHub
parent f3f3d7df5b
commit cc7577a648
9 changed files with 57 additions and 797 deletions

View File

@@ -1,4 +1,3 @@
import ms from 'ms';
import url, { URL } from 'url';
import http from 'http';
import fs from 'fs-extra';
@@ -62,11 +61,6 @@ import { devRouter, getRoutesTypes } from './router';
import getMimeType from './mime-type';
import { executeBuild, getBuildMatches, shutdownBuilder } from './builder';
import { generateErrorMessage, generateHttpStatusDescription } from './errors';
import {
installBuilders,
updateBuilders,
builderDirPromise,
} from './builder-cache';
// HTML templates
import errorTemplate from './templates/error';
@@ -171,8 +165,6 @@ export default class DevServer {
private vercelConfigWarning: boolean;
private getVercelConfigPromise: Promise<VercelConfig> | null;
private blockingBuildsPromise: Promise<void> | null;
private updateBuildersPromise: Promise<void> | null;
private updateBuildersTimeout: NodeJS.Timeout | undefined;
private startPromise: Promise<void> | null;
private systemEnvValues: string[];
@@ -211,7 +203,6 @@ export default class DevServer {
this.vercelConfigWarning = false;
this.getVercelConfigPromise = null;
this.blockingBuildsPromise = null;
this.updateBuildersPromise = null;
this.startPromise = null;
this.watchAggregationId = null;
@@ -493,33 +484,6 @@ export default class DevServer {
);
}
async invalidateBuildMatches(
vercelConfig: VercelConfig,
updatedBuilders: string[]
): Promise<void> {
if (updatedBuilders.length === 0) {
this.output.debug('No builders were updated');
return;
}
// Delete any build matches that have the old builder required already
for (const buildMatch of this.buildMatches.values()) {
const {
src,
builderWithPkg: { package: pkg },
} = buildMatch;
if (isOfficialRuntime('static', pkg.name)) continue;
if (pkg.name && updatedBuilders.includes(pkg.name)) {
shutdownBuilder(buildMatch, this.output);
this.buildMatches.delete(src);
this.output.debug(`Invalidated build match for "${src}"`);
}
}
// Re-add the build matches that were just removed, but with the new builder
await this.updateBuildMatches(vercelConfig);
}
async getLocalEnv(fileName: string, base?: Env): Promise<Env> {
// TODO: use the file watcher to only invalidate the env `dotfile`
// once a change to the `fileName` occurs
@@ -948,30 +912,8 @@ export default class DevServer {
}
}
const builders = new Set<string>(
(vercelConfig.builds || [])
.filter((b: Builder) => b.use)
.map((b: Builder) => b.use)
);
await installBuilders(builders, this.output);
await this.updateBuildMatches(vercelConfig, true);
// Updating builders happens lazily, and any builders that were updated
// get their "build matches" invalidated so that the new version is used.
this.updateBuildersTimeout = setTimeout(() => {
this.updateBuildersPromise = updateBuilders(builders, this.output)
.then(updatedBuilders => {
this.updateBuildersPromise = null;
this.invalidateBuildMatches(vercelConfig, updatedBuilders);
})
.catch(err => {
this.updateBuildersPromise = null;
this.output.prettyError(err);
this.output.debug(err.stack);
});
}, ms('30s'));
// Builders that do not define a `shouldServe()` function need to be
// executed at boot-up time in order to get the initial assets and/or routes
// that can be served by the builder.
@@ -1047,16 +989,11 @@ export default class DevServer {
* Shuts down the `vercel dev` server, and cleans up any temporary resources.
*/
async stop(exitCode?: number): Promise<void> {
const { devProcess } = this;
const { debug } = this.output;
if (this.stopping) return;
this.stopping = true;
if (typeof this.updateBuildersTimeout !== 'undefined') {
clearTimeout(this.updateBuildersTimeout);
}
const { devProcess } = this;
const { debug } = this.output;
const ops: Promise<any>[] = [];
for (const match of this.buildMatches.values()) {
@@ -1074,18 +1011,10 @@ export default class DevServer {
ops.push(this.watcher.close());
}
if (this.updateBuildersPromise) {
debug(`Waiting for builders update to complete`);
ops.push(this.updateBuildersPromise);
}
for (const pid of this.devServerPids) {
ops.push(this.killBuilderDevServer(pid));
}
// Ensure that the builders module cache is created
ops.push(builderDirPromise);
try {
await Promise.all(ops);
} catch (err: unknown) {
@@ -1484,8 +1413,9 @@ export default class DevServer {
// the middleware server for every HTTP request?
const { envConfigs, files, devCacheDir, cwd: workPath } = this;
try {
startMiddlewareResult =
await middleware.builderWithPkg.builder.startDevServer?.({
const { builder } = middleware.builderWithPkg;
if (builder.version === 3) {
startMiddlewareResult = await builder.startDevServer?.({
files,
entrypoint: middleware.entrypoint,
workPath,
@@ -1499,6 +1429,7 @@ export default class DevServer {
buildEnv: { ...envConfigs.buildEnv },
},
});
}
if (startMiddlewareResult) {
const { port, pid } = startMiddlewareResult;
@@ -1911,8 +1842,8 @@ export default class DevServer {
// up a single-serve dev HTTP server that vercel dev will proxy this HTTP request
// to. Once the proxied request is finished, vercel dev shuts down the dev
// server child process.
const { builder, package: builderPkg } = match.builderWithPkg;
if (typeof builder.startDevServer === 'function') {
const { builder, pkg: builderPkg } = match.builderWithPkg;
if (builder.version === 3 && typeof builder.startDevServer === 'function') {
let devServerResult: StartDevServerResult = null;
try {
const { envConfigs, files, devCacheDir, cwd: workPath } = this;