New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

@stencil/angular-output-target

Package Overview
Dependencies
Maintainers
11
Versions
108
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@stencil/angular-output-target - npm Package Compare versions

Comparing version 0.6.1-dev.11657573317.16e0205c to 0.6.1-dev.11662151255.17ea4066

dist/generate-angular-modules.d.ts

24

angular-component-lib/utils.ts

@@ -7,3 +7,3 @@ /* eslint-disable */

const Prototype = Cmp.prototype;
inputs.forEach(item => {
inputs.forEach((item) => {
Object.defineProperty(Prototype, item, {

@@ -15,3 +15,3 @@ get() {

this.z.runOutsideAngular(() => (this.el[item] = val));
}
},
});

@@ -23,8 +23,6 @@ });

const Prototype = Cmp.prototype;
methods.forEach(methodName => {
methods.forEach((methodName) => {
Prototype[methodName] = function () {
const args = arguments;
return this.z.runOutsideAngular(() =>
this.el[methodName].apply(this.el, args)
);
return this.z.runOutsideAngular(() => this.el[methodName].apply(this.el, args));
};

@@ -35,17 +33,13 @@ });

export const proxyOutputs = (instance: any, el: any, events: string[]) => {
events.forEach(eventName => instance[eventName] = fromEvent(el, eventName));
}
events.forEach((eventName) => (instance[eventName] = fromEvent(el, eventName)));
};
export const defineCustomElement = (tagName: string, customElement: any) => {
if (
customElement !== undefined &&
typeof customElements !== 'undefined' &&
!customElements.get(tagName)
) {
if (customElement !== undefined && typeof customElements !== 'undefined' && !customElements.get(tagName)) {
customElements.define(tagName, customElement);
}
}
};
// tslint:disable-next-line: only-arrow-functions
export function ProxyCmp(opts: { defineCustomElementFn?: () => void, inputs?: any; methods?: any }) {
export function ProxyCmp(opts: { defineCustomElementFn?: () => void; inputs?: any; methods?: any }) {
const decorator = function (cls: any) {

@@ -52,0 +46,0 @@ const { defineCustomElementFn, inputs, methods } = opts;

@@ -1,2 +0,22 @@

import type { ComponentCompilerMeta } from '@stencil/core/internal';
export declare const createComponentDefinition: (componentCorePackage: string, distTypesDir: string, rootDir: string, includeImportCustomElements?: boolean, customElementsDir?: string) => (cmpMeta: ComponentCompilerMeta) => string;
import type { ComponentCompilerEvent } from '@stencil/core/internal';
/**
* Creates an Angular component declaration from formatted Stencil compiler metadata.
*
* @param tagName The tag name of the component.
* @param inputs The inputs of the Stencil component (e.g. ['myInput']).
* @param outputs The outputs/events of the Stencil component. (e.g. ['myOutput']).
* @param methods The methods of the Stencil component. (e.g. ['myMethod']).
* @param includeImportCustomElements Whether to define the component as a custom element.
* @returns The component declaration as a string.
*/
export declare const createAngularComponentDefinition: (tagName: string, inputs: readonly string[], outputs: readonly string[], methods: readonly string[], includeImportCustomElements?: boolean) => string;
/**
* Creates the component interface type definition.
* @param tagNameAsPascal The tag name as PascalCase.
* @param events The events to generate the interface properties for.
* @param componentCorePackage The component core package.
* @param includeImportCustomElements Whether to include the import for the custom element definition.
* @param customElementsDir The custom elements directory.
* @returns The component interface type definition as a string.
*/
export declare const createComponentTypeDefinition: (tagNameAsPascal: string, events: readonly ComponentCompilerEvent[], componentCorePackage: string, includeImportCustomElements?: boolean, customElementsDir?: string | undefined) => string;

@@ -1,101 +0,124 @@

import { dashToPascalCase, normalizePath } from './utils';
export const createComponentDefinition = (componentCorePackage, distTypesDir, rootDir, includeImportCustomElements = false, customElementsDir = 'components') => (cmpMeta) => {
// Collect component meta
const inputs = [
...cmpMeta.properties.filter((prop) => !prop.internal).map((prop) => prop.name),
...cmpMeta.virtualProperties.map((prop) => prop.name),
].sort();
const outputs = cmpMeta.events.filter((ev) => !ev.internal).map((prop) => prop);
const methods = cmpMeta.methods.filter((method) => !method.internal).map((prop) => prop.name);
// Process meta
import { createComponentEventTypeImports, dashToPascalCase, formatToQuotedList } from './utils';
/**
* Creates an Angular component declaration from formatted Stencil compiler metadata.
*
* @param tagName The tag name of the component.
* @param inputs The inputs of the Stencil component (e.g. ['myInput']).
* @param outputs The outputs/events of the Stencil component. (e.g. ['myOutput']).
* @param methods The methods of the Stencil component. (e.g. ['myMethod']).
* @param includeImportCustomElements Whether to define the component as a custom element.
* @returns The component declaration as a string.
*/
export const createAngularComponentDefinition = (tagName, inputs, outputs, methods, includeImportCustomElements = false) => {
const tagNameAsPascal = dashToPascalCase(tagName);
const hasInputs = inputs.length > 0;
const hasOutputs = outputs.length > 0;
// Generate Angular @Directive
const directiveOpts = [
`selector: \'${cmpMeta.tagName}\'`,
`changeDetection: ChangeDetectionStrategy.OnPush`,
`template: '<ng-content></ng-content>'`,
];
if (inputs.length > 0) {
directiveOpts.push(`inputs: ['${inputs.join(`', '`)}']`);
const hasMethods = methods.length > 0;
// Formats the input strings into comma separated, single quoted values.
const formattedInputs = formatToQuotedList(inputs);
// Formats the output strings into comma separated, single quoted values.
const formattedOutputs = formatToQuotedList(outputs);
// Formats the method strings into comma separated, single quoted values.
const formattedMethods = formatToQuotedList(methods);
const proxyCmpOptions = [];
if (includeImportCustomElements) {
const defineCustomElementFn = `define${tagNameAsPascal}`;
proxyCmpOptions.push(`\n defineCustomElementFn: ${defineCustomElementFn}`);
}
const tagNameAsPascal = dashToPascalCase(cmpMeta.tagName);
const outputsInterface = new Set();
const outputReferenceRemap = {};
outputs.forEach((output) => {
Object.entries(output.complexType.references).forEach(([reference, refObject]) => {
// Add import line for each local/import reference, and add new mapping name.
// `outputReferenceRemap` should be updated only if the import interface is set in outputsInterface,
// this will prevent global types to be remapped.
const remappedReference = `I${cmpMeta.componentClassName}${reference}`;
if (refObject.location === 'local' || refObject.location === 'import') {
outputReferenceRemap[reference] = remappedReference;
let importLocation = componentCorePackage;
if (componentCorePackage !== undefined) {
const dirPath = includeImportCustomElements ? `/${customElementsDir || 'components'}` : '';
importLocation = `${normalizePath(componentCorePackage)}${dirPath}`;
}
outputsInterface.add(`import type { ${reference} as ${remappedReference} } from '${importLocation}';`);
}
});
});
const componentEvents = [
'' // Empty first line
];
// Generate outputs
outputs.forEach((output, index) => {
componentEvents.push(` /**
* ${output.docs.text} ${output.docs.tags.map((tag) => `@${tag.name} ${tag.text}`)}
*/`);
/**
* The original attribute contains the original type defined by the devs.
* This regexp normalizes the reference, by removing linebreaks,
* replacing consecutive spaces with a single space, and adding a single space after commas.
**/
const outputTypeRemapped = Object.entries(outputReferenceRemap).reduce((type, [src, dst]) => {
return type
.replace(new RegExp(`^${src}$`, 'g'), `${dst}`)
.replace(new RegExp(`([^\\w])${src}([^\\w])`, 'g'), (v, p1, p2) => [p1, dst, p2].join(''));
}, output.complexType.original
.replace(/\n/g, ' ')
.replace(/\s{2,}/g, ' ')
.replace(/,\s*/g, ', '));
componentEvents.push(` ${output.name}: EventEmitter<CustomEvent<${outputTypeRemapped.trim()}>>;`);
if (index === outputs.length - 1) {
// Empty line to push end `}` to new line
componentEvents.push('\n');
}
});
const lines = [
'',
`${[...outputsInterface].join('\n')}
export declare interface ${tagNameAsPascal} extends Components.${tagNameAsPascal} {${componentEvents.length > 1 ? componentEvents.join('\n') : ''}}
${getProxyCmp(cmpMeta.tagName, includeImportCustomElements, inputs, methods)}
if (hasInputs) {
proxyCmpOptions.push(`\n inputs: [${formattedInputs}]`);
}
if (hasMethods) {
proxyCmpOptions.push(`\n methods: [${formattedMethods}]`);
}
/**
* Notes on the generated output:
* - We disable @angular-eslint/no-inputs-metadata-property, so that
* Angular does not complain about the inputs property. The output target
* uses the inputs property to define the inputs of the component instead of
* having to use the @Input decorator (and manually define the type and default value).
*/
const output = `@ProxyCmp({${proxyCmpOptions.join(',')}\n})
@Component({
${directiveOpts.join(',\n ')}
selector: '${tagName}',
template: '<ng-content></ng-content>',
changeDetection: ChangeDetectionStrategy.OnPush,
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
inputs: [${formattedInputs}],
})
export class ${tagNameAsPascal} {`,
];
lines.push(' protected el: HTMLElement;');
lines.push(` constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) {
export class ${tagNameAsPascal} {
protected el: HTMLElement;
constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) {
c.detach();
this.el = r.nativeElement;`);
if (hasOutputs) {
lines.push(` proxyOutputs(this, this.el, ['${outputs.map((output) => output.name).join(`', '`)}']);`);
this.el = r.nativeElement;${hasOutputs
? `
proxyOutputs(this, this.el, [${formattedOutputs}]);`
: ''}
}
}`;
return output;
};
/**
* Sanitizes and formats the component event type.
* @param componentClassName The class name of the component (e.g. 'MyComponent')
* @param event The Stencil component event.
* @returns The sanitized event type as a string.
*/
const formatOutputType = (componentClassName, event) => {
/**
* The original attribute contains the original type defined by the devs.
* This regexp normalizes the reference, by removing linebreaks,
* replacing consecutive spaces with a single space, and adding a single space after commas.
*/
return Object.entries(event.complexType.references)
.filter(([_, refObject]) => refObject.location === 'local' || refObject.location === 'import')
.reduce((type, [src, dst]) => {
const renamedType = `I${componentClassName}${type}`;
return renamedType
.replace(new RegExp(`^${src}$`, 'g'), `${dst}`)
.replace(new RegExp(`([^\\w])${src}([^\\w])`, 'g'), (v, p1, p2) => [p1, dst, p2].join(''));
}, event.complexType.original
.replace(/\n/g, ' ')
.replace(/\s{2,}/g, ' ')
.replace(/,\s*/g, ', '));
};
/**
* Creates a formatted comment block based on the JS doc comment.
* @param doc The compiler jsdoc.
* @returns The formatted comment block as a string.
*/
const createDocComment = (doc) => {
if (doc.text.trim().length === 0 && doc.tags.length === 0) {
return '';
}
lines.push(` }`);
lines.push(`}`);
return lines.join('\n');
return `/**
* ${doc.text}${doc.tags.length > 0 ? ' ' : ''}${doc.tags.map((tag) => `@${tag.name} ${tag.text}`)}
*/`;
};
function getProxyCmp(tagName, includeCustomElement, inputs, methods) {
const hasInputs = inputs.length > 0;
const hasMethods = methods.length > 0;
const proxMeta = [
`defineCustomElementFn: ${includeCustomElement ? 'define' + dashToPascalCase(tagName) : 'undefined'}`
];
if (hasInputs)
proxMeta.push(`inputs: ['${inputs.join(`', '`)}']`);
if (hasMethods)
proxMeta.push(`methods: ['${methods.join(`', '`)}']`);
return `@ProxyCmp({\n ${proxMeta.join(',\n ')}\n})`;
}
/**
* Creates the component interface type definition.
* @param tagNameAsPascal The tag name as PascalCase.
* @param events The events to generate the interface properties for.
* @param componentCorePackage The component core package.
* @param includeImportCustomElements Whether to include the import for the custom element definition.
* @param customElementsDir The custom elements directory.
* @returns The component interface type definition as a string.
*/
export const createComponentTypeDefinition = (tagNameAsPascal, events, componentCorePackage, includeImportCustomElements = false, customElementsDir) => {
const typeDefinition = `${createComponentEventTypeImports(tagNameAsPascal, events, {
componentCorePackage,
includeImportCustomElements,
customElementsDir,
})}
export interface ${tagNameAsPascal} extends Components.${tagNameAsPascal} {
${events
.map((event) => {
const comment = createDocComment(event.docs);
return `${comment.length > 0 ? ` ${comment}` : ''}
${event.name}: EventEmitter<CustomEvent<${formatOutputType(tagNameAsPascal, event)}>>;`;
})
.join('\n')}
}`;
return typeDefinition;
};

@@ -7,3 +7,3 @@ import { dashToPascalCase, relativeImport } from './utils';

}
const proxyPath = relativeImport(outputTarget.directivesArrayFile, outputTarget.directivesProxyFile, '.ts');
const proxyPath = relativeImport(outputTarget.directivesArrayFile, outputTarget.proxyDeclarationFile, '.ts');
const directives = components

@@ -10,0 +10,0 @@ .map((cmpMeta) => dashToPascalCase(cmpMeta.tagName))

import { EOL } from 'os';
import path from 'path';
export default async function generateValueAccessors(compilerCtx, components, outputTarget, config) {
if (!Array.isArray(outputTarget.valueAccessorConfigs) ||
outputTarget.valueAccessorConfigs.length === 0) {
if (!Array.isArray(outputTarget.valueAccessorConfigs) || outputTarget.valueAccessorConfigs.length === 0) {
return;
}
const targetDir = path.dirname(outputTarget.directivesProxyFile);
const targetDir = path.dirname(outputTarget.proxyDeclarationFile);
const normalizedValueAccessors = outputTarget.valueAccessorConfigs.reduce((allAccessors, va) => {
const elementSelectors = Array.isArray(va.elementSelectors)
? va.elementSelectors
: [va.elementSelectors];
const elementSelectors = Array.isArray(va.elementSelectors) ? va.elementSelectors : [va.elementSelectors];
const type = va.type;

@@ -14,0 +11,0 @@ let allElementSelectors = [];

@@ -84,107 +84,175 @@ 'use strict';

}
const EXTENDED_PATH_REGEX = /^\\\\\?\\/;
const NON_ASCII_REGEX = /[^\x00-\x80]+/;
const SLASH_REGEX = /\\/g;
const createComponentDefinition = (componentCorePackage, distTypesDir, rootDir, includeImportCustomElements = false, customElementsDir = 'components') => (cmpMeta) => {
// Collect component meta
const inputs = [
...cmpMeta.properties.filter((prop) => !prop.internal).map((prop) => prop.name),
...cmpMeta.virtualProperties.map((prop) => prop.name),
].sort();
const outputs = cmpMeta.events.filter((ev) => !ev.internal).map((prop) => prop);
const methods = cmpMeta.methods.filter((method) => !method.internal).map((prop) => prop.name);
// Process meta
const hasOutputs = outputs.length > 0;
// Generate Angular @Directive
const directiveOpts = [
`selector: \'${cmpMeta.tagName}\'`,
`changeDetection: ChangeDetectionStrategy.OnPush`,
`template: '<ng-content></ng-content>'`,
];
if (inputs.length > 0) {
directiveOpts.push(`inputs: ['${inputs.join(`', '`)}']`);
/**
* Formats an array of strings to a string of quoted, comma separated values.
* @param list The list of unformatted strings to format
* @returns The formatted array of strings. (e.g. ['foo', 'bar']) => `'foo', 'bar'`
*/
const formatToQuotedList = (list) => list.map((item) => `'${item}'`).join(', ');
/**
* Creates an import statement for a list of named imports from a module.
* @param imports The list of named imports.
* @param module The module to import from.
*
* @returns The import statement as a string.
*/
const createImportStatement = (imports, module) => {
if (imports.length === 0) {
return '';
}
const tagNameAsPascal = dashToPascalCase(cmpMeta.tagName);
const outputsInterface = new Set();
const outputReferenceRemap = {};
outputs.forEach((output) => {
Object.entries(output.complexType.references).forEach(([reference, refObject]) => {
// Add import line for each local/import reference, and add new mapping name.
// `outputReferenceRemap` should be updated only if the import interface is set in outputsInterface,
// this will prevent global types to be remapped.
const remappedReference = `I${cmpMeta.componentClassName}${reference}`;
return `import { ${imports.join(', ')} } from '${module}';`;
};
/**
* Creates the collection of import statements for a component based on the component's events type dependencies.
* @param componentTagName The tag name of the component (pascal case).
* @param events The events compiler metadata.
* @param options The options for generating the import statements (e.g. whether to import from the custom elements directory).
* @returns The import statements as an array of strings.
*/
const createComponentEventTypeImports = (componentTagName, events, options) => {
const { componentCorePackage, includeImportCustomElements, customElementsDir } = options;
const imports = [];
const namedImports = new Set();
const importPathName = normalizePath(componentCorePackage) + (includeImportCustomElements ? `/${customElementsDir || 'components'}` : '');
events.forEach((event) => {
Object.entries(event.complexType.references).forEach(([typeName, refObject]) => {
if (refObject.location === 'local' || refObject.location === 'import') {
outputReferenceRemap[reference] = remappedReference;
let importLocation = componentCorePackage;
if (componentCorePackage !== undefined) {
const dirPath = includeImportCustomElements ? `/${customElementsDir || 'components'}` : '';
importLocation = `${normalizePath(componentCorePackage)}${dirPath}`;
const newTypeName = `I${componentTagName}${typeName}`;
// Prevents duplicate imports for the same type.
if (!namedImports.has(newTypeName)) {
imports.push(`import type { ${typeName} as ${newTypeName} } from '${importPathName}';`);
namedImports.add(newTypeName);
}
outputsInterface.add(`import type { ${reference} as ${remappedReference} } from '${importLocation}';`);
}
});
});
const componentEvents = [
'' // Empty first line
];
// Generate outputs
outputs.forEach((output, index) => {
componentEvents.push(` /**
* ${output.docs.text} ${output.docs.tags.map((tag) => `@${tag.name} ${tag.text}`)}
*/`);
/**
* The original attribute contains the original type defined by the devs.
* This regexp normalizes the reference, by removing linebreaks,
* replacing consecutive spaces with a single space, and adding a single space after commas.
**/
const outputTypeRemapped = Object.entries(outputReferenceRemap).reduce((type, [src, dst]) => {
return type
.replace(new RegExp(`^${src}$`, 'g'), `${dst}`)
.replace(new RegExp(`([^\\w])${src}([^\\w])`, 'g'), (v, p1, p2) => [p1, dst, p2].join(''));
}, output.complexType.original
.replace(/\n/g, ' ')
.replace(/\s{2,}/g, ' ')
.replace(/,\s*/g, ', '));
componentEvents.push(` ${output.name}: EventEmitter<CustomEvent<${outputTypeRemapped.trim()}>>;`);
if (index === outputs.length - 1) {
// Empty line to push end `}` to new line
componentEvents.push('\n');
}
});
const lines = [
'',
`${[...outputsInterface].join('\n')}
export declare interface ${tagNameAsPascal} extends Components.${tagNameAsPascal} {${componentEvents.length > 1 ? componentEvents.join('\n') : ''}}
return imports.join('\n');
};
const EXTENDED_PATH_REGEX = /^\\\\\?\\/;
const NON_ASCII_REGEX = /[^\x00-\x80]+/;
const SLASH_REGEX = /\\/g;
${getProxyCmp(cmpMeta.tagName, includeImportCustomElements, inputs, methods)}
/**
* Creates an Angular component declaration from formatted Stencil compiler metadata.
*
* @param tagName The tag name of the component.
* @param inputs The inputs of the Stencil component (e.g. ['myInput']).
* @param outputs The outputs/events of the Stencil component. (e.g. ['myOutput']).
* @param methods The methods of the Stencil component. (e.g. ['myMethod']).
* @param includeImportCustomElements Whether to define the component as a custom element.
* @returns The component declaration as a string.
*/
const createAngularComponentDefinition = (tagName, inputs, outputs, methods, includeImportCustomElements = false) => {
const tagNameAsPascal = dashToPascalCase(tagName);
const hasInputs = inputs.length > 0;
const hasOutputs = outputs.length > 0;
const hasMethods = methods.length > 0;
// Formats the input strings into comma separated, single quoted values.
const formattedInputs = formatToQuotedList(inputs);
// Formats the output strings into comma separated, single quoted values.
const formattedOutputs = formatToQuotedList(outputs);
// Formats the method strings into comma separated, single quoted values.
const formattedMethods = formatToQuotedList(methods);
const proxyCmpOptions = [];
if (includeImportCustomElements) {
const defineCustomElementFn = `define${tagNameAsPascal}`;
proxyCmpOptions.push(`\n defineCustomElementFn: ${defineCustomElementFn}`);
}
if (hasInputs) {
proxyCmpOptions.push(`\n inputs: [${formattedInputs}]`);
}
if (hasMethods) {
proxyCmpOptions.push(`\n methods: [${formattedMethods}]`);
}
/**
* Notes on the generated output:
* - We disable @angular-eslint/no-inputs-metadata-property, so that
* Angular does not complain about the inputs property. The output target
* uses the inputs property to define the inputs of the component instead of
* having to use the @Input decorator (and manually define the type and default value).
*/
const output = `@ProxyCmp({${proxyCmpOptions.join(',')}\n})
@Component({
${directiveOpts.join(',\n ')}
selector: '${tagName}',
template: '<ng-content></ng-content>',
changeDetection: ChangeDetectionStrategy.OnPush,
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
inputs: [${formattedInputs}],
})
export class ${tagNameAsPascal} {`,
];
lines.push(' protected el: HTMLElement;');
lines.push(` constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) {
export class ${tagNameAsPascal} {
protected el: HTMLElement;
constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) {
c.detach();
this.el = r.nativeElement;`);
if (hasOutputs) {
lines.push(` proxyOutputs(this, this.el, ['${outputs.map((output) => output.name).join(`', '`)}']);`);
this.el = r.nativeElement;${hasOutputs
? `
proxyOutputs(this, this.el, [${formattedOutputs}]);`
: ''}
}
}`;
return output;
};
/**
* Sanitizes and formats the component event type.
* @param componentClassName The class name of the component (e.g. 'MyComponent')
* @param event The Stencil component event.
* @returns The sanitized event type as a string.
*/
const formatOutputType = (componentClassName, event) => {
/**
* The original attribute contains the original type defined by the devs.
* This regexp normalizes the reference, by removing linebreaks,
* replacing consecutive spaces with a single space, and adding a single space after commas.
*/
return Object.entries(event.complexType.references)
.filter(([_, refObject]) => refObject.location === 'local' || refObject.location === 'import')
.reduce((type, [src, dst]) => {
const renamedType = `I${componentClassName}${type}`;
return renamedType
.replace(new RegExp(`^${src}$`, 'g'), `${dst}`)
.replace(new RegExp(`([^\\w])${src}([^\\w])`, 'g'), (v, p1, p2) => [p1, dst, p2].join(''));
}, event.complexType.original
.replace(/\n/g, ' ')
.replace(/\s{2,}/g, ' ')
.replace(/,\s*/g, ', '));
};
/**
* Creates a formatted comment block based on the JS doc comment.
* @param doc The compiler jsdoc.
* @returns The formatted comment block as a string.
*/
const createDocComment = (doc) => {
if (doc.text.trim().length === 0 && doc.tags.length === 0) {
return '';
}
lines.push(` }`);
lines.push(`}`);
return lines.join('\n');
return `/**
* ${doc.text}${doc.tags.length > 0 ? ' ' : ''}${doc.tags.map((tag) => `@${tag.name} ${tag.text}`)}
*/`;
};
function getProxyCmp(tagName, includeCustomElement, inputs, methods) {
const hasInputs = inputs.length > 0;
const hasMethods = methods.length > 0;
const proxMeta = [
`defineCustomElementFn: ${includeCustomElement ? 'define' + dashToPascalCase(tagName) : 'undefined'}`
];
if (hasInputs)
proxMeta.push(`inputs: ['${inputs.join(`', '`)}']`);
if (hasMethods)
proxMeta.push(`methods: ['${methods.join(`', '`)}']`);
return `@ProxyCmp({\n ${proxMeta.join(',\n ')}\n})`;
}
/**
* Creates the component interface type definition.
* @param tagNameAsPascal The tag name as PascalCase.
* @param events The events to generate the interface properties for.
* @param componentCorePackage The component core package.
* @param includeImportCustomElements Whether to include the import for the custom element definition.
* @param customElementsDir The custom elements directory.
* @returns The component interface type definition as a string.
*/
const createComponentTypeDefinition = (tagNameAsPascal, events, componentCorePackage, includeImportCustomElements = false, customElementsDir) => {
const typeDefinition = `${createComponentEventTypeImports(tagNameAsPascal, events, {
componentCorePackage,
includeImportCustomElements,
customElementsDir,
})}
export interface ${tagNameAsPascal} extends Components.${tagNameAsPascal} {
${events
.map((event) => {
const comment = createDocComment(event.docs);
return `${comment.length > 0 ? ` ${comment}` : ''}
${event.name}: EventEmitter<CustomEvent<${formatOutputType(tagNameAsPascal, event)}>>;`;
})
.join('\n')}
}`;
return typeDefinition;
};
function generateAngularDirectivesFile(compilerCtx, components, outputTarget) {

@@ -195,3 +263,3 @@ // Only create the file if it is defined in the stencil configuration

}
const proxyPath = relativeImport(outputTarget.directivesArrayFile, outputTarget.directivesProxyFile, '.ts');
const proxyPath = relativeImport(outputTarget.directivesArrayFile, outputTarget.proxyDeclarationFile, '.ts');
const directives = components

@@ -212,11 +280,8 @@ .map((cmpMeta) => dashToPascalCase(cmpMeta.tagName))

async function generateValueAccessors(compilerCtx, components, outputTarget, config) {
if (!Array.isArray(outputTarget.valueAccessorConfigs) ||
outputTarget.valueAccessorConfigs.length === 0) {
if (!Array.isArray(outputTarget.valueAccessorConfigs) || outputTarget.valueAccessorConfigs.length === 0) {
return;
}
const targetDir = path__default['default'].dirname(outputTarget.directivesProxyFile);
const targetDir = path__default['default'].dirname(outputTarget.proxyDeclarationFile);
const normalizedValueAccessors = outputTarget.valueAccessorConfigs.reduce((allAccessors, va) => {
const elementSelectors = Array.isArray(va.elementSelectors)
? va.elementSelectors
: [va.elementSelectors];
const elementSelectors = Array.isArray(va.elementSelectors) ? va.elementSelectors : [va.elementSelectors];
const type = va.type;

@@ -270,2 +335,19 @@ let allElementSelectors = [];

/**
* Creates an Angular module declaration for a component wrapper.
* @param componentTagName The tag name of the Stencil component.
* @returns The Angular module declaration as a string.
*/
const generateAngularModuleForComponent = (componentTagName) => {
const tagNameAsPascal = dashToPascalCase(componentTagName);
const componentClassName = `${tagNameAsPascal}`;
const moduleClassName = `${tagNameAsPascal}Module`;
const moduleDefinition = `@NgModule({
declarations: [${componentClassName}],
exports: [${componentClassName}]
})
export class ${moduleClassName} { }`;
return moduleDefinition;
};
async function angularDirectiveProxyOutput(compilerCtx, outputTarget, components, config) {

@@ -277,3 +359,3 @@ const filteredComponents = getFilteredComponents(outputTarget.excludeComponents, components);

await Promise.all([
compilerCtx.fs.writeFile(outputTarget.directivesProxyFile, finalText),
compilerCtx.fs.writeFile(outputTarget.proxyDeclarationFile, finalText),
copyResources$1(config, outputTarget),

@@ -292,3 +374,3 @@ generateAngularDirectivesFile(compilerCtx, filteredComponents, outputTarget),

const srcDirectory = path__default['default'].join(__dirname, '..', 'angular-component-lib');
const destDirectory = path__default['default'].join(path__default['default'].dirname(outputTarget.directivesProxyFile), 'angular-component-lib');
const destDirectory = path__default['default'].join(path__default['default'].dirname(outputTarget.proxyDeclarationFile), 'angular-component-lib');
return config.sys.copy([

@@ -304,9 +386,30 @@ {

function generateProxies(components, pkgData, outputTarget, rootDir) {
var _a;
const distTypesDir = path__default['default'].dirname(pkgData.types);
const dtsFilePath = path__default['default'].join(rootDir, distTypesDir, GENERATED_DTS);
const componentsTypeFile = relativeImport(outputTarget.directivesProxyFile, dtsFilePath, '.d.ts');
const componentsTypeFile = relativeImport(outputTarget.proxyDeclarationFile, dtsFilePath, '.d.ts');
const createSingleComponentAngularModules = (_a = outputTarget.createSingleComponentAngularModules) !== null && _a !== void 0 ? _a : false;
/**
* The collection of named imports from @angular/core.
*/
const angularCoreImports = [
'ChangeDetectionStrategy',
'ChangeDetectorRef',
'Component',
'ElementRef',
'EventEmitter',
'NgZone',
];
/**
* The collection of named imports from the angular-component-lib/utils.
*/
const componentLibImports = ['ProxyCmp', 'proxyOutputs'];
if (createSingleComponentAngularModules) {
angularCoreImports.push('NgModule');
}
const imports = `/* tslint:disable */
/* auto-generated angular directive proxies */
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, EventEmitter, NgZone } from '@angular/core';
import { ProxyCmp, proxyOutputs } from './angular-component-lib/utils';\n`;
${createImportStatement(angularCoreImports, '@angular/core')}
${createImportStatement(componentLibImports, './angular-component-lib/utils')}\n`;
/**

@@ -319,4 +422,8 @@ * Generate JSX import type from correct location.

const generateTypeImports = () => {
let importLocation = outputTarget.componentCorePackage ? normalizePath(outputTarget.componentCorePackage) : normalizePath(componentsTypeFile);
importLocation += outputTarget.includeImportCustomElements ? `/${outputTarget.customElementsDir || 'components'}` : '';
let importLocation = outputTarget.componentCorePackage
? normalizePath(outputTarget.componentCorePackage)
: normalizePath(componentsTypeFile);
importLocation += outputTarget.includeImportCustomElements
? `/${outputTarget.customElementsDir || 'components'}`
: '';
return `import ${outputTarget.includeImportCustomElements ? 'type ' : ''}{ ${IMPORT_TYPES} } from '${importLocation}';\n`;

@@ -333,17 +440,48 @@ };

if (outputTarget.includeImportCustomElements && outputTarget.componentCorePackage !== undefined) {
const cmpImports = components.map(component => {
const cmpImports = components.map((component) => {
const pascalImport = dashToPascalCase(component.tagName);
return `import { defineCustomElement as define${pascalImport} } from '${normalizePath(outputTarget.componentCorePackage)}/${outputTarget.customElementsDir ||
'components'}/${component.tagName}.js';`;
return `import { defineCustomElement as define${pascalImport} } from '${normalizePath(outputTarget.componentCorePackage)}/${outputTarget.customElementsDir || 'components'}/${component.tagName}.js';`;
});
sourceImports = cmpImports.join('\n');
}
const final = [
imports,
typeImports,
sourceImports,
components
.map(createComponentDefinition(outputTarget.componentCorePackage, distTypesDir, rootDir, outputTarget.includeImportCustomElements, outputTarget.customElementsDir))
.join('\n'),
];
if (createSingleComponentAngularModules) {
// Generating Angular modules is only supported in the dist-custom-elements build
if (!outputTarget.includeImportCustomElements) {
throw new Error('Generating single component Angular modules requires the "includeImportCustomElements" option to be set to true.');
}
}
const proxyFileOutput = [];
const filterInternalProps = (prop) => !prop.internal;
const mapPropName = (prop) => prop.name;
const { includeImportCustomElements, componentCorePackage, customElementsDir } = outputTarget;
for (let cmpMeta of components) {
const tagNameAsPascal = dashToPascalCase(cmpMeta.tagName);
const inputs = [];
if (cmpMeta.properties) {
inputs.push(...cmpMeta.properties.filter(filterInternalProps).map(mapPropName));
}
if (cmpMeta.virtualProperties) {
inputs.push(...cmpMeta.virtualProperties.map(mapPropName));
}
inputs.sort();
const outputs = [];
if (cmpMeta.events) {
outputs.push(...cmpMeta.events.filter(filterInternalProps).map(mapPropName));
}
const methods = [];
if (cmpMeta.methods) {
methods.push(...cmpMeta.methods.filter(filterInternalProps).map(mapPropName));
}
/**
* For each component, we need to generate:
* 1. The @Component decorated class
* 2. Optionally the @NgModule decorated class (if createSingleComponentAngularModules is true)
* 3. The component interface (using declaration merging for types).
*/
const componentDefinition = createAngularComponentDefinition(cmpMeta.tagName, inputs, outputs, methods, includeImportCustomElements);
const moduleDefinition = generateAngularModuleForComponent(cmpMeta.tagName);
const componentTypeDefinition = createComponentTypeDefinition(tagNameAsPascal, cmpMeta.events, componentCorePackage, includeImportCustomElements, customElementsDir);
proxyFileOutput.push(componentDefinition, '\n', createSingleComponentAngularModules ? moduleDefinition : '', '\n', componentTypeDefinition, '\n');
}
const final = [imports, typeImports, sourceImports, ...proxyFileOutput];
return final.join('\n') + '\n';

@@ -367,12 +505,15 @@ }

function normalizeOutputTarget(config, outputTarget) {
const results = Object.assign(Object.assign({}, outputTarget), { excludeComponents: outputTarget.excludeComponents || [], valueAccessorConfig: outputTarget.valueAccessorConfig || [] });
const results = Object.assign(Object.assign({}, outputTarget), { excludeComponents: outputTarget.excludeComponents || [], valueAccessorConfigs: outputTarget.valueAccessorConfigs || [] });
if (config.rootDir == null) {
throw new Error('rootDir is not set and it should be set by stencil itself');
}
if (outputTarget.directivesProxyFile == null) {
throw new Error('directivesProxyFile is required');
if (outputTarget.directivesProxyFile !== undefined) {
throw new Error('directivesProxyFile has been removed. Use proxyDeclarationFile instead.');
}
if (outputTarget.directivesProxyFile && !path__default['default'].isAbsolute(outputTarget.directivesProxyFile)) {
results.directivesProxyFile = normalizePath(path__default['default'].join(config.rootDir, outputTarget.directivesProxyFile));
if (outputTarget.proxyDeclarationFile == null) {
throw new Error('proxyDeclarationFile is required. Please set it in the Stencil config.');
}
if (outputTarget.proxyDeclarationFile && !path__default['default'].isAbsolute(outputTarget.proxyDeclarationFile)) {
results.proxyDeclarationFile = normalizePath(path__default['default'].join(config.rootDir, outputTarget.proxyDeclarationFile));
}
if (outputTarget.directivesArrayFile && !path__default['default'].isAbsolute(outputTarget.directivesArrayFile)) {

@@ -379,0 +520,0 @@ results.directivesArrayFile = normalizePath(path__default['default'].join(config.rootDir, outputTarget.directivesArrayFile));

@@ -76,107 +76,175 @@ import path from 'path';

}
const EXTENDED_PATH_REGEX = /^\\\\\?\\/;
const NON_ASCII_REGEX = /[^\x00-\x80]+/;
const SLASH_REGEX = /\\/g;
const createComponentDefinition = (componentCorePackage, distTypesDir, rootDir, includeImportCustomElements = false, customElementsDir = 'components') => (cmpMeta) => {
// Collect component meta
const inputs = [
...cmpMeta.properties.filter((prop) => !prop.internal).map((prop) => prop.name),
...cmpMeta.virtualProperties.map((prop) => prop.name),
].sort();
const outputs = cmpMeta.events.filter((ev) => !ev.internal).map((prop) => prop);
const methods = cmpMeta.methods.filter((method) => !method.internal).map((prop) => prop.name);
// Process meta
const hasOutputs = outputs.length > 0;
// Generate Angular @Directive
const directiveOpts = [
`selector: \'${cmpMeta.tagName}\'`,
`changeDetection: ChangeDetectionStrategy.OnPush`,
`template: '<ng-content></ng-content>'`,
];
if (inputs.length > 0) {
directiveOpts.push(`inputs: ['${inputs.join(`', '`)}']`);
/**
* Formats an array of strings to a string of quoted, comma separated values.
* @param list The list of unformatted strings to format
* @returns The formatted array of strings. (e.g. ['foo', 'bar']) => `'foo', 'bar'`
*/
const formatToQuotedList = (list) => list.map((item) => `'${item}'`).join(', ');
/**
* Creates an import statement for a list of named imports from a module.
* @param imports The list of named imports.
* @param module The module to import from.
*
* @returns The import statement as a string.
*/
const createImportStatement = (imports, module) => {
if (imports.length === 0) {
return '';
}
const tagNameAsPascal = dashToPascalCase(cmpMeta.tagName);
const outputsInterface = new Set();
const outputReferenceRemap = {};
outputs.forEach((output) => {
Object.entries(output.complexType.references).forEach(([reference, refObject]) => {
// Add import line for each local/import reference, and add new mapping name.
// `outputReferenceRemap` should be updated only if the import interface is set in outputsInterface,
// this will prevent global types to be remapped.
const remappedReference = `I${cmpMeta.componentClassName}${reference}`;
return `import { ${imports.join(', ')} } from '${module}';`;
};
/**
* Creates the collection of import statements for a component based on the component's events type dependencies.
* @param componentTagName The tag name of the component (pascal case).
* @param events The events compiler metadata.
* @param options The options for generating the import statements (e.g. whether to import from the custom elements directory).
* @returns The import statements as an array of strings.
*/
const createComponentEventTypeImports = (componentTagName, events, options) => {
const { componentCorePackage, includeImportCustomElements, customElementsDir } = options;
const imports = [];
const namedImports = new Set();
const importPathName = normalizePath(componentCorePackage) + (includeImportCustomElements ? `/${customElementsDir || 'components'}` : '');
events.forEach((event) => {
Object.entries(event.complexType.references).forEach(([typeName, refObject]) => {
if (refObject.location === 'local' || refObject.location === 'import') {
outputReferenceRemap[reference] = remappedReference;
let importLocation = componentCorePackage;
if (componentCorePackage !== undefined) {
const dirPath = includeImportCustomElements ? `/${customElementsDir || 'components'}` : '';
importLocation = `${normalizePath(componentCorePackage)}${dirPath}`;
const newTypeName = `I${componentTagName}${typeName}`;
// Prevents duplicate imports for the same type.
if (!namedImports.has(newTypeName)) {
imports.push(`import type { ${typeName} as ${newTypeName} } from '${importPathName}';`);
namedImports.add(newTypeName);
}
outputsInterface.add(`import type { ${reference} as ${remappedReference} } from '${importLocation}';`);
}
});
});
const componentEvents = [
'' // Empty first line
];
// Generate outputs
outputs.forEach((output, index) => {
componentEvents.push(` /**
* ${output.docs.text} ${output.docs.tags.map((tag) => `@${tag.name} ${tag.text}`)}
*/`);
/**
* The original attribute contains the original type defined by the devs.
* This regexp normalizes the reference, by removing linebreaks,
* replacing consecutive spaces with a single space, and adding a single space after commas.
**/
const outputTypeRemapped = Object.entries(outputReferenceRemap).reduce((type, [src, dst]) => {
return type
.replace(new RegExp(`^${src}$`, 'g'), `${dst}`)
.replace(new RegExp(`([^\\w])${src}([^\\w])`, 'g'), (v, p1, p2) => [p1, dst, p2].join(''));
}, output.complexType.original
.replace(/\n/g, ' ')
.replace(/\s{2,}/g, ' ')
.replace(/,\s*/g, ', '));
componentEvents.push(` ${output.name}: EventEmitter<CustomEvent<${outputTypeRemapped.trim()}>>;`);
if (index === outputs.length - 1) {
// Empty line to push end `}` to new line
componentEvents.push('\n');
}
});
const lines = [
'',
`${[...outputsInterface].join('\n')}
export declare interface ${tagNameAsPascal} extends Components.${tagNameAsPascal} {${componentEvents.length > 1 ? componentEvents.join('\n') : ''}}
return imports.join('\n');
};
const EXTENDED_PATH_REGEX = /^\\\\\?\\/;
const NON_ASCII_REGEX = /[^\x00-\x80]+/;
const SLASH_REGEX = /\\/g;
${getProxyCmp(cmpMeta.tagName, includeImportCustomElements, inputs, methods)}
/**
* Creates an Angular component declaration from formatted Stencil compiler metadata.
*
* @param tagName The tag name of the component.
* @param inputs The inputs of the Stencil component (e.g. ['myInput']).
* @param outputs The outputs/events of the Stencil component. (e.g. ['myOutput']).
* @param methods The methods of the Stencil component. (e.g. ['myMethod']).
* @param includeImportCustomElements Whether to define the component as a custom element.
* @returns The component declaration as a string.
*/
const createAngularComponentDefinition = (tagName, inputs, outputs, methods, includeImportCustomElements = false) => {
const tagNameAsPascal = dashToPascalCase(tagName);
const hasInputs = inputs.length > 0;
const hasOutputs = outputs.length > 0;
const hasMethods = methods.length > 0;
// Formats the input strings into comma separated, single quoted values.
const formattedInputs = formatToQuotedList(inputs);
// Formats the output strings into comma separated, single quoted values.
const formattedOutputs = formatToQuotedList(outputs);
// Formats the method strings into comma separated, single quoted values.
const formattedMethods = formatToQuotedList(methods);
const proxyCmpOptions = [];
if (includeImportCustomElements) {
const defineCustomElementFn = `define${tagNameAsPascal}`;
proxyCmpOptions.push(`\n defineCustomElementFn: ${defineCustomElementFn}`);
}
if (hasInputs) {
proxyCmpOptions.push(`\n inputs: [${formattedInputs}]`);
}
if (hasMethods) {
proxyCmpOptions.push(`\n methods: [${formattedMethods}]`);
}
/**
* Notes on the generated output:
* - We disable @angular-eslint/no-inputs-metadata-property, so that
* Angular does not complain about the inputs property. The output target
* uses the inputs property to define the inputs of the component instead of
* having to use the @Input decorator (and manually define the type and default value).
*/
const output = `@ProxyCmp({${proxyCmpOptions.join(',')}\n})
@Component({
${directiveOpts.join(',\n ')}
selector: '${tagName}',
template: '<ng-content></ng-content>',
changeDetection: ChangeDetectionStrategy.OnPush,
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
inputs: [${formattedInputs}],
})
export class ${tagNameAsPascal} {`,
];
lines.push(' protected el: HTMLElement;');
lines.push(` constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) {
export class ${tagNameAsPascal} {
protected el: HTMLElement;
constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) {
c.detach();
this.el = r.nativeElement;`);
if (hasOutputs) {
lines.push(` proxyOutputs(this, this.el, ['${outputs.map((output) => output.name).join(`', '`)}']);`);
this.el = r.nativeElement;${hasOutputs
? `
proxyOutputs(this, this.el, [${formattedOutputs}]);`
: ''}
}
}`;
return output;
};
/**
* Sanitizes and formats the component event type.
* @param componentClassName The class name of the component (e.g. 'MyComponent')
* @param event The Stencil component event.
* @returns The sanitized event type as a string.
*/
const formatOutputType = (componentClassName, event) => {
/**
* The original attribute contains the original type defined by the devs.
* This regexp normalizes the reference, by removing linebreaks,
* replacing consecutive spaces with a single space, and adding a single space after commas.
*/
return Object.entries(event.complexType.references)
.filter(([_, refObject]) => refObject.location === 'local' || refObject.location === 'import')
.reduce((type, [src, dst]) => {
const renamedType = `I${componentClassName}${type}`;
return renamedType
.replace(new RegExp(`^${src}$`, 'g'), `${dst}`)
.replace(new RegExp(`([^\\w])${src}([^\\w])`, 'g'), (v, p1, p2) => [p1, dst, p2].join(''));
}, event.complexType.original
.replace(/\n/g, ' ')
.replace(/\s{2,}/g, ' ')
.replace(/,\s*/g, ', '));
};
/**
* Creates a formatted comment block based on the JS doc comment.
* @param doc The compiler jsdoc.
* @returns The formatted comment block as a string.
*/
const createDocComment = (doc) => {
if (doc.text.trim().length === 0 && doc.tags.length === 0) {
return '';
}
lines.push(` }`);
lines.push(`}`);
return lines.join('\n');
return `/**
* ${doc.text}${doc.tags.length > 0 ? ' ' : ''}${doc.tags.map((tag) => `@${tag.name} ${tag.text}`)}
*/`;
};
function getProxyCmp(tagName, includeCustomElement, inputs, methods) {
const hasInputs = inputs.length > 0;
const hasMethods = methods.length > 0;
const proxMeta = [
`defineCustomElementFn: ${includeCustomElement ? 'define' + dashToPascalCase(tagName) : 'undefined'}`
];
if (hasInputs)
proxMeta.push(`inputs: ['${inputs.join(`', '`)}']`);
if (hasMethods)
proxMeta.push(`methods: ['${methods.join(`', '`)}']`);
return `@ProxyCmp({\n ${proxMeta.join(',\n ')}\n})`;
}
/**
* Creates the component interface type definition.
* @param tagNameAsPascal The tag name as PascalCase.
* @param events The events to generate the interface properties for.
* @param componentCorePackage The component core package.
* @param includeImportCustomElements Whether to include the import for the custom element definition.
* @param customElementsDir The custom elements directory.
* @returns The component interface type definition as a string.
*/
const createComponentTypeDefinition = (tagNameAsPascal, events, componentCorePackage, includeImportCustomElements = false, customElementsDir) => {
const typeDefinition = `${createComponentEventTypeImports(tagNameAsPascal, events, {
componentCorePackage,
includeImportCustomElements,
customElementsDir,
})}
export interface ${tagNameAsPascal} extends Components.${tagNameAsPascal} {
${events
.map((event) => {
const comment = createDocComment(event.docs);
return `${comment.length > 0 ? ` ${comment}` : ''}
${event.name}: EventEmitter<CustomEvent<${formatOutputType(tagNameAsPascal, event)}>>;`;
})
.join('\n')}
}`;
return typeDefinition;
};
function generateAngularDirectivesFile(compilerCtx, components, outputTarget) {

@@ -187,3 +255,3 @@ // Only create the file if it is defined in the stencil configuration

}
const proxyPath = relativeImport(outputTarget.directivesArrayFile, outputTarget.directivesProxyFile, '.ts');
const proxyPath = relativeImport(outputTarget.directivesArrayFile, outputTarget.proxyDeclarationFile, '.ts');
const directives = components

@@ -204,11 +272,8 @@ .map((cmpMeta) => dashToPascalCase(cmpMeta.tagName))

async function generateValueAccessors(compilerCtx, components, outputTarget, config) {
if (!Array.isArray(outputTarget.valueAccessorConfigs) ||
outputTarget.valueAccessorConfigs.length === 0) {
if (!Array.isArray(outputTarget.valueAccessorConfigs) || outputTarget.valueAccessorConfigs.length === 0) {
return;
}
const targetDir = path.dirname(outputTarget.directivesProxyFile);
const targetDir = path.dirname(outputTarget.proxyDeclarationFile);
const normalizedValueAccessors = outputTarget.valueAccessorConfigs.reduce((allAccessors, va) => {
const elementSelectors = Array.isArray(va.elementSelectors)
? va.elementSelectors
: [va.elementSelectors];
const elementSelectors = Array.isArray(va.elementSelectors) ? va.elementSelectors : [va.elementSelectors];
const type = va.type;

@@ -262,2 +327,19 @@ let allElementSelectors = [];

/**
* Creates an Angular module declaration for a component wrapper.
* @param componentTagName The tag name of the Stencil component.
* @returns The Angular module declaration as a string.
*/
const generateAngularModuleForComponent = (componentTagName) => {
const tagNameAsPascal = dashToPascalCase(componentTagName);
const componentClassName = `${tagNameAsPascal}`;
const moduleClassName = `${tagNameAsPascal}Module`;
const moduleDefinition = `@NgModule({
declarations: [${componentClassName}],
exports: [${componentClassName}]
})
export class ${moduleClassName} { }`;
return moduleDefinition;
};
async function angularDirectiveProxyOutput(compilerCtx, outputTarget, components, config) {

@@ -269,3 +351,3 @@ const filteredComponents = getFilteredComponents(outputTarget.excludeComponents, components);

await Promise.all([
compilerCtx.fs.writeFile(outputTarget.directivesProxyFile, finalText),
compilerCtx.fs.writeFile(outputTarget.proxyDeclarationFile, finalText),
copyResources$1(config, outputTarget),

@@ -284,3 +366,3 @@ generateAngularDirectivesFile(compilerCtx, filteredComponents, outputTarget),

const srcDirectory = path.join(__dirname, '..', 'angular-component-lib');
const destDirectory = path.join(path.dirname(outputTarget.directivesProxyFile), 'angular-component-lib');
const destDirectory = path.join(path.dirname(outputTarget.proxyDeclarationFile), 'angular-component-lib');
return config.sys.copy([

@@ -296,9 +378,30 @@ {

function generateProxies(components, pkgData, outputTarget, rootDir) {
var _a;
const distTypesDir = path.dirname(pkgData.types);
const dtsFilePath = path.join(rootDir, distTypesDir, GENERATED_DTS);
const componentsTypeFile = relativeImport(outputTarget.directivesProxyFile, dtsFilePath, '.d.ts');
const componentsTypeFile = relativeImport(outputTarget.proxyDeclarationFile, dtsFilePath, '.d.ts');
const createSingleComponentAngularModules = (_a = outputTarget.createSingleComponentAngularModules) !== null && _a !== void 0 ? _a : false;
/**
* The collection of named imports from @angular/core.
*/
const angularCoreImports = [
'ChangeDetectionStrategy',
'ChangeDetectorRef',
'Component',
'ElementRef',
'EventEmitter',
'NgZone',
];
/**
* The collection of named imports from the angular-component-lib/utils.
*/
const componentLibImports = ['ProxyCmp', 'proxyOutputs'];
if (createSingleComponentAngularModules) {
angularCoreImports.push('NgModule');
}
const imports = `/* tslint:disable */
/* auto-generated angular directive proxies */
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, EventEmitter, NgZone } from '@angular/core';
import { ProxyCmp, proxyOutputs } from './angular-component-lib/utils';\n`;
${createImportStatement(angularCoreImports, '@angular/core')}
${createImportStatement(componentLibImports, './angular-component-lib/utils')}\n`;
/**

@@ -311,4 +414,8 @@ * Generate JSX import type from correct location.

const generateTypeImports = () => {
let importLocation = outputTarget.componentCorePackage ? normalizePath(outputTarget.componentCorePackage) : normalizePath(componentsTypeFile);
importLocation += outputTarget.includeImportCustomElements ? `/${outputTarget.customElementsDir || 'components'}` : '';
let importLocation = outputTarget.componentCorePackage
? normalizePath(outputTarget.componentCorePackage)
: normalizePath(componentsTypeFile);
importLocation += outputTarget.includeImportCustomElements
? `/${outputTarget.customElementsDir || 'components'}`
: '';
return `import ${outputTarget.includeImportCustomElements ? 'type ' : ''}{ ${IMPORT_TYPES} } from '${importLocation}';\n`;

@@ -325,17 +432,48 @@ };

if (outputTarget.includeImportCustomElements && outputTarget.componentCorePackage !== undefined) {
const cmpImports = components.map(component => {
const cmpImports = components.map((component) => {
const pascalImport = dashToPascalCase(component.tagName);
return `import { defineCustomElement as define${pascalImport} } from '${normalizePath(outputTarget.componentCorePackage)}/${outputTarget.customElementsDir ||
'components'}/${component.tagName}.js';`;
return `import { defineCustomElement as define${pascalImport} } from '${normalizePath(outputTarget.componentCorePackage)}/${outputTarget.customElementsDir || 'components'}/${component.tagName}.js';`;
});
sourceImports = cmpImports.join('\n');
}
const final = [
imports,
typeImports,
sourceImports,
components
.map(createComponentDefinition(outputTarget.componentCorePackage, distTypesDir, rootDir, outputTarget.includeImportCustomElements, outputTarget.customElementsDir))
.join('\n'),
];
if (createSingleComponentAngularModules) {
// Generating Angular modules is only supported in the dist-custom-elements build
if (!outputTarget.includeImportCustomElements) {
throw new Error('Generating single component Angular modules requires the "includeImportCustomElements" option to be set to true.');
}
}
const proxyFileOutput = [];
const filterInternalProps = (prop) => !prop.internal;
const mapPropName = (prop) => prop.name;
const { includeImportCustomElements, componentCorePackage, customElementsDir } = outputTarget;
for (let cmpMeta of components) {
const tagNameAsPascal = dashToPascalCase(cmpMeta.tagName);
const inputs = [];
if (cmpMeta.properties) {
inputs.push(...cmpMeta.properties.filter(filterInternalProps).map(mapPropName));
}
if (cmpMeta.virtualProperties) {
inputs.push(...cmpMeta.virtualProperties.map(mapPropName));
}
inputs.sort();
const outputs = [];
if (cmpMeta.events) {
outputs.push(...cmpMeta.events.filter(filterInternalProps).map(mapPropName));
}
const methods = [];
if (cmpMeta.methods) {
methods.push(...cmpMeta.methods.filter(filterInternalProps).map(mapPropName));
}
/**
* For each component, we need to generate:
* 1. The @Component decorated class
* 2. Optionally the @NgModule decorated class (if createSingleComponentAngularModules is true)
* 3. The component interface (using declaration merging for types).
*/
const componentDefinition = createAngularComponentDefinition(cmpMeta.tagName, inputs, outputs, methods, includeImportCustomElements);
const moduleDefinition = generateAngularModuleForComponent(cmpMeta.tagName);
const componentTypeDefinition = createComponentTypeDefinition(tagNameAsPascal, cmpMeta.events, componentCorePackage, includeImportCustomElements, customElementsDir);
proxyFileOutput.push(componentDefinition, '\n', createSingleComponentAngularModules ? moduleDefinition : '', '\n', componentTypeDefinition, '\n');
}
const final = [imports, typeImports, sourceImports, ...proxyFileOutput];
return final.join('\n') + '\n';

@@ -359,12 +497,15 @@ }

function normalizeOutputTarget(config, outputTarget) {
const results = Object.assign(Object.assign({}, outputTarget), { excludeComponents: outputTarget.excludeComponents || [], valueAccessorConfig: outputTarget.valueAccessorConfig || [] });
const results = Object.assign(Object.assign({}, outputTarget), { excludeComponents: outputTarget.excludeComponents || [], valueAccessorConfigs: outputTarget.valueAccessorConfigs || [] });
if (config.rootDir == null) {
throw new Error('rootDir is not set and it should be set by stencil itself');
}
if (outputTarget.directivesProxyFile == null) {
throw new Error('directivesProxyFile is required');
if (outputTarget.directivesProxyFile !== undefined) {
throw new Error('directivesProxyFile has been removed. Use proxyDeclarationFile instead.');
}
if (outputTarget.directivesProxyFile && !path.isAbsolute(outputTarget.directivesProxyFile)) {
results.directivesProxyFile = normalizePath(path.join(config.rootDir, outputTarget.directivesProxyFile));
if (outputTarget.proxyDeclarationFile == null) {
throw new Error('proxyDeclarationFile is required. Please set it in the Stencil config.');
}
if (outputTarget.proxyDeclarationFile && !path.isAbsolute(outputTarget.proxyDeclarationFile)) {
results.proxyDeclarationFile = normalizePath(path.join(config.rootDir, outputTarget.proxyDeclarationFile));
}
if (outputTarget.directivesArrayFile && !path.isAbsolute(outputTarget.directivesArrayFile)) {

@@ -371,0 +512,0 @@ results.directivesArrayFile = normalizePath(path.join(config.rootDir, outputTarget.directivesArrayFile));

import path from 'path';
import { relativeImport, normalizePath, sortBy, readPackageJson, dashToPascalCase } from './utils';
import { createComponentDefinition } from './generate-angular-component';
import { relativeImport, normalizePath, sortBy, readPackageJson, dashToPascalCase, createImportStatement, } from './utils';
import { createAngularComponentDefinition, createComponentTypeDefinition } from './generate-angular-component';
import { generateAngularDirectivesFile } from './generate-angular-directives-file';
import generateValueAccessors from './generate-value-accessors';
import { generateAngularModuleForComponent } from './generate-angular-modules';
export async function angularDirectiveProxyOutput(compilerCtx, outputTarget, components, config) {

@@ -12,3 +13,3 @@ const filteredComponents = getFilteredComponents(outputTarget.excludeComponents, components);

await Promise.all([
compilerCtx.fs.writeFile(outputTarget.directivesProxyFile, finalText),
compilerCtx.fs.writeFile(outputTarget.proxyDeclarationFile, finalText),
copyResources(config, outputTarget),

@@ -27,3 +28,3 @@ generateAngularDirectivesFile(compilerCtx, filteredComponents, outputTarget),

const srcDirectory = path.join(__dirname, '..', 'angular-component-lib');
const destDirectory = path.join(path.dirname(outputTarget.directivesProxyFile), 'angular-component-lib');
const destDirectory = path.join(path.dirname(outputTarget.proxyDeclarationFile), 'angular-component-lib');
return config.sys.copy([

@@ -39,9 +40,30 @@ {

export function generateProxies(components, pkgData, outputTarget, rootDir) {
var _a;
const distTypesDir = path.dirname(pkgData.types);
const dtsFilePath = path.join(rootDir, distTypesDir, GENERATED_DTS);
const componentsTypeFile = relativeImport(outputTarget.directivesProxyFile, dtsFilePath, '.d.ts');
const componentsTypeFile = relativeImport(outputTarget.proxyDeclarationFile, dtsFilePath, '.d.ts');
const createSingleComponentAngularModules = (_a = outputTarget.createSingleComponentAngularModules) !== null && _a !== void 0 ? _a : false;
/**
* The collection of named imports from @angular/core.
*/
const angularCoreImports = [
'ChangeDetectionStrategy',
'ChangeDetectorRef',
'Component',
'ElementRef',
'EventEmitter',
'NgZone',
];
/**
* The collection of named imports from the angular-component-lib/utils.
*/
const componentLibImports = ['ProxyCmp', 'proxyOutputs'];
if (createSingleComponentAngularModules) {
angularCoreImports.push('NgModule');
}
const imports = `/* tslint:disable */
/* auto-generated angular directive proxies */
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, EventEmitter, NgZone } from '@angular/core';
import { ProxyCmp, proxyOutputs } from './angular-component-lib/utils';\n`;
${createImportStatement(angularCoreImports, '@angular/core')}
${createImportStatement(componentLibImports, './angular-component-lib/utils')}\n`;
/**

@@ -54,4 +76,8 @@ * Generate JSX import type from correct location.

const generateTypeImports = () => {
let importLocation = outputTarget.componentCorePackage ? normalizePath(outputTarget.componentCorePackage) : normalizePath(componentsTypeFile);
importLocation += outputTarget.includeImportCustomElements ? `/${outputTarget.customElementsDir || 'components'}` : '';
let importLocation = outputTarget.componentCorePackage
? normalizePath(outputTarget.componentCorePackage)
: normalizePath(componentsTypeFile);
importLocation += outputTarget.includeImportCustomElements
? `/${outputTarget.customElementsDir || 'components'}`
: '';
return `import ${outputTarget.includeImportCustomElements ? 'type ' : ''}{ ${IMPORT_TYPES} } from '${importLocation}';\n`;

@@ -68,17 +94,48 @@ };

if (outputTarget.includeImportCustomElements && outputTarget.componentCorePackage !== undefined) {
const cmpImports = components.map(component => {
const cmpImports = components.map((component) => {
const pascalImport = dashToPascalCase(component.tagName);
return `import { defineCustomElement as define${pascalImport} } from '${normalizePath(outputTarget.componentCorePackage)}/${outputTarget.customElementsDir ||
'components'}/${component.tagName}.js';`;
return `import { defineCustomElement as define${pascalImport} } from '${normalizePath(outputTarget.componentCorePackage)}/${outputTarget.customElementsDir || 'components'}/${component.tagName}.js';`;
});
sourceImports = cmpImports.join('\n');
}
const final = [
imports,
typeImports,
sourceImports,
components
.map(createComponentDefinition(outputTarget.componentCorePackage, distTypesDir, rootDir, outputTarget.includeImportCustomElements, outputTarget.customElementsDir))
.join('\n'),
];
if (createSingleComponentAngularModules) {
// Generating Angular modules is only supported in the dist-custom-elements build
if (!outputTarget.includeImportCustomElements) {
throw new Error('Generating single component Angular modules requires the "includeImportCustomElements" option to be set to true.');
}
}
const proxyFileOutput = [];
const filterInternalProps = (prop) => !prop.internal;
const mapPropName = (prop) => prop.name;
const { includeImportCustomElements, componentCorePackage, customElementsDir } = outputTarget;
for (let cmpMeta of components) {
const tagNameAsPascal = dashToPascalCase(cmpMeta.tagName);
const inputs = [];
if (cmpMeta.properties) {
inputs.push(...cmpMeta.properties.filter(filterInternalProps).map(mapPropName));
}
if (cmpMeta.virtualProperties) {
inputs.push(...cmpMeta.virtualProperties.map(mapPropName));
}
inputs.sort();
const outputs = [];
if (cmpMeta.events) {
outputs.push(...cmpMeta.events.filter(filterInternalProps).map(mapPropName));
}
const methods = [];
if (cmpMeta.methods) {
methods.push(...cmpMeta.methods.filter(filterInternalProps).map(mapPropName));
}
/**
* For each component, we need to generate:
* 1. The @Component decorated class
* 2. Optionally the @NgModule decorated class (if createSingleComponentAngularModules is true)
* 3. The component interface (using declaration merging for types).
*/
const componentDefinition = createAngularComponentDefinition(cmpMeta.tagName, inputs, outputs, methods, includeImportCustomElements);
const moduleDefinition = generateAngularModuleForComponent(cmpMeta.tagName);
const componentTypeDefinition = createComponentTypeDefinition(tagNameAsPascal, cmpMeta.events, componentCorePackage, includeImportCustomElements, customElementsDir);
proxyFileOutput.push(componentDefinition, '\n', createSingleComponentAngularModules ? moduleDefinition : '', '\n', componentTypeDefinition, '\n');
}
const final = [imports, typeImports, sourceImports, ...proxyFileOutput];
return final.join('\n') + '\n';

@@ -85,0 +142,0 @@ }

import type { Config, OutputTargetCustom } from '@stencil/core/internal';
import type { OutputTargetAngular } from './types';
export declare const angularOutputTarget: (outputTarget: OutputTargetAngular) => OutputTargetCustom;
export declare function normalizeOutputTarget(config: Config, outputTarget: any): OutputTargetAngular;
export declare function normalizeOutputTarget(config: Config, outputTarget: OutputTargetAngular): OutputTargetAngular;

@@ -17,12 +17,15 @@ import { normalizePath } from './utils';

export function normalizeOutputTarget(config, outputTarget) {
const results = Object.assign(Object.assign({}, outputTarget), { excludeComponents: outputTarget.excludeComponents || [], valueAccessorConfig: outputTarget.valueAccessorConfig || [] });
const results = Object.assign(Object.assign({}, outputTarget), { excludeComponents: outputTarget.excludeComponents || [], valueAccessorConfigs: outputTarget.valueAccessorConfigs || [] });
if (config.rootDir == null) {
throw new Error('rootDir is not set and it should be set by stencil itself');
}
if (outputTarget.directivesProxyFile == null) {
throw new Error('directivesProxyFile is required');
if (outputTarget.directivesProxyFile !== undefined) {
throw new Error('directivesProxyFile has been removed. Use proxyDeclarationFile instead.');
}
if (outputTarget.directivesProxyFile && !path.isAbsolute(outputTarget.directivesProxyFile)) {
results.directivesProxyFile = normalizePath(path.join(config.rootDir, outputTarget.directivesProxyFile));
if (outputTarget.proxyDeclarationFile == null) {
throw new Error('proxyDeclarationFile is required. Please set it in the Stencil config.');
}
if (outputTarget.proxyDeclarationFile && !path.isAbsolute(outputTarget.proxyDeclarationFile)) {
results.proxyDeclarationFile = normalizePath(path.join(config.rootDir, outputTarget.proxyDeclarationFile));
}
if (outputTarget.directivesArrayFile && !path.isAbsolute(outputTarget.directivesArrayFile)) {

@@ -29,0 +32,0 @@ results.directivesArrayFile = normalizePath(path.join(config.rootDir, outputTarget.directivesArrayFile));

export interface OutputTargetAngular {
componentCorePackage?: string;
directivesProxyFile: string;
/**
* The package name of the component library.
* This is used to generate the import statements.
*/
componentCorePackage: string;
/**
* @deprecated Use `proxyDeclarationFile` instead. This property has been replaced.
*/
directivesProxyFile?: string;
directivesArrayFile?: string;
directivesUtilsFile?: string;
/**
* The path to the proxy file that will be generated. This can be an absolute path
* or a relative path from the root directory of the Stencil library.
*/
proxyDeclarationFile: string;
valueAccessorConfigs?: ValueAccessorConfig[];

@@ -10,2 +22,6 @@ excludeComponents?: string[];

customElementsDir?: string;
/**
* `true` to generate a single component Angular module for each component.
*/
createSingleComponentAngularModules?: boolean;
}

@@ -12,0 +28,0 @@ export declare type ValueAccessorTypes = 'text' | 'radio' | 'select' | 'number' | 'boolean';

@@ -1,2 +0,2 @@

import { Config } from '@stencil/core/internal';
import { ComponentCompilerEvent, Config } from '@stencil/core/internal';
import type { PackageJSON } from './types';

@@ -10,1 +10,27 @@ export declare const toLowerCase: (str: string) => string;

export declare function readPackageJson(config: Config, rootDir: string): Promise<PackageJSON>;
/**
* Formats an array of strings to a string of quoted, comma separated values.
* @param list The list of unformatted strings to format
* @returns The formatted array of strings. (e.g. ['foo', 'bar']) => `'foo', 'bar'`
*/
export declare const formatToQuotedList: (list: readonly string[]) => string;
/**
* Creates an import statement for a list of named imports from a module.
* @param imports The list of named imports.
* @param module The module to import from.
*
* @returns The import statement as a string.
*/
export declare const createImportStatement: (imports: string[], module: string) => string;
/**
* Creates the collection of import statements for a component based on the component's events type dependencies.
* @param componentTagName The tag name of the component (pascal case).
* @param events The events compiler metadata.
* @param options The options for generating the import statements (e.g. whether to import from the custom elements directory).
* @returns The import statements as an array of strings.
*/
export declare const createComponentEventTypeImports: (componentTagName: string, events: readonly ComponentCompilerEvent[], options: {
componentCorePackage: string;
includeImportCustomElements?: boolean;
customElementsDir?: string;
}) => string;

@@ -77,4 +77,49 @@ import path from 'path';

}
/**
* Formats an array of strings to a string of quoted, comma separated values.
* @param list The list of unformatted strings to format
* @returns The formatted array of strings. (e.g. ['foo', 'bar']) => `'foo', 'bar'`
*/
export const formatToQuotedList = (list) => list.map((item) => `'${item}'`).join(', ');
/**
* Creates an import statement for a list of named imports from a module.
* @param imports The list of named imports.
* @param module The module to import from.
*
* @returns The import statement as a string.
*/
export const createImportStatement = (imports, module) => {
if (imports.length === 0) {
return '';
}
return `import { ${imports.join(', ')} } from '${module}';`;
};
/**
* Creates the collection of import statements for a component based on the component's events type dependencies.
* @param componentTagName The tag name of the component (pascal case).
* @param events The events compiler metadata.
* @param options The options for generating the import statements (e.g. whether to import from the custom elements directory).
* @returns The import statements as an array of strings.
*/
export const createComponentEventTypeImports = (componentTagName, events, options) => {
const { componentCorePackage, includeImportCustomElements, customElementsDir } = options;
const imports = [];
const namedImports = new Set();
const importPathName = normalizePath(componentCorePackage) + (includeImportCustomElements ? `/${customElementsDir || 'components'}` : '');
events.forEach((event) => {
Object.entries(event.complexType.references).forEach(([typeName, refObject]) => {
if (refObject.location === 'local' || refObject.location === 'import') {
const newTypeName = `I${componentTagName}${typeName}`;
// Prevents duplicate imports for the same type.
if (!namedImports.has(newTypeName)) {
imports.push(`import type { ${typeName} as ${newTypeName} } from '${importPathName}';`);
namedImports.add(newTypeName);
}
}
});
});
return imports.join('\n');
};
const EXTENDED_PATH_REGEX = /^\\\\\?\\/;
const NON_ASCII_REGEX = /[^\x00-\x80]+/;
const SLASH_REGEX = /\\/g;
{
"name": "@stencil/angular-output-target",
"version": "0.6.1-dev.11657573317.16e0205c",
"version": "0.6.1-dev.11662151255.17ea4066",
"description": "Angular output target for @stencil/core components.",

@@ -23,2 +23,5 @@ "main": "dist/index.cjs.js",

"version": "npm run build",
"prettier": "npm run prettier.base -- --write",
"prettier.base": "prettier \"./({angular-component-lib,src,test,__tests__}/**/*.{ts,tsx,js,jsx})|*.{ts,tsx,js,jsx}\"",
"prettier.dry-run": "npm run prettier.base -- --list-different",
"release": "np",

@@ -59,3 +62,3 @@ "test": "jest --passWithNoTests",

},
"gitHead": "6e0205c1e021addd4683bd5fc976cc6c9a126c3a"
"gitHead": "7ea40666153180bc16a62602feba11b767d8e396"
}

@@ -30,3 +30,3 @@ # @stencil/angular-output-target

componentCorePackage: 'component-library',
directivesProxyFile: '../component-library-angular/src/directives/proxies.ts',
proxyDeclarationFile: '../component-library-angular/src/directives/proxies.ts',
directivesArrayFile: '../component-library-angular/src/directives/index.ts',

@@ -47,3 +47,3 @@ }),

| `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. |
| `proxyDeclarationFile` | 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. |

@@ -50,0 +50,0 @@ | `valueAccessorConfigs` | The configuration object for how individual web components behave with Angular control value accessors. |

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc