Skip to content

Commit

Permalink
feat(angular-output-target): generate standalone components (#367)
Browse files Browse the repository at this point in the history
  • Loading branch information
sean-perkins committed Aug 10, 2023
1 parent 74d54f9 commit 481cd05
Show file tree
Hide file tree
Showing 11 changed files with 166 additions and 144 deletions.
5 changes: 4 additions & 1 deletion .github/workflows/dev-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,11 @@ jobs:
- uses: actions/setup-node@e33196f7422957bea03ed53f6fbb155025ffc7b8 # v3.7.0
with:
node-version-file: './package.json'
- name: Install root dependencies
run: npm ci
shell: bash
- name: Install Dependencies
run: npm ci && lerna bootstrap --ignore-scripts -- --legacy-peer-deps
run: npx lerna bootstrap --ignore-scripts -- --legacy-peer-deps
shell: bash
- name: Prepare NPM Token
run: echo //registry.npmjs.org/:_authToken=${NPM_TOKEN} > .npmrc
Expand Down
18 changes: 9 additions & 9 deletions packages/angular-output-target/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,12 @@ export const config: Config = {

## Config Options

| Property | Description |
| ----------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `componentCorePackage` | The NPM package name of your Stencil component library. This package is used as a dependency for your Angular wrappers. |
| `directivesProxyFile` | The output file of all the component wrappers generated by the output target. This file path should point to a location within your Angular library/project. |
| `directivesArrayFile` | The output file of a constant of all the generated component wrapper classes. Used for easily declaring and exporting the generated components from an `NgModule`. This file path should point to a location within your Angular library/project. |
| `valueAccessorConfigs` | The configuration object for how individual web components behave with Angular control value accessors. |
| `excludeComponents` | An array of tag names to exclude from generating component wrappers for. This is helpful when have a custom framework implementation of a specific component or need to extend the base component wrapper behavior. |
| `includeImportCustomElements` | If `true`, the output target will import the custom element instance and register it with the Custom Elements Registry when the component is imported inside of a user's app. This can only be used with the [Custom Elements Bundle](https://stenciljs.com/docs/custom-elements) and will not work with lazy loaded components. |
| `customElementsDir` | This is the directory where the custom elements are imported from when using the [Custom Elements Bundle](https://stenciljs.com/docs/custom-elements). Defaults to the `components` directory. Only applies when `includeImportCustomElements` is `true`. |
| Property | Description |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `componentCorePackage` | The NPM package name of your Stencil component library. This package is used as a dependency for your Angular wrappers. |
| `directivesProxyFile` | The output file of all the component wrappers generated by the output target. This file path should point to a location within your Angular library/project. |
| `directivesArrayFile` | The output file of a constant of all the generated component wrapper classes. Used for easily declaring and exporting the generated components from an `NgModule`. This file path should point to a location within your Angular library/project. |
| `valueAccessorConfigs` | The configuration object for how individual web components behave with Angular control value accessors. |
| `excludeComponents` | An array of tag names to exclude from generating component wrappers for. This is helpful when have a custom framework implementation of a specific component or need to extend the base component wrapper behavior. |
| `outputType` | Specifies the type of output to be generated. It can take one of the following values: <br />1. `component`: Generates all the component wrappers to be declared on an Angular module. This option is required for Stencil projects using the `dist` hydrated output.<br /> 2. `scam`: Generates a separate Angular module for each component.<br /> 3. `standalone`: Generates standalone component wrappers.<br /> Both `scam` and `standalone` options are compatible with the `dist-custom-elements` output. <br />Note: Please choose the appropriate `outputType` based on your project's requirements and the desired output structure. Defaults to `component`. |
| `customElementsDir` | This is the directory where the custom elements are imported from when using the [Custom Elements Bundle](https://stenciljs.com/docs/custom-elements). Defaults to the `components` directory. Only applies for `outputType: "scam"` or `outputType: "standalone"`. |
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,29 @@ export class MyComponent {
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
inputs: [],
})
export class MyComponent {
protected el: HTMLElement;
constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) {
c.detach();
this.el = r.nativeElement;
}
}`);
});

it('generates a standalone component', () => {
const component = createAngularComponentDefinition('my-component', [], [], [], true, true);

expect(component).toEqual(`@ProxyCmp({
defineCustomElementFn: defineMyComponent
})
@Component({
selector: 'my-component',
changeDetection: ChangeDetectionStrategy.OnPush,
template: '<ng-content></ng-content>',
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
inputs: [],
standalone: true
})
export class MyComponent {
protected el: HTMLElement;
constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) {
Expand Down Expand Up @@ -266,7 +289,7 @@ describe('createComponentTypeDefinition()', () => {

describe('www build', () => {
it('creates a type definition', () => {
const definition = createComponentTypeDefinition('MyComponent', testEvents, '@ionic/core', false);
const definition = createComponentTypeDefinition('component', 'MyComponent', testEvents, '@ionic/core');

expect(definition).toEqual(
`import type { MyEvent as IMyComponentMyEvent } from '@ionic/core';
Expand Down Expand Up @@ -296,10 +319,10 @@ export declare interface MyComponent extends Components.MyComponent {
describe('with a custom elements directory provided', () => {
it('creates a type definition', () => {
const definition = createComponentTypeDefinition(
'standalone',
'MyComponent',
testEvents,
'@ionic/core',
true,
'custom-elements'
);

Expand All @@ -309,34 +332,6 @@ import type { MyOtherEvent as IMyComponentMyOtherEvent } from '@ionic/core/custo
import type { MyDoclessEvent as IMyComponentMyDoclessEvent } from '@ionic/core/custom-elements';
import type { MyKebabEvent as IMyComponentMyKebabEvent } from '@ionic/core/custom-elements';
export declare interface MyComponent extends Components.MyComponent {
/**
* This is an example event. @Foo Bar
*/
myEvent: EventEmitter<CustomEvent<IMyComponentMyEvent>>;
/**
* This is the other event.
*/
myOtherEvent: EventEmitter<CustomEvent<IMyComponentMyOtherEvent>>;
myDoclessEvent: EventEmitter<CustomEvent<IMyComponentMyDoclessEvent>>;
'my-kebab-event': EventEmitter<CustomEvent<IMyComponentMyKebabEvent>>;
}`
);
});
});

describe('without a custom elements directory provided', () => {
it('creates a type definition', () => {
const definition = createComponentTypeDefinition('MyComponent', testEvents, '@ionic/core', true);

expect(definition).toEqual(
`import type { MyEvent as IMyComponentMyEvent } from '@ionic/core/components';
import type { MyOtherEvent as IMyComponentMyOtherEvent } from '@ionic/core/components';
import type { MyDoclessEvent as IMyComponentMyDoclessEvent } from '@ionic/core/components';
import type { MyKebabEvent as IMyComponentMyKebabEvent } from '@ionic/core/components';
export declare interface MyComponent extends Components.MyComponent {
/**
* This is an example event. @Foo Bar
Expand Down
46 changes: 7 additions & 39 deletions packages/angular-output-target/__tests__/output-angular.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ describe('generateProxies', () => {
const outputTarget: OutputTargetAngular = {
componentCorePackage: 'component-library',
directivesProxyFile: '../component-library-angular/src/proxies.ts',
outputType: 'component',
};

const finalText = generateProxies(components, pkgData, outputTarget, rootDir);
Expand Down Expand Up @@ -59,7 +60,7 @@ describe('generateProxies', () => {
},
],
},
] as ComponentCompilerMeta[];
] as unknown as ComponentCompilerMeta[];

const finalText = generateProxies(components, pkgData, outputTarget, rootDir);
expect(
Expand Down Expand Up @@ -100,7 +101,7 @@ describe('generateProxies', () => {
},
],
},
] as ComponentCompilerMeta[];
] as unknown as ComponentCompilerMeta[];

const finalText = generateProxies(components, pkgData, outputTarget, rootDir);
expect(
Expand All @@ -111,43 +112,11 @@ describe('generateProxies', () => {
expect(finalText.includes(`import { ProxyCmp } from './angular-component-lib/utils';`)).toBeTruthy();
});

describe('when includeSingleComponentAngularModules is true', () => {
it('should throw an error if includeImportCustomElements is false', () => {
const outputTarget: OutputTargetAngular = {
directivesProxyFile: '../component-library-angular/src/proxies.ts',
includeSingleComponentAngularModules: true,
includeImportCustomElements: false,
} as OutputTargetAngular;

expect(() => {
generateProxies(components, pkgData, outputTarget, rootDir);
}).toThrow(
new Error(
'Generating single component Angular modules requires the "includeImportCustomElements" option to be set to true.'
)
);
});

it('should throw an error if includeImportCustomElements is undefined', () => {
const outputTarget: OutputTargetAngular = {
directivesProxyFile: '../component-library-angular/src/proxies.ts',
includeSingleComponentAngularModules: true,
} as OutputTargetAngular;

expect(() => {
generateProxies(components, pkgData, outputTarget, rootDir);
}).toThrow(
new Error(
'Generating single component Angular modules requires the "includeImportCustomElements" option to be set to true.'
)
);
});

describe('when outputType is scam', () => {
it('should include an Angular module for each component', () => {
const outputTarget: OutputTargetAngular = {
directivesProxyFile: '../component-library-angular/src/proxies.ts',
includeImportCustomElements: true,
includeSingleComponentAngularModules: true,
outputType: 'scam',
componentCorePackage: '@ionic/core',
};

Expand All @@ -166,12 +135,11 @@ describe('generateProxies', () => {
});
});

describe('when includeSingleComponentAngularModules is false', () => {
describe('when outputType is component', () => {
it('should not include an Angular module for each component', () => {
const outputTarget: OutputTargetAngular = {
directivesProxyFile: '../component-library-angular/src/proxies.ts',
includeImportCustomElements: true,
includeSingleComponentAngularModules: false,
outputType: 'component',
componentCorePackage: '@ionic/core',
};

Expand Down
19 changes: 14 additions & 5 deletions packages/angular-output-target/__tests__/plugin.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,19 @@ describe('normalizeOutputTarget', () => {
directivesProxyFile: '/component-library-angular/src/components.ts',
} as OutputTargetAngular);

expect(results).toEqual({
directivesProxyFile: '/component-library-angular/src/components.ts',
excludeComponents: [],
valueAccessorConfigs: [],
});
expect(results.excludeComponents).toEqual([]);
expect(results.valueAccessorConfigs).toEqual([]);
});

it('should return defaults for outputType', () => {
const results = normalizeOutputTarget(config, { directivesProxyFile: '' } as OutputTargetAngular);

expect(results.outputType).toEqual('component');
});

it('should return defaults for customElementsDir', () => {
const results = normalizeOutputTarget(config, { directivesProxyFile: '' } as OutputTargetAngular);

expect(results.customElementsDir).toEqual('components');
});
});
Loading

0 comments on commit 481cd05

Please sign in to comment.