Skip to content

Commit

Permalink
feat: support define and context env variables for apps
Browse files Browse the repository at this point in the history
  • Loading branch information
ocombe committed Feb 1, 2024
1 parent 28e37d6 commit 357cdf2
Show file tree
Hide file tree
Showing 7 changed files with 98 additions and 27 deletions.
22 changes: 20 additions & 2 deletions angular/app-types/angular-app-type/angular.application.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,16 @@ export class AngularApp implements Application {
return context as any as EnvContext;
}

private async getEnvFile(mode: string, rootDir: string, overrides?: Record<string, string>) {
// TODO: enable this one we have ESM envs, otherwise we get a warning message about loading the deprecated CJS build of Vite
// const vite = await loadEsmModule('vite');
// const dotenv = vite.loadEnv(mode, rootDir);
return {
...overrides,
// ...dotenv
}
}

// TODO: fix return type once bit has a new stable version
async run(context: AppContext): Promise<ApplicationInstance> {
const depsResolver = context.getAspect<DependencyResolverMain>(DependencyResolverAspect.id);
Expand All @@ -165,6 +175,7 @@ export class AngularApp implements Application {
this.generateTsConfig(bitCmps, appRootPath, appTsconfigPath, tsconfigPath, depsResolver, workspace);

if (Number(VERSION.major) >= 16) {
const envVars = await this.getEnvFile('development', appRootPath, context.envVariables as any);
await serveApplication({
angularOptions: {
...this.options.angularBuildOptions as ApplicationOptions,
Expand All @@ -174,7 +185,10 @@ export class AngularApp implements Application {
workspaceRoot: appRootPath,
port,
logger: logger,
tempFolder: tempFolder
tempFolder: tempFolder,
envVars: {
'process.env': envVars
}
});
} else {
const devServerContext = this.getDevServerContext(context, appRootPath);
Expand Down Expand Up @@ -206,6 +220,7 @@ export class AngularApp implements Application {
this.generateTsConfig([capsule.component], appRootPath, appTsconfigPath, tsconfigPath, depsResolver, undefined, entryServer);

if (!this.options.bundler && Number(VERSION.major) >= 16) {
const envVars = await this.getEnvFile('production', appRootPath, context.envVariables as any);
await buildApplication({
angularOptions: {
...appOptions,
Expand All @@ -216,7 +231,10 @@ export class AngularApp implements Application {
workspaceRoot: context.capsule.path,
logger: logger,
tempFolder: tempFolder,
entryServer
entryServer,
envVars: {
'process.env': envVars
}
});
} else {
let bundler: Bundler;
Expand Down
23 changes: 10 additions & 13 deletions angular/app-types/angular-app-type/application.bundler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { outputFileSync } from 'fs-extra';
// @ts-ignore
import type { NitroConfig } from 'nitropack';
import { basename, extname, join, posix, relative, resolve } from 'path';
import definePlugin from './plugins/define.plugin';
import { getIndexInputFile } from './utils';

export type BuildApplicationOptions = {
Expand All @@ -28,31 +29,30 @@ export type BuildApplicationOptions = {
logger: Logger;
tempFolder: string;
entryServer?: string;
envVars: any;
}

// TODO allow customizing this
const BUILDER_NAME = '@angular-devkit/build-angular:application';
const CACHE_PATH = 'angular/cache';

export async function buildApplication(options: BuildApplicationOptions): Promise<void> {
const { angularOptions: { tsConfig, ssr } } = options;
const isSsr = !!ssr && Number(VERSION.major) >= 17;

const { angularOptions: { tsConfig, ssr, define }, envVars } = options;
assert(tsConfig, 'tsConfig option is required');

if(isSsr) {
const isSsr = !!ssr && Number(VERSION.major) >= 17;
if (isSsr) {
addEntryServer(options);
}

const appOptions = getAppOptions(options, isSsr);
const builderContext = getBuilderContext(options, appOptions);
const builderPlugins: any[] = [];
const codePlugins = [definePlugin({ ...envVars, ...define || {} })];
const extensions: any = (Number(VERSION.major) >= 17 && Number(VERSION.minor) >= 1) ? { codePlugins } : [];

for await (const result of buildApplicationInternal(
appOptions as any,
builderContext,
{ write: true },
builderPlugins
extensions
)) {
if (!result.success && result.errors) {
throw new Error(result.errors.map((err: any) => err.text).join('\n'));
Expand Down Expand Up @@ -104,7 +104,7 @@ function getAppOptions(options: BuildApplicationOptions, isSsr: boolean): any {
const normalizedBrowser = `./${ join(sourceRoot, 'main.ts') }`;

const dedupedAssets = dedupPaths([posix.join(sourceRoot, `assets/**/*`), ...(angularOptions.assets ?? [])]);
const dedupedStyles = dedupPaths([posix.join(sourceRoot, `styles.${angularOptions.inlineStyleLanguage}`), ...(angularOptions.styles ?? [])]);
const dedupedStyles = dedupPaths([posix.join(sourceRoot, `styles.${ angularOptions.inlineStyleLanguage }`), ...(angularOptions.styles ?? [])]);

return {
...angularOptions,
Expand Down Expand Up @@ -157,10 +157,7 @@ function getBuilderContext(options: BuildApplicationOptions, appOptions: Applica
cli: { cache: { enabled: true, path: resolve(tempFolder, 'angular/cache') } }
});
},
addTeardown: (teardown: () => Promise<void> | void) => {
teardown();
return;
},
addTeardown: () => {},
getBuilderNameForTarget: () => Promise.resolve(BUILDER_NAME),
getTargetOptions: () => Promise.resolve(appOptions as any),
validateOptions: () => Promise.resolve(appOptions as any)
Expand Down
19 changes: 9 additions & 10 deletions angular/app-types/angular-app-type/application.dev-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import type { NitroConfig } from 'nitropack';
import { join, posix, relative, resolve } from 'path';
// @ts-ignore
import type { Connect } from 'vite';
import definePlugin from './plugins/define.plugin';

export type ServeApplicationOptions = {
angularOptions: Partial<ApplicationBuilderOptions & DevServerBuilderOptions>;
Expand All @@ -27,6 +28,7 @@ export type ServeApplicationOptions = {
logger: Logger;
port: number;
tempFolder: string;
envVars: any;
}

// TODO allow customizing this
Expand All @@ -36,24 +38,21 @@ const BUILDER_NAME = '@angular-devkit/build-angular:application';
const CACHE_PATH = 'angular/cache';

export async function serveApplication(options: ServeApplicationOptions): Promise<void> {
const {
angularOptions
} = options;
const isSsr = !!angularOptions.server && Number(VERSION.major) >= 17;

assert(angularOptions.tsConfig, 'tsConfig option is required');

const appOptions = getAppOptions(options, isSsr);
const builderContext = getBuilderContext(options, appOptions);
// intercept SIGINT and exit cleanly, otherwise the process will not exit properly on Ctrl+C
process.on('SIGINT', () => process.exit(1));

const { angularOptions: { server, tsConfig, define }, envVars } = options;
assert(tsConfig, 'tsConfig option is required');
const isSsr = !!server && Number(VERSION.major) >= 17;
const appOptions = getAppOptions(options, isSsr);
const builderContext = getBuilderContext(options, appOptions);
const devServerOptions = isSsr ? {
buildPlugins: [definePlugin({ ...envVars, ...define })],
middleware: [await createNitroApiMiddleware(options)]
} : undefined;

// @ts-ignore only v17+ has 4 arguments, previous versions only have 3
const res = await executeDevServerBuilder(appOptions, builderContext as any, undefined, devServerOptions).toPromise();
await executeDevServerBuilder(appOptions, builderContext as any, undefined, devServerOptions).toPromise();
}

function getAppOptions(options: ServeApplicationOptions, isSsr: boolean): ApplicationBuilderOptions & DevServerBuilderOptions {
Expand Down
1 change: 1 addition & 0 deletions angular/app-types/angular-app-type/component.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"peerDependencies": {
"@angular-devkit/build-angular": ">= 0.0.1",
"@angular/cli": ">= 13.0.0",
"esbuild": ">= 0.14.0",
"typescript": ">= 3.5.3"
}
}
Expand Down
48 changes: 48 additions & 0 deletions angular/app-types/angular-app-type/plugins/define.plugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { Plugin, PluginBuild } from 'esbuild';

export const stringifyDefine = (define: any) => {
return Object.entries(define).reduce((acc: any, [key, value]) => {
acc[key] = JSON.stringify(value);
return acc;
}, {});
};

/**
* Pass environment variables to esbuild.
* @returns An esbuild plugin.
*/
export default function(defineValues = {}) {
// set variables on global so that they also work during ssr
const keys = Object.keys(defineValues);
keys.forEach((key: any) => {
// @ts-ignore
if (global[key]) {
throw new Error(`Define plugin: key ${ key } already exists on global`);
} else {
// @ts-ignore
global[key] = defineValues[key];
}
});

const plugin: Plugin = {
name: 'env',

setup(build: PluginBuild) {
const { platform, define = {} } = build.initialOptions;
if (platform === 'node') {
return;
}

if (typeof defineValues !== 'string') {
defineValues = stringifyDefine(defineValues);
}

build.initialOptions.define = {
...defineValues,
...define
};
}
};

return plugin;
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@ export let buildApplicationInternal = (
options: ApplicationBuilderOptions,
context: BuilderContext & {
signal?: AbortSignal;
}, infrastructureSettings?: {
},
infrastructureSettings?: {
write?: boolean;
}, plugins?: Plugin[]
},
plugins?: Plugin[] | { codePlugins: Plugin[], indexHtmlTransformer: any }
// @ts-ignore
) => AsyncIterable<BuilderOutput & {
outputFiles?: OutputFile[];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@ export interface ApplicationBuilderOptions {
* Delete the output path before building.
*/
deleteOutputPath?: boolean;
/**
* Defines global identifiers that will be replaced with a specified constant value when found in any JavaScript or TypeScript code including libraries.
* The value will be used directly.
* String values must be put in quotes. Identifiers within Angular metadata such as Component Decorators will not be replaced.
*/
define?: any;
/**
* Exclude the listed external dependencies from being bundled into the bundle. Instead, the
* created bundle relies on these dependencies to be available during runtime.
Expand Down

0 comments on commit 357cdf2

Please sign in to comment.