Skip to content

Commit

Permalink
feat: use new app type format
Browse files Browse the repository at this point in the history
  • Loading branch information
ocombe committed Jan 30, 2024
1 parent 3639292 commit 0e5b546
Show file tree
Hide file tree
Showing 24 changed files with 219 additions and 206 deletions.
6 changes: 6 additions & 0 deletions angular/app-types/angular-app-type/angular-app-options.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
AngularEnvOptions,
ApplicationOptions,
BrowserOptions,
DevServerOptions
Expand Down Expand Up @@ -52,4 +53,9 @@ export type AngularAppOptions = {
* Angular options for `bit run`
*/
angularServeOptions: (BrowserOptions & DevServerOptions) | (ApplicationOptions & DevServerOptions);

/**
* Env-specific options depending on the version of Angular used.
*/
ngEnvOptions: AngularEnvOptions;
};
30 changes: 9 additions & 21 deletions angular/app-types/angular-app-type/angular.app-type.ts
Original file line number Diff line number Diff line change
@@ -1,39 +1,27 @@
import { GenericAngularEnv, getWorkspace, NG_APP_NAME } from '@bitdev/angular.dev-services.common';
import { NG_APP_NAME } from '@bitdev/angular.dev-services.common';
import { Application, ApplicationType } from '@teambit/application';
import { DependencyResolverAspect, DependencyResolverMain } from '@teambit/dependency-resolver';
import { EnvContext, EnvHandler } from '@teambit/envs';
import { Logger } from '@teambit/logger';
import { Workspace } from '@teambit/workspace';
import { EnvHandler } from '@teambit/envs';
import { AngularAppOptions } from './angular-app-options';
import { AngularApp } from './angular.application';

interface AngularAppTypeOptions {
name?: string;
angularEnv: GenericAngularEnv;
}

export class AngularAppType implements ApplicationType<AngularAppOptions> {
constructor(readonly name: string, private angularEnv: GenericAngularEnv, private context: EnvContext, private depsResolver: DependencyResolverMain, private logger: Logger, private workspace?: Workspace) {
}
constructor(readonly name: string) {}

createApp(options: AngularAppOptions): Application {
return new AngularApp(
this.angularEnv,
this.context,
options,
this.depsResolver,
this.logger,
this.workspace
);
return new AngularApp({
...options,
name: this.name
});
}

static from(options: AngularAppTypeOptions): EnvHandler<AngularAppType> {
return (context: EnvContext) => {
return () => {
const name = options.name || NG_APP_NAME;
const depsResolver = context.getAspect<DependencyResolverMain>(DependencyResolverAspect.id);
const workspace = getWorkspace(context);
const logger = context.createLogger(name);
return new AngularAppType(name, options.angularEnv, context, depsResolver, logger, workspace);
return new AngularAppType(name);
};
}
}
172 changes: 93 additions & 79 deletions angular/app-types/angular-app-type/angular.application.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
import { VERSION } from '@angular/cli';
import {
ApplicationOptions,
GenericAngularEnv,
getWorkspace,
NG_APP_NAME,
normalizePath
} from '@bitdev/angular.dev-services.common';
import { AngularPreview } from '@bitdev/angular.dev-services.preview.preview';
import {
AngularPreview,
BundlerProvider,
DevServerProvider
} from '@bitdev/angular.dev-services.preview.preview';
import { AppBuildContext, AppContext, Application } from '@teambit/application';
import { Bundler, BundlerContext, DevServer, DevServerContext } from '@teambit/bundler';
AppBuildContext,
AppContext,
Application,
ApplicationInstance
} from '@teambit/application';
import { Bundler, BundlerContext, DevServerContext } from '@teambit/bundler';
import { Component } from '@teambit/component';
import { DependencyResolverMain } from '@teambit/dependency-resolver';
import { DependencyResolverAspect, DependencyResolverMain } from '@teambit/dependency-resolver';
import { EnvContext, EnvHandler } from '@teambit/envs';
import { CACHE_ROOT } from '@teambit/legacy/dist/constants';
import { Logger } from '@teambit/logger';
import { Preview } from '@teambit/preview';
import { Port } from '@teambit/toolbox.network.get-port';
import { Workspace } from '@teambit/workspace';
Expand All @@ -35,34 +36,28 @@ const writeHash = new Map<string, string>();

export class AngularApp implements Application {
readonly name: string;

readonly preview: EnvHandler<Preview>;

readonly tempFolder: string;

readonly tsconfigPath: string;
readonly idName: string;

constructor(
private angularEnv: GenericAngularEnv,
private envContext: EnvContext,
readonly options: AngularAppOptions,
private depsResolver: DependencyResolverMain,
private logger: Logger,
private workspace?: Workspace
readonly options: AngularAppOptions
) {
this.name = options.name;
this.name = options.name || NG_APP_NAME;
this.idName = `bitdev.angular/${ this.name }`;
}

const idName = `bitdev.angular/${ this.name }`;
this.tempFolder = workspace?.getTempDir(idName) || join(CACHE_ROOT, idName);
if (!existsSync(this.tempFolder)) {
mkdirSync(this.tempFolder, { recursive: true });
}
readonly publicDir = 'public';

this.tsconfigPath = normalizePath(join(this.tempFolder, `tsconfig/tsconfig-${ Date.now() }.json`));
this.preview = this.getPreview();
private getTempFolder(workspace?: Workspace): string {
const tempFolder = workspace?.getTempDir(this.idName) || join(CACHE_ROOT, this.idName);
if (!existsSync(tempFolder)) {
mkdirSync(tempFolder, { recursive: true });
}
return tempFolder;
}

readonly publicDir = 'public';
private getTsconfigPath(tempFolder: string): string {
return normalizePath(join(tempFolder, `tsconfig/tsconfig-${ Date.now() }.json`));
}

private getPublicDir(artifactsDir: string) {
return join(artifactsDir, this.name);
Expand Down Expand Up @@ -95,35 +90,33 @@ export class AngularApp implements Application {
});
}

private getPreview(): EnvHandler<Preview> {
const ngEnvOptions = this.angularEnv.getNgEnvOptions();

const angularServeOptions: any = Object.assign(cloneDeep(this.options.angularServeOptions), { tsConfig: this.tsconfigPath });
const angularBuildOptions: any = Object.assign(cloneDeep(this.options.angularBuildOptions), { tsConfig: this.tsconfigPath });
private getPreview(tsconfigPath: string): EnvHandler<Preview> {
const angularServeOptions: any = Object.assign(cloneDeep(this.options.angularServeOptions), { tsConfig: tsconfigPath });
const angularBuildOptions: any = Object.assign(cloneDeep(this.options.angularBuildOptions), { tsConfig: tsconfigPath });

return AngularPreview.from({
webpackServeTransformers: this.options.webpackServeTransformers,
webpackBuildTransformers: this.options.webpackBuildTransformers,
angularServeOptions,
angularBuildOptions,
ngEnvOptions,
ngEnvOptions: this.options.ngEnvOptions,
sourceRoot: this.options.sourceRoot,
});

}

private generateTsConfig(bitCmps: Component[], appRootPath: string, tsconfigPath: string, serverEntry?: string): void {
const tsconfigJSON: JsonObject = readConfigFile(tsconfigPath, sys.readFile).config;
private generateTsConfig(bitCmps: Component[], appRootPath: string, appTsconfigPath: string, tsconfigPath: string, depsResolver: DependencyResolverMain, workspace?: Workspace, serverEntry?: string): void {
const tsconfigJSON: JsonObject = readConfigFile(appTsconfigPath, sys.readFile).config;

// Add the paths to tsconfig to remap bit components to local folders
tsconfigJSON.compilerOptions.paths = tsconfigJSON.compilerOptions.paths || {};
bitCmps.forEach((dep: Component) => {
let componentDir = this.workspace?.componentDir(dep.id, {
let componentDir = workspace?.componentDir(dep.id, {
ignoreVersion: true
});
if (componentDir) {
componentDir = normalizePath(componentDir);
const pkgName = this.depsResolver.getPackageName(dep);
const pkgName = depsResolver.getPackageName(dep);
// TODO we should find a way to use the real entry file based on the component config because people can change it
if (existsSync(join(componentDir, 'public-api.ts'))) {
tsconfigJSON.compilerOptions.paths[pkgName] = [`${ componentDir }/public-api.ts`, `${ componentDir }`];
Expand All @@ -136,93 +129,114 @@ export class AngularApp implements Application {
tsconfigJSON.files.push(serverEntry);
}

const tsconfigContent = expandIncludeExclude(tsconfigJSON, this.tsconfigPath, [appRootPath]);
const tsconfigContent = expandIncludeExclude(tsconfigJSON, tsconfigPath, [appRootPath]);
const hash = objectHash(tsconfigContent);
// write only if link has changed (prevents triggering fs watches)
if (writeHash.get(this.tsconfigPath) !== hash) {
outputJsonSync(this.tsconfigPath, tsconfigContent, { spaces: 2 });
writeHash.set(this.tsconfigPath, hash);
if (writeHash.get(tsconfigPath) !== hash) {
outputJsonSync(tsconfigPath, tsconfigContent, { spaces: 2 });
writeHash.set(tsconfigPath, hash);
}
}

async getDevServer(context: AppContext, appRootPath: string): Promise<DevServer> {
const devServerContext = this.getDevServerContext(context, appRootPath);
const preview = this.preview(this.envContext);

return preview.getDevServer(devServerContext)(this.envContext);
/**
* Transform the app context into env context to make typescript happy.
* Technically, we only use methods that exist in both interfaces, so it's fine.
*/
private getEnvContext(context: AppContext | AppBuildContext): EnvContext {
return context as any as EnvContext;
}

// TODO: fix return type once bit has a new stable version
async run(context: AppContext): Promise<any> {
assert(this.workspace, 'Workspace is not defined');
async run(context: AppContext): Promise<ApplicationInstance> {
const depsResolver = context.getAspect<DependencyResolverMain>(DependencyResolverAspect.id);
assert(depsResolver, 'Dependency resolver is not defined');
const workspace = getWorkspace(context);
assert(workspace, 'Workspace is not defined');
const logger = context.createLogger(this.name);
const port = context.port || (await Port.getPortFromRange(this.options.portRange || [3000, 4000]));
const appRootPath = this.workspace.componentDir(context.appComponent.id, {
const appRootPath = workspace.componentDir(context.appComponent.id, {
ignoreVersion: true
});
const tsconfigPath = join(appRootPath, this.options.angularServeOptions.tsConfig);
const workspaceCmpsIDs = await this.workspace.listIds();
const bitCmps = await this.workspace.getMany(workspaceCmpsIDs);
this.generateTsConfig(bitCmps, appRootPath, tsconfigPath);
const appTsconfigPath = join(appRootPath, this.options.angularServeOptions.tsConfig);
const workspaceCmpsIDs = await workspace.listIds();
const bitCmps = await workspace.getMany(workspaceCmpsIDs);
const tempFolder = this.getTempFolder(workspace);
const tsconfigPath = this.getTsconfigPath(tempFolder);
this.generateTsConfig(bitCmps, appRootPath, appTsconfigPath, tsconfigPath, depsResolver, workspace);

if (Number(VERSION.major) >= 16) {
await serveApplication({
angularOptions: {
...this.options.angularBuildOptions as ApplicationOptions,
tsConfig: this.tsconfigPath
tsConfig: tsconfigPath
},
sourceRoot: this.options.sourceRoot || 'src',
workspaceRoot: appRootPath,
port,
logger: this.logger,
tempFolder: this.tempFolder
logger: logger,
tempFolder: tempFolder
});
return port;
}

const devServer = await this.getDevServer(context, appRootPath);
await devServer.listen(port);
return port;
}
} else {
const devServerContext = this.getDevServerContext(context, appRootPath);
const envContext = this.getEnvContext(context);
const preview = this.getPreview(tsconfigPath)(envContext);

async getBundler(context: AppBuildContext): Promise<Bundler> {
if (this.options.bundler) {
return this.options.bundler;
const devServer = await preview.getDevServer(devServerContext)(envContext);
await devServer.listen(port);
}

const bundlerContext = this.getBundlerContext(context);
const preview = this.preview(this.envContext);

return preview.getBundler(bundlerContext)(this.envContext);
return {
appName: this.name,
port
};
}

async build(context: AppBuildContext): Promise<AngularAppBuildResult> {
const { capsule } = context;
const depsResolver = context.getAspect<DependencyResolverMain>(DependencyResolverAspect.id);
assert(depsResolver, 'Dependency resolver is not defined');
const logger = context.createLogger(this.name);
const outputPath = this.getPublicDir(context.artifactsDir);
const appRootPath = capsule.path;
const tsconfigPath = join(appRootPath, this.options.angularBuildOptions.tsConfig);
const appTsconfigPath = join(appRootPath, this.options.angularBuildOptions.tsConfig);
const appOptions = this.options.angularBuildOptions as ApplicationOptions;
const entryServer = appOptions.ssr && Number(VERSION.major) >= 17 ? './entry.server.ts' : undefined;
this.generateTsConfig([capsule.component], appRootPath, tsconfigPath, entryServer);
const tempFolder = this.getTempFolder();
const tsconfigPath = this.getTsconfigPath(tempFolder);
this.generateTsConfig([capsule.component], appRootPath, appTsconfigPath, tsconfigPath, depsResolver, undefined, entryServer);

if (!this.options.bundler && Number(VERSION.major) >= 16) {
await buildApplication({
angularOptions: {
...appOptions,
tsConfig: this.tsconfigPath
tsConfig: tsconfigPath
},
outputPath,
sourceRoot: this.options.sourceRoot || 'src',
workspaceRoot: context.capsule.path,
logger: this.logger,
tempFolder: this.tempFolder,
logger: logger,
tempFolder: tempFolder,
entryServer
});
} else {
const bundler = await this.getBundler(context);
let bundler: Bundler;
if (this.options.bundler) {
bundler = this.options.bundler;
} else {
const bundlerContext = this.getBundlerContext(context);
const envContext = this.getEnvContext(context);
const preview = this.getPreview(tsconfigPath)(envContext);

bundler = await preview.getBundler(bundlerContext)(envContext);
}
await bundler.run();
}
return {
publicDir: outputPath
};
}

static from(options: AngularAppOptions): Application {
return new AngularApp(options);
}
}
1 change: 1 addition & 0 deletions angular/app-types/angular-app-type/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ export { AngularAppType } from './angular.app-type';
export type { AngularDeployContext } from './deploy-context';
export type { AngularAppOptions } from './angular-app-options';
export { NG_APP_NAME, NG_APP_PATTERN } from '@bitdev/angular.dev-services.common';
export { AngularApp } from './angular.application';
8 changes: 4 additions & 4 deletions angular/devkit/common/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import normalize from 'normalize-path';
import objectHash from 'object-hash';
import { dirname, join, posix, resolve } from 'path';

export const NG_APP_NAME = 'ng-app';
export const NG_APP_NAME = 'bit-app';
export const NG_APP_PATTERN = `*.${ NG_APP_NAME }.*`;

export enum BundlerSetup {
Expand All @@ -33,7 +33,7 @@ export function componentIsApp(component: Component, application: ApplicationMai
/**
* Returns the workspace instance from the context, if it's available, or undefined otherwise.
*/
export function getWorkspace(context: EnvContext): Workspace | undefined {
export function getWorkspace(context: EnvContext | AppContext): Workspace | undefined {
// TODO: replace this try catch with context.hasAspect once it's available from harmony
try {
return context.getAspect<Workspace>(WorkspaceAspect.id);
Expand Down Expand Up @@ -115,7 +115,7 @@ export function cmpIdToPkgName(componentId: ComponentID) {
return `@${ partsToJoin.join('.') }`;
}

export function isBuildContext(context: DevServerContext | BundlerContext): context is BundlerContext {
export function isBuildContext(context: any): context is BundlerContext {
return (context as BundlerContext).capsuleNetwork !== undefined;
}

Expand Down Expand Up @@ -169,7 +169,7 @@ export function generateTsConfig(
* Generates the tsconfig to load the preview app with compositions dynamically.
*/
export function writeTsconfig(
context: DevServerContext | BundlerContext,
context: DevServerContext | BundlerContext | AppContext | AppBuildContext,
rootPath: string,
tempFolder: string,
application: ApplicationMain,
Expand Down
3 changes: 2 additions & 1 deletion angular/devkit/preview/preview/angular-preview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,8 @@ export class AngularPreview implements Preview {
getDevEnvId() {
const objToHash = {
webpack: this.ngEnvOptions.webpackModulePath,
webpackDevServer: this.ngEnvOptions.webpackDevServerModulePath
webpackDevServer: this.ngEnvOptions.webpackDevServerModulePath,
transformers: this.webpackServeTransformers
};
return objectHash(objToHash);
}
Expand Down
Loading

0 comments on commit 0e5b546

Please sign in to comment.